// // Copyright 2019 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // crash_handler_posix: // ANGLE's crash handling and stack walking code. Modified from Skia's: // https://github.com/google/skia/blob/master/tools/CrashHandler.cpp // #include "util/test_utils.h" #include "common/FixedVector.h" #include "common/angleutils.h" #include "common/string_utils.h" #include "common/system_utils.h" #include #include #include #include #include #include #include #include #if !defined(ANGLE_PLATFORM_ANDROID) && !defined(ANGLE_PLATFORM_FUCHSIA) # if defined(ANGLE_PLATFORM_APPLE) // We only use local unwinding, so we can define this to select a faster implementation. # define UNW_LOCAL_ONLY # include # include # include # elif defined(ANGLE_PLATFORM_POSIX) // We'd use libunwind here too, but it's a pain to get installed for // both 32 and 64 bit on bots. Doesn't matter much: catchsegv is best anyway. # include # include # include # include # include # include # include # endif // defined(ANGLE_PLATFORM_APPLE) #endif // !defined(ANGLE_PLATFORM_ANDROID) && !defined(ANGLE_PLATFORM_FUCHSIA) // This code snippet is coped from Chromium's base/posix/eintr_wrapper.h. #if defined(NDEBUG) # define HANDLE_EINTR(x) \ ({ \ decltype(x) eintr_wrapper_result; \ do \ { \ eintr_wrapper_result = (x); \ } while (eintr_wrapper_result == -1 && errno == EINTR); \ eintr_wrapper_result; \ }) #else # define HANDLE_EINTR(x) \ ({ \ int eintr_wrapper_counter = 0; \ decltype(x) eintr_wrapper_result; \ do \ { \ eintr_wrapper_result = (x); \ } while (eintr_wrapper_result == -1 && errno == EINTR && \ eintr_wrapper_counter++ < 100); \ eintr_wrapper_result; \ }) #endif // NDEBUG namespace angle { #if defined(ANGLE_PLATFORM_ANDROID) || defined(ANGLE_PLATFORM_FUCHSIA) void PrintStackBacktrace() { // No implementations yet. } void InitCrashHandler(CrashCallback *callback) { // No implementations yet. } void TerminateCrashHandler() { // No implementations yet. } #else namespace { CrashCallback *gCrashHandlerCallback; } // namespace # if defined(ANGLE_PLATFORM_APPLE) void PrintStackBacktrace() { printf("Backtrace:\n"); unw_context_t context; unw_getcontext(&context); unw_cursor_t cursor; unw_init_local(&cursor, &context); while (unw_step(&cursor) > 0) { static const size_t kMax = 256; char mangled[kMax], demangled[kMax]; unw_word_t offset; unw_get_proc_name(&cursor, mangled, kMax, &offset); int ok; size_t len = kMax; abi::__cxa_demangle(mangled, demangled, &len, &ok); printf(" %s (+0x%zx)\n", ok == 0 ? demangled : mangled, (size_t)offset); } printf("\n"); } static void Handler(int sig) { if (gCrashHandlerCallback) { (*gCrashHandlerCallback)(); } printf("\nSignal %d:\n", sig); PrintStackBacktrace(); // Exit NOW. Don't notify other threads, don't call anything registered with atexit(). _Exit(sig); } # elif defined(ANGLE_PLATFORM_POSIX) // Can control this at a higher level if required. # define ANGLE_HAS_ADDR2LINE # if defined(ANGLE_HAS_ADDR2LINE) namespace { // The following code was adapted from Chromium's "stack_trace_posix.cc". // Describes a region of mapped memory and the path of the file mapped. struct MappedMemoryRegion { enum Permission { READ = 1 << 0, WRITE = 1 << 1, EXECUTE = 1 << 2, PRIVATE = 1 << 3, // If set, region is private, otherwise it is shared. }; // The address range [start,end) of mapped memory. uintptr_t start; uintptr_t end; // Byte offset into |path| of the range mapped into memory. unsigned long long offset; // Image base, if this mapping corresponds to an ELF image. uintptr_t base; // Bitmask of read/write/execute/private/shared permissions. uint8_t permissions; // Name of the file mapped into memory. // // NOTE: path names aren't guaranteed to point at valid files. For example, // "[heap]" and "[stack]" are used to represent the location of the process' // heap and stack, respectively. std::string path; }; using MemoryRegionArray = std::vector; bool ReadProcMaps(std::string *proc_maps) { // seq_file only writes out a page-sized amount on each call. Refer to header // file for details. const long kReadSize = sysconf(_SC_PAGESIZE); int fd(HANDLE_EINTR(open("/proc/self/maps", O_RDONLY))); if (fd == -1) { fprintf(stderr, "Couldn't open /proc/self/maps\n"); return false; } proc_maps->clear(); while (true) { // To avoid a copy, resize |proc_maps| so read() can write directly into it. // Compute |buffer| afterwards since resize() may reallocate. size_t pos = proc_maps->size(); proc_maps->resize(pos + kReadSize); void *buffer = &(*proc_maps)[pos]; ssize_t bytes_read = HANDLE_EINTR(read(fd, buffer, kReadSize)); if (bytes_read < 0) { fprintf(stderr, "Couldn't read /proc/self/maps\n"); proc_maps->clear(); close(fd); return false; } // ... and don't forget to trim off excess bytes. proc_maps->resize(pos + bytes_read); if (bytes_read == 0) break; } close(fd); return true; } bool ParseProcMaps(const std::string &input, MemoryRegionArray *regions_out) { ASSERT(regions_out); MemoryRegionArray regions; // This isn't async safe nor terribly efficient, but it doesn't need to be at // this point in time. std::vector lines = SplitString(input, "\n", TRIM_WHITESPACE, SPLIT_WANT_ALL); for (size_t i = 0; i < lines.size(); ++i) { // Due to splitting on '\n' the last line should be empty. if (i == lines.size() - 1) { if (!lines[i].empty()) { fprintf(stderr, "ParseProcMaps: Last line not empty"); return false; } break; } MappedMemoryRegion region; const char *line = lines[i].c_str(); char permissions[5] = {'\0'}; // Ensure NUL-terminated string. uint8_t dev_major = 0; uint8_t dev_minor = 0; long inode = 0; int path_index = 0; // Sample format from man 5 proc: // // address perms offset dev inode pathname // 08048000-08056000 r-xp 00000000 03:0c 64593 /usr/sbin/gpm // // The final %n term captures the offset in the input string, which is used // to determine the path name. It *does not* increment the return value. // Refer to man 3 sscanf for details. if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %4c %llx %hhx:%hhx %ld %n", ®ion.start, ®ion.end, permissions, ®ion.offset, &dev_major, &dev_minor, &inode, &path_index) < 7) { fprintf(stderr, "ParseProcMaps: sscanf failed for line: %s\n", line); return false; } region.permissions = 0; if (permissions[0] == 'r') region.permissions |= MappedMemoryRegion::READ; else if (permissions[0] != '-') return false; if (permissions[1] == 'w') region.permissions |= MappedMemoryRegion::WRITE; else if (permissions[1] != '-') return false; if (permissions[2] == 'x') region.permissions |= MappedMemoryRegion::EXECUTE; else if (permissions[2] != '-') return false; if (permissions[3] == 'p') region.permissions |= MappedMemoryRegion::PRIVATE; else if (permissions[3] != 's' && permissions[3] != 'S') // Shared memory. return false; // Pushing then assigning saves us a string copy. regions.push_back(region); regions.back().path.assign(line + path_index); } regions_out->swap(regions); return true; } // Set the base address for each memory region by reading ELF headers in // process memory. void SetBaseAddressesForMemoryRegions(MemoryRegionArray ®ions) { int mem_fd(HANDLE_EINTR(open("/proc/self/mem", O_RDONLY | O_CLOEXEC))); if (mem_fd == -1) return; auto safe_memcpy = [&mem_fd](void *dst, uintptr_t src, size_t size) { return HANDLE_EINTR(pread(mem_fd, dst, size, src)) == ssize_t(size); }; uintptr_t cur_base = 0; for (MappedMemoryRegion &r : regions) { ElfW(Ehdr) ehdr; static_assert(SELFMAG <= sizeof(ElfW(Ehdr)), "SELFMAG too large"); if ((r.permissions & MappedMemoryRegion::READ) && safe_memcpy(&ehdr, r.start, sizeof(ElfW(Ehdr))) && memcmp(ehdr.e_ident, ELFMAG, SELFMAG) == 0) { switch (ehdr.e_type) { case ET_EXEC: cur_base = 0; break; case ET_DYN: // Find the segment containing file offset 0. This will correspond // to the ELF header that we just read. Normally this will have // virtual address 0, but this is not guaranteed. We must subtract // the virtual address from the address where the ELF header was // mapped to get the base address. // // If we fail to find a segment for file offset 0, use the address // of the ELF header as the base address. cur_base = r.start; for (unsigned i = 0; i != ehdr.e_phnum; ++i) { ElfW(Phdr) phdr; if (safe_memcpy(&phdr, r.start + ehdr.e_phoff + i * sizeof(phdr), sizeof(phdr)) && phdr.p_type == PT_LOAD && phdr.p_offset == 0) { cur_base = r.start - phdr.p_vaddr; break; } } break; default: // ET_REL or ET_CORE. These aren't directly executable, so they // don't affect the base address. break; } } r.base = cur_base; } close(mem_fd); } // Parses /proc/self/maps in order to compile a list of all object file names // for the modules that are loaded in the current process. // Returns true on success. bool CacheMemoryRegions(MemoryRegionArray ®ions) { // Reads /proc/self/maps. std::string contents; if (!ReadProcMaps(&contents)) { fprintf(stderr, "CacheMemoryRegions: Failed to read /proc/self/maps\n"); return false; } // Parses /proc/self/maps. if (!ParseProcMaps(contents, ®ions)) { fprintf(stderr, "CacheMemoryRegions: Failed to parse the contents of /proc/self/maps\n"); return false; } SetBaseAddressesForMemoryRegions(regions); return true; } constexpr size_t kAddr2LineMaxParameters = 50; using Addr2LineCommandLine = angle::FixedVector; void CallAddr2Line(const Addr2LineCommandLine &commandLine) { pid_t pid = fork(); if (pid < 0) { std::cerr << "Error: Failed to fork()" << std::endl; } else if (pid > 0) { int status; waitpid(pid, &status, 0); // Ignore the status, since we aren't going to handle it anyway. } else { // Child process executes addr2line // // See comment in test_utils_posix.cpp::PosixProcess regarding const_cast. execv(commandLine[0], const_cast(commandLine.data())); std::cerr << "Error: Child process returned from exevc()" << std::endl; _exit(EXIT_FAILURE); // exec never returns } } constexpr size_t kMaxAddressLen = 1024; using AddressBuffer = angle::FixedVector; const char *ResolveAddress(const MemoryRegionArray ®ions, const std::string &resolvedModule, const char *address, AddressBuffer &buffer) { size_t lastModuleSlash = resolvedModule.rfind('/'); ASSERT(lastModuleSlash != std::string::npos); std::string baseModule = resolvedModule.substr(lastModuleSlash); for (const MappedMemoryRegion ®ion : regions) { size_t pathSlashPos = region.path.rfind('/'); if (pathSlashPos != std::string::npos && region.path.substr(pathSlashPos) == baseModule) { uintptr_t scannedAddress; int scanReturn = sscanf(address, "%lX", &scannedAddress); ASSERT(scanReturn == 1); scannedAddress -= region.base; char printBuffer[255] = {}; size_t scannedSize = sprintf(printBuffer, "0x%lX", scannedAddress); size_t bufferSize = buffer.size(); buffer.resize(bufferSize + scannedSize + 1, 0); memcpy(&buffer[bufferSize], printBuffer, scannedSize); return &buffer[bufferSize]; } } return address; } } // anonymous namespace # endif // defined(ANGLE_HAS_ADDR2LINE) void PrintStackBacktrace() { printf("Backtrace:\n"); void *stack[64]; const int count = backtrace(stack, ArraySize(stack)); char **symbols = backtrace_symbols(stack, count); # if defined(ANGLE_HAS_ADDR2LINE) MemoryRegionArray regions; CacheMemoryRegions(regions); // Child process executes addr2line constexpr size_t kAddr2LineFixedParametersCount = 6; Addr2LineCommandLine commandLineArgs = { "/usr/bin/addr2line", // execv requires an absolute path to find addr2line "-s", "-p", "-f", "-C", "-e", }; const char *currentModule = ""; std::string resolvedModule; AddressBuffer addressBuffer; for (int i = 0; i < count; i++) { char *symbol = symbols[i]; // symbol looks like the following: // // path/to/module(+localAddress) [address] // // If module is not an absolute path, it needs to be resolved. char *module = symbol; char *address = strchr(symbol, '[') + 1; *strchr(module, '(') = 0; *strchr(address, ']') = 0; // If module is the same as last, continue batching addresses. If commandLineArgs has // reached its capacity however, make the call to addr2line already. Note that there should // be one entry left for the terminating nullptr at the end of the command line args. if (strcmp(module, currentModule) == 0 && commandLineArgs.size() + 1 < commandLineArgs.max_size()) { commandLineArgs.push_back( ResolveAddress(regions, resolvedModule, address, addressBuffer)); continue; } // If there's a command batched, execute it before modifying currentModule (a pointer to // which is stored in the command line args). if (currentModule[0] != 0) { commandLineArgs.push_back(nullptr); CallAddr2Line(commandLineArgs); addressBuffer.clear(); } // Reset the command line and remember this module as the current. resolvedModule = currentModule = module; commandLineArgs.resize(kAddr2LineFixedParametersCount); // We need an absolute path to get to the executable and all of the various shared objects, // but the caller may have used a relative path to launch the executable, so build one up if // we don't see a leading '/'. if (resolvedModule.at(0) != GetPathSeparator()) { const Optional &cwd = angle::GetCWD(); if (!cwd.valid()) { std::cerr << "Error getting CWD to print the backtrace." << std::endl; } else { std::string absolutePath = cwd.value(); size_t lastPathSepLoc = resolvedModule.find_last_of(GetPathSeparator()); std::string relativePath = resolvedModule.substr(0, lastPathSepLoc); // Remove "." from the relativePath path // For example: ./out/LinuxDebug/angle_perftests size_t pos = relativePath.find('.'); if (pos != std::string::npos) { // If found then erase it from string relativePath.erase(pos, 1); } // Remove the overlapping relative path from the CWD so we can build the full // absolute path. // For example: // absolutePath = /home/timvp/code/angle/out/LinuxDebug // relativePath = /out/LinuxDebug pos = absolutePath.find(relativePath); if (pos != std::string::npos) { // If found then erase it from string absolutePath.erase(pos, relativePath.length()); } resolvedModule = absolutePath + GetPathSeparator() + resolvedModule; } } // Check if this is a symlink. We assume the symlinks are relative to the target. constexpr size_t kBufSize = 1000; char linkBuf[kBufSize] = {}; ssize_t readLinkRet = readlink(resolvedModule.c_str(), linkBuf, kBufSize); if (readLinkRet != -1) { ASSERT(strchr(linkBuf, '/') == nullptr); size_t lastSlash = resolvedModule.rfind('/'); ASSERT(lastSlash != std::string::npos); resolvedModule = resolvedModule.substr(0, lastSlash + 1) + linkBuf; } const char *resolvedAddress = ResolveAddress(regions, resolvedModule, address, addressBuffer); commandLineArgs.push_back(resolvedModule.c_str()); commandLineArgs.push_back(resolvedAddress); } // Call addr2line for the last batch of addresses. if (currentModule[0] != 0) { commandLineArgs.push_back(nullptr); CallAddr2Line(commandLineArgs); } # else for (int i = 0; i < count; i++) { Dl_info info; if (dladdr(stack[i], &info) && info.dli_sname) { // Make sure this is large enough to hold the fully demangled names, otherwise we could // segault/hang here. For example, Vulkan validation layer errors can be deep enough // into the stack that very large symbol names are generated. char demangled[4096]; size_t len = ArraySize(demangled); int ok; abi::__cxa_demangle(info.dli_sname, demangled, &len, &ok); if (ok == 0) { printf(" %s\n", demangled); continue; } } printf(" %s\n", symbols[i]); } # endif // defined(ANGLE_HAS_ADDR2LINE) } static void Handler(int sig) { if (gCrashHandlerCallback) { (*gCrashHandlerCallback)(); } printf("\nSignal %d [%s]:\n", sig, strsignal(sig)); PrintStackBacktrace(); // Exit NOW. Don't notify other threads, don't call anything registered with atexit(). _Exit(sig); } # endif // defined(ANGLE_PLATFORM_APPLE) static constexpr int kSignals[] = { SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP, }; void InitCrashHandler(CrashCallback *callback) { gCrashHandlerCallback = callback; for (int sig : kSignals) { // Register our signal handler unless something's already done so (e.g. catchsegv). void (*prev)(int) = signal(sig, Handler); if (prev != SIG_DFL) { signal(sig, prev); } } } void TerminateCrashHandler() { gCrashHandlerCallback = nullptr; for (int sig : kSignals) { void (*prev)(int) = signal(sig, SIG_DFL); if (prev != Handler && prev != SIG_DFL) { signal(sig, prev); } } } #endif // defined(ANGLE_PLATFORM_ANDROID) || defined(ANGLE_PLATFORM_FUCHSIA) } // namespace angle