diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d967bfdd..b6d1e8571 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -978,6 +978,11 @@ elseif(SENTRY_BACKEND_NATIVE) ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/src/backends/native ) + if(SENTRY_WITH_LIBUNWINDSTACK) + target_include_directories(sentry-crash PRIVATE + ${PROJECT_SOURCE_DIR}/external/libunwindstack-ndk/include + ) + endif() # Link same libraries as sentry target_link_libraries(sentry-crash PRIVATE ${_SENTRY_PLATFORM_LIBS}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 11ee4f4c9..2aa4739b7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -178,6 +178,12 @@ elseif(SENTRY_BACKEND_NATIVE) backends/native/sentry_crash_ipc.c backends/native/sentry_crash_handler.c ) + if(SENTRY_WITH_LIBUNWINDSTACK) + target_compile_definitions(sentry PRIVATE SENTRY_WITH_UNWINDER_LIBUNWINDSTACK_REMOTE) + sentry_target_sources_cwd(sentry + unwinder/sentry_unwinder_libunwindstack_remote.cpp + ) + endif() # Add include directory for native backend headers target_include_directories(sentry PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/backends/native) diff --git a/src/backends/native/minidump/sentry_minidump_linux.c b/src/backends/native/minidump/sentry_minidump_linux.c index 39a2d5be9..25c314ea1 100644 --- a/src/backends/native/minidump/sentry_minidump_linux.c +++ b/src/backends/native/minidump/sentry_minidump_linux.c @@ -862,11 +862,12 @@ write_thread_context( } /** - * Extract Build ID from ELF file + * Extract Build ID from the ELF image at `elf_offset` in a file * Returns the Build ID length, or 0 if not found */ static size_t -extract_elf_build_id(const char *elf_path, uint8_t *build_id, size_t max_len) +extract_elf_build_id(const char *elf_path, uint64_t elf_offset, + uint8_t *build_id, size_t max_len) { int fd = sentry__elf_open(elf_path); if (fd < 0) { @@ -880,7 +881,7 @@ extract_elf_build_id(const char *elf_path, uint8_t *build_id, size_t max_len) Elf32_Ehdr ehdr; # endif - if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr)) { + if (pread(fd, &ehdr, sizeof(ehdr), (off_t)elf_offset) != sizeof(ehdr)) { close(fd); return 0; } @@ -906,7 +907,8 @@ extract_elf_build_id(const char *elf_path, uint8_t *build_id, size_t max_len) return 0; } - if (lseek(fd, ehdr.e_shoff, SEEK_SET) != (off_t)ehdr.e_shoff + uint64_t shoff = elf_offset + ehdr.e_shoff; + if (lseek(fd, (off_t)shoff, SEEK_SET) != (off_t)shoff || read(fd, shdr_buf, shdr_size) != (ssize_t)shdr_size) { sentry_free(shdr_buf); close(fd); @@ -932,8 +934,8 @@ extract_elf_build_id(const char *elf_path, uint8_t *build_id, size_t max_len) if (!note_buf) continue; - if (lseek(fd, sections[i].sh_offset, SEEK_SET) - == (off_t)sections[i].sh_offset + uint64_t note_offset = elf_offset + sections[i].sh_offset; + if (lseek(fd, (off_t)note_offset, SEEK_SET) == (off_t)note_offset && read(fd, note_buf, note_size) == (ssize_t)note_size) { // Find the GNU build-id note. SHT_NOTE uses 4-byte alignment. size_t desc_size = 0; @@ -958,87 +960,6 @@ extract_elf_build_id(const char *elf_path, uint8_t *build_id, size_t max_len) return build_id_len; } -/** - * Compute the virtual memory size of an ELF file from its PT_LOAD segments. - * This matches Crashpad's behavior: size = max(p_vaddr + p_memsz) - - * min(p_vaddr) across all PT_LOAD segments, rather than using page-aligned - * /proc/maps sizes. - * Returns the computed size, or 0 on failure (caller should fall back to - * /proc/maps size). - */ -static uint64_t -compute_elf_size_from_phdrs(const char *elf_path) -{ - int fd = sentry__elf_open(elf_path); - if (fd < 0) { - return 0; - } - -# if defined(__x86_64__) || defined(__aarch64__) - Elf64_Ehdr ehdr; -# else - Elf32_Ehdr ehdr; -# endif - - if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr) - || memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0 || ehdr.e_phnum == 0) { - close(fd); - return 0; - } - - if (!sentry__elf_has_phdr_size(ehdr.e_ident, ehdr.e_phentsize)) { - close(fd); - return 0; - } - - // Read program headers - size_t phdr_size = (size_t)ehdr.e_phentsize * ehdr.e_phnum; - void *phdr_buf = sentry_malloc(phdr_size); - if (!phdr_buf) { - close(fd); - return 0; - } - - if (lseek(fd, ehdr.e_phoff, SEEK_SET) != (off_t)ehdr.e_phoff - || read(fd, phdr_buf, phdr_size) != (ssize_t)phdr_size) { - sentry_free(phdr_buf); - close(fd); - return 0; - } - -# if defined(__x86_64__) || defined(__aarch64__) - Elf64_Phdr *phdrs = (Elf64_Phdr *)phdr_buf; -# else - Elf32_Phdr *phdrs = (Elf32_Phdr *)phdr_buf; -# endif - - uint64_t min_vaddr = UINT64_MAX; - uint64_t max_vaddr = 0; - bool found_load = false; - - for (int i = 0; i < ehdr.e_phnum; i++) { - if (phdrs[i].p_type == PT_LOAD) { - found_load = true; - if (phdrs[i].p_vaddr < min_vaddr) { - min_vaddr = phdrs[i].p_vaddr; - } - uint64_t end = phdrs[i].p_vaddr + phdrs[i].p_memsz; - if (end > max_vaddr) { - max_vaddr = end; - } - } - } - - sentry_free(phdr_buf); - close(fd); - - if (!found_load || max_vaddr <= min_vaddr) { - return 0; - } - - return max_vaddr - min_vaddr; -} - /** * Read the DT_SONAME from an ELF file's dynamic section. * Crashpad uses SONAME as the module name, falling back to the /proc/maps @@ -1047,7 +968,8 @@ compute_elf_size_from_phdrs(const char *elf_path) * Returns true if SONAME was found and written to soname_buf. */ static bool -read_elf_soname(const char *elf_path, char *soname_buf, size_t soname_buf_size) +read_elf_soname(const char *elf_path, uint64_t elf_offset, char *soname_buf, + size_t soname_buf_size) { int fd = sentry__elf_open(elf_path); if (fd < 0) { @@ -1066,7 +988,7 @@ read_elf_soname(const char *elf_path, char *soname_buf, size_t soname_buf_size) bool result = false; - if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr) + if (pread(fd, &ehdr, sizeof(ehdr), (off_t)elf_offset) != sizeof(ehdr) || memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0) { close(fd); return false; @@ -1085,7 +1007,8 @@ read_elf_soname(const char *elf_path, char *soname_buf, size_t soname_buf_size) return false; } - if (lseek(fd, ehdr.e_shoff, SEEK_SET) != (off_t)ehdr.e_shoff + uint64_t shoff = elf_offset + ehdr.e_shoff; + if (lseek(fd, (off_t)shoff, SEEK_SET) != (off_t)shoff || read(fd, shdr_buf, shdr_size) != (ssize_t)shdr_size) { sentry_free(shdr_buf); close(fd); @@ -1123,7 +1046,7 @@ read_elf_soname(const char *elf_path, char *soname_buf, size_t soname_buf_size) // Read .dynstr char *dynstr = sentry__elf_read_metadata_section( - fd, dynstr_shdr->sh_offset, dynstr_shdr->sh_size); + fd, elf_offset + dynstr_shdr->sh_offset, dynstr_shdr->sh_size); if (!dynstr) { sentry_free(shdr_buf); close(fd); @@ -1133,7 +1056,7 @@ read_elf_soname(const char *elf_path, char *soname_buf, size_t soname_buf_size) // Read .dynamic entries and find DT_SONAME void *dyn_buf = sentry__elf_read_metadata_section( - fd, dynamic_shdr->sh_offset, dynamic_shdr->sh_size); + fd, elf_offset + dynamic_shdr->sh_offset, dynamic_shdr->sh_size); if (!dyn_buf) { sentry_free(dynstr); sentry_free(shdr_buf); @@ -1540,6 +1463,7 @@ typedef struct { uint64_t end; // End of last mapping for this file char *name; // Pointer into mappings[].name (not owned) char soname[256]; // SONAME from ELF dynamic section (empty if not found) + uint64_t elf_offset; // File offset of the ELF image (non-zero in an APK) uint64_t elf_size; // Size from PT_LOAD segments (0 = use maps size) uint8_t build_id[32]; size_t build_id_len; @@ -1562,6 +1486,11 @@ typedef struct { * * base_of_image is set to the address of the offset==0 mapping — the real * ELF load address — which is what Breakpad/Crashpad/rust-minidump expect. + * + * Android maps uncompressed libraries straight from the APK, so one file can + * hold several ELF images. Such an image starts at the mapping whose file + * offset holds the ELF header, and its other segments are grouped by the + * image's load range instead of by file name. */ static size_t resolve_modules(const minidump_writer_t *writer, resolved_module_t *modules, @@ -1596,15 +1525,21 @@ resolve_modules(const minidump_writer_t *writer, resolved_module_t *modules, break; } } - if (already_skipped) { + // A non-ELF file can still hold ELF images at non-zero offsets. + if (already_skipped && mapping->offset == 0) { continue; } // Find an existing module entry by name. This merges across // non-contiguous /proc/maps lines — fixes duplicate ld-linux entries. + // An ELF image inside a larger file only owns its load range. resolved_module_t *mod = NULL; for (size_t j = 0; j < module_count; j++) { - if (strcmp(modules[j].name, mapping->name) == 0) { + if (strcmp(modules[j].name, mapping->name) == 0 + && (modules[j].elf_offset == 0 + || (mapping->start >= modules[j].base + && mapping->start + < modules[j].base + modules[j].elf_size))) { mod = &modules[j]; break; } @@ -1627,10 +1562,18 @@ resolve_modules(const minidump_writer_t *writer, resolved_module_t *modules, continue; } + // An ELF image can also start inside a larger file, such as a library + // that Android maps straight from an APK. + bool embedded = mapping->offset != 0 + && sentry__elf_is_file_at(mapping->name, mapping->offset); + if (!embedded && already_skipped) { + continue; + } + // First time we see this file — confirm it's actually an ELF before // adding it as a module. This drops sentry-native's own IPC shm, // deleted semaphores, and any other non-ELF named mapping. - if (!sentry__elf_is_file(mapping->name)) { + if (!embedded && !sentry__elf_is_file(mapping->name)) { if (skip_count < SENTRY_CRASH_MAX_MODULES) { skip_names[skip_count++] = mapping->name; } @@ -1647,15 +1590,18 @@ resolve_modules(const minidump_writer_t *writer, resolved_module_t *modules, mod->name = (char *)mapping->name; mod->base = mapping->start; mod->end = mapping->end; + mod->elf_offset = embedded ? mapping->offset : 0; + mod->elf_size + = sentry__elf_compute_size_from_phdrs(mod->name, mod->elf_offset); } - // Extract Build IDs, ELF sizes, and SONAMEs for each resolved module + // Extract Build IDs and SONAMEs for each resolved module for (size_t i = 0; i < module_count; i++) { - modules[i].build_id_len = extract_elf_build_id( - modules[i].name, modules[i].build_id, sizeof(modules[i].build_id)); - modules[i].elf_size = compute_elf_size_from_phdrs(modules[i].name); - if (!read_elf_soname(modules[i].name, modules[i].soname, - sizeof(modules[i].soname))) { + modules[i].build_id_len + = extract_elf_build_id(modules[i].name, modules[i].elf_offset, + modules[i].build_id, sizeof(modules[i].build_id)); + if (!read_elf_soname(modules[i].name, modules[i].elf_offset, + modules[i].soname, sizeof(modules[i].soname))) { modules[i].soname[0] = '\0'; } } diff --git a/src/backends/native/sentry_crash_context.h b/src/backends/native/sentry_crash_context.h index 359df783f..8a64cf6a3 100644 --- a/src/backends/native/sentry_crash_context.h +++ b/src/backends/native/sentry_crash_context.h @@ -155,6 +155,8 @@ typedef struct { uint32_t pdb_age; // PDB age (Windows PE only, appended to debug_id) #if defined(SENTRY_PLATFORM_WINDOWS) char pdb_name[SENTRY_CRASH_MAX_PATH]; // PDB filename (Windows PE only) +#elif defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) + uint64_t elf_offset; // File offset of the ELF image (non-zero in an APK) #endif } sentry_module_info_t; diff --git a/src/backends/native/sentry_crash_daemon.c b/src/backends/native/sentry_crash_daemon.c index 44a296875..118b8fa9a 100644 --- a/src/backends/native/sentry_crash_daemon.c +++ b/src/backends/native/sentry_crash_daemon.c @@ -536,7 +536,8 @@ build_registers_from_ctx(const sentry_crash_context_t *ctx, size_t thread_idx) return sentry__build_registers(&uctx); } -#ifdef SENTRY_WITH_UNWINDER_LIBUNWIND_REMOTE +#if defined(SENTRY_WITH_UNWINDER_LIBUNWIND_REMOTE) \ + || defined(SENTRY_WITH_UNWINDER_LIBUNWINDSTACK_REMOTE) static sentry_value_t build_registers_from_remote_registers( const sentry_remote_registers_t *remote_registers) @@ -1002,10 +1003,11 @@ build_stacktrace_for_thread( int frame_count = 0; #if defined(SENTRY_PLATFORM_LINUX) - // Remote DWARF unwinding via libunwind ptrace accessors. Do not attach to + // Remote DWARF unwinding via ptrace. Do not attach to // the crashed thread from the daemon; use the saved fault context below. { -# ifdef SENTRY_WITH_UNWINDER_LIBUNWIND_REMOTE +# if defined(SENTRY_WITH_UNWINDER_LIBUNWIND_REMOTE) \ + || defined(SENTRY_WITH_UNWINDER_LIBUNWINDSTACK_REMOTE) pid_t tid = 0; if (thread_idx == SIZE_MAX) { tid = ctx->crashed_tid; @@ -1075,6 +1077,12 @@ build_stacktrace_for_thread( sentry_value_set_by_key(stacktrace, "registers", build_registers_from_remote_registers(®isters)); } +# ifdef SENTRY_WITH_UNWINDER_LIBUNWINDSTACK_REMOTE + // libunwindstack already adjusts the caller frames + sentry_value_set_by_key(stacktrace, + "instruction_addr_adjustment", + sentry_value_new_string("none")); +# endif sentry_free(remote_frames); return stacktrace; } @@ -1272,12 +1280,13 @@ build_stacktrace_for_thread( # include /** - * Extract Build ID from ELF file (for debug_meta) + * Extract Build ID from the ELF image at `elf_offset` in a file + * (for debug_meta) * Returns the Build ID length, or 0 if not found */ static size_t -extract_elf_build_id_for_module( - const char *elf_path, uint8_t *build_id, size_t max_len) +extract_elf_build_id_for_module(const char *elf_path, uint64_t elf_offset, + uint8_t *build_id, size_t max_len) { int fd = sentry__elf_open(elf_path); if (fd < 0) { @@ -1291,7 +1300,7 @@ extract_elf_build_id_for_module( Elf32_Ehdr ehdr; # endif - if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr)) { + if (pread(fd, &ehdr, sizeof(ehdr), (off_t)elf_offset) != sizeof(ehdr)) { close(fd); return 0; } @@ -1315,7 +1324,8 @@ extract_elf_build_id_for_module( return 0; } - if (lseek(fd, ehdr.e_shoff, SEEK_SET) != (off_t)ehdr.e_shoff + uint64_t shoff = elf_offset + ehdr.e_shoff; + if (lseek(fd, (off_t)shoff, SEEK_SET) != (off_t)shoff || read(fd, shdr_buf, shdr_size) != (ssize_t)shdr_size) { sentry_free(shdr_buf); close(fd); @@ -1345,8 +1355,8 @@ extract_elf_build_id_for_module( continue; } - if (lseek(fd, sections[i].sh_offset, SEEK_SET) - != (off_t)sections[i].sh_offset + uint64_t note_offset = elf_offset + sections[i].sh_offset; + if (lseek(fd, (off_t)note_offset, SEEK_SET) != (off_t)note_offset || read(fd, note_buf, note_size) != (ssize_t)note_size) { sentry_free(note_buf); continue; @@ -1571,7 +1581,7 @@ get_sym_source(const sentry_module_info_t *mod, uint32_t mod_idx) if (debuglink[0]) { uint8_t mod_bid[16]; size_t mod_bid_len = extract_elf_build_id_for_module( - mod->name, mod_bid, sizeof(mod_bid)); + mod->name, mod->elf_offset, mod_bid, sizeof(mod_bid)); const char *slash = strrchr(mod->name, '/'); int dir_len = slash ? (int)(slash - mod->name) : 0; @@ -1605,7 +1615,7 @@ get_sym_source(const sentry_module_info_t *mod, uint32_t mod_idx) uint8_t dbg_bid[16]; size_t dbg_bid_len = extract_elf_build_id_for_module( - candidate, dbg_bid, sizeof(dbg_bid)); + candidate, 0, dbg_bid, sizeof(dbg_bid)); if (mod_bid_len && dbg_bid_len && (mod_bid_len != dbg_bid_len || memcmp(mod_bid, dbg_bid, mod_bid_len) != 0)) { @@ -1816,20 +1826,26 @@ capture_modules_from_proc_maps(sentry_crash_context_t *ctx) continue; } - if (offset > start) { - continue; - } - // Check if this file is already captured - if so, extend size if needed // A mapping at offset 0 starts another load of the file, unless the // file was captured last (small libraries can map all segments at // offset 0) sentry_module_info_t *existing_mod = NULL; for (uint32_t j = ctx->module_count; j > 0; j--) { - if (strncmp(ctx->modules[j - 1].name, pathname, len) == 0 - && ctx->modules[j - 1].name[len] == '\0') { + sentry_module_info_t *other = &ctx->modules[j - 1]; + if (strncmp(other->name, pathname, len) == 0 + && other->name[len] == '\0') { + // An ELF image inside a larger file only owns its load range + if (other->elf_offset != 0) { + if (start >= other->base_address + && start < other->base_address + other->size) { + existing_mod = other; + break; + } + continue; + } if (offset != 0 || j == ctx->module_count) { - existing_mod = &ctx->modules[j - 1]; + existing_mod = other; } break; } @@ -1863,7 +1879,17 @@ capture_modules_from_proc_maps(sentry_crash_context_t *ctx) memcpy(mod->name, pathname, copy_len); mod->name[copy_len] = '\0'; - if (!sentry__elf_is_file(mod->name)) { + // An ELF image can also start inside a larger file, such as a library + // that Android maps straight from an APK. Its load size is known up + // front to group the image's other segments. + mod->elf_offset = 0; + if (offset != 0 && sentry__elf_is_file_at(mod->name, offset)) { + uint64_t elf_size + = sentry__elf_compute_size_from_phdrs(mod->name, offset); + mod->elf_offset = offset; + mod->base_address = start; + mod->size = MAX(end - start, elf_size); + } else if (offset > start || !sentry__elf_is_file(mod->name)) { continue; } @@ -1871,7 +1897,7 @@ capture_modules_from_proc_maps(sentry_crash_context_t *ctx) memset(mod->uuid, 0, sizeof(mod->uuid)); mod->pdb_age = 0; // Not used on Linux, only for Windows PE modules extract_elf_build_id_for_module( - mod->name, mod->uuid, sizeof(mod->uuid)); + mod->name, mod->elf_offset, mod->uuid, sizeof(mod->uuid)); // Convert to little-endian GUID format for Sentry debug_id sentry__uuid_swap_guid_bytes(mod->uuid); diff --git a/src/backends/native/sentry_crash_handler.c b/src/backends/native/sentry_crash_handler.c index db8ce2291..f9a83e60c 100644 --- a/src/backends/native/sentry_crash_handler.c +++ b/src/backends/native/sentry_crash_handler.c @@ -125,6 +125,8 @@ static sentry_handler_strategy_t g_handler_strategy = SENTRY_HANDLER_STRATEGY_DEFAULT; static struct sigaction g_previous_handlers[16]; static stack_t g_signal_stack = { 0 }; +static volatile long g_handlers_installed = 0; +static volatile long g_preloaded = 0; static void reset_signal_handlers(void) @@ -973,14 +975,22 @@ crash_signal_handler(int signum, siginfo_t *info, void *context) reraise_signal(signum); } -int -sentry__crash_handler_init(sentry_crash_ipc_t *ipc, +static int +install_signal_handlers(sentry_crash_ipc_t *ipc, bool preload, sentry_handler_strategy_t strategy, void (*callback)(void)) { - if (!ipc && !callback) { + if (!preload && !ipc && !callback) { return -1; } + if (sentry__atomic_fetch(&g_handlers_installed)) { + if (!preload) { + g_crash_ipc = ipc; + g_handler_strategy = strategy; + } + return 0; + } + g_crash_ipc = ipc; g_handler_strategy = strategy; g_crash_callback = callback; @@ -1019,6 +1029,12 @@ sentry__crash_handler_init(sentry_crash_ipc_t *ipc, installed_count++; } + g_crash_ipc = ipc; + sentry__atomic_store(&g_handlers_installed, 1); + if (preload) { + sentry__atomic_store(&g_preloaded, 1); + } + // Prime libunwind's internal cache by performing a short unwind. // This forces dl_iterate_phdr to be called now (while safe) rather than // in the signal handler where it could deadlock if the crash occurs @@ -1052,6 +1068,20 @@ sentry__crash_handler_init(sentry_crash_ipc_t *ipc, return 0; } +int +sentry__crash_handler_init(sentry_crash_ipc_t *ipc, + sentry_handler_strategy_t strategy, void (*callback)(void)) +{ + return install_signal_handlers(ipc, false, strategy, callback); +} + +int +sentry__crash_handler_preload(void) +{ + return install_signal_handlers( + NULL, true, SENTRY_HANDLER_STRATEGY_DEFAULT, NULL); +} + int sentry__crash_handler_reinstall(void) { @@ -1087,6 +1117,16 @@ sentry__crash_handler_reinstall(void) void sentry__crash_handler_shutdown(void) { + if (sentry__atomic_fetch(&g_preloaded)) { + g_crash_ipc = NULL; + SENTRY_DEBUG("crash handler deactivated, keeping preloaded handlers"); + return; + } + + if (!sentry__atomic_fetch(&g_handlers_installed)) { + return; + } + // Restore previous signal handlers reset_signal_handlers(); @@ -1095,6 +1135,8 @@ sentry__crash_handler_shutdown(void) g_crash_ipc = NULL; g_crash_callback = NULL; + sentry__atomic_store(&g_handlers_installed, 0); + sentry__atomic_store(&g_preloaded, 0); SENTRY_DEBUG("crash handler shutdown"); } diff --git a/src/backends/native/sentry_crash_handler.h b/src/backends/native/sentry_crash_handler.h index a2c12054b..cf409f996 100644 --- a/src/backends/native/sentry_crash_handler.h +++ b/src/backends/native/sentry_crash_handler.h @@ -15,6 +15,11 @@ int sentry__crash_handler_init(sentry_crash_ipc_t *ipc, */ int sentry__crash_handler_reinstall(void); +/** + * Preload crash handler before full initialization. + */ +int sentry__crash_handler_preload(void); + /** * Shutdown crash handler (restore previous handlers) */ diff --git a/src/backends/sentry_backend_native.c b/src/backends/sentry_backend_native.c index 3a486e117..b595ab34d 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -1662,6 +1662,11 @@ native_backend_reinstall(sentry_backend_t *backend) void sentry__backend_preload(void) { +#ifdef SENTRY_PLATFORM_UNIX + if (sentry__crash_handler_preload() < 0) { + SENTRY_WARN("failed to preload native crash handler"); + } +#endif } /** diff --git a/src/sentry_elf.h b/src/sentry_elf.h index bb80d3f01..9c161bce0 100644 --- a/src/sentry_elf.h +++ b/src/sentry_elf.h @@ -37,9 +37,11 @@ sentry__elf_open(const char *path) * Used to filter out non-ELF mappings (e.g. files under /dev/shm, deleted * files, sentry-native's own IPC shared memory) from the module list — LLDB and * other consumers can choke on entries that look like modules but aren't. + * A non-zero offset checks for an ELF image inside a larger file, such as a + * library that Android maps straight from an APK. */ static inline bool -sentry__elf_is_file(const char *path) +sentry__elf_is_file_at(const char *path, uint64_t offset) { int fd = sentry__elf_open(path); if (fd < 0) { @@ -47,12 +49,18 @@ sentry__elf_is_file(const char *path) } unsigned char magic[SELFMAG]; - bool is_elf = read(fd, magic, SELFMAG) == (ssize_t)SELFMAG + bool is_elf = pread(fd, magic, SELFMAG, (off_t)offset) == (ssize_t)SELFMAG && memcmp(magic, ELFMAG, SELFMAG) == 0; close(fd); return is_elf; } +static inline bool +sentry__elf_is_file(const char *path) +{ + return sentry__elf_is_file_at(path, 0); +} + static inline bool sentry__elf_is_native_class(const unsigned char e_ident[EI_NIDENT]) { @@ -203,6 +211,88 @@ sentry__elf_find_note(const void *buf, size_t buf_size, size_t alignment, return NULL; } +/** + * Compute the virtual memory size of an ELF file from its PT_LOAD segments. + * This matches Crashpad's behavior: size = max(p_vaddr + p_memsz) - + * min(p_vaddr) across all PT_LOAD segments, rather than using page-aligned + * /proc/maps sizes. + * Returns the computed size, or 0 on failure (caller should fall back to + * /proc/maps size). + */ +static inline uint64_t +sentry__elf_compute_size_from_phdrs(const char *elf_path, uint64_t elf_offset) +{ + int fd = sentry__elf_open(elf_path); + if (fd < 0) { + return 0; + } + +# if defined(__x86_64__) || defined(__aarch64__) + Elf64_Ehdr ehdr; +# else + Elf32_Ehdr ehdr; +# endif + + if (pread(fd, &ehdr, sizeof(ehdr), (off_t)elf_offset) != sizeof(ehdr) + || memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0 || ehdr.e_phnum == 0) { + close(fd); + return 0; + } + + if (!sentry__elf_has_phdr_size(ehdr.e_ident, ehdr.e_phentsize)) { + close(fd); + return 0; + } + + // Read program headers + size_t phdr_size = (size_t)ehdr.e_phentsize * ehdr.e_phnum; + void *phdr_buf = sentry_malloc(phdr_size); + if (!phdr_buf) { + close(fd); + return 0; + } + + uint64_t phoff = elf_offset + ehdr.e_phoff; + if (lseek(fd, (off_t)phoff, SEEK_SET) != (off_t)phoff + || read(fd, phdr_buf, phdr_size) != (ssize_t)phdr_size) { + sentry_free(phdr_buf); + close(fd); + return 0; + } + +# if defined(__x86_64__) || defined(__aarch64__) + Elf64_Phdr *phdrs = (Elf64_Phdr *)phdr_buf; +# else + Elf32_Phdr *phdrs = (Elf32_Phdr *)phdr_buf; +# endif + + uint64_t min_vaddr = UINT64_MAX; + uint64_t max_vaddr = 0; + bool found_load = false; + + for (int i = 0; i < ehdr.e_phnum; i++) { + if (phdrs[i].p_type == PT_LOAD) { + found_load = true; + if (phdrs[i].p_vaddr < min_vaddr) { + min_vaddr = phdrs[i].p_vaddr; + } + uint64_t end = phdrs[i].p_vaddr + phdrs[i].p_memsz; + if (end > max_vaddr) { + max_vaddr = end; + } + } + } + + sentry_free(phdr_buf); + close(fd); + + if (!found_load || max_vaddr <= min_vaddr) { + return 0; + } + + return max_vaddr - min_vaddr; +} + #endif #endif diff --git a/src/unwinder/sentry_unwinder.c b/src/unwinder/sentry_unwinder.c index 68737b5ce..24c160456 100644 --- a/src/unwinder/sentry_unwinder.c +++ b/src/unwinder/sentry_unwinder.c @@ -1,6 +1,17 @@ #include "sentry_unwinder.h" #include "sentry_boot.h" +#if defined(SENTRY_WITH_UNWINDER_LIBUNWIND_REMOTE) \ + || defined(SENTRY_WITH_UNWINDER_LIBUNWINDSTACK_REMOTE) +# include "sentry_logger.h" + +# include +# include +# include +# include +# include +#endif + #define DEFINE_UNWINDER(Func) \ size_t sentry__unwind_stack_##Func(void *addr, \ const sentry_ucontext_t *uctx, void **ptrs, size_t max_frames) @@ -36,6 +47,7 @@ DEFINE_UNWINDER(psunwind); } while (0) DEFINE_THREAD_UNWINDER(libunwind_remote); +DEFINE_THREAD_UNWINDER(libunwindstack_remote); #endif static size_t @@ -88,7 +100,61 @@ sentry__unwind_stack_from_thread(pid_t tid, sentry_remote_frame_t *frames, (void)symbolize; # ifdef SENTRY_WITH_UNWINDER_LIBUNWIND_REMOTE TRY_THREAD_UNWINDER(libunwind_remote); +# endif +# ifdef SENTRY_WITH_UNWINDER_LIBUNWINDSTACK_REMOTE + TRY_THREAD_UNWINDER(libunwindstack_remote); # endif return 0; } + +# if defined(SENTRY_WITH_UNWINDER_LIBUNWIND_REMOTE) \ + || defined(SENTRY_WITH_UNWINDER_LIBUNWINDSTACK_REMOTE) +/** + * Attach to a thread of another process and wait until it stops. + * Returns false, leaving the thread detached, if either fails. + */ +bool +sentry__ptrace_attach_thread(pid_t tid) +{ + if (ptrace(PTRACE_ATTACH, tid, NULL, NULL) != 0) { + SENTRY_WARNF("remote_unwind: ptrace attach failed for %d: %s", tid, + strerror(errno)); + return false; + } + + // Poll with WNOHANG to avoid hanging on threads in D state. PTRACE_ATTACH + // sends SIGSTOP but the thread won't stop until it leaves uninterruptible + // sleep, which may never happen. + int status = 0; + int retries = 20; + for (;;) { + pid_t waited = waitpid(tid, &status, __WALL | WNOHANG); + if (waited == tid) { + break; + } + if (waited < 0) { + SENTRY_WARNF("remote_unwind: waitpid failed for %d: %s", tid, + strerror(errno)); + ptrace(PTRACE_DETACH, tid, NULL, NULL); + return false; + } + if (--retries <= 0) { + SENTRY_WARNF("remote_unwind: waitpid timed out for %d", tid); + ptrace(PTRACE_DETACH, tid, NULL, NULL); + return false; + } + usleep(100000); + } + + if (!WIFSTOPPED(status)) { + SENTRY_WARNF( + "remote_unwind: thread %d did not stop after attach: status=%d", + tid, status); + ptrace(PTRACE_DETACH, tid, NULL, NULL); + return false; + } + + return true; +} +# endif #endif diff --git a/src/unwinder/sentry_unwinder.h b/src/unwinder/sentry_unwinder.h index 75add8d77..32548f0ac 100644 --- a/src/unwinder/sentry_unwinder.h +++ b/src/unwinder/sentry_unwinder.h @@ -38,6 +38,8 @@ size_t sentry__unwind_stack_from_thread(pid_t tid, sentry_remote_frame_t *frames, size_t max_frames, sentry_remote_registers_t *registers, bool symbolize); +bool sentry__ptrace_attach_thread(pid_t tid); + #endif #endif // SENTRY_UNWINDER_H_INCLUDED diff --git a/src/unwinder/sentry_unwinder_libunwind_remote.c b/src/unwinder/sentry_unwinder_libunwind_remote.c index ca99aa7d5..1c1743841 100644 --- a/src/unwinder/sentry_unwinder_libunwind_remote.c +++ b/src/unwinder/sentry_unwinder_libunwind_remote.c @@ -15,11 +15,8 @@ # include "sentry_logger.h" -# include # include # include -# include -# include # include # include @@ -78,41 +75,7 @@ sentry__unwind_stack_from_thread_libunwind_remote(pid_t tid, memset(registers, 0, sizeof(*registers)); } - if (ptrace(PTRACE_ATTACH, tid, NULL, NULL) != 0) { - SENTRY_WARNF("remote_unwind: ptrace attach failed for %d: %s", tid, - strerror(errno)); - return 0; - } - - // Poll with WNOHANG to avoid hanging on threads in D state. PTRACE_ATTACH - // sends SIGSTOP but the thread won't stop until it leaves uninterruptible - // sleep, which may never happen. - int status = 0; - int retries = 20; - for (;;) { - pid_t waited = waitpid(tid, &status, __WALL | WNOHANG); - if (waited == tid) { - break; - } - if (waited < 0) { - SENTRY_WARNF("remote_unwind: waitpid failed for %d: %s", tid, - strerror(errno)); - ptrace(PTRACE_DETACH, tid, NULL, NULL); - return 0; - } - if (--retries <= 0) { - SENTRY_WARNF("remote_unwind: waitpid timed out for %d", tid); - ptrace(PTRACE_DETACH, tid, NULL, NULL); - return 0; - } - usleep(100000); - } - - if (!WIFSTOPPED(status)) { - SENTRY_WARNF( - "remote_unwind: thread %d did not stop after attach: status=%d", - tid, status); - ptrace(PTRACE_DETACH, tid, NULL, NULL); + if (!sentry__ptrace_attach_thread(tid)) { return 0; } diff --git a/src/unwinder/sentry_unwinder_libunwindstack_remote.cpp b/src/unwinder/sentry_unwinder_libunwindstack_remote.cpp new file mode 100644 index 000000000..93a09d1a9 --- /dev/null +++ b/src/unwinder/sentry_unwinder_libunwindstack_remote.cpp @@ -0,0 +1,120 @@ +extern "C" { +#include "sentry_boot.h" +#include "sentry_logger.h" +#include "sentry_unwinder.h" +} + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void +capture_registers(unwindstack::Regs *regs, sentry_remote_registers_t *registers) +{ + regs->IterateRegisters([registers](const char *name, uint64_t value) { + size_t len = strlen(name); + if (registers->count >= SENTRY_REMOTE_UNWIND_MAX_REGISTERS || len == 0 + || len >= sizeof(registers->values[0].name)) { + return; + } + sentry_remote_register_t *remote_register + = ®isters->values[registers->count++]; + memcpy(remote_register->name, name, len + 1); + remote_register->value = value; + }); +} + +static size_t +unwind_thread(pid_t tid, sentry_remote_frame_t *frames, size_t max_frames, + sentry_remote_registers_t *registers, bool symbolize) +{ + // an uncaught exception, e.g. std::bad_alloc, would abort the daemon + try { + std::unique_ptr regs( + unwindstack::Regs::RemoteGet(tid)); + if (!regs) { + SENTRY_WARNF("libunwindstack failed to read registers of %d", tid); + return 0; + } + if (registers) { + capture_registers(regs.get(), registers); + } + + unwindstack::RemoteMaps maps(tid); + if (!maps.Parse()) { + SENTRY_WARNF("libunwindstack failed to parse /proc/%d/maps", tid); + return 0; + } + + std::shared_ptr process_memory + = unwindstack::Memory::CreateProcessMemoryCached(tid); + if (!process_memory) { + SENTRY_WARNF( + "libunwindstack failed to read process memory for %d", tid); + return 0; + } + + unwindstack::Unwinder unwinder( + max_frames, &maps, regs.get(), process_memory); + unwinder.SetResolveNames(symbolize); + unwinder.Unwind(); + + if (unwinder.LastErrorCode() != unwindstack::ERROR_NONE) { + SENTRY_DEBUGF("libunwindstack finished with %s at 0x%llx", + unwinder.LastErrorCodeString(), + (unsigned long long)unwinder.LastErrorAddress()); + } + + size_t count = 0; + for (const unwindstack::FrameData &frame : unwinder.frames()) { + if (count >= max_frames) { + break; + } + frames[count].ip = frame.pc; + frames[count].symbol[0] = '\0'; + frames[count].symbol_offset = 0; + if (!frame.function_name.empty()) { + snprintf(frames[count].symbol, sizeof(frames[count].symbol), + "%s", frame.function_name.c_str()); + frames[count].symbol_offset = frame.function_offset; + } + count++; + } + + SENTRY_DEBUGF( + "libunwindstack remote unwind captured %zu frames", count); + return count; + } catch (...) { + SENTRY_WARN("libunwindstack failed with an exception"); + return 0; + } +} + +extern "C" size_t +sentry__unwind_stack_from_thread_libunwindstack_remote(pid_t tid, + sentry_remote_frame_t *frames, size_t max_frames, + sentry_remote_registers_t *registers, bool symbolize) +{ + if (registers) { + registers->count = 0; + } + + if (tid <= 0 || !frames || max_frames == 0) { + return 0; + } + + if (!sentry__ptrace_attach_thread(tid)) { + return 0; + } + + size_t count = unwind_thread(tid, frames, max_frames, registers, symbolize); + + ptrace(PTRACE_DETACH, tid, NULL, NULL); + return count; +} diff --git a/tests/unit/test_native_backend.c b/tests/unit/test_native_backend.c index ff7bcaf17..d95f8da1c 100644 --- a/tests/unit/test_native_backend.c +++ b/tests/unit/test_native_backend.c @@ -241,6 +241,27 @@ SENTRY_TEST(elf_rejects_non_regular_files) #endif } +SENTRY_TEST(elf_offset) +{ +#if !defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) + SKIP_TEST(); +#else + // an APK-like zip with an uncompressed library at a page-aligned offset + char path[] = "/tmp/sentry-minidump-apk-XXXXXX"; + int fd = mkstemp(path); + TEST_ASSERT(fd >= 0); + TEST_ASSERT(write(fd, "PK\3\4", 4) == 4); + TEST_ASSERT(pwrite(fd, ELFMAG, SELFMAG, 4096) == (ssize_t)SELFMAG); + close(fd); + + TEST_CHECK(!sentry__elf_is_file(path)); + TEST_CHECK(sentry__elf_is_file_at(path, 4096)); + TEST_CHECK(!sentry__elf_is_file_at(path, 8192)); + + TEST_CHECK(unlink(path) == 0); +#endif +} + /** * Test minidump directory entry structure */ diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 33b2c78ce..db9c2d38e 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -206,6 +206,7 @@ XX(effective_org_id_resolution) XX(elf_find_note) XX(elf_header_entry_sizes) XX(elf_metadata_section) +XX(elf_offset) XX(elf_rejects_non_regular_files) XX(embedded_info_basic) XX(embedded_info_build_id)