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/sentry_crash_daemon.c b/src/backends/native/sentry_crash_daemon.c index 288866468..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; } 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; +}