[英]C++ display stack trace on exception
如果拋出異常,我想有一種方法向用戶報告堆棧跟蹤。 做這個的最好方式是什么? 是否需要大量額外的代碼?
回答問題:
如果可能的話,我希望它是便攜式的。 我希望彈出信息,以便用戶可以復制堆棧跟蹤,並在出現錯誤時通過電子郵件將其發送給我。
這取決於哪個平台。
在 GCC 上它非常簡單,有關更多詳細信息,請參閱此帖子。
在 MSVC 上,您可以使用StackWalker庫來處理 Windows 所需的所有底層 API 調用。
您必須找出將此功能集成到您的應用程序中的最佳方式,但您需要編寫的代碼量應該最少。
安德魯·格蘭特的回答並不能幫助獲得投擲功能的堆棧跟蹤,至少不能與海灣合作委員會,因為throw語句不保存自身當前的堆棧跟蹤和捕捉處理程序將無法訪問的堆棧跟蹤那一點再說。
使用 GCC 解決此問題的唯一方法是確保在 throw 指令點生成堆棧跟蹤,並將其與異常對象一起保存。
當然,此方法要求拋出異常的每個代碼都使用該特定的 Exception 類。
2017 年 7 月 11 日更新:有關一些有用的代碼,請查看 cahit beyaz 的答案,該答案指向http://stacktrace.sourceforge.net - 我還沒有使用它,但看起來很有希望。
如果您使用的是 Boost 1.65 或更高版本,則可以使用boost::stacktrace :
#include <boost/stacktrace.hpp>
// ... somewhere inside the bar(int) function that is called recursively:
std::cout << boost::stacktrace::stacktrace();
我想添加一個標准庫選項(即跨平台)如何生成異常回溯,該選項已在C++11 中可用:
std::nested_exception
和std::throw_with_nested
這不會給你一個堆棧放松,但在我看來是下一個最好的事情。 StackOverflow在此處和此處對此進行了描述,您可以通過簡單地編寫適當的異常處理程序來重新拋出嵌套異常,從而在無需調試器或繁瑣的日志記錄的情況下獲得代碼中異常的回溯。
由於您可以使用任何派生的異常類來執行此操作,因此您可以向此類回溯添加大量信息! 您也可以在 GitHub 上查看我的MWE ,其中的回溯如下所示:
Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"
如果您使用的是 C++ 並且不想/不能使用 Boost,您可以使用以下代碼[link to the original site]打印帶有 demangled 名稱的回溯。
請注意,此解決方案特定於 Linux。 它使用 GNU 的 libc 函數 backtrace()/backtrace_symbols()(來自 execinfo.h)來獲取回溯,然后使用 __cxa_demangle()(來自 cxxabi.h)來對回溯符號名稱進行整理。
// stacktrace.h (c) 2008, Timo Bingmann from http://idlebox.net/
// published under the WTFPL v2.0
#ifndef _STACKTRACE_H_
#define _STACKTRACE_H_
#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
#include <cxxabi.h>
/** Print a demangled stack backtrace of the caller function to FILE* out. */
static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames = 63)
{
fprintf(out, "stack trace:\n");
// storage array for stack trace address data
void* addrlist[max_frames+1];
// retrieve current stack addresses
int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*));
if (addrlen == 0) {
fprintf(out, " <empty, possibly corrupt>\n");
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);
}
#endif // _STACKTRACE_H_
哼!
AFAIK libunwind 非常便攜,到目前為止我還沒有發現任何更容易使用的東西。
我推薦http://stacktrace.sourceforge.net/項目。 它支持 Windows、Mac OS 和 Linux
由於在進入 catch 塊時堆棧已經展開,因此在我的情況下,解決方案是不捕獲某些會導致 SIGABRT 的異常。 在 SIGABRT 的信號處理程序中,我然后 fork() 和 execl() gdb(在調試版本中)或 Google breakpads stackwalk(在發布版本中)。 此外,我嘗試僅使用信號處理程序安全功能。
GDB:
static const char BACKTRACE_START[] = "<2>--- backtrace of entire stack ---\n";
static const char BACKTRACE_STOP[] = "<2>--- backtrace finished ---\n";
static char *ltrim(char *s)
{
while (' ' == *s) {
s++;
}
return s;
}
void Backtracer::print()
{
int child_pid = ::fork();
if (child_pid == 0) {
// redirect stdout to stderr
::dup2(2, 1);
// create buffer for parent pid (2+16+1 spaces to allow up to a 64 bit hex parent pid)
char pid_buf[32];
const char* stem = " ";
const char* s = stem;
char* d = &pid_buf[0];
while (static_cast<bool>(*s))
{
*d++ = *s++;
}
*d-- = '\0';
char* hexppid = d;
// write parent pid to buffer and prefix with 0x
int ppid = getppid();
while (ppid != 0) {
*hexppid = ((ppid & 0xF) + '0');
if(*hexppid > '9') {
*hexppid += 'a' - '0' - 10;
}
--hexppid;
ppid >>= 4;
}
*hexppid-- = 'x';
*hexppid = '0';
// invoke GDB
char name_buf[512];
name_buf[::readlink("/proc/self/exe", &name_buf[0], 511)] = 0;
ssize_t r = ::write(STDERR_FILENO, &BACKTRACE_START[0], sizeof(BACKTRACE_START));
(void)r;
::execl("/usr/bin/gdb",
"/usr/bin/gdb", "--batch", "-n", "-ex", "thread apply all bt full", "-ex", "quit",
&name_buf[0], ltrim(&pid_buf[0]), nullptr);
::exit(1); // if GDB failed to start
} else if (child_pid == -1) {
::exit(1); // if forking failed
} else {
// make it work for non root users
if (0 != getuid()) {
::prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);
}
::waitpid(child_pid, nullptr, 0);
ssize_t r = ::write(STDERR_FILENO, &BACKTRACE_STOP[0], sizeof(BACKTRACE_STOP));
(void)r;
}
}
minidump_stackwalk:
static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded)
{
int child_pid = ::fork();
if (child_pid == 0) {
::dup2(open("/dev/null", O_WRONLY), 2); // ignore verbose output on stderr
ssize_t r = ::write(STDOUT_FILENO, &MINIDUMP_STACKWALK_START[0], sizeof(MINIDUMP_STACKWALK_START));
(void)r;
::execl("/usr/bin/minidump_stackwalk", "/usr/bin/minidump_stackwalk", descriptor.path(), "/usr/share/breakpad-syms", nullptr);
::exit(1); // if minidump_stackwalk failed to start
} else if (child_pid == -1) {
::exit(1); // if forking failed
} else {
::waitpid(child_pid, nullptr, 0);
ssize_t r = ::write(STDOUT_FILENO, &MINIDUMP_STACKWALK_STOP[0], sizeof(MINIDUMP_STACKWALK_STOP));
(void)r;
}
::remove(descriptor.path()); // this is not signal safe anymore but should still work
return succeeded;
}
編輯:為了使其適用於breakpad,我還必須添加以下內容:
std::set_terminate([]()
{
ssize_t r = ::write(STDERR_FILENO, EXCEPTION, sizeof(EXCEPTION));
(void)r;
google_breakpad::ExceptionHandler::WriteMinidump(std::string("/tmp"), dumpCallback, NULL);
exit(1); // avoid creating a second dump by not calling std::abort
});
來源: 如何使用帶有行號信息的 gcc 獲取 C++ 的堆棧跟蹤? 是否可以將 gdb 附加到崩潰的進程(又名“即時”調試)
在 Windows 上,查看BugTrap 。 它不再位於原始鏈接中,但在 CodeProject 上仍然可用。
我有類似的問題,雖然我喜歡可移植性,但我只需要 gcc 支持。 在 gcc 中,可以使用 execinfo.h 和回溯調用。 為了對函數名稱進行修飾,Bingmann 先生有一段不錯的代碼。 為了在異常上轉儲回溯,我創建了一個在構造函數中打印回溯的異常。 如果我希望它能夠處理庫中拋出的異常,則可能需要重建/鏈接以便使用回溯異常。
/******************************************
#Makefile with flags for printing backtrace with function names
# compile with symbols for backtrace
CXXFLAGS=-g
# add symbols to dynamic symbol table for backtrace
LDFLAGS=-rdynamic
turducken: turducken.cc
******************************************/
#include <cstdio>
#include <stdexcept>
#include <execinfo.h>
#include "stacktrace.h" /* https://panthema.net/2008/0901-stacktrace-demangled/ */
// simple exception that prints backtrace when constructed
class btoverflow_error: public std::overflow_error
{
public:
btoverflow_error( const std::string& arg ) :
std::overflow_error( arg )
{
print_stacktrace();
};
};
void chicken(void)
{
throw btoverflow_error( "too big" );
}
void duck(void)
{
chicken();
}
void turkey(void)
{
duck();
}
int main( int argc, char *argv[])
{
try
{
turkey();
}
catch( btoverflow_error e)
{
printf( "caught exception: %s\n", e.what() );
}
}
使用 gcc 4.8.4 編譯和運行它會產生一個回溯,其中包含很好的 C++ 函數名稱:
stack trace:
./turducken : btoverflow_error::btoverflow_error(std::string const&)+0x43
./turducken : chicken()+0x48
./turducken : duck()+0x9
./turducken : turkey()+0x9
./turducken : main()+0x15
/lib/x86_64-linux-gnu/libc.so.6 : __libc_start_main()+0xf5
./turducken() [0x401629]
Poppy不僅可以收集堆棧跟蹤,還可以收集參數值、局部變量等 - 導致崩潰的一切。
以下代碼在拋出異常后立即停止執行。 您需要設置 windows_exception_handler 以及終止處理程序。 我在 MinGW 32 位中對此進行了測試。
void beforeCrash(void);
static const bool SET_TERMINATE = std::set_terminate(beforeCrash);
void beforeCrash() {
__asm("int3");
}
int main(int argc, char *argv[])
{
SetUnhandledExceptionFilter(windows_exception_handler);
...
}
檢查 windows_exception_handler 函數的以下代碼: http : //www.codedisqus.com/0ziVPgVPUk/exception-handling-and-stacktrace-under-windows-mingwgcc.html
Cpp-tool ex_diag - 輕量級,多平台,最少的資源使用,跟蹤簡單靈活。
OSX 的一個工作示例(現在在 Catalina 10.15 上測試)。 顯然不能移植到 linux/windows。 可能它對某人有用。
在“Mew-exception”字符串中,您可以使用 backtrace 和/或 backtrace_symbols 函數
#include <stdexcept>
#include <typeinfo>
#include <dlfcn.h>
extern "C" void __cxa_throw(void *thrown_object, std::type_info *tinfo, void (*dest)(void *));
static void (*__cxa_throw_orig)(void *thrown_object, std::type_info *tinfo, void (*dest)(void *));
extern "C" void luna_cxa_throw(void *thrown_object, std::type_info *tinfo, void (*dest)(void *))
{
printf("Mew-exception you can catch your backtrace here!");
__cxa_throw_orig(thrown_object, tinfo, dest);
}
//__attribute__ ((used))
//__attribute__ ((section ("__DATA,__interpose")))
static struct replace_pair_t {
void *replacement, *replacee;
} replace_pair = { (void*)luna_cxa_throw, (void*)__cxa_throw };
extern "C" const struct mach_header __dso_handle;
extern "C" void dyld_dynamic_interpose(const struct mach_header*,
const replace_pair_t replacements[],
size_t count);
int fn()
{
int a = 10; ++a;
throw std::runtime_error("Mew!");
}
int main(int argc, const char * argv[]) {
__cxa_throw_orig = (void (*)(void *thrown_object, std::type_info *tinfo, void (*dest)(void *)))dlsym(RTLD_DEFAULT, "__cxa_throw");
dyld_dynamic_interpose(&__dso_handle, &replace_pair, 1);
fn();
return 0;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.