| // Copyright 2015 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "android/crashreport/CrashReporter.h" |
| |
| #include "android/crashreport/crash-handler.h" |
| #include "android/base/files/PathUtils.h" |
| #include "android/base/system/System.h" |
| #include "android/base/system/Win32UnicodeString.h" |
| #include "android/base/Uuid.h" |
| #include "android/metrics/metrics.h" |
| #include "android/metrics/MetricsReporter.h" |
| #include "android/metrics/proto/studio_stats.pb.h" |
| #include "android/utils/debug.h" |
| #include "android/utils/eintr_wrapper.h" |
| #include "android/utils/path.h" |
| |
| #include <fstream> |
| |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <stdint.h> |
| #include <sys/stat.h> |
| |
| #ifdef _WIN32 |
| #include <io.h> |
| #endif |
| |
| #define E(...) derror(__VA_ARGS__) |
| #define W(...) dwarning(__VA_ARGS__) |
| #define D(...) VERBOSE_PRINT(init, __VA_ARGS__) |
| #define I(...) printf(__VA_ARGS__) |
| |
| using android::base::PathUtils; |
| using android::base::StringView; |
| using android::base::System; |
| using android::base::Uuid; |
| |
| namespace android { |
| namespace crashreport { |
| |
| const char* const CrashReporter::kDumpMessageFileName = |
| "internal-error-msg.txt"; |
| const char* const CrashReporter::kProcessMemoryInfoFileName = |
| "process-memory-info.txt"; |
| const char* const CrashReporter::kCrashOnExitFileName = |
| "crash-on-exit.txt"; |
| const char* const CrashReporter::kProcessListFileName = |
| "system-process-list.txt"; |
| const char* const CrashReporter::kCrashOnExitPattern = |
| "Crash on exit"; |
| |
| const char* const CrashReporter::kProcessCrashesQuietlyKey = |
| "set/processCrashesQuietly"; |
| |
| CrashReporter::CrashReporter() |
| : mDumpDir(System::get()->getTempDir().c_str()), |
| // TODO: add a function that can create a directory or error-out |
| // if it exists atomically. For now let's just allow UUIDs to do their |
| // job to keep these unique |
| mDataExchangeDir( |
| PathUtils::join(mDumpDir, Uuid::generateFast().toString()) |
| .c_str()) { |
| const auto res = path_mkdir_if_needed(mDataExchangeDir.c_str(), 0744); |
| if (res < 0) { |
| E("Failed to create temp directory for crash service communication: " |
| "'%s'", |
| mDataExchangeDir.c_str()); |
| } |
| } |
| |
| CrashReporter::~CrashReporter() = default; |
| |
| const std::string& CrashReporter::getDumpDir() const { |
| return mDumpDir; |
| } |
| |
| const std::string& CrashReporter::getDataExchangeDir() const { |
| return mDataExchangeDir; |
| } |
| |
| void CrashReporter::GenerateDump(const char* message) { |
| passDumpMessage(message); |
| writeDump(); |
| } |
| |
| void CrashReporter::GenerateDumpAndDie(const char* message) { |
| android_metrics_stop(); |
| passDumpMessage(message); |
| // this is the most cross-platform way of crashing |
| // any other I know about has its flows: |
| // - abort() isn't caught by Breakpad on Windows |
| // - null() may screw the callstack |
| // - explicit *null = 1 can be optimized out |
| // - requesting dump and exiting later has a very noticable delay in |
| // between, so some real crash could stick in the middle |
| volatile int* volatile ptr = nullptr; |
| *ptr = 1313; // die |
| } |
| |
| void CrashReporter::SetExitMode(const char* msg) { |
| mIsInExitMode = true; |
| // This is a temporary patch fix for an issue with too many crashes on exit |
| // Stop the metrics reporter as soon as we started exiting, not in the last |
| // moment. |
| // TODO: after exit crashes are fixed, just report the 'exit started' metric |
| // message here. |
| // Bug=http://b.android.com/200665 |
| android::metrics::MetricsReporter::get().report( |
| [](android_studio::AndroidStudioEvent* event) { |
| event->mutable_emulator_details()->set_exit_started(true); |
| event->mutable_emulator_details()->set_session_phase( |
| android_studio::EmulatorDetails::EXIT_GENERAL); |
| }); |
| android_metrics_stop(); |
| attachData(kCrashOnExitFileName, msg); |
| } |
| |
| bool CrashReporter::isInExitMode() const { |
| return mIsInExitMode; |
| } |
| |
| void CrashReporter::passDumpMessage(const char* message) { |
| attachData(kDumpMessageFileName, message); |
| } |
| |
| // Construct the full name of a file to put the data for the crash reporter |
| // Don't allocate! |
| template <size_t N> |
| static void formatDataFileName(char (&buffer)[N], StringView baseName) { |
| static_assert(N >= PATH_MAX, "Too small buffer for a path"); |
| |
| // don't do any dynamic allocation here - it might be called during dump |
| // writing, e.g. because of OOM exception |
| memset(&buffer[0], 0, N); |
| snprintf(buffer, N - 1, |
| "%s%c%s", |
| CrashReporter::get()->getDataExchangeDir().c_str(), |
| System::kDirSeparator, |
| (baseName.empty() ? "additional_data.txt" : baseName.c_str())); |
| } |
| |
| void CrashReporter::attachData(StringView name, StringView data, bool replace) { |
| const int bufferLength = PATH_MAX + 1; |
| char fullName[bufferLength]; |
| formatDataFileName(fullName, name); |
| |
| // Open the communication file in append mode to make sure we won't |
| // overwrite any existing message (e.g. if several threads are writing at |
| // once) |
| #ifdef _WIN32 |
| wchar_t wideFullPath[bufferLength] = {}; |
| android::base::Win32UnicodeString::convertFromUtf8(wideFullPath, |
| bufferLength, |
| fullName); |
| int fd = _wopen(wideFullPath, |
| _O_WRONLY | _O_CREAT | _O_NOINHERIT | _O_TEXT |
| | (replace ? _O_TRUNC : _O_APPEND), |
| _S_IREAD | _S_IWRITE); |
| #else |
| int fd = HANDLE_EINTR(open(fullName, |
| O_WRONLY | O_CREAT | O_CLOEXEC |
| | (replace ? O_TRUNC : O_APPEND), 0644)); |
| #endif |
| if (fd < 0) { |
| W("Failed to open a temp file '%s' for writing", fullName); |
| return; |
| } |
| |
| HANDLE_EINTR(write(fd, data.data(), data.size())); |
| HANDLE_EINTR(write(fd, "\n", 1)); |
| |
| close(fd); |
| } |
| |
| bool CrashReporter::attachFile(StringView sourceFullName, |
| StringView destBaseName) { |
| char fullName[PATH_MAX + 1]; |
| formatDataFileName(fullName, destBaseName); |
| |
| return path_copy_file(fullName, sourceFullName.c_str()) >= 0; |
| } |
| |
| static void attachUptime() { |
| const uint64_t wallClockTime = System::get()->getProcessTimes().wallClockMs; |
| |
| // format the time into a string buffer (no allocations, we've just crashed) |
| // and put it both into the file and into the file name. This way |
| // it's much easier to see the time in the crash report window |
| char timeStr[32] = {}; |
| snprintf(timeStr, sizeof(timeStr) - 1, "%" PRIu64 "ms", wallClockTime); |
| |
| char fileName[sizeof(timeStr) + 16] = {}; |
| snprintf(fileName, sizeof(fileName) - 1, "uptime-%s.txt", timeStr); |
| |
| CrashReporter::get()->attachData(fileName, timeStr); |
| } |
| |
| bool CrashReporter::onCrash() { |
| // store the uptime first - as Breakpad doesn't do it sometimes |
| attachUptime(); |
| |
| for (const auto& callback : CrashReporter::get()->mCrashCallbacks) { |
| callback(); |
| } |
| |
| return CrashReporter::get()->onCrashPlatformSpecific(); |
| } |
| |
| } // namespace crashreport |
| } // namespace android |
| |
| using android::crashreport::CrashReporter; |
| using android::crashreport::CrashSystem; |
| |
| extern "C" { |
| |
| bool crashhandler_init() { |
| if (CrashSystem::CrashType::CRASHUPLOAD == CrashSystem::CrashType::NONE) { |
| return false; |
| } |
| |
| if (!CrashSystem::get()->validatePaths()) { |
| return false; |
| } |
| |
| CrashSystem::CrashPipe crashpipe(CrashSystem::get()->getCrashPipe()); |
| |
| const std::string procident = CrashSystem::get()->getProcessId(); |
| |
| if (procident.empty()) { |
| return false; |
| } |
| |
| if (!crashpipe.isValid()) { |
| return false; |
| } |
| |
| std::vector<std::string> cmdline = |
| CrashSystem::get()->getCrashServiceCmdLine(crashpipe.mServer, |
| procident); |
| |
| int pid = CrashSystem::spawnService(cmdline); |
| if (pid > 0) { |
| CrashReporter::get()->setupChildCrashProcess(pid); |
| } else { |
| W("Could not spawn crash service\n"); |
| return false; |
| } |
| |
| if (!CrashReporter::get()->waitServicePipeReady(crashpipe.mClient)) { |
| W("Crash service did not start\n"); |
| return false; |
| } |
| |
| return CrashReporter::get()->attachCrashHandler(crashpipe); |
| } |
| |
| void crashhandler_die(const char* message) { |
| if (const auto reporter = CrashReporter::get()) { |
| reporter->GenerateDumpAndDie(message); |
| // convince the compiler and everyone else that we will never return |
| abort(); |
| } else { |
| I("Emulator: exiting becase of the internal error '%s'\n", message); |
| _exit(1); |
| } |
| } |
| |
| void crashhandler_die_format(const char* format, ...) { |
| char message[2048] = {}; |
| va_list args; |
| |
| va_start(args, format); |
| vsnprintf(message, sizeof(message) - 1, format, args); |
| va_end(args); |
| |
| crashhandler_die(message); |
| } |
| |
| void crashhandler_add_string(const char* name, const char* string) { |
| CrashReporter::get()->attachData(name, string); |
| } |
| |
| void crashhandler_exitmode(const char* message) { |
| CrashReporter::get()->SetExitMode(message); |
| } |
| |
| bool crashhandler_copy_attachment(const char* destination, const char* source) { |
| return CrashReporter::get()->attachFile(source, destination); |
| } |
| |
| } // extern "C" |
| |