diff --git a/.gitmodules b/.gitmodules index 49db342..5e40bc3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,7 @@ url = https://github.com/ivanarh/libunwind-ndk.git [submodule "external/libunwindstack-ndk"] path = external/libunwindstack-ndk - url = https://github.com/ivanarh/libunwindstack-ndk.git + url = https://github.com/dferret/libunwindstack-ndk.git [submodule "external/libcorkscrew-ndk"] path = external/libcorkscrew-ndk url = https://github.com/ivanarh/libcorkscrew-ndk.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 76d753d..05bfb85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,12 @@ cmake_minimum_required(VERSION 3.4.1) -project(ndcrash) +project(ndcrash C CXX ASM) + +include(GNUInstallDirs) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=implicit-function-declaration -Werror=incompatible-function-pointer-types") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") #For libunwindstack only. +add_definitions(-D_GNU_SOURCE -D_POSIX_C_SOURCE) set(NDCRASH_SOURCE_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/src) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include ${NDCRASH_SOURCE_ROOT}) @@ -98,4 +101,11 @@ else() endif() add_library(ndcrash STATIC ${NDCRASH_SOURCES}) -target_link_libraries(ndcrash ${LINK_LIBRARIES}) \ No newline at end of file +target_link_libraries(ndcrash ${LINK_LIBRARIES}) + +install(TARGETS ndcrash DESTINATION ${CMAKE_INSTALL_LIBDIR}) +install(FILES include/ndcrash.h DESTINATION include/ndcrash) + +configure_file("${PROJECT_SOURCE_DIR}/libndcrash.pc.in" "${PROJECT_BINARY_DIR}/libndcrash.pc") +install(FILES "${PROJECT_BINARY_DIR}/libndcrash.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + diff --git a/README.md b/README.md index ee86909..3751f8c 100644 --- a/README.md +++ b/README.md @@ -1,293 +1,11 @@ -# NDCrash # +# NDCrash fork # **NDCrash** is a powerful crash reporting library for Android NDK applications. The author was inspired by PLCrashReporter and Google Breakpad. Note that this library is new and has a an experimental status. -## Key features ## +Fork from [ivanarh](https://github.com/ivanarh/ndcrash). -* Written in C99 so it may be used in plain C projects. -* On-device stack unwinding. -* [ndk-stack](https://developer.android.com/ndk/guides/ndk-stack.html) compatible human-readable report format. This tool can be easilly used to access line numbers. -* Supports 2 crash handling modes: *in-process* and *out-of-process*. -* Supports 5 different stack unwinders. -* *out-of-process* mode supports stack traces collection of all application threads. -* 32 and 64 bit architectures support (depends on unwinder). -* Easy-to-use Java wrapper https://github.com/ivanarh/jndcrash -* Minimum tested Android version is 4.0.3 but theoretically it may work even on Android 2.3. +## Added features ## -## Roadmap ## - -These features are not currently implemented but they are in plans. They are likely to be implemented only for out-of-process mode due to in-process mode restrictions. - -* Stack dump. -* Dump of memory around addresses stored in registers. -* Memory map dump. - -## Crash handling modes ## - -NDCrash supports 2 crash handling modes. Mode is a way how and where a crash report data is collected. The same concept is used by Google Breakpad, see its [documentation](https://chromium.googlesource.com/breakpad/breakpad/+show/master/docs/exception_handling.md) -Both of these modes are supported in the library at once but only one of them needs to be activated at run-time (useful for A/B testing). Also any of them can be disabled by compilation flags for optimization purposes. - -### In-process mode ### - -In-process means crash report is created within crashing process, it happens in a signal handler. In most cases this approach works but there are 2 major problems when you use this mode: *signal safety* and *stack restrictions*. It means you should prefer *out-of-process* mode if it's possible. - -#### Signal safety #### - -In general signal handler requires its code to be *signal safe*. It's safe to run only a very limited set of functions, see [this man page](http://man7.org/linux/man-pages/man7/signal-safety.7.html). More details could be read in [glibc documentation](http://www.gnu.org/software/libc/manual/html_node/Defining-Handlers.html). -A lot of functions habitual for each and every developer are not signal safe, for example heap memory allocation by malloc/free. The worst case that may happen if your handler isn't signal safe is a deadlock during signal handler execution, in such event a crash report won't be created and user will have to destroy your application explicitly. -But it doesn't mean that these stuff couldn't be used in signal handler for crash reporting, because an application process will be terminated anyway after signal handler execution and all we need is to create a report. Therefore, a good idea is to minimize unsafe stuff usage in order to make a crash reporter work properly for most part of crashes. - -#### Stack restrictions #### - -Starting from Android 4.4 bionic uses an alternative stack for signal handlers. See [bionic source](https://android.googlesource.com/platform/bionic/+/kitkat-dev/libc/bionic/pthread_create.cpp) and [sigaltstack documentation](http://man7.org/linux/man-pages/man2/sigaltstack.2.html). This stack has fixed size, by default SIGSTKSZ constant value is used (8 kilobytes on 32-bit ARM Platform). -It's very useful feature when a crash due to stack overflow happens. However, this stack size could be insufficient because heap allocations are not safe and you are forced to allocate a memory on a stack. For example, libunwind's unw_cursor_t has a huge size (4 kilobytes) and it's a very big trade-off where to allocate a memory for it. Of course, some static buffer may be used but it's not thread safe, signal handlers may execute concurrently for different threads. Libunwind provides a special "memory pool" mechanism for this case. -A workaround for this problem is possible: you can allocate a stack of any size and set it by sigaltstack function. But it should be done for every thread of your application, so some wrapper around pthread is required. - -### Out-of-process mode ### - -Out-of-process means crash report is generated in a proces other than crashing. This is possible due to [ptrace](http://man7.org/linux/man-pages/man2/ptrace.2.html) system call that allows some process to inspect a state of another process. Originally out-of-process mode is used by Android system debugger (debuggerd). - -When **NDCrash** works in out-of-process mode it has 2 different parts: - -* **Crash reporting daemon**. It's a special service with **android:process** attribute in its manifest definition, see [service tag documentation](https://developer.android.com/guide/topics/manifest/service-element.html#proc). It will make it run in a separate process with own PID and address space. This daemon plays a role of debugger, so later "debugger" word will be used with the same meaning. -* **Signal handler**. It's run in the main application process. It's a very simple and lightweight: all it should done is to communicate with debugger and wait until crash report is generated. - -#### Out-of-process mode flow #### - -Details how out-of-process mode works are described below: - -* When a daemon is started it opens a listening UNIX domain socket which allows crashing process to communicate with. It remains in sleeping state until crash happens or explicit stop is requested. -* When a crash happens a signal handler within crashing process is executed. Which in turn connects to a listening UNIX domain socket previously opened by daemon. Then a handler sends some data about a crash (pid, tid, register values) to debugger. This data is necessary to generate a crash report. After that handler sleeps by blocking "recv" operation (waits for a response from daemon). -* Daemon receives data from crashing app and attaches to it by ptrace mechanism. At this point daemon has access to a state of crashing process. -* Daemon generates a crash report, by default it's saved to a file and written to logcat. Crash report generation includes **stack unwinding** operation, see information below. -* After a crash report is generated daemon sends one byte response to a socket, closes it (disconnects) and starts listening for another connection. -* A crashing process receives this byte (recv operation wakes), restores a previous signal handler (that was set by bionic library) and re-raises a signal. - -A restoration of previous signal handler is necessary to preserve operating status of standard Android debugger (debuggerd), the bionic library registers this handler in order to initiate crash report generation by debuggerd. This is because we can't obtain registers state for stack unwinding by ptrace (we send it by a socket). To do this we would install a default signal handler (SIG_DFL) and re-raise a signal. This is exactly how google breakpad behaves and broken debuggerd is one of big disadvantages of this crash reporting library. - -## Stack unwinding ## - -The most interesting information in a report is, of course, a **backtrace**, also known as **stack trace**. At the same time, obtaining of this data is the most difficult task during crash report generation. To obtain a backtrace we need to analyze a stack data by walking through all **stack frames**. This process is called **stack unwinding**. -There are several ways how to perform stack unwinding, also different third party code may be used for this purpose. This led to support of different **stack unwinders** in NDCrash library. Each unwinder is a module within the library that provides a code that unwinds a stack and writes a backtrace to a crash report. All unwinders are supported by library at one but only one of them should be selected in the moment of library initialization. This may be useful for A/B testing. - -### Ways to unwind a stack ### - -Stack may be unwound using different algorithms. The main challenge for them in crash reporting is to analyze stack data, determine bounds of every stack frame and extract all return addresses from it. Stack data may be easily accessed by reading memory at an address from "stack pointer" register. - -- *Full stack scanning.* This is the most inaccurate unwinding algorithm: taking every stack element (machine word) and searching a function with this address. If it's found, adding it to a backtrace. Used by [Bugsnag SDK](https://github.com/bugsnag/bugsnag-android-ndk/blob/989f3410f87c4d578bbfb264bab8510995038216/ndk/src/main/jni/bugsnag_unwind.c). -- *DWARF call frame information data.* Located in ELF section **.eh_frame** or .debug_frame, used on most processor architectures for C++ exceptions handling and by debuggers, such as gdb and lldb. See [documentation, chapter 6.4](http://www.dwarfstd.org/doc/DWARF4.pdf). -- *ARM Exception Tables.* This is a replacement of DWARF call frame information data for 32-bit ARM architecture, locates in ELF section **.ARM.extab**. The same binary may contain both .ARM.extab and .debug_frame sections but the second only used by debuggers and doesn't used during C++ exceptions handling, it's also stripped on release builds. See [documentation](http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038b/IHI0038B_ehabi.pdf) about this tables. - -On some architectures that not currently supported by NDK (such as MIPS) proper stack unwinding is possible without information in additional sections. These architectures are not supported by NDCrash and it doesn't make sense to mention them. - -Details about each unwinder supported in NDCrash are described below. - -### "cxxabi" unwinder ### - -It uses standard C++ library facilities to unwind a stack (the same functionality is used during C++ exception handling). Specifically, it uses *_Unwind_Backtrace* and *_Unwind_GetIP* functions to unwind a stack plus POSIX *dladdr* function to obtain an information about module and function. - -**Supported processor architectures:** All. - -**Ways to unwind a stack:** ARM Exception Tables on 32-bit ARM, DWARF .eh_frame on another architectures. - -**Supported modes:** In-process only. - -**Advantages:** Simple, doesn't require any 3rd party code. - -**Disadvantages:** It's impossible to set an initial processor state to unwind a stack. On Android <= 5.0 on ARM architecture it can't unwind a stack properly due to bug in bionic, see [this commit](https://android.googlesource.com/platform/bionic/+/5054e1a121fc5aca814815625fe230c4a8abd5a5). On newer versions of Android it will lead to surplus lines in backtrace that are not related to crash itself. - -### "libcorkscrew" unwinder ### - -It uses obsolete [libcorkscrew](https://android.googlesource.com/platform/system/core/+/kitkat-dev/libcorkscrew/) library from Android sources that was used by debuggerd on Android 4.1 - 4.4 versions. To make it work on any Android version it's linked statically. A special [fork](https://github.com/ivanarh/libcorkscrew-ndk) with patches to build with NDK toolchain is used. - -**Language:** C89 - -**Supported processor architectures:** ARM, x86 - -**Ways to unwind a stack:** ARM Exception Tables on ARM, DWARF .eh_frame on x86. - -**Supported modes:** In-process & Out-of-process. - -**Advantages:** A very tiny size. - -**Disadvantages:** Obsolete. Lack of 64-bit architectures support. - -### "libunwind" unwinder ### - -Uses [android fork](https://android.googlesource.com/platform/external/libunwind/) of [libunwind](https://www.nongnu.org/libunwind/) library that was used as a replacement for libcorkscrew since Android 5.0. Like for libcorkscrew, some patches has been applied to make build with standard NDK toolchain possible, fork with patches is [here](https://github.com/ivanarh/libunwind-ndk). - -**Supported processor architectures:** All supported by NDK plus some extra. - -**Language:** C89 - -**Ways to unwind a stack:** DWARF on all architectures, ARM Exception Tables on ARM. - -**Supported modes:** In-process & Out-of-process - -**Advantages:** Powerful, a lot of supported architectures, stable. - -**Disadvantages:** It's going to be obsolete in newer Android versions because new **libunwindstack** library is actively developed. - -### "libunwind" unwinder ### - -This library is being actively developed especially for Android, you can obtain a source code [here](https://android.googlesource.com/platform/system/core/+/master/libunwindstack/). This is going to be a replacement for libunwnd in modern Android versions. - -**Supported processor architectures:** All supported by NDK. - -**Language:** C++11 - -**Ways to unwind a stack:** DWARF on all architectures, ARM Exception Tables on ARM. - -**Supported modes:** In-process & Out-of-process - -**Advantages:** Powerful, modern, actively developed. - -**Disadvantages:** Unstable. Requires massive C++11 standard library, so it's not a good solution for plain C projects. - -### "stackscan" unwinder ### - -The most simple unwinder. Not recommended to use in general cases. - -**Supported processor architectures:** All supported by NDK. - -**Ways to unwind a stack:** Full stack scanning. - -**Supported modes:** In-process only. - -**Advantages:** Doesn't require additional sections such as .ARM.extab or .eh_frame, so they can be stripped. - -**Disadvantages:** Inaccurate results, not optimal. - -## Integration ## - -For easier integration you can use [java wrapper](https://github.com/ivanarh/jndcrash) - -Current build system uses CMake and builds **NDCrash** as a static library. It's a recommended way to use. A good idea is to add its source code as a submodule. Assuming NDCrash submodule is checked out to *libndcrash* subdirectory in the same directory where CMakeLists file is located, you need to add a subdirectory with **NDCrash** library, add its "include" directory to include paths and use this static library. Please add these lines to your CMakeLists.txt: - -``` -add_subdirectory(libndcrash) -include_directories(${CMAKE_SOURCE_DIR}/libndcrash/include) -... -find_library(LOG_LIB log) -target_link_libraries(myproject ndcrash ${LOG_LIB}) -``` -Please note that **log** library linkage is required. - -## Customization ## - -For optimization purposes you can turn on or turn off unused modules or library usage mode (in-process or out-of-process). The following CMake variables are used, all of these are boolean-typed. Absense of variable is treated as false. - -- **ENABLE_INPROCESS** Enables in-process mode for a library. -- **ENABLE_OUTOFPROCESS** Enables in-process mode for a library. -- **ENABLE_OUTOFPROCESS_ALL_THREADS** Enables all threads unwinding for in-process mode. Ignored if out-process-mode is disabled. -- **ENABLE_LIBCORKSCREW** Enables "libcorkscrew" unwinder. -- **ENABLE_LIBUNWIND** Enables "libunwind" unwinder. -- **ENABLE_LIBUNWINDSTACK** Enables "libunwindstack" unwinder. -- **ENABLE_CXXABI** Enables "cxxabi" unwinder. -- **ENABLE_STACKSCAN** Enables "stackscan" unwinder. - -Note that it's possible to build a library with all flags set to "false", in this case it would return error on initialization. - -A good place to set this variables is *build.gradle* file of a module where NDK library is built: -``` - externalNativeBuild { - cmake { - arguments "-DANDROID_STL=c++_static", - "-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON", - "-DENABLE_LIBCORKSCREW:BOOL=ON", - "-DENABLE_LIBUNWIND:BOOL=ON", - "-DENABLE_LIBUNWINDSTACK:BOOL=ON", - "-DENABLE_CXXABI:BOOL=ON", - "-DENABLE_STACKSCAN:BOOL=ON", - "-DENABLE_INPROCESS:BOOL=ON", - "-DENABLE_OUTOFPROCESS:BOOL=ON" - "-DEENABLE_OUTOFPROCESS_ALL_THREADS:BOOL=ON" - } - } - ndk { - abiFilters "x86", "armeabi-v7a", "x86_64", "arm64-v8a" - } -``` -Also in this place you can customize processor architectures for what a library should be built. **NDCrash** supports 32-bit and 64-bit ARM and x86 architectures. - -## Usage ## - -All public API of this library is located in a single header file, to use it please add to your sources: `include ` -To start using library you need to **initialize** it. **NDCrash** requires 2 parameters to be set in both modes: -- Unwinder. **NDCrash** library may be built with multiple unwinders simultaneously and an application code should determine what unwinder it will use. For example, different unwinders may be used for different operating system versions or processor architectures. Also A/B testing is possible in order to determine what unwinder could produce a better result. If a specified unwinder is disabled on compile time an initialization function will return an error. -- Crash report output file full path. **NDCrash** doesn't take care what file name to use to write a crash report when a crash happens. It may be either a same file for different launches or random-generated file name. If this file exists it will be overwritten. A path should follow these requirements: - - A directory where this file is going to be created should exist. NDCrash won't create a directory in this case. - - An application should have permissions to create and write this file. - -It's an application developer responsibility to process this file in some way: send it to a server, offer a user to write a letter to developers, etc. Typically it may be done on a next application launch after a crash (a report existence may be an indicator whether a last launch has finished by crash). But out-of-process mode supports a special callback that could be executed straight after crash report generation, it's done in background service process and allows to send a report to a server immediately (but don't forget that in this case user interaction abilities are very limited). - -Please note that **NDCrash** may be initialized only once, a repeated initialization will return an error. An initialization is done for a whole process and this operation is *not reentrant*. A good place for it is before any initialization code of application, for example `Application.onCreate` Java method or `JNI_OnLoad` function, before any background thread is created. - -For examples you can take a look at [java wrapper source code](https://github.com/ivanarh/jndcrash). - -### In-process ### - -For this mode you should only provide an unwinder enum value and full path to a crash reprot. For example, initialization with "libunwind" unwinder: - -``` - const char *crashReportPath = ... - const enum ndcrash_error error = ndcrash_in_init(ndcrash_unwinder_libunwind, crashReportPath); - if (error == ndcrash_ok) { - // Initialization is successful. - } else { - // Initialization failed, check error value. - } -``` -It will set up a signal handler that generates a crash report. In case when an old signal handler should be restored please use `ndcrash_in_deinit()` function, it doesn't have any arguments. - -### Out-of-process ### - -An initialization in this mode is quite more difficult: we should initialize 2 components that are run in different processes: -- Main process signal handler. -- Background process crash reporting daemon. - -An UNIX domain socket is used by crashing process to communicate with daemon. To identify this socket **NDCrash** requires to specify one parameter yet: a special string called *socket name*. It should be unique for all applications that are installed on a device, so it's a good idea is to include an application package name to a socket name. - -#### Main process signal handler initialization #### - -As a signal handler in out-of-process mode doesn't generate a crash report, an unwinder and crash report path are not used for initialization. All you need is to specify a socket name: -``` - const char *socket_name = ... - const enum ndcrash_error error = ndcrash_out_init(socket_name); - if (error == ndcrash_ok) { - // Initialization is successful. - } else { - // Initialization failed, check error value. - } -``` - -#### Background process crash reporting daemon initialization #### - -For this mode you should only provide a crash report path, an unwinder enum value and full path to a crash reprot. For example, initialization with "libunwind" unwinder: - -``` - const char *socket_name = ... - const char *crashReportPath = ... - const enum ndcrash_error error = ndcrash_in_init( - socket_name, - ndcrash_unwinder_libunwind, - crashReportPath, - NULL, - NULL, - NULL, - NULL); - if (error == ndcrash_ok) { - // Initialization is successful. - } else { - // Initialization failed, check error value. - } -``` - -The last 4 parameters of `ndcrash_in_init` that have NULL values are optional, this is daemon lifecycle callbacks that may be used to run a special code when crash happens. For example, we can send a report to server in a crash callback, it's called immediately after crash happens. For details please take a look at `jndcrash.c` source file. -All callbacks are run in background thread of crash reporting service process. The following callbacks can be set: - -- Daemon start callback. It's run when a daemon is successfully started, when listening socket is bound before waiting for a new client is started. It may be used to attach this background thread to JNI in order to make JNI calls possible from crash callback. -- Crash callback. Called when a report is generated. A crash report path is passed to this callback as an argument. -- Daemon stop callback. Useful to detach a background thread from JNI. - -Also 4th argument may be set: it's an auxiliary argument that is saved inside a library and passed to all callbacks. This argument can be obtained at any time by `ndcrash_out_get_daemon_callbacks_arg()` function. \ No newline at end of file +* Added ndcrash_in_dump_backtrace : Dump current backtrace into file using unwinder (only libunwindstack supported). +* Added ndcrash_out_trigger_dump : Triggers a manual dump of thread states. +* Added back Linux/x86 support for libunwindstack (tested on Debian). diff --git a/external/libunwindstack-ndk b/external/libunwindstack-ndk index 0717e9c..407a4cf 160000 --- a/external/libunwindstack-ndk +++ b/external/libunwindstack-ndk @@ -1 +1 @@ -Subproject commit 0717e9c849655f661ad303d8bff61ceb796505bd +Subproject commit 407a4cfb3d237a2ce02619bfbf81329bf4b5a88e diff --git a/include/ndcrash.h b/include/ndcrash.h index 3f32e35..8c1e291 100644 --- a/include/ndcrash.h +++ b/include/ndcrash.h @@ -3,6 +3,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + /** * Enum representing supported unwinders for stack unwinding. */ @@ -45,6 +49,12 @@ enum ndcrash_error { /// A background out-of-process service has failed to start. ndcrash_error_service_start_failed, + + /// Socket communication with out-of-process daemon failed + ndcrash_error_service_communication_failed, + + // Cannot create dump file + ndcrash_file_inaccessible, }; /** @@ -56,6 +66,14 @@ enum ndcrash_error { */ enum ndcrash_error ndcrash_in_init(const enum ndcrash_unwinder unwinder, const char *log_file); +/** + * Dump current backtrace into file using unwinder (only libunwindstack supported) + * @param unwinder libunwindstack + * @param log_file Path to backtrace report file where to write it. + * @return generation result + */ +enum ndcrash_error ndcrash_in_dump_backtrace(const enum ndcrash_unwinder unwinder, const char *log_file); + /** * De-initialize crash reporting library in in-process mode. This call will restore previous signal * handlers used for crash reporting. @@ -132,6 +150,19 @@ bool ndcrash_out_stop_daemon(); * Should be called before ndcrash_out_stop_daemon. * @return Argument value. */ -void * ndcrash_out_get_daemon_callbacks_arg(); +void *ndcrash_out_get_daemon_callbacks_arg(); + + +/** + * Triggers a manual dump of thread states + * @return dump creation result + */ +enum ndcrash_error ndcrash_out_trigger_dump(); + +#ifdef __cplusplus +#include +extern "C" void ndcrash_dump_backtrace(std::string &out); +} +#endif // __cplusplus #endif //NDCRASHDEMO_NDCRASH_H diff --git a/libndcrash.pc.in b/libndcrash.pc.in new file mode 100644 index 0000000..10c70dc --- /dev/null +++ b/libndcrash.pc.in @@ -0,0 +1,5 @@ +Name: Ndcrash +Version: @NDCRASH_VERSION@ +Description: NDCRASH Library +Libs: -L${CMAKE_INSTALL_FULL_LIBDIR} -lndcrash -lunwindstack -llzma ${EXTRA_LDFLAGS} +Cflags: -I${CMAKE_INSTALL_PREFIX}/include diff --git a/src/ndcrash_dump.c b/src/ndcrash_dump.c index e08493e..bfeb436 100644 --- a/src/ndcrash_dump.c +++ b/src/ndcrash_dump.c @@ -6,12 +6,16 @@ #include #include #include -#include #include #include #include +#ifdef ANDROID #include +#else +#define PROP_VALUE_MAX 256 +#endif #include +#include #include #if __LP64__ @@ -67,13 +71,20 @@ void ndcrash_dump_write_line(int fd, const char *format, ...) { char buffer[NDCRASH_LOG_BUFFER_SIZE]; // First writing to a log as is. +#ifdef ANDROID { va_list args; va_start(args, format); __android_log_vprint(ANDROID_LOG_ERROR, NDCRASH_LOG_TAG, format, args); va_end(args); } - +#else + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + va_end(args); +#endif // Writing file to log may be disabled. if (fd <= 0) return; @@ -193,14 +204,19 @@ static inline void ndcrash_dump_signal_info( str_buffer); } -void ndcrash_dump_header(int outfile, pid_t pid, pid_t tid, int signo, int si_code, void *faultaddr, - struct ucontext *context) { +/** + * Writes a process information line to a crash report. + * @param outfile Output file descriptor for a crash report. + * @param pid main process id + * @param tid main thread id + * @param str_buffer A buffer where a fault address is written. Passing as an argument to reduce a stack usage. + * @param str_buffer_size A size of passed process_name_buffer in bytes. + */ +static void ndcrash_dump_process_header(int outfile, pid_t pid, pid_t tid, char *str_buffer, size_t str_buffer_size) { // A special marker of crash report beginning. ndcrash_dump_write_line(outfile, "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***"); - // This buffer we use to read data from system properties and to read other data from files. - char str_buffer[PROP_VALUE_MAX]; - +#ifdef ANDROID { // Getting system properties and writing them to report. __system_property_get("ro.build.fingerprint", str_buffer); @@ -208,6 +224,7 @@ void ndcrash_dump_header(int outfile, pid_t pid, pid_t tid, int signo, int si_co __system_property_get("ro.revision", str_buffer); ndcrash_dump_write_line(outfile, "Revision: '0'"); } +#endif // Writing processor architecture. #ifdef __arm__ @@ -221,7 +238,17 @@ void ndcrash_dump_header(int outfile, pid_t pid, pid_t tid, int signo, int si_co #endif // Writing a line about process and thread. Re-using str_buffer for a process name. - ndcrash_write_process_and_thread_info(outfile, pid, tid, str_buffer, sizeofa(str_buffer)); + ndcrash_write_process_and_thread_info(outfile, pid, tid, str_buffer, str_buffer_size); + +} + +void ndcrash_dump_header(int outfile, pid_t pid, pid_t tid, int signo, int si_code, void *faultaddr, + struct ucontext *context) { + // This buffer we use to read data from system properties and to read other data from files. + char str_buffer[PROP_VALUE_MAX]; + + // Write process header + ndcrash_dump_process_header(outfile, pid, tid, str_buffer, sizeofa(str_buffer)); // Writing an information about signal. ndcrash_dump_signal_info(outfile, signo, si_code, faultaddr, str_buffer, sizeofa(str_buffer)); @@ -293,6 +320,14 @@ void ndcrash_dump_header(int outfile, pid_t pid, pid_t tid, int signo, int si_co ndcrash_write_backtrace_title(outfile); } +void ndcrash_dump_short_header(int outfile, pid_t pid, pid_t tid) { + // This buffer we use to read data from system properties and to read other data from files. + char str_buffer[PROP_VALUE_MAX]; + + // Write process header + ndcrash_dump_process_header(outfile, pid, tid, str_buffer, sizeofa(str_buffer)); +} + /** * Obtains other thread registers by ptrace and dumps them to a report. * @param outfile Output file for a report. diff --git a/src/ndcrash_dump.h b/src/ndcrash_dump.h index a10ccca..e2f5089 100644 --- a/src/ndcrash_dump.h +++ b/src/ndcrash_dump.h @@ -38,6 +38,14 @@ void ndcrash_dump_write_line(int fd, const char *format, ...); void ndcrash_dump_header(int outfile, pid_t pid, pid_t tid, int signo, int si_code, void *faultaddr, struct ucontext *context); +/** + * Write a crash report header to a file and to log. + * @param outfile Output file descriptor for a crash report. + * @param pid process identifier. + * @param tid thread identifier. + */ +void ndcrash_dump_short_header(int outfile, pid_t pid, pid_t tid); + /** * Write an other thread info (which is not crashed) to a file and to a log. * @param outfile Output file descriptor for a crash report. diff --git a/src/ndcrash_fd_utils.c b/src/ndcrash_fd_utils.c index 0f93682..602accc 100644 --- a/src/ndcrash_fd_utils.c +++ b/src/ndcrash_fd_utils.c @@ -1,7 +1,6 @@ #include "ndcrash_fd_utils.h" #include "ndcrash_log.h" #include -#include #include #include #include diff --git a/src/ndcrash_in.c b/src/ndcrash_in.c index b705985..4f45d33 100644 --- a/src/ndcrash_in.c +++ b/src/ndcrash_in.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #ifdef ENABLE_INPROCESS @@ -30,7 +31,7 @@ struct ndcrash_in_context { struct ndcrash_in_context *ndcrash_in_context_instance = NULL; /// Main signal handling function. -void ndcrash_in_signal_handler(int signo, struct siginfo *siginfo, void *ctxvoid) { +void ndcrash_in_signal_handler(int signo, siginfo_t *siginfo, void *ctxvoid) { // Restoring an old handler to make built-in Android crash mechanism work. sigaction(signo, &ndcrash_in_context_instance->old_handlers[signo], NULL); @@ -124,6 +125,37 @@ enum ndcrash_error ndcrash_in_init(const enum ndcrash_unwinder unwinder, const c return ndcrash_ok; } +enum ndcrash_error ndcrash_in_dump_backtrace(const enum ndcrash_unwinder unwinder, const char *log_file) { + int outfile = 0; + outfile = ndcrash_dump_create_file(log_file); + if (outfile <= 0) { + return ndcrash_file_inaccessible; + } + + // Dumping header of a crash dump. + ndcrash_dump_short_header(outfile, getpid(), gettid()); + + // Calling unwinding function. + switch (unwinder) { +#ifdef ENABLE_LIBUNWINDSTACK + case ndcrash_unwinder_libunwindstack: + ndcrash_in_unwind_libunwindstack_local_btdump(outfile); + break; +#endif + default: + break; + } + + // Final new line of crash dump. + ndcrash_dump_write_line(outfile, " "); + + // Closing an output file. + if (outfile) { + close(outfile); + } + return ndcrash_ok; +} + bool ndcrash_in_deinit() { if (!ndcrash_in_context_instance) return false; ndcrash_unregister_signal_handler(ndcrash_in_context_instance->old_handlers); diff --git a/src/ndcrash_log.h b/src/ndcrash_log.h index c87b23b..1f16ac0 100644 --- a/src/ndcrash_log.h +++ b/src/ndcrash_log.h @@ -1,6 +1,5 @@ #ifndef NDCRASHDEMO_NDCRASH_LOG_H #define NDCRASHDEMO_NDCRASH_LOG_H -#include #ifndef NDCRASH_LOG_TAG #define NDCRASH_LOG_TAG "NDCRASH" @@ -9,7 +8,17 @@ #ifdef NDCRASH_NO_LOG #define NDCRASHLOG(level, ...) #else +#ifdef ANDROID +#include #define NDCRASHLOG(level, ...) __android_log_print(ANDROID_LOG_##level, NDCRASH_LOG_TAG, __VA_ARGS__) +#else +#include +#define NDCRASHLOG(level, ...) do { \ + fprintf(stderr, NDCRASH_LOG_TAG": "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ +} while (0) +#endif #endif #endif //NDCRASHDEMO_NDCRASH_LOG_H diff --git a/src/ndcrash_out.c b/src/ndcrash_out.c index 1d90068..e2200e1 100644 --- a/src/ndcrash_out.c +++ b/src/ndcrash_out.c @@ -7,14 +7,13 @@ #include #include #include -#include #include #include #include #include #include #include - +#include #ifdef ENABLE_OUTOFPROCESS struct ndcrash_out_context { @@ -33,35 +32,15 @@ struct ndcrash_out_context { /// Global instance of out-of-process context. struct ndcrash_out_context *ndcrash_out_context_instance = NULL; -/// Signal handling function for out-of-process architecture. -void ndcrash_out_signal_handler(int signo, struct siginfo *siginfo, void *ctxvoid) { - // Restoring an old handler to make built-in Android crash mechanism work. - sigaction(signo, &ndcrash_out_context_instance->old_handlers[signo], NULL); - - // Filling message fields. - struct ndcrash_out_message msg; - msg.pid = getpid(); - msg.tid = gettid(); - msg.signo = signo; - msg.si_code = siginfo->si_code; - msg.faultaddr = siginfo->si_addr; - memcpy(&msg.context, ctxvoid, sizeof(struct ucontext)); - - NDCRASHLOG( - ERROR, - "Signal caught: %d (%s), code %d (%s) pid: %d, tid: %d", - signo, - ndcrash_get_signame(signo), - siginfo->si_code, - ndcrash_get_sigcode(signo, siginfo->si_code), - msg.pid, - msg.tid); - +/// Send message +int ndcrash_out_send_message(struct ndcrash_out_message *msg) { // Connecting to service using UNIX domain socket, sending message to it. + int r = 0; // Using blocking sockets! const int sock = socket(PF_LOCAL, SOCK_STREAM, 0); if (sock < 0) { NDCRASHLOG(ERROR,"Couldn't create socket, error: %s (%d)", strerror(errno), errno); + r = -1; } else { // Connecting. if (connect( @@ -69,17 +48,20 @@ void ndcrash_out_signal_handler(int signo, struct siginfo *siginfo, void *ctxvoi (struct sockaddr *) &ndcrash_out_context_instance->socket_address, sizeof(struct sockaddr_un))) { NDCRASHLOG(ERROR, "Couldn't connect socket, error: %s (%d)", strerror(errno), errno); + r = -2; } else { // Sending. - const ssize_t sent = send(sock, &msg, sizeof(msg), MSG_NOSIGNAL); + const ssize_t sent = send(sock, msg, sizeof(*msg), MSG_NOSIGNAL); if (sent < 0) { NDCRASHLOG(ERROR, "Send error: %s (%d)", strerror(errno), errno); - } else if (sent != sizeof(msg)) { + r = -3; + } else if (sent != sizeof(*msg)) { NDCRASHLOG( ERROR, "Error: couldn't send whole message, sent bytes: %d, message size: %d", (int) sent, - (int) sizeof(msg)); + (int) sizeof(*msg)); + r = -4; } else { NDCRASHLOG(INFO, "Successfuly sent data to crash service."); } @@ -88,12 +70,42 @@ void ndcrash_out_signal_handler(int signo, struct siginfo *siginfo, void *ctxvoi char c = 0; if (recv(sock, &c, 1, MSG_NOSIGNAL) < 0) { NDCRASHLOG(ERROR, "Recv error: %s (%d)", strerror(errno), errno); + r = -5; } } // Closing a socket. close(sock); } + return r; +} + +/// Signal handling function for out-of-process architecture. +void ndcrash_out_signal_handler(int signo, siginfo_t *siginfo, void *ctxvoid) { + // Restoring an old handler to make built-in Android crash mechanism work. + sigaction(signo, &ndcrash_out_context_instance->old_handlers[signo], NULL); + + // Filling message fields. + struct ndcrash_out_message msg; + msg.pid = getpid(); + msg.tid = gettid(); + msg.signo = signo; + msg.si_code = siginfo->si_code; + msg.faultaddr = siginfo->si_addr; + memcpy(&msg.context, ctxvoid, sizeof(struct ucontext)); + + NDCRASHLOG( + ERROR, + "Signal caught: %d (%s), code %d (%s) pid: %d, tid: %d", + signo, + ndcrash_get_signame(signo), + siginfo->si_code, + ndcrash_get_sigcode(signo, siginfo->si_code), + msg.pid, + msg.tid); + + // Send message on socket + ndcrash_out_send_message(&msg); // In some cases we need to re-send a signal to run standard bionic handler. if (siginfo->si_code <= 0 || signo == SIGABRT) { @@ -103,6 +115,26 @@ void ndcrash_out_signal_handler(int signo, struct siginfo *siginfo, void *ctxvoi } } +enum ndcrash_error ndcrash_out_trigger_dump() { + // Filling message fields. + struct ndcrash_out_message msg; + msg.pid = getpid(); + msg.tid = gettid(); + msg.signo = 0; + + NDCRASHLOG( + ERROR, + "Dump: pid: %d, tid: %d", + msg.pid, + msg.tid); + + // Send message on socket + if (ndcrash_out_send_message(&msg) < 0) { + return ndcrash_error_service_communication_failed; + } + return ndcrash_ok; +} + enum ndcrash_error ndcrash_out_init(const char *socket_name) { if (ndcrash_out_context_instance) { return ndcrash_error_already_initialized; @@ -152,4 +184,4 @@ bool ndcrash_out_deinit() { return true; } -#endif //ENABLE_OUTOFPROCESS \ No newline at end of file +#endif //ENABLE_OUTOFPROCESS diff --git a/src/ndcrash_out_daemon.c b/src/ndcrash_out_daemon.c index c6afaf5..ac7cddb 100644 --- a/src/ndcrash_out_daemon.c +++ b/src/ndcrash_out_daemon.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -65,6 +64,7 @@ static const int SOCKET_BACKLOG = 1; */ static bool ndcrash_out_ptrace_attach(pid_t tid) { // Attaching. + //NDCRASHLOG(INFO, "Ptrace attaching to tid:%d", (int) tid); if (ptrace(PTRACE_ATTACH, tid, NULL, NULL) == -1) { NDCRASHLOG(INFO, "Ptrace attach failed to tid: %d errno: %d (%s)", (int) tid, errno, strerror(errno)); return false; @@ -76,6 +76,7 @@ static bool ndcrash_out_ptrace_attach(pid_t tid) { * Detaches from specified thread by ptrace. All errors are ignored. */ static void ndcrash_out_ptrace_detach(pid_t tid) { + //NDCRASHLOG(INFO, "Ptrace detach from tid:%d", (int) tid); ptrace(PTRACE_DETACH, tid, NULL, NULL); } @@ -92,10 +93,11 @@ static bool ndcrash_out_daemon_create_report(struct ndcrash_out_message *message #ifdef ENABLE_OUTOFPROCESS_ALL_THREADS pid_t tids[64]; const size_t tids_size = ndcrash_get_threads(message->pid, tids, sizeofa(tids)); + NDCRASHLOG(INFO, "Client threads: %d", (int)tids_size); for (pid_t *it = tids, *end = tids + tids_size; it != end; ++it) { // Attaching errors for background threads are not fatal, just skipping such threads by // setting tid to 0. - if (!ndcrash_out_ptrace_attach(*it)) { + if ((*it == message->tid) || !ndcrash_out_ptrace_attach(*it)) { *it = 0; } } @@ -164,6 +166,82 @@ static bool ndcrash_out_daemon_create_report(struct ndcrash_out_message *message return outfile >= 0; } +/** + * Creates and fills a new state dump. + * @param message A message received from a signal handler. + * @return Flag if output file for report has been created successfully. + */ +static bool ndcrash_out_daemon_create_dump(struct ndcrash_out_message *message) { + + // Getting not crashed threads list and attaching to all of them. +#ifdef ENABLE_OUTOFPROCESS_ALL_THREADS + pid_t tids[64]; + const size_t tids_size = ndcrash_get_threads(message->pid, tids, sizeofa(tids)); + NDCRASHLOG(INFO, "Client threads: %d", (int)tids_size); + for (pid_t *it = tids, *end = tids + tids_size; it != end; ++it) { + // Attaching errors for background threads are not fatal, just skipping such threads by + // setting tid to 0. + if (!ndcrash_out_ptrace_attach(*it)) { + *it = 0; + } + } +#endif //ENABLE_OUTOFPROCESS_ALL_THREADS + + //Opening output file + int outfile = -1; + if (ndcrash_out_daemon_context_instance->log_file) { + outfile = ndcrash_dump_create_file(ndcrash_out_daemon_context_instance->log_file); + } + + // Writing a crash dump header + ndcrash_dump_short_header( + outfile, + message->pid, + message->tid); + + // Unwinder initialization, should be done before any thread unwinding. + void * const unwinder_data = ndcrash_out_daemon_context_instance->unwinder_init(message->pid); + +#ifdef ENABLE_OUTOFPROCESS_ALL_THREADS + // Processing other threads: printing a header and stack trace. + for (pid_t *it = tids, *end = tids + tids_size; it != end; ++it) { + + // Skipping threads failed to attach. + if (!*it) continue; + + /// Writing other thread header. + ndcrash_dump_other_thread_header(outfile, message->pid, *it); + + // Stack unwinding for a secondary thread. + ndcrash_out_daemon_context_instance->unwind_function(outfile, *it, NULL, unwinder_data); + } +#endif //ENABLE_OUTOFPROCESS_ALL_THREADS + + // Unwinder de-initialization. + ndcrash_out_daemon_context_instance->unwinder_deinit(unwinder_data); + + // Final line of crash dump. + ndcrash_dump_write_line(outfile, " "); + + // Closing output file. + if (outfile >= 0) { + //Closing file + close(outfile); + } + + // Detaching from all threads. +#ifdef ENABLE_OUTOFPROCESS_ALL_THREADS + for (pid_t *it = tids, *end = tids + tids_size; it != end; ++it) { + if (!*it) continue; + ndcrash_out_ptrace_detach(*it); + } +#endif //ENABLE_OUTOFPROCESS_ALL_THREADS + + // Returning a if output file for report has been created successfully. + // Note that outfile is currently closed, we use it only to check if file was created. + return outfile >= 0; +} + /** * Processes a client request: receives a data from client and creates a crash report. * @param clientsock A socket to communicate with a client. @@ -205,8 +283,14 @@ static void ndcrash_out_daemon_process_client(int clientsock) { NDCRASHLOG(INFO, "Client info received, pid: %d tid: %d", message.pid, message.tid); // Creating a report. - const bool report_file_created = ndcrash_out_daemon_create_report(&message); + bool report_file_created; + if (message.signo != 0) { + report_file_created = ndcrash_out_daemon_create_report(&message); + } else { + report_file_created = ndcrash_out_daemon_create_dump(&message); + } + NDCRASHLOG(INFO, "Ack client"); //Write 1 byte as a response. write(clientsock, "\0", 1); diff --git a/src/ndcrash_private.h b/src/ndcrash_private.h index 34c9a16..5c8380d 100644 --- a/src/ndcrash_private.h +++ b/src/ndcrash_private.h @@ -4,6 +4,11 @@ #include #include +#ifndef ANDROID +#include +#define gettid() syscall(SYS_gettid) +#endif + /// Array of constants with signal numbers to catch. static const int SIGNALS_TO_CATCH[] = { SIGABRT, diff --git a/src/ndcrash_signal_utils.c b/src/ndcrash_signal_utils.c index ae3903e..a7cddfa 100644 --- a/src/ndcrash_signal_utils.c +++ b/src/ndcrash_signal_utils.c @@ -162,8 +162,10 @@ const char *ndcrash_get_sigcode(int signo, int code) { return "SI_SIGIO"; case SI_TKILL: return "SI_TKILL"; +#ifdef ANDROID case SI_DETHREAD: return "SI_DETHREAD"; +#endif } return "?"; } @@ -188,4 +190,4 @@ void ndcrash_unregister_signal_handler(struct sigaction old_handlers[NSIG]) { if (!old_handler->sa_handler) continue; sigaction(signo, old_handler, NULL); } -} \ No newline at end of file +} diff --git a/src/ndcrash_signal_utils.h b/src/ndcrash_signal_utils.h index 86214ad..447d9ae 100644 --- a/src/ndcrash_signal_utils.h +++ b/src/ndcrash_signal_utils.h @@ -27,7 +27,7 @@ const char *ndcrash_get_signame(int sig); const char *ndcrash_get_sigcode(int signo, int code); /// Type for signal handling function pointer. Should be the same as declared in sigaction struct. -typedef void (* ndcrash_signal_handler_function) (int, struct siginfo *, void *); +typedef void (* ndcrash_signal_handler_function) (int, siginfo_t *, void *); /** * Registers a passed signal handler saving old handlers to passed array. diff --git a/src/ndcrash_unwinders.h b/src/ndcrash_unwinders.h index 5055432..e2aada7 100644 --- a/src/ndcrash_unwinders.h +++ b/src/ndcrash_unwinders.h @@ -12,6 +12,7 @@ struct ucontext; void ndcrash_in_unwind_libcorkscrew(int outfile, struct ucontext *context); void ndcrash_in_unwind_libunwind(int outfile, struct ucontext *context); void ndcrash_in_unwind_libunwindstack(int outfile, struct ucontext *context); +void ndcrash_in_unwind_libunwindstack_local_btdump(int outfile); void ndcrash_in_unwind_cxxabi(int outfile, struct ucontext *context); void ndcrash_in_unwind_stackscan(int outfile, struct ucontext *context); diff --git a/src/unwinders/libunwindstack/ndcrash_libunwindstack.cpp b/src/unwinders/libunwindstack/ndcrash_libunwindstack.cpp index e8e6193..8604946 100644 --- a/src/unwinders/libunwindstack/ndcrash_libunwindstack.cpp +++ b/src/unwinders/libunwindstack/ndcrash_libunwindstack.cpp @@ -2,12 +2,15 @@ #include "ndcrash_log.h" #include "ndcrash_dump.h" #include "ndcrash_private.h" -#include #include #include #include #include #include +#include +#include +#include +#include extern "C" { @@ -90,6 +93,59 @@ static inline void ndcrash_common_unwind_libunwindstack( NULL, 0); } + // Trying to switch to a next frame. + bool finished = false; + if (!elf->Step(rel_pc, adjusted_rel_pc, map_info->elf_offset, regs.get(), memory.get(), &finished)) { + break; + } + } +} + +static inline void ndcrash_common_unwind_libunwindstack_oss( + std::ostringstream &oss, + const std::unique_ptr ®s, + Maps &maps, + const std::shared_ptr &memory, + bool withDebugData) { + // String for function name. + std::string unw_function_name; + + for (size_t frame_num = 0; frame_num < NDCRASH_MAX_FRAMES; frame_num++) { + oss << " #" << std::setw(2) << std::setfill('0') << frame_num; + // Looking for a map info item for pc on this unwinding step. + MapInfo * const map_info = maps.Find(regs->pc()); + if (!map_info) { + oss << " pc " << std::hex << std::setw(8) << std::setfill('0') << regs->pc(); + oss << " " << std::endl; + break; + } + + // Loading data from ELF + Elf * const elf = map_info->GetElf(memory, withDebugData); + if (!elf) { + oss << " pc " << std::hex << std::setw(8) << std::setfill('0') << regs->pc(); + oss << " " << (map_info->name.empty() ? "" : map_info->name) << std::endl; + break; + } + + // Getting value of program counter relative module where a function is located. + const uint64_t rel_pc = elf->GetRelPc(regs->pc(), map_info); + uint64_t adjusted_rel_pc = rel_pc; + if (frame_num != 0) { + // If it's not a first frame we need to rewind program counter value to previous instruction. + // For the first frame pc from ucontext points exactly to a failed instruction, for other + // frames rel_pc will contain return address after function call instruction. + adjusted_rel_pc -= regs->GetPcAdjustment(rel_pc, elf); + } + + oss << " pc " << std::hex << std::setw(8) << std::setfill('0') << rel_pc; + oss << " " << (map_info->name.empty() ? "" : map_info->name); + // Getting function name and writing value to a log. + uint64_t func_offset = 0; + if (elf->GetFunctionName(rel_pc, &unw_function_name, &func_offset)) { + oss << " (" << unw_function_name << "+" << func_offset << ")"; + } + oss << std::endl; // Trying to switch to a next frame. bool finished = false; @@ -122,6 +178,44 @@ void ndcrash_in_unwind_libunwindstack(int outfile, struct ucontext *context) { false); } +void ndcrash_in_unwind_libunwindstack_local_btdump(int outfile) { + // Initializing /proc/self/maps cache. + LocalMaps maps; + if (!maps.Parse()) { + NDCRASHLOG(ERROR, "libunwindstack: failed to parse local /proc/pid/maps."); + return; + } + // Unwinding stack. + const std::shared_ptr memory(new MemoryLocal); + // GNU debug symbols usage is disabled, it's quite expensive and unwinding may fail because + // in signal handler we have a very limited stack size. + std::unique_ptr regs(Regs::CreateFromLocal()); + unwindstack::RegsGetLocal(regs.get()); + ndcrash_common_unwind_libunwindstack(outfile, regs, maps, memory, false); +} + +void ndcrash_dump_backtrace(std::string &out) { + std::ostringstream oss; + // Initializing /proc/self/maps cache. + LocalMaps maps; + if (!maps.Parse()) { + NDCRASHLOG(ERROR, "libunwindstack: failed to parse local /proc/pid/maps."); + return; + } + // Unwinding stack. + const std::shared_ptr memory(new MemoryLocal); + // GNU debug symbols usage is disabled, it's quite expensive and unwinding may fail because + // in signal handler we have a very limited stack size. + std::unique_ptr regs(Regs::CreateFromLocal()); + unwindstack::RegsGetLocal(regs.get()); + + oss << "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***" << std::endl; + oss << "pid: " << getpid() << ", tid: " << gettid() << std::endl; + ndcrash_common_unwind_libunwindstack_oss(oss, regs, maps, memory, false); + oss << std::endl; + out = oss.str(); +} + #endif //ENABLE_INPROCESS #ifdef ENABLE_OUTOFPROCESS @@ -157,4 +251,4 @@ void ndcrash_out_unwind_libunwindstack(int outfile, pid_t tid, struct ucontext * #endif //ENABLE_OUTOFPROCESS -} \ No newline at end of file +}