From 79ef475390d7d838213876b915ca108a6c38d672 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 28 Oct 2026 12:37:58 +0100 Subject: [PATCH 1/3] feat(native): Support ELF modules at non-zero file offsets The minidump writer and `capture_modules_from_proc_maps()` grouped `/proc//maps` entries by file name and expected an ELF header at offset 0. An ELF image can also start inside a larger file, such as an uncompressed library that Android maps straight from an APK, and such images were dropped from the module list. Start a module at each mapping whose file offset holds an ELF header, group the image's other segments by its load range, and read the build ID, size, and SONAME at that offset. Move `compute_elf_size_from_phdrs()` to `sentry_elf.h` to share it. This prepares the native backend for Android, where AGP's default packaging maps every app library from the APK. Ref: #1725 --- .../native/minidump/sentry_minidump_linux.c | 148 ++++++------------ src/backends/native/sentry_crash_context.h | 2 + src/backends/native/sentry_crash_daemon.c | 50 ++++-- src/sentry_elf.h | 94 ++++++++++- tests/unit/test_native_backend.c | 21 +++ tests/unit/tests.inc | 1 + 6 files changed, 199 insertions(+), 117 deletions(-) diff --git a/src/backends/native/minidump/sentry_minidump_linux.c b/src/backends/native/minidump/sentry_minidump_linux.c index 39a2d5be97..25c314ea16 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 359df783fb..8a64cf6a36 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 44a296875a..eddcbe81d4 100644 --- a/src/backends/native/sentry_crash_daemon.c +++ b/src/backends/native/sentry_crash_daemon.c @@ -1272,12 +1272,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 +1292,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; } @@ -1345,8 +1346,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; @@ -1442,7 +1443,8 @@ elf_locate_symtab(const char *path, int allow_dynsym, sym_source_t *src, 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); @@ -1571,7 +1573,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 +1607,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)) { @@ -1826,10 +1828,20 @@ capture_modules_from_proc_maps(sentry_crash_context_t *ctx) // 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 +1875,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 (!sentry__elf_is_file(mod->name)) { continue; } @@ -1871,7 +1893,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/sentry_elf.h b/src/sentry_elf.h index bb80d3f019..9c161bce0d 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/tests/unit/test_native_backend.c b/tests/unit/test_native_backend.c index ff7bcaf171..d95f8da1c2 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 33b2c78ce2..db9c2d38e2 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) From 1bfff50f780a216e487442175f526e59dc84c570 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 28 Oct 2026 15:16:34 +0100 Subject: [PATCH 2/3] fix(native): Apply the ELF offset to the build ID section headers The previous commit adjusted the section header offset in `elf_locate_symtab()` instead of `extract_elf_build_id_for_module()`, where `elf_offset` is not declared, so the crash daemon failed to compile. Move the adjustment to where the build ID is read. --- src/backends/native/sentry_crash_daemon.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backends/native/sentry_crash_daemon.c b/src/backends/native/sentry_crash_daemon.c index eddcbe81d4..946fbc4b92 100644 --- a/src/backends/native/sentry_crash_daemon.c +++ b/src/backends/native/sentry_crash_daemon.c @@ -1316,7 +1316,8 @@ extract_elf_build_id_for_module(const char *elf_path, uint64_t elf_offset, 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); @@ -1443,8 +1444,7 @@ elf_locate_symtab(const char *path, int allow_dynsym, sym_source_t *src, return 0; } - uint64_t shoff = elf_offset + ehdr.e_shoff; - if (lseek(fd, (off_t)shoff, SEEK_SET) != (off_t)shoff + if (lseek(fd, ehdr.e_shoff, SEEK_SET) != (off_t)ehdr.e_shoff || read(fd, shdr_buf, shdr_size) != (ssize_t)shdr_size) { sentry_free(shdr_buf); close(fd); From 6388a2f0e906091eeba4e81d9d0cc3e42fcf773e Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 28 Oct 2026 15:19:08 +0100 Subject: [PATCH 3/3] fix(native): Keep embedded ELFs mapped below their file offset The `offset > start` guard in `capture_modules_from_proc_maps()` dropped mappings before an ELF image inside a larger file could be detected. The guard only protects the `start - offset` load base of a regular ELF file, so apply it there instead. --- src/backends/native/sentry_crash_daemon.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/backends/native/sentry_crash_daemon.c b/src/backends/native/sentry_crash_daemon.c index 946fbc4b92..2888664686 100644 --- a/src/backends/native/sentry_crash_daemon.c +++ b/src/backends/native/sentry_crash_daemon.c @@ -1818,10 +1818,6 @@ 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 @@ -1885,7 +1881,7 @@ capture_modules_from_proc_maps(sentry_crash_context_t *ctx) mod->elf_offset = offset; mod->base_address = start; mod->size = MAX(end - start, elf_size); - } else if (!sentry__elf_is_file(mod->name)) { + } else if (offset > start || !sentry__elf_is_file(mod->name)) { continue; }