Add option to use Cpptrace to print stack trace

Cpptrace works also on platforms libretroshare did lack
print_stacktrace implementation, and display very nice colored stack traces
Move implementation details into rsstacktrace.cc keeping header lean and
mean to reduce compile time and binary size (skip first frame instead of
inlining the function around)
This commit is contained in:
Gioacchino Mazzurco 2024-01-21 18:45:32 +01:00
parent 96169cac56
commit 954178f813
No known key found for this signature in database
GPG Key ID: A1FBCA3872E87051
5 changed files with 347 additions and 270 deletions

View File

@ -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)

View File

@ -613,6 +613,7 @@ list(
util/rsdnsutils.cc
util/rsnet.cc
util/rsnet_ss.cc
util/rsstacktrace.cc
util/rsthreads.cc )
# util/i2pcommon.cpp

View File

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: (C) 2004-2021 Retroshare Team <contact@retroshare.cc>
# SPDX-FileCopyrightText: (C) 2004-2024 Retroshare Team <contact@retroshare.cc>
# 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

313
src/util/rsstacktrace.cc Normal file
View File

@ -0,0 +1,313 @@
/*******************************************************************************
* libretroshare *
* *
* Copyright (C) 2016-2024 Gioacchino Mazzurco <gio@retroshare.cc>
* Copyright (C) 2024 Asociación Civil Altermundi <info@altermundi.net>
* *
* 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 <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#include <cstdio>
#include <csignal>
#include <cstdlib>
#include "util/stacktrace.h"
#if defined(RS_JEREMY_RIFKIN_CPPTRACE)
#include <cpptrace/cpptrace.hpp>
void print_stacktrace(
bool /*demangle*/, FILE* /*out*/, unsigned int maxFrames )
{
cpptrace::generate_trace(1, maxFrames).print();
}
#elif defined(__linux__) && defined(__GLIBC__)
#include <execinfo.h>
#include <cxxabi.h>
/* 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, " <empty, possibly corrupt>\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 <unwind.h>
#include <dlfcn.h>
#include <cxxabi.h>
#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<RsAndroidBacktraceState*>(arg);
uintptr_t pc = _Unwind_GetIP(context);
if(pc)
{
if (state->current == state->end) return _URC_END_OF_STACK;
*state->current++ = reinterpret_cast<void*>(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<int>(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);
}

View File

@ -1,8 +1,8 @@
/*******************************************************************************
* libretroshare *
* *
* Copyright (C) 2016-2022 Gioacchino Mazzurco <gio@retroshare.cc> *
* Copyright (C) 2008 Timo Bingmann http://idlebox.net/ *
* Copyright (C) 2016-2024 Gioacchino Mazzurco <gio@retroshare.cc> *
* Copyright (C) 2024 Asociación Civil Altermundi <info@altermundi.net>
* *
* 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 <cstdio>
#include <csignal>
#include <cstdlib>
#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 <execinfo.h>
#include <cxxabi.h>
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, " <empty, possibly corrupt>\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 <unwind.h>
#include <dlfcn.h>
#include <cxxabi.h>
struct RsAndroidBacktraceState
{
void** current;
void** end;
};
static _Unwind_Reason_Code android_unwind_callback(
struct _Unwind_Context* context, void* arg )
{
RsAndroidBacktraceState* state = static_cast<RsAndroidBacktraceState*>(arg);
uintptr_t pc = _Unwind_GetIP(context);
if(pc)
{
if (state->current == state->end) return _URC_END_OF_STACK;
*state->current++ = reinterpret_cast<void*>(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<int>(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);
};