Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

**Features**:

- Native/Linux: symbolicate stack frames in the crash daemon. ([#1747](https://github.com/getsentry/sentry-native/pull/1747))

**Fixes**:

- 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))
Expand Down
12 changes: 11 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,10 @@ endif()

if(SENTRY_WITH_LIBUNWIND)
if(LINUX)
# Build remote unwinding support for the native backend's crash daemon
if(SENTRY_BACKEND_NATIVE)
set(LIBUNWIND_BUILD_REMOTE TRUE)
endif()
# Use vendored libunwind
add_subdirectory(vendor/libunwind)
target_link_libraries(sentry PRIVATE unwind)
Expand Down Expand Up @@ -835,6 +839,10 @@ elseif(SENTRY_BACKEND_NATIVE)
SENTRY_BUILD_STATIC
)

if(SENTRY_WITH_LIBUNWIND AND LINUX)
target_compile_definitions(sentry-crash PRIVATE SENTRY_WITH_UNWINDER_LIBUNWIND_REMOTE)
endif()

# Copy include directories from sentry target
target_include_directories(sentry-crash PRIVATE
${PROJECT_SOURCE_DIR}/include
Expand Down Expand Up @@ -874,7 +882,9 @@ elseif(SENTRY_BACKEND_NATIVE)
endif()

if(SENTRY_WITH_LIBUNWIND AND LINUX)
target_link_libraries(sentry-crash PRIVATE unwind)
# Use unwind_remote for the daemon (includes ptrace accessors
# for remote DWARF unwinding of the crashed process)
target_link_libraries(sentry-crash PRIVATE unwind_remote)
endif()

# Make sentry library depend on crash daemon so it's always built together
Expand Down
7 changes: 7 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@ elseif(SENTRY_BACKEND_NATIVE)
backends/native/minidump/sentry_minidump_writer.h
)

# Remote unwinding (Linux only — uses libunwind ptrace accessors)
if(LINUX)
sentry_target_sources_cwd(sentry
unwinder/sentry_unwinder_libunwind_remote.c
)
endif()

# Platform-specific minidump writers
if(LINUX OR ANDROID)
sentry_target_sources_cwd(sentry
Expand Down
108 changes: 105 additions & 3 deletions src/backends/native/sentry_crash_daemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
# include <mach-o/dyld.h>
# include <spawn.h>
# endif
# if defined(SENTRY_PLATFORM_LINUX)
# include "unwinder/sentry_unwinder.h"
# endif
#elif defined(SENTRY_PLATFORM_WINDOWS)
# include <dbghelp.h>
# include <fcntl.h>
Expand Down Expand Up @@ -544,6 +547,24 @@ build_registers_from_ctx(const sentry_crash_context_t *ctx, size_t thread_idx)
return registers;
}

#if defined(SENTRY_PLATFORM_LINUX)
static sentry_value_t
build_registers_from_remote_registers(
const sentry_remote_registers_t *remote_registers)
{
sentry_value_t registers = sentry_value_new_object();

for (size_t i = 0; i < remote_registers->count; i++) {
const sentry_remote_register_t *remote_register
= &remote_registers->values[i];
sentry_value_set_by_key(registers, remote_register->name,
sentry__value_new_addr(remote_register->value));
}

return registers;
}
#endif

/**
* Maximum number of frames to unwind
*/
Expand Down Expand Up @@ -900,10 +921,88 @@ build_stacktrace_for_thread(
sentry_value_t temp_frames[MAX_STACK_FRAMES];
int frame_count = 0;

#if defined(SENTRY_PLATFORM_LINUX)
// Remote DWARF unwinding via libunwind ptrace accessors. Do not attach to
// the crashed thread from the daemon; use the saved fault context below.
{
pid_t tid = 0;
if (thread_idx == SIZE_MAX || thread_idx == 0) {
tid = ctx->crashed_tid;
} else if (thread_idx < ctx->platform.num_threads) {
tid = ctx->platform.threads[thread_idx].tid;
}

bool is_crashed_thread
= thread_idx == SIZE_MAX || tid == ctx->crashed_tid;

if (tid > 0 && !is_crashed_thread) {
sentry_remote_registers_t registers = { 0 };
sentry_remote_frame_t *remote_frames
= sentry_malloc(sizeof(*remote_frames) * MAX_STACK_FRAMES);
size_t remote_count = remote_frames
? sentry__unwind_stack_from_thread(
tid, remote_frames, MAX_STACK_FRAMES, &registers)
: 0;

if (remote_count > 0) {
SENTRY_DEBUGF("Remote unwound %zu frames for thread %d",
remote_count, tid);

for (size_t i = 0;
i < remote_count && frame_count < MAX_STACK_FRAMES; i++) {
if (remote_frames[i].ip == 0
|| !is_valid_code_addr(remote_frames[i].ip)) {
continue;
}
temp_frames[frame_count] = sentry_value_new_object();
sentry_value_set_by_key(temp_frames[frame_count],
"instruction_addr",
sentry__value_new_addr(remote_frames[i].ip));
// Trust describes the unwind source, not the emitted
// frame index. If the initial cursor frame is filtered
// out, the next emitted frame was still reached via CFI.
sentry_value_set_by_key(temp_frames[frame_count], "trust",
sentry_value_new_string(i == 0 ? "context" : "cfi"));
Comment thread
sentry[bot] marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.
enrich_frame_with_module_info(
ctx, temp_frames[frame_count], remote_frames[i].ip);
if (remote_frames[i].symbol[0]) {
sentry_value_set_by_key(temp_frames[frame_count],
"function",
sentry_value_new_string(remote_frames[i].symbol));
}
frame_count++;
}

if (frame_count > 0) {
if (stack_buf) {
sentry_free(stack_buf);
}

for (int i = frame_count - 1; i >= 0; i--) {
sentry_value_append(frames, temp_frames[i]);
}
sentry_value_set_by_key(stacktrace, "frames", frames);
if (registers.count > 0) {
sentry_value_set_by_key(stacktrace, "registers",
build_registers_from_remote_registers(&registers));
}
sentry_free(remote_frames);
return stacktrace;
Comment thread
cursor[bot] marked this conversation as resolved.
}
Comment thread
cursor[bot] marked this conversation as resolved.
}

if (remote_frames) {
sentry_free(remote_frames);
}
}
}
// Fall through to pre-captured backtrace or FP-walking if remote
// unwinding failed
#endif

#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID)
// Use pre-captured libunwind backtrace if available (DWARF-based, works
// without frame pointers). This is preferred over FP-based walking for
// the crashed thread.
// Fallback: use pre-captured libunwind backtrace if available
// (DWARF-based, works without frame pointers).
if (ctx->platform.backtrace_count > 0
&& (thread_idx == SIZE_MAX || thread_idx == 0)) {
SENTRY_DEBUGF("Using pre-captured libunwind backtrace (%zu frames)",
Expand All @@ -919,6 +1018,9 @@ build_stacktrace_for_thread(
temp_frames[frame_count] = sentry_value_new_object();
sentry_value_set_by_key(temp_frames[frame_count],
"instruction_addr", sentry__value_new_addr(frame_ip));
// Trust describes the unwind source, not the emitted frame index.
// If the initial cursor frame is filtered out, the next emitted
// frame was still reached via CFI.
sentry_value_set_by_key(temp_frames[frame_count], "trust",
sentry_value_new_string(i == 0 ? "context" : "cfi"));
enrich_frame_with_module_info(
Expand Down
35 changes: 35 additions & 0 deletions src/unwinder/sentry_unwinder.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "sentry_unwinder.h"
#include "sentry_boot.h"

#define DEFINE_UNWINDER(Func) \
Expand All @@ -19,6 +20,24 @@ DEFINE_UNWINDER(libunwind);
DEFINE_UNWINDER(libunwind_mac);
DEFINE_UNWINDER(psunwind);

#if defined(SENTRY_PLATFORM_LINUX)
# define DEFINE_THREAD_UNWINDER(Func) \
size_t sentry__unwind_stack_from_thread_##Func(pid_t tid, \
sentry_remote_frame_t *frames, size_t max_frames, \
sentry_remote_registers_t *registers)

# define TRY_THREAD_UNWINDER(Func) \
do { \
size_t rv = sentry__unwind_stack_from_thread_##Func( \
tid, frames, max_frames, registers); \
if (rv > 0) { \
return rv; \
} \
} while (0)

DEFINE_THREAD_UNWINDER(libunwind_remote);
#endif

static size_t
unwind_stack(
void *addr, const sentry_ucontext_t *uctx, void **ptrs, size_t max_frames)
Expand Down Expand Up @@ -56,3 +75,19 @@ sentry_unwind_stack_from_ucontext(
{
return unwind_stack(NULL, uctx, stacktrace_out, max_len);
}

#if defined(SENTRY_PLATFORM_LINUX)
size_t
sentry__unwind_stack_from_thread(pid_t tid, sentry_remote_frame_t *frames,
size_t max_frames, sentry_remote_registers_t *registers)
{
(void)tid;
(void)frames;
(void)max_frames;
(void)registers;
# ifdef SENTRY_WITH_UNWINDER_LIBUNWIND_REMOTE
TRY_THREAD_UNWINDER(libunwind_remote);
# endif
return 0;
}
#endif
35 changes: 35 additions & 0 deletions src/unwinder/sentry_unwinder.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,43 @@
#ifndef SENTRY_UNWINDER_H_INCLUDED
#define SENTRY_UNWINDER_H_INCLUDED

#include "sentry_boot.h"

#include <stdint.h>

typedef struct {
uintptr_t lo, hi;
} mem_range_t;

#if defined(SENTRY_PLATFORM_LINUX)

# include <stddef.h>
# include <sys/types.h>

# define SENTRY_REMOTE_UNWIND_MAX_REGISTER_NAME 16
# define SENTRY_REMOTE_UNWIND_MAX_REGISTERS 64
# define SENTRY_REMOTE_UNWIND_MAX_SYMBOL 256

typedef struct {
char name[SENTRY_REMOTE_UNWIND_MAX_REGISTER_NAME];
uint64_t value;
} sentry_remote_register_t;

typedef struct {
uint64_t ip;
char symbol[SENTRY_REMOTE_UNWIND_MAX_SYMBOL];
uint64_t symbol_offset;
} sentry_remote_frame_t;

typedef struct {
size_t count;
sentry_remote_register_t values[SENTRY_REMOTE_UNWIND_MAX_REGISTERS];
} sentry_remote_registers_t;

size_t sentry__unwind_stack_from_thread(pid_t tid,
sentry_remote_frame_t *frames, size_t max_frames,
sentry_remote_registers_t *registers);

#endif

#endif // SENTRY_UNWINDER_H_INCLUDED
Loading
Loading