Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

**Fixes**:

- Native/Linux: resolve function names for the crashed thread's stacktrace from on-disk ELF symbol tables in the crash daemon, so the most important thread gets symbolicated without ptrace. ([#1764](https://github.com/getsentry/sentry-native/pull/1764))

- Finish active trace on crash. ([#1667](https://github.com/getsentry/sentry-native/pull/1667))
- Native/macOS: fix module `image_size` computation, which could have caused the symbolicator to misattribute every frame to the lowest-addressed image (typically `dyld` or `libsystem`). ([#1740](https://github.com/getsentry/sentry-native/pull/1740))
- Native: raise `SENTRY_CRASH_MAX_MODULES` from `512` to `2048` so processes that load many shared libraries no longer have their minidump module list truncated, which left frames in unrecorded modules without a `debug_id` and unsymbolicatable.
Expand All @@ -22,6 +24,7 @@
- Native/macOS: honor the `system_crash_reporter_enabled` option. ([#1743](https://github.com/getsentry/sentry-native/pull/1743))
- Cap rate-limit retry-after values at 24 hours to prevent a MITM-provided response from disabling event delivery for the process lifetime. ([#1744](https://github.com/getsentry/sentry-native/pull/1744))
- Fix a shutdown-time use-after-free window in `sentry_close()`. ([#1750](https://github.com/getsentry/sentry-native/pull/1750))
- Native/Linux: resolve symbol names for crashed thread on Linux. ([#1764](https://github.com/getsentry/sentry-native/pull/1764))
- Native: validate ELF header entry sizes. ([#1746](https://github.com/getsentry/sentry-native/pull/1746))
- Structured logs: respect printf argument widths when extracting log parameters to avoid stack-data disclosure and corrupted attributes on 32-bit platforms. ([#1752](https://github.com/getsentry/sentry-native/pull/1752))
- Fix a potential out-of-bounds read when parsing non-NUL-terminated `sentry-trace` headers. ([#1749](https://github.com/getsentry/sentry-native/pull/1749))
Expand Down
170 changes: 170 additions & 0 deletions src/backends/native/sentry_crash_daemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,11 @@ enrich_frame_with_module_info(
(unsigned long long)addr, ctx->module_count);
}

#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID)
static void enrich_frame_with_symbol(
const sentry_crash_context_t *ctx, sentry_value_t frame, uint64_t addr);
#endif

/**
* Build stacktrace frames for a specific thread using frame pointer-based
* unwinding. Reads the captured stack memory and walks the frame chain.
Expand Down Expand Up @@ -923,6 +928,7 @@ build_stacktrace_for_thread(
sentry_value_new_string(i == 0 ? "context" : "cfi"));
enrich_frame_with_module_info(
ctx, temp_frames[frame_count], frame_ip);
enrich_frame_with_symbol(ctx, temp_frames[frame_count], frame_ip);
frame_count++;
}

Expand Down Expand Up @@ -957,6 +963,9 @@ build_stacktrace_for_thread(
sentry_value_set_by_key(temp_frames[frame_count], "trust",
sentry_value_new_string("context"));
enrich_frame_with_module_info(ctx, temp_frames[frame_count], ip);
#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID)
enrich_frame_with_symbol(ctx, temp_frames[frame_count], ip);
#endif
frame_count++;
}

Expand Down Expand Up @@ -1015,6 +1024,10 @@ build_stacktrace_for_thread(
sentry_value_new_string("fp"));
enrich_frame_with_module_info(
ctx, temp_frames[frame_count], return_addr);
#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID)
enrich_frame_with_symbol(
ctx, temp_frames[frame_count], return_addr);
#endif
frame_count++;
walk_count++;

Expand Down Expand Up @@ -1189,6 +1202,163 @@ extract_elf_build_id_for_module(
return build_id_len;
}

/**
* Resolve the symbol name for a given instruction address from an ELF symbol
* table and set the "function" key on the frame value.
*/
static void
enrich_frame_with_symbol(
const sentry_crash_context_t *ctx, sentry_value_t frame, uint64_t addr)
{
for (uint32_t i = 0; i < ctx->module_count; i++) {
const sentry_module_info_t *mod = &ctx->modules[i];
if (addr < mod->base_address || addr >= mod->base_address + mod->size) {
continue;
}

uint64_t offset = addr - mod->base_address;

int fd = open(mod->name, O_RDONLY);
if (fd < 0) {
return;
}

# if defined(__x86_64__) || defined(__aarch64__)
Elf64_Ehdr ehdr;
# else
Elf32_Ehdr ehdr;
# endif
if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr)) {
close(fd);
return;
}

if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0
|| !sentry__elf_is_native_class(ehdr.e_ident)
|| !sentry__elf_has_shdr_size(ehdr.e_ident, ehdr.e_shentsize)) {
close(fd);
return;
}

size_t shdr_size = (size_t)ehdr.e_shentsize * ehdr.e_shnum;
void *shdr_buf = sentry_malloc(shdr_size);
if (!shdr_buf) {
close(fd);
return;
}

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);
return;
}

# if defined(__x86_64__) || defined(__aarch64__)
Elf64_Shdr *sections = (Elf64_Shdr *)shdr_buf;
# else
Elf32_Shdr *sections = (Elf32_Shdr *)shdr_buf;
# endif

// Find symbol table section - prefer .dynsym (always present in .so),
// fall back to .symtab
int symtab_idx = -1;
for (int j = 0; j < ehdr.e_shnum; j++) {
if (sections[j].sh_type == SHT_DYNSYM) {
symtab_idx = j;
break;
}
}
if (symtab_idx < 0) {
for (int j = 0; j < ehdr.e_shnum; j++) {
if (sections[j].sh_type == SHT_SYMTAB) {
symtab_idx = j;
break;
}
}
}

if (symtab_idx >= 0
&& sentry__elf_has_sym_entsize(
ehdr.e_ident, sections[symtab_idx].sh_entsize)) {
// For ET_DYN (shared libs, PIE) st_value is base-relative; for
// ET_EXEC (non-PIE) st_value is an absolute virtual address.
uint64_t sym_target = ehdr.e_type == ET_EXEC ? addr : offset;
size_t sym_size = sections[symtab_idx].sh_size;
size_t sym_count = sym_size / sections[symtab_idx].sh_entsize;
Comment thread
sentry[bot] marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.
int strtab_idx = sections[symtab_idx].sh_link;
Comment thread
cursor[bot] marked this conversation as resolved.
if (strtab_idx < 0 || (size_t)strtab_idx >= ehdr.e_shnum) {
sentry_free(shdr_buf);
close(fd);
return;
}

void *sym_buf = sentry_malloc(sym_size);
void *strtab_buf = NULL;

if (sym_buf
&& lseek(fd, sections[symtab_idx].sh_offset, SEEK_SET)
== (off_t)sections[symtab_idx].sh_offset
&& read(fd, sym_buf, sym_size) == (ssize_t)sym_size) {

size_t strtab_size = sections[strtab_idx].sh_size;
Comment thread
sentry[bot] marked this conversation as resolved.
strtab_buf = sentry_malloc(strtab_size);
if (strtab_buf
&& lseek(fd, sections[strtab_idx].sh_offset, SEEK_SET)
== (off_t)sections[strtab_idx].sh_offset
&& read(fd, strtab_buf, strtab_size)
== (ssize_t)strtab_size) {

const char *best_name = NULL;
uint64_t best_value = 0;

# if defined(__x86_64__) || defined(__aarch64__)
Elf64_Sym *syms = (Elf64_Sym *)sym_buf;
for (size_t k = 1; k < sym_count; k++) {
unsigned char sym_type = ELF64_ST_TYPE(syms[k].st_info);
# else
Elf32_Sym *syms = (Elf32_Sym *)sym_buf;
for (size_t k = 1; k < sym_count; k++) {
unsigned char sym_type = ELF32_ST_TYPE(syms[k].st_info);
# endif
if (syms[k].st_value == 0
|| syms[k].st_value > sym_target) {
continue;
}
if (sym_type != STT_FUNC && sym_type != STT_NOTYPE) {
continue;
}
if (!best_name || syms[k].st_value > best_value) {
if (syms[k].st_name < strtab_size) {
const char *name = (const char *)strtab_buf
+ syms[k].st_name;
if (name[0]
&& memchr(name, '\0',
strtab_size - syms[k].st_name)) {
best_name = name;
best_value = syms[k].st_value;
}
}
}
}

if (best_name) {
sentry_value_set_by_key(frame, "function",
sentry_value_new_string(best_name));
}
}
}

sentry_free(sym_buf);
sentry_free(strtab_buf);
}

sentry_free(shdr_buf);
close(fd);
return;
}
}

/**
* Capture modules from /proc/<pid>/maps for debug_meta
* This is called from the daemon to populate ctx->modules[] on Linux,
Expand Down
15 changes: 15 additions & 0 deletions src/backends/native/sentry_elf.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ sentry__elf_has_shdr_size(
# endif
}

static inline bool
sentry__elf_has_sym_entsize(
const unsigned char e_ident[EI_NIDENT], size_t sh_entsize)
{
if (!sentry__elf_is_native_class(e_ident)) {
return false;
}

# if defined(__x86_64__) || defined(__aarch64__)
return sh_entsize == sizeof(Elf64_Sym);
# else
return sh_entsize == sizeof(Elf32_Sym);
# endif
}

static inline bool
sentry__elf_has_phdr_size(
const unsigned char e_ident[EI_NIDENT], size_t e_phentsize)
Expand Down
4 changes: 2 additions & 2 deletions tests/test_integration_native.py
Original file line number Diff line number Diff line change
Expand Up @@ -894,7 +894,7 @@ def test_crash_mode_native_only(cmake, httpserver):
for frame in exc["stacktrace"]["frames"]:
assert "instruction_addr" in frame

if sys.platform == "win32":
if sys.platform == "win32" or sys.platform == "linux":
# At least some frames should have symbolicated function names
assert any(
frame.get("function") is not None for frame in exc["stacktrace"]["frames"]
Expand Down Expand Up @@ -941,7 +941,7 @@ def test_crash_mode_native_with_minidump(cmake, httpserver):
assert exc["mechanism"]["type"] == "signalhandler"
assert "stacktrace" in exc
assert len(exc["stacktrace"]["frames"]) > 0
if sys.platform == "win32":
if sys.platform == "win32" or sys.platform == "linux":
# At least some frames should have symbolicated function names
assert any(
frame.get("function") is not None for frame in exc["stacktrace"]["frames"]
Expand Down
Loading