| // |
| // 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 <fcntl.h> |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| #include <iostream> |
| |
| #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 <cxxabi.h> |
| # include <libunwind.h> |
| # include <signal.h> |
| # 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 <cxxabi.h> |
| # include <dlfcn.h> |
| # include <execinfo.h> |
| # include <libgen.h> |
| # include <link.h> |
| # include <signal.h> |
| # include <string.h> |
| # 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<MappedMemoryRegion>; |
| |
| 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<std::string> 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<const char *, kAddr2LineMaxParameters>; |
| |
| 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<char *const *>(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<char, kMaxAddressLen>; |
| |
| 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<std::string> &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 |