diff --git a/CMakeLists.txt b/CMakeLists.txt index 5fd986664..fbad3447b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,11 +159,18 @@ cmake_dependent_option( # doesn't make the library fat as the debugging symbols are stored # separately, enable us to symbolicate crash reports produced by release builds # @see https://www.productive-cpp.com/improving-cpp-builds-with-split-dwarf/ +# https://web.archive.org/web/20231127210015/https://www.productive-cpp.com/improving-cpp-builds-with-split-dwarf/ option( RS_SPLIT_DEBUG "Generate split debug symbols, useful for debugabble release builds" OFF ) +option( + RS_CPPTRACE_STACKTRACE + "Use Jeremy Rifkin Cpptrace library to print stacktrace instead of \ + our implementation" + OFF ) + set( RS_MAJOR_VERSION "" @@ -702,3 +709,20 @@ endif() if(CMAKE_BUILD_TYPE STREQUAL "Release") set_property(TARGET ${PROJECT_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION ON) endif() + +################################################################################ + +if(RS_CPPTRACE_STACKTRACE) + FetchContent_Declare( + cpptrace + GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git + GIT_TAG v0.3.1 + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + TIMEOUT 10 + ) + FetchContent_MakeAvailable(cpptrace) + target_link_libraries(${PROJECT_NAME} PRIVATE cpptrace::cpptrace) + target_compile_definitions( + ${LIBRARY_NAME} PRIVATE RS_JEREMY_RIFKIN_CPPTRACE ) +endif(RS_CPPTRACE_STACKTRACE) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fc715e709..85ecf19ef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -613,6 +613,7 @@ list( util/rsdnsutils.cc util/rsnet.cc util/rsnet_ss.cc + util/rsstacktrace.cc util/rsthreads.cc ) # util/i2pcommon.cpp diff --git a/src/libretroshare.pro b/src/libretroshare.pro index 611918af0..923f878ce 100644 --- a/src/libretroshare.pro +++ b/src/libretroshare.pro @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: (C) 2004-2021 Retroshare Team +# SPDX-FileCopyrightText: (C) 2004-2024 Retroshare Team # SPDX-License-Identifier: CC0-1.0 !include("../../retroshare.pri"): error("Could not include file ../../retroshare.pri") @@ -658,6 +658,7 @@ SOURCES += util/folderiterator.cc \ util/rsrandom.cc \ util/rstickevent.cc \ util/rsrecogn.cc \ + util/rsstacktrace.cc \ util/rstime.cc \ util/rsurl.cc \ util/rsbase64.cc diff --git a/src/util/rsstacktrace.cc b/src/util/rsstacktrace.cc new file mode 100644 index 000000000..3bbc27703 --- /dev/null +++ b/src/util/rsstacktrace.cc @@ -0,0 +1,313 @@ +/******************************************************************************* + * libretroshare * + * * + * Copyright (C) 2016-2024 Gioacchino Mazzurco + * Copyright (C) 2024 Asociación Civil Altermundi + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with this program. If not, see . * + * * + *******************************************************************************/ + +#include +#include +#include + +#include "util/stacktrace.h" + +#if defined(RS_JEREMY_RIFKIN_CPPTRACE) + +#include + +void print_stacktrace( + bool /*demangle*/, FILE* /*out*/, unsigned int maxFrames ) +{ + cpptrace::generate_trace(1, maxFrames).print(); +} + +#elif defined(__linux__) && defined(__GLIBC__) + +#include +#include + +/* Freely inspired by Timo Bingmann implementation + * https://panthema.net/2008/0901-stacktrace-demangled/ + * https://web.archive.org/web/20230815072644/https://panthema.net/2008/0901-stacktrace-demangled/ + */ +void print_stacktrace( + bool demangle, FILE* out, unsigned int maxFrames ) +{ + if(!out) + { + fprintf(stderr, "print_stacktrace invalid output file!\n"); + return; + } + + fprintf(out, "stack trace:\n"); + + // storage array for stack trace address data + void* addrlist[maxFrames+1]; + + // retrieve current stack addresses + int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*)); + + if (addrlen == 0) + { + fprintf(out, " \n"); + return; + } + + /* Skip first address which is print_stacktrace return address itself */ + auto callerRetPtr = addrlist + 1; + + if(!demangle) + { + int outFd = fileno(out); + if(outFd < 0) + { + fprintf(stderr, "print_stacktrace invalid output file descriptor!\n"); + return; + } + + backtrace_symbols_fd(callerRetPtr, addrlen, outFd); + return; + } + + // resolve addresses into strings containing "filename(function+address)", + // this array must be free()-ed + char** symbollist = backtrace_symbols(callerRetPtr, addrlen); + + // allocate string which will be filled with the demangled function name + size_t funcnamesize = 256; + char* funcname = (char*)malloc(funcnamesize); + + // iterate over the returned symbol lines. skip the first, it is + // the address of this function. + for (int i = 1; i < addrlen; i++) + { + char *begin_name = 0, *begin_offset = 0, *end_offset = 0; + + /* find parentheses and +address offset surrounding the mangled + * name: ./module(function+0x15c) [0x8048a6d] */ + for (char *p = symbollist[i]; *p; ++p) + { + if (*p == '(') begin_name = p; + else if (*p == '+') begin_offset = p; + else if (*p == ')' && begin_offset) + { + end_offset = p; + break; + } + } + + if ( begin_name && begin_offset && end_offset + && begin_name < begin_offset ) + { + *begin_name++ = '\0'; + *begin_offset++ = '\0'; + *end_offset = '\0'; + + // mangled name is now in [begin_name, begin_offset) and caller + // offset in [begin_offset, end_offset). now apply + // __cxa_demangle(): + + int status; + char* ret = abi::__cxa_demangle( + begin_name, funcname, &funcnamesize, &status ); + if (status == 0) + { + funcname = ret; // use possibly realloc()-ed string + fprintf( out, " %s : %s+%s\n", + symbollist[i], funcname, begin_offset ); + } + else + { + // demangling failed. Output function name as a C function + // with no arguments. + fprintf( out, " %s : %s()+%s\n", + symbollist[i], begin_name, begin_offset ); + } + } + else + { + // couldn't parse the line? print the whole line. + fprintf(out, " %s\n", symbollist[i]); + } + } + + free(funcname); + free(symbollist); +} +#elif defined(__ANDROID__) // defined(__linux__) && defined(__GLIBC__) + +/* Freely inspired by the solution proposed by Louis Semprini on this thread + * https://stackoverflow.com/questions/8115192/android-ndk-getting-the-backtrace/35586148 + * https://web.archive.org/web/20240121102501/https://stackoverflow.com/questions/8115192/android-ndk-getting-the-backtrace/35586148 + */ + +#include +#include +#include + +#include "util/rsdebug.h" + +struct RsAndroidBacktraceState +{ + void** current; + void** end; +}; + +static _Unwind_Reason_Code android_unwind_callback( + struct _Unwind_Context* context, void* arg ) +{ + RsAndroidBacktraceState* state = static_cast(arg); + uintptr_t pc = _Unwind_GetIP(context); + if(pc) + { + if (state->current == state->end) return _URC_END_OF_STACK; + + *state->current++ = reinterpret_cast(pc); + } + return _URC_NO_REASON; +} + +void print_stacktrace( + bool demangle, FILE* /*out*/, unsigned int /*maxFrames*/) +{ + constexpr int max = 5000; + void* buffer[max]; + + RsAndroidBacktraceState state; + state.current = buffer; + state.end = buffer + max; + + _Unwind_Backtrace(android_unwind_callback, &state); + + RsDbg() << std::endl << std::endl + << 0 << " " << buffer[0] << " " << __PRETTY_FUNCTION__ << std::endl; + + // Skip first frame which is print_stacktrace + int count = static_cast(state.current - buffer); + for(int idx = 1; idx < count; ++idx) + { + const void* addr = buffer[idx]; + + /* Ignore null addresses. + * They sometimes happen when using _Unwind_Backtrace() + * with compiler optimizations, when the Link Register is overwritten + * by the inner stack frames. */ + if(!addr) continue; + + /* Ignore duplicate addresses. + * They sometimes happen when using _Unwind_Backtrace() with compiler + * optimizations. */ + if(addr == buffer[idx-1]) continue; + + Dl_info info; + if( !(dladdr(addr, &info) && info.dli_sname) ) + { + RsDbg() << idx << " " << addr << " " << info.dli_fname + << " symbol not found" << std::endl; + continue; + } + + if(demangle) + { + int status = 0; + char* demangled = __cxxabiv1::__cxa_demangle( + info.dli_sname, nullptr, nullptr, &status ); + + if(demangled && (status == 0)) + RsDbg() << idx << " " << addr << " " << demangled << std::endl; + else + RsDbg() << idx << " " << addr << " " + << (info.dli_sname ? info.dli_sname : info.dli_fname) + << " __cxa_demangle failed with: " << status + << std::endl; + + free(demangled); + } + else RsDbg() << idx << " " << addr << " " + << (info.dli_sname ? info.dli_sname : info.dli_fname) + << std::endl; + } + + RsDbg() << std::endl << std::endl; +} + +#else // defined(__linux__) && defined(__GLIBC__) + +void print_stacktrace( + bool /*demangle*/, FILE* out, unsigned int /*max_frames*/ ) +{ + /** Notify the user which signal was caught. We use printf, because this + * is the most basic output function. Once you get a crash, it is + * possible that more complex output systems like streams and the like + * may be corrupted. So we make the most basic call possible + * to the lowest level, most standard print function. */ + fprintf(out, "print_stacktrace Not implemented yet for this platform\n"); +} +#endif // defined(__linux__) && defined(__GLIBC__) + + +/* Freely inspired to: + * https://oroboro.com/stack-trace-on-crash/ + * https://web.archive.org/web/20221005052001/https://oroboro.com/stack-trace-on-crash/ + */ +CrashStackTrace::CrashStackTrace() +{ + signal(SIGABRT, &CrashStackTrace::abortHandler); + signal(SIGSEGV, &CrashStackTrace::abortHandler); + signal(SIGILL, &CrashStackTrace::abortHandler); + signal(SIGFPE, &CrashStackTrace::abortHandler); +#ifdef SIGBUS + signal(SIGBUS, &CrashStackTrace::abortHandler); +#endif +} + +[[ noreturn ]] +/*static*/ void CrashStackTrace::abortHandler(int signum) +{ + // associate each signal with a signal name string. + const char* name = nullptr; + switch(signum) + { + case SIGABRT: name = "SIGABRT"; break; + case SIGSEGV: name = "SIGSEGV"; break; + case SIGILL: name = "SIGILL"; break; + case SIGFPE: name = "SIGFPE"; break; +#ifdef SIGBUS + case SIGBUS: name = "SIGBUS"; break; +#endif + } + +#ifndef __ANDROID__ + /** Notify the user which signal was caught. We use fprintf, because this + * is the most basic output function. Once you get a crash, it is + * possible that more complex output systems like streams and the like + * may be corrupted. So we make the most basic call possible + * to the lowest level, most standard print function. */ + if(name) + fprintf(stderr, "Caught signal %d (%s)\n", signum, name); + else + fprintf(stderr, "Caught signal %d\n", signum); +#else // ndef __ANDROID__ + /** On Android the best we can to is to rely on RS debug utils */ + RsFatal() << __PRETTY_FUNCTION__ << " Caught signal " << signum << " " + << (name ? name : "") << std::endl; +#endif + + print_stacktrace(false); + + exit(-signum); +} diff --git a/src/util/stacktrace.h b/src/util/stacktrace.h index 352a16d80..db5049e03 100644 --- a/src/util/stacktrace.h +++ b/src/util/stacktrace.h @@ -1,8 +1,8 @@ /******************************************************************************* * libretroshare * * * - * Copyright (C) 2016-2022 Gioacchino Mazzurco * - * Copyright (C) 2008 Timo Bingmann http://idlebox.net/ * + * Copyright (C) 2016-2024 Gioacchino Mazzurco * + * Copyright (C) 2024 Asociación Civil Altermundi * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License as * @@ -21,12 +21,7 @@ #pragma once #include -#include -#include -#ifdef __ANDROID__ -# include "util/rsdebug.h" -#endif /** * @brief Print a backtrace to FILE* out. @@ -37,275 +32,18 @@ * @param[in] out output file * @param[in] maxFrames maximum number of stack frames you want to bu printed */ -static void print_stacktrace( +void print_stacktrace( bool demangle = true, FILE *out = stderr, unsigned int maxFrames = 63 ); - -#if defined(__linux__) && defined(__GLIBC__) - -#include -#include - -static void print_stacktrace( - bool demangle, FILE* out, unsigned int maxFrames ) -{ - if(!out) - { - fprintf(stderr, "print_stacktrace invalid output file!\n"); - return; - } - - fprintf(out, "stack trace:\n"); - - // storage array for stack trace address data - void* addrlist[maxFrames+1]; - - // retrieve current stack addresses - int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*)); - - if (addrlen == 0) - { - fprintf(out, " \n"); - return; - } - - if(!demangle) - { - int outFd = fileno(out); - if(outFd < 0) - { - fprintf(stderr, "print_stacktrace invalid output file descriptor!\n"); - return; - } - - backtrace_symbols_fd(addrlist, addrlen, outFd); - return; - } - - // resolve addresses into strings containing "filename(function+address)", - // this array must be free()-ed - char** symbollist = backtrace_symbols(addrlist, addrlen); - - // allocate string which will be filled with the demangled function name - size_t funcnamesize = 256; - char* funcname = (char*)malloc(funcnamesize); - - // iterate over the returned symbol lines. skip the first, it is - // the address of this function. - for (int i = 1; i < addrlen; i++) - { - char *begin_name = 0, *begin_offset = 0, *end_offset = 0; - - /* find parentheses and +address offset surrounding the mangled - * name: ./module(function+0x15c) [0x8048a6d] */ - for (char *p = symbollist[i]; *p; ++p) - { - if (*p == '(') begin_name = p; - else if (*p == '+') begin_offset = p; - else if (*p == ')' && begin_offset) - { - end_offset = p; - break; - } - } - - if ( begin_name && begin_offset && end_offset - && begin_name < begin_offset ) - { - *begin_name++ = '\0'; - *begin_offset++ = '\0'; - *end_offset = '\0'; - - // mangled name is now in [begin_name, begin_offset) and caller - // offset in [begin_offset, end_offset). now apply - // __cxa_demangle(): - - int status; - char* ret = abi::__cxa_demangle( - begin_name, funcname, &funcnamesize, &status ); - if (status == 0) - { - funcname = ret; // use possibly realloc()-ed string - fprintf( out, " %s : %s+%s\n", - symbollist[i], funcname, begin_offset ); - } - else - { - // demangling failed. Output function name as a C function - // with no arguments. - fprintf( out, " %s : %s()+%s\n", - symbollist[i], begin_name, begin_offset ); - } - } - else - { - // couldn't parse the line? print the whole line. - fprintf(out, " %s\n", symbollist[i]); - } - } - - free(funcname); - free(symbollist); -} -#elif defined(__ANDROID__) // defined(__linux__) && defined(__GLIBC__) - -/* Inspired by the solution proposed by Louis Semprini on this thread - * https://stackoverflow.com/questions/8115192/android-ndk-getting-the-backtrace/35586148 - */ - -#include -#include -#include - -struct RsAndroidBacktraceState -{ - void** current; - void** end; -}; - -static _Unwind_Reason_Code android_unwind_callback( - struct _Unwind_Context* context, void* arg ) -{ - RsAndroidBacktraceState* state = static_cast(arg); - uintptr_t pc = _Unwind_GetIP(context); - if(pc) - { - if (state->current == state->end) return _URC_END_OF_STACK; - - *state->current++ = reinterpret_cast(pc); - } - return _URC_NO_REASON; -} - -static void print_stacktrace( - bool demangle, FILE* /*out*/, unsigned int /*maxFrames*/) -{ - constexpr int max = 5000; - void* buffer[max]; - - RsAndroidBacktraceState state; - state.current = buffer; - state.end = buffer + max; - - _Unwind_Backtrace(android_unwind_callback, &state); - - RsDbg() << std::endl << std::endl - << 0 << " " << buffer[0] << " " << __PRETTY_FUNCTION__ << std::endl; - - // Skip first frame which is print_stacktrace - int count = static_cast(state.current - buffer); - for(int idx = 1; idx < count; ++idx) - { - const void* addr = buffer[idx]; - - /* Ignore null addresses. - * They sometimes happen when using _Unwind_Backtrace() - * with compiler optimizations, when the Link Register is overwritten - * by the inner stack frames. */ - if(!addr) continue; - - /* Ignore duplicate addresses. - * They sometimes happen when using _Unwind_Backtrace() with compiler - * optimizations. */ - if(addr == buffer[idx-1]) continue; - - Dl_info info; - if( !(dladdr(addr, &info) && info.dli_sname) ) - { - RsDbg() << idx << " " << addr << " " << info.dli_fname - << " symbol not found" << std::endl; - continue; - } - - if(demangle) - { - int status = 0; - char* demangled = __cxxabiv1::__cxa_demangle( - info.dli_sname, nullptr, nullptr, &status ); - - if(demangled && (status == 0)) - RsDbg() << idx << " " << addr << " " << demangled << std::endl; - else - RsDbg() << idx << " " << addr << " " - << (info.dli_sname ? info.dli_sname : info.dli_fname) - << " __cxa_demangle failed with: " << status - << std::endl; - - free(demangled); - } - else RsDbg() << idx << " " << addr << " " - << (info.dli_sname ? info.dli_sname : info.dli_fname) - << std::endl; - } - - RsDbg() << std::endl << std::endl; -} - -#else // defined(__linux__) && defined(__GLIBC__) - -static void print_stacktrace( - bool /*demangle*/, FILE* out, unsigned int /*max_frames*/ ) -{ - /** Notify the user which signal was caught. We use printf, because this - * is the most basic output function. Once you get a crash, it is - * possible that more complex output systems like streams and the like - * may be corrupted. So we make the most basic call possible - * to the lowest level, most standard print function. */ - fprintf(out, "print_stacktrace Not implemented yet for this platform\n"); -} -#endif // defined(__linux__) && defined(__GLIBC__) - /** * @brief CrashStackTrace catch crash signals and print stack trace - * Inspired too https://oroboro.com/stack-trace-on-crash/ + * Place an instance of this in your main file to get stacktraces printed + * automatically on crash */ struct CrashStackTrace { - CrashStackTrace() - { - signal(SIGABRT, &CrashStackTrace::abortHandler); - signal(SIGSEGV, &CrashStackTrace::abortHandler); - signal(SIGILL, &CrashStackTrace::abortHandler); - signal(SIGFPE, &CrashStackTrace::abortHandler); -#ifdef SIGBUS - signal(SIGBUS, &CrashStackTrace::abortHandler); -#endif - } + CrashStackTrace(); [[ noreturn ]] - static void abortHandler(int signum) - { - // associate each signal with a signal name string. - const char* name = nullptr; - switch(signum) - { - case SIGABRT: name = "SIGABRT"; break; - case SIGSEGV: name = "SIGSEGV"; break; - case SIGILL: name = "SIGILL"; break; - case SIGFPE: name = "SIGFPE"; break; -#ifdef SIGBUS - case SIGBUS: name = "SIGBUS"; break; -#endif - } - -#ifndef __ANDROID__ - /** Notify the user which signal was caught. We use printf, because this - * is the most basic output function. Once you get a crash, it is - * possible that more complex output systems like streams and the like - * may be corrupted. So we make the most basic call possible - * to the lowest level, most standard print function. */ - if(name) - fprintf(stderr, "Caught signal %d (%s)\n", signum, name); - else - fprintf(stderr, "Caught signal %d\n", signum); -#else // ndef __ANDROID__ - /** On Android the best we can to is to rely on RS debug utils */ - RsFatal() << __PRETTY_FUNCTION__ << " Caught signal " << signum << " " - << (name ? name : "") << std::endl; -#endif - - print_stacktrace(false); - - exit(-signum); - } + static void abortHandler(int signum); };