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
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
6 changes: 6 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 11 additions & 3 deletions src/backends/native/sentry_crash_daemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1075,6 +1077,12 @@ build_stacktrace_for_thread(
sentry_value_set_by_key(stacktrace, "registers",
build_registers_from_remote_registers(&registers));
}
# 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;
}
Expand Down
66 changes: 66 additions & 0 deletions src/unwinder/sentry_unwinder.c
Original file line number Diff line number Diff line change
@@ -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 <errno.h>
# include <string.h>
# include <sys/ptrace.h>
# include <sys/wait.h>
# include <unistd.h>
#endif

#define DEFINE_UNWINDER(Func) \
size_t sentry__unwind_stack_##Func(void *addr, \
const sentry_ucontext_t *uctx, void **ptrs, size_t max_frames)
Expand Down Expand Up @@ -36,6 +47,7 @@ DEFINE_UNWINDER(psunwind);
} while (0)

DEFINE_THREAD_UNWINDER(libunwind_remote);
DEFINE_THREAD_UNWINDER(libunwindstack_remote);
#endif

static size_t
Expand Down Expand Up @@ -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
2 changes: 2 additions & 0 deletions src/unwinder/sentry_unwinder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
39 changes: 1 addition & 38 deletions src/unwinder/sentry_unwinder_libunwind_remote.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,8 @@

# include "sentry_logger.h"

# include <errno.h>
# include <string.h>
# include <sys/ptrace.h>
# include <sys/wait.h>
# include <unistd.h>

# include <libunwind-ptrace.h>
# include <libunwind.h>
Expand Down Expand Up @@ -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;
}

Expand Down
120 changes: 120 additions & 0 deletions src/unwinder/sentry_unwinder_libunwindstack_remote.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
extern "C" {
#include "sentry_boot.h"
#include "sentry_logger.h"
#include "sentry_unwinder.h"
}

#include <cstdio>
#include <cstring>
#include <memory>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <unwindstack/Maps.h>
#include <unwindstack/Memory.h>
#include <unwindstack/Regs.h>
#include <unwindstack/Unwinder.h>

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
= &registers->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<unwindstack::Regs> 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<unwindstack::Memory> 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;
}
Loading