Nat*_*man 98 c c++ linux callstack
有没有办法在每次调用某个函数时在C或C++中正在运行的进程中转储调用堆栈?我的想法是这样的:
void foo()
{
print_stack_trace();
// foo's body
return
}
Run Code Online (Sandbox Code Playgroud)
其中的print_stack_trace工作方式类似于callerPerl.
或类似的东西:
int main (void)
{
// will print out debug info every time foo() is called
register_stack_trace_function(foo);
// etc...
}
Run Code Online (Sandbox Code Playgroud)
在哪里register_stack_trace_function放置某种内部断点,这将导致在调用时打印堆栈跟踪foo.
在某些标准C库中是否存在这样的事情?
我正在使用GCC在Linux上工作.
我有一个测试运行,基于一些不应该影响此行为的命令行开关,行为不同.我的代码有一个伪随机数生成器,我假设它是基于这些开关被不同地调用的.我希望能够使用每组开关运行测试,并查看随机数生成器是否针对每个开关进行不同的调用.
Ida*_*n K 73
对于仅支持linux的解决方案,您可以使用backtrace(3),它只返回一个数组void *(实际上每个都指向相应堆栈帧的返回地址).要将这些翻译成有用的东西,有backtrace_symbols(3).
如果不使用特殊链接器选项,则符号名称可能不可用.对于使用GNU链接器的系统,必须使用-rdynamic链接器选项.请注意,"静态"函数的名称不会公开,并且在回溯中不可用.
Cir*_*四事件 15
提升堆栈跟踪
记录在:https : //www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack
这是我到目前为止看到的最方便的选项,因为它是:
可以实际打印出行号。
但是,它只会调用addr2line,这很丑陋,如果您跟踪的次数过多,可能会很慢。
默认情况下,
Boost仅是标头,因此极有可能无需修改构建系统
main.cpp
#include <iostream>
#define BOOST_STACKTRACE_USE_ADDR2LINE
#include <boost/stacktrace.hpp>
void my_func_2(void) {
std::cout << boost::stacktrace::stacktrace() << std::endl;
}
void my_func_1(double f) {
my_func_2();
}
void my_func_1(int i) {
my_func_2();
}
int main() {
my_func_1(1); /* line 19 */
my_func_1(2.0); /* line 20 */
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,这似乎是一个较新的功能,而该软件包libboost-stacktrace-dev仅在18.04中没有出现在Ubuntu 16.04中:
sudo apt-get install libboost-stacktrace-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o main.out -std=c++11 \
-Wall -Wextra -pedantic-errors main.cpp -ldl
Run Code Online (Sandbox Code Playgroud)
我们必须-ldl在末尾添加,否则编译将失败。
然后:
./main.out
Run Code Online (Sandbox Code Playgroud)
给出:
0# my_func_2() at /root/lkmc/main.cpp:7
1# my_func_1(int) at /root/lkmc/main.cpp:16
2# main at /root/lkmc/main.cpp:20
3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
4# _start in ./main.out
0# my_func_2() at /root/lkmc/main.cpp:7
1# my_func_1(double) at /root/lkmc/main.cpp:12
2# main at /root/lkmc/main.cpp:21
3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
4# _start in ./main.out
Run Code Online (Sandbox Code Playgroud)
请注意,由于函数重载而被破坏的my_func_1(int) and和my_func_1(float),对我们来说是很好的分解方法。
和-O3:
0# my_func_2() at /usr/include/boost/stacktrace/stacktrace.hpp:217
1# my_func_1(double) at /root/lkmc/main.cpp:11
2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
3# _start in ./main.out
0# my_func_2() at /usr/include/boost/stacktrace/stacktrace.hpp:217
1# main at /root/lkmc/main.cpp:21
2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
3# _start in ./main.out
Run Code Online (Sandbox Code Playgroud)
输出和在类似的下面的“ glibc backtrace”部分中进一步说明。
请记住,优化通常无法弥补回溯。尾部呼叫优化是一个著名的例子:什么是尾部呼叫优化?
每次回溯打印似乎都需要数百毫秒,因此请注意,如果回溯非常频繁地发生,程序性能将受到严重影响。
在Ubuntu 18.04,GCC 7.3.0,Boost 1.65.1上进行了测试。
glibc backtrace
记录在:https : //www.gnu.org/software/libc/manual/html_node/Backtraces.html
main.c
#include <stdio.h>
#include <stdlib.h>
/* Paste this on the file you want to debug. */
#include <stdio.h>
#include <execinfo.h>
void print_trace(void) {
char **strings;
size_t i, size;
enum Constexpr { MAX_SIZE = 1024 };
void *array[MAX_SIZE];
size = backtrace(array, MAX_SIZE);
strings = backtrace_symbols(array, size);
for (i = 0; i < size; i++)
printf("%s\n", strings[i]);
puts("");
free(strings);
}
void my_func_3(void) {
print_trace();
}
void my_func_2(void) {
my_func_3();
}
void my_func_1(void) {
my_func_3();
}
int main(void) {
my_func_1(); /* line 33 */
my_func_2(); /* line 34 */
return 0;
}
Run Code Online (Sandbox Code Playgroud)
编译:
gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -rdynamic -std=c99 \
-Wall -Wextra -pedantic-errors main.c
Run Code Online (Sandbox Code Playgroud)
-rdynamic 是关键的必需选项。
跑:
./main.out
Run Code Online (Sandbox Code Playgroud)
输出:
./main.out(print_trace+0x2d) [0x400a3d]
./main.out(main+0x9) [0x4008f9]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]
./main.out(_start+0x29) [0x400939]
./main.out(print_trace+0x2d) [0x400a3d]
./main.out(main+0xe) [0x4008fe]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]
./main.out(_start+0x29) [0x400939]
Run Code Online (Sandbox Code Playgroud)
因此,我们立即看到发生了内联优化,并且某些功能从跟踪中丢失了。
如果我们尝试获取地址:
addr2line -e main.out 0x4008f9 0x4008fe
Run Code Online (Sandbox Code Playgroud)
我们获得:
/home/ciro/main.c:21
/home/ciro/main.c:36
Run Code Online (Sandbox Code Playgroud)
完全关闭了。
如果-O0相反,则./main.out给出正确的完整跟踪:
./main.out(print_trace+0x2e) [0x4009a4]
./main.out(my_func_3+0x9) [0x400a50]
./main.out(my_func_1+0x9) [0x400a68]
./main.out(main+0x9) [0x400a74]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]
./main.out(_start+0x29) [0x4008a9]
./main.out(print_trace+0x2e) [0x4009a4]
./main.out(my_func_3+0x9) [0x400a50]
./main.out(my_func_2+0x9) [0x400a5c]
./main.out(main+0xe) [0x400a79]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]
./main.out(_start+0x29) [0x4008a9]
Run Code Online (Sandbox Code Playgroud)
然后:
addr2line -e main.out 0x400a74 0x400a79
Run Code Online (Sandbox Code Playgroud)
给出:
/home/cirsan01/test/main.c:34
/home/cirsan01/test/main.c:35
Run Code Online (Sandbox Code Playgroud)
所以线只有一条,TODO为什么呢?但这可能仍然可用。
结论:回溯只能通过完美显示-O0。通过优化,原始回溯将在编译后的代码中进行根本性的修改。
我找不到一个简单的方法来自动对此C ++符号进行脱胶,但是,这里有一些技巧:
在Ubuntu 16.04,GCC 6.4.0,libc 2.23上进行了测试。
glibc backtrace_symbols_fd
该帮助程序比更加方便backtrace_symbols,并产生基本相同的输出:
/* Paste this on the file you want to debug. */
#include <execinfo.h>
#include <stdio.h>
#include <unistd.h>
void print_trace(void) {
size_t i, size;
enum Constexpr { MAX_SIZE = 1024 };
void *array[MAX_SIZE];
size = backtrace(array, MAX_SIZE);
backtrace_symbols_fd(array, size, STDOUT_FILENO);
puts("");
}
Run Code Online (Sandbox Code Playgroud)
在Ubuntu 16.04,GCC 6.4.0,libc 2.23上进行了测试。
backtrace带C ++的glibc 解散hack 1:-export-dynamic+dladdr
改编自:https : //gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
这是一个“ hack”,因为它需要使用来更改ELF -export-dynamic。
glibc_ldl.cpp
#include <dlfcn.h> // for dladdr
#include <cxxabi.h> // for __cxa_demangle
#include <cstdio>
#include <string>
#include <sstream>
#include <iostream>
// This function produces a stack backtrace with demangled function & method names.
std::string backtrace(int skip = 1)
{
void *callstack[128];
const int nMaxFrames = sizeof(callstack) / sizeof(callstack[0]);
char buf[1024];
int nFrames = backtrace(callstack, nMaxFrames);
char **symbols = backtrace_symbols(callstack, nFrames);
std::ostringstream trace_buf;
for (int i = skip; i < nFrames; i++) {
Dl_info info;
if (dladdr(callstack[i], &info)) {
char *demangled = NULL;
int status;
demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
std::snprintf(
buf,
sizeof(buf),
"%-3d %*p %s + %zd\n",
i,
(int)(2 + sizeof(void*) * 2),
callstack[i],
status == 0 ? demangled : info.dli_sname,
(char *)callstack[i] - (char *)info.dli_saddr
);
free(demangled);
} else {
std::snprintf(buf, sizeof(buf), "%-3d %*p\n",
i, (int)(2 + sizeof(void*) * 2), callstack[i]);
}
trace_buf << buf;
std::snprintf(buf, sizeof(buf), "%s\n", symbols[i]);
trace_buf << buf;
}
free(symbols);
if (nFrames == nMaxFrames)
trace_buf << "[truncated]\n";
return trace_buf.str();
}
void my_func_2(void) {
std::cout << backtrace() << std::endl;
}
void my_func_1(double f) {
(void)f;
my_func_2();
}
void my_func_1(int i) {
(void)i;
my_func_2();
}
int main() {
my_func_1(1);
my_func_1(2.0);
}
Run Code Online (Sandbox Code Playgroud)
编译并运行:
g++ -fno-pie -ggdb3 -O0 -no-pie -o glibc_ldl.out -std=c++11 -Wall -Wextra \
-pedantic-errors -fpic glibc_ldl.cpp -export-dynamic -ldl
./glibc_ldl.out
Run Code Online (Sandbox Code Playgroud)
输出:
1 0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2 0x40139e my_func_1(int) + 16
./glibc_ldl.out(_Z9my_func_1i+0x10) [0x40139e]
3 0x4013b3 main + 18
./glibc_ldl.out(main+0x12) [0x4013b3]
4 0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5 0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]
1 0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2 0x40138b my_func_1(double) + 18
./glibc_ldl.out(_Z9my_func_1d+0x12) [0x40138b]
3 0x4013c8 main + 39
./glibc_ldl.out(main+0x27) [0x4013c8]
4 0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5 0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]
Run Code Online (Sandbox Code Playgroud)
在Ubuntu 18.04上测试。
backtrace使用C ++分解hack的glibc 2:解析回溯输出
显示在:https : //panthema.net/2008/0901-stacktrace-demangled/
这是一个hack,因为它需要解析。
TODO将其编译并在此处显示。
libunwind
TODO与glibc backtrace相比有什么优势吗?非常相似的输出,也需要修改build命令,但不是glibc的一部分,因此需要额外的软件包安装。
代码改编自:https : //eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
main.c
/* This must be on top. */
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <stdlib.h>
/* Paste this on the file you want to debug. */
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include <stdio.h>
void print_trace() {
char sym[256];
unw_context_t context;
unw_cursor_t cursor;
unw_getcontext(&context);
unw_init_local(&cursor, &context);
while (unw_step(&cursor) > 0) {
unw_word_t offset, pc;
unw_get_reg(&cursor, UNW_REG_IP, &pc);
if (pc == 0) {
break;
}
printf("0x%lx:", pc);
if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
printf(" (%s+0x%lx)\n", sym, offset);
} else {
printf(" -- error: unable to obtain symbol name for this frame\n");
}
}
puts("");
}
void my_func_3(void) {
print_trace();
}
void my_func_2(void) {
my_func_3();
}
void my_func_1(void) {
my_func_3();
}
int main(void) {
my_func_1(); /* line 46 */
my_func_2(); /* line 47 */
return 0;
}
Run Code Online (Sandbox Code Playgroud)
编译并运行:
sudo apt-get install libunwind-dev
gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -std=c99 \
-Wall -Wextra -pedantic-errors main.c -lunwind
Run Code Online (Sandbox Code Playgroud)
要么#define _XOPEN_SOURCE 700必须在最上面,要么我们必须使用-std=gnu99:
跑:
./main.out
Run Code Online (Sandbox Code Playgroud)
输出:
0x4007db: (main+0xb)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)
0x4007e2: (main+0x12)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)
Run Code Online (Sandbox Code Playgroud)
和:
addr2line -e main.out 0x4007db 0x4007e2
Run Code Online (Sandbox Code Playgroud)
给出:
/home/ciro/main.c:34
/home/ciro/main.c:49
Run Code Online (Sandbox Code Playgroud)
与-O0:
0x4009cf: (my_func_3+0xe)
0x4009e7: (my_func_1+0x9)
0x4009f3: (main+0x9)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)
0x4009cf: (my_func_3+0xe)
0x4009db: (my_func_2+0x9)
0x4009f8: (main+0xe)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)
Run Code Online (Sandbox Code Playgroud)
和:
addr2line -e main.out 0x4009f3 0x4009f8
Run Code Online (Sandbox Code Playgroud)
给出:
/home/ciro/main.c:47
/home/ciro/main.c:48
Run Code Online (Sandbox Code Playgroud)
glibcbacktrace
已在Ubuntu 16.04,GCC 6.4.0,libunwind 1.1上进行了测试。
具有C ++名称分解的libunwind
代码改编自:https : //eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
unwind.cpp
#define UNW_LOCAL_ONLY
#include <cxxabi.h>
#include <libunwind.h>
#include <cstdio>
#include <cstdlib>
#include <iostream>
void backtrace() {
unw_cursor_t cursor;
unw_context_t context;
// Initialize cursor to current frame for local unwinding.
unw_getcontext(&context);
unw_init_local(&cursor, &context);
// Unwind frames one by one, going up the frame stack.
while (unw_step(&cursor) > 0) {
unw_word_t offset, pc;
unw_get_reg(&cursor, UNW_REG_IP, &pc);
if (pc == 0) {
break;
}
std::printf("0x%lx:", pc);
char sym[256];
if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
char* nameptr = sym;
int status;
char* demangled = abi::__cxa_demangle(sym, nullptr, nullptr, &status);
if (status == 0) {
nameptr = demangled;
}
std::printf(" (%s+0x%lx)\n", nameptr, offset);
std::free(demangled);
} else {
std::printf(" -- error: unable to obtain symbol name for this frame\n");
}
}
}
void my_func_2(void) {
backtrace();
std::cout << std::endl; // line 43
}
void my_func_1(double f) {
(void)f;
my_func_2();
}
void my_func_1(int i) {
(void)i;
my_func_2();
} // line 54
int main() {
my_func_1(1);
my_func_1(2.0);
}
Run Code Online (Sandbox Code Playgroud)
编译并运行:
sudo apt-get install libunwind-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o unwind.out -std=c++11 \
-Wall -Wextra -pedantic-errors unwind.cpp -lunwind -pthread
./unwind.out
Run Code Online (Sandbox Code Playgroud)
输出:
0x400c80: (my_func_2()+0x9)
0x400cb7: (my_func_1(int)+0x10)
0x400ccc: (main+0x12)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)
0x400c80: (my_func_2()+0x9)
0x400ca4: (my_func_1(double)+0x12)
0x400ce1: (main+0x27)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)
Run Code Online (Sandbox Code Playgroud)
然后我们就可以找到的线条my_func_2和my_func_1(int)用:
addr2line -e unwind.out 0x400c80 0x400cb7
Run Code Online (Sandbox Code Playgroud)
这使:
/home/ciro/test/unwind.cpp:43
/home/ciro/test/unwind.cpp:54
Run Code Online (Sandbox Code Playgroud)
待办事项:为什么线下只有一条线?
在Ubuntu 18.04,GCC 7.4.0,libunwind 1.2.1上测试。
GDB自动化
我们还可以使用GDB进行此操作,而无需使用以下方法进行重新编译:在GDB中命中某个断点时如何执行特定操作?
尽管如果您要打印大量回溯记录,那么它的速度可能不如其他选项快,但是也许我们可以使用来达到本机速度compile code,但是我现在懒惰地对其进行测试:如何在gdb中调用Assembly?
main.cpp
void my_func_2(void) {}
void my_func_1(double f) {
my_func_2();
}
void my_func_1(int i) {
my_func_2();
}
int main() {
my_func_1(1);
my_func_1(2.0);
}
Run Code Online (Sandbox Code Playgroud)
main.gdb
start
break my_func_2
commands
silent
backtrace
printf "\n"
continue
end
continue
Run Code Online (Sandbox Code Playgroud)
编译并运行:
g++ -ggdb3 -o main.out main.cpp
gdb -nh -batch -x main.gdb main.out
Run Code Online (Sandbox Code Playgroud)
输出:
Temporary breakpoint 1 at 0x1158: file main.cpp, line 12.
Temporary breakpoint 1, main () at main.cpp:12
12 my_func_1(1);
Breakpoint 2 at 0x555555555129: file main.cpp, line 1.
#0 my_func_2 () at main.cpp:1
#1 0x0000555555555151 in my_func_1 (i=1) at main.cpp:8
#2 0x0000555555555162 in main () at main.cpp:12
#0 my_func_2 () at main.cpp:1
#1 0x000055555555513e in my_func_1 (f=2) at main.cpp:4
#2 0x000055555555516f in main () at main.cpp:13
[Inferior 1 (process 14193) exited normally]
Run Code Online (Sandbox Code Playgroud)
TODO我只想-ex通过命令行来执行此操作,而不必创建它,main.gdb但是我无法在commands那里进行工作。
已在Ubuntu 19.04,GDB 8.2中测试。
Linux内核
也可以看看
lui*_*fls 13
在C++23中,会有<stacktrace>,然后你可以这样做:
#include <stacktrace>\n\n/* ... */\n\nstd::cout << std::stacktrace::current();\nRun Code Online (Sandbox Code Playgroud)\n更多详细信息:
\n\xc2\xa0\xc2\xa0\xe2\x80\xa2 https://en.cppreference.com/w/cpp/header/stacktrace
\n\xc2\xa0\xc2\xa0\xe2\x80 \xa2 https://en.cppreference.com/w/cpp/utility/basic_stacktrace/operator_ltlt
特定于 Linux,TLDR:
backtraceinglibc仅在-lunwind链接时生成准确的堆栈跟踪(未记录的特定于平台的功能)。#include <elfutils/libdwfl.h>(此库仅记录在其头文件中)。backtrace_symbols并且backtrace_symbolsd_fd信息最少。在现代 Linux 上,您可以使用 function 获取堆栈跟踪地址backtrace。backtrace在流行平台上生成更准确地址的未记录方法是链接-lunwind(libunwind-dev在 Ubuntu 18.04 上)(请参阅下面的示例输出)。backtrace使用函数_Unwind_Backtrace,默认情况下后者来自libgcc_s.so.1并且该实现是最可移植的。当-lunwind连接它提供了一个更准确的版本_Unwind_Backtrace,但这个库的可移植性(见支持的体系结构libunwind/src)。
不幸的是,大约十年以来,伴侣backtrace_symbolsd和backtrace_symbols_fd函数一直无法将堆栈跟踪地址解析为带有源文件名和行号的函数名(请参阅下面的示例输出)。
但是,还有另一种方法可以将地址解析为符号,它会生成最有用的跟踪,包括函数名、源文件和行号。方法是#include <elfutils/libdwfl.h>与-ldw(libdw-dev在 Ubuntu 18.04 上)链接。
工作 C++ 示例 ( test.cc):
#include <stdexcept>
#include <iostream>
#include <cassert>
#include <cstdlib>
#include <string>
#include <boost/core/demangle.hpp>
#include <execinfo.h>
#include <elfutils/libdwfl.h>
struct DebugInfoSession {
Dwfl_Callbacks callbacks = {};
char* debuginfo_path = nullptr;
Dwfl* dwfl = nullptr;
DebugInfoSession() {
callbacks.find_elf = dwfl_linux_proc_find_elf;
callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
callbacks.debuginfo_path = &debuginfo_path;
dwfl = dwfl_begin(&callbacks);
assert(dwfl);
int r;
r = dwfl_linux_proc_report(dwfl, getpid());
assert(!r);
r = dwfl_report_end(dwfl, nullptr, nullptr);
assert(!r);
static_cast<void>(r);
}
~DebugInfoSession() {
dwfl_end(dwfl);
}
DebugInfoSession(DebugInfoSession const&) = delete;
DebugInfoSession& operator=(DebugInfoSession const&) = delete;
};
struct DebugInfo {
void* ip;
std::string function;
char const* file;
int line;
DebugInfo(DebugInfoSession const& dis, void* ip)
: ip(ip)
, file()
, line(-1)
{
// Get function name.
uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
Dwfl_Module* module = dwfl_addrmodule(dis.dwfl, ip2);
char const* name = dwfl_module_addrname(module, ip2);
function = name ? boost::core::demangle(name) : "<unknown>";
// Get source filename and line number.
if(Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
Dwarf_Addr addr;
file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
}
}
};
std::ostream& operator<<(std::ostream& s, DebugInfo const& di) {
s << di.ip << ' ' << di.function;
if(di.file)
s << " at " << di.file << ':' << di.line;
return s;
}
void terminate_with_stacktrace() {
void* stack[512];
int stack_size = ::backtrace(stack, sizeof stack / sizeof *stack);
// Print the exception info, if any.
if(auto ex = std::current_exception()) {
try {
std::rethrow_exception(ex);
}
catch(std::exception& e) {
std::cerr << "Fatal exception " << boost::core::demangle(typeid(e).name()) << ": " << e.what() << ".\n";
}
catch(...) {
std::cerr << "Fatal unknown exception.\n";
}
}
DebugInfoSession dis;
std::cerr << "Stacktrace of " << stack_size << " frames:\n";
for(int i = 0; i < stack_size; ++i) {
std::cerr << i << ": " << DebugInfo(dis, stack[i]) << '\n';
}
std::cerr.flush();
std::_Exit(EXIT_FAILURE);
}
int main() {
std::set_terminate(terminate_with_stacktrace);
throw std::runtime_error("test exception");
}
Run Code Online (Sandbox Code Playgroud)
使用 gcc-8.3 在 Ubuntu 18.04.4 LTS 上编译:
g++ -o test.o -c -m{arch,tune}=native -std=gnu++17 -W{all,extra,error} -g -Og -fstack-protector-all test.cc
g++ -o test -g test.o -ldw -lunwind
Run Code Online (Sandbox Code Playgroud)
输出:
Fatal exception std::runtime_error: test exception.
Stacktrace of 7 frames:
0: 0x55f3837c1a8c terminate_with_stacktrace() at /home/max/src/test/test.cc:76
1: 0x7fbc1c845ae5 <unknown>
2: 0x7fbc1c845b20 std::terminate()
3: 0x7fbc1c845d53 __cxa_throw
4: 0x55f3837c1a43 main at /home/max/src/test/test.cc:103
5: 0x7fbc1c3e3b96 __libc_start_main at ../csu/libc-start.c:310
6: 0x55f3837c17e9 _start
Run Code Online (Sandbox Code Playgroud)
当 no-lunwind被链接时,它会产生一个不太准确的堆栈跟踪:
0: 0x5591dd9d1a4d terminate_with_stacktrace() at /home/max/src/test/test.cc:76
1: 0x7f3c18ad6ae6 <unknown>
2: 0x7f3c18ad6b21 <unknown>
3: 0x7f3c18ad6d54 <unknown>
4: 0x5591dd9d1a04 main at /home/max/src/test/test.cc:103
5: 0x7f3c1845cb97 __libc_start_main at ../csu/libc-start.c:344
6: 0x5591dd9d17aa _start
Run Code Online (Sandbox Code Playgroud)
为了进行比较,backtrace_symbols_fd相同堆栈跟踪的输出信息最少:
/home/max/src/test/debug/gcc/test(+0x192f)[0x5601c5a2092f]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(+0x92ae5)[0x7f95184f5ae5]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(_ZSt9terminatev+0x10)[0x7f95184f5b20]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(__cxa_throw+0x43)[0x7f95184f5d53]
/home/max/src/test/debug/gcc/test(+0x1ae7)[0x5601c5a20ae7]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe6)[0x7f9518093b96]
/home/max/src/test/debug/gcc/test(+0x1849)[0x5601c5a20849]
Run Code Online (Sandbox Code Playgroud)
在生产版本(以及 C 语言版本)中,您可能希望通过替换boost::core::demangle,std::string以及std::cout它们的底层调用来使此代码更加健壮。
您还可以覆盖__cxa_throw以在抛出异常时捕获堆栈跟踪,并在捕获异常时打印它。当它进入catchblock 时,堆栈已经展开,因此调用 为时已晚backtrace,这就是为什么必须捕获堆栈的原因,该堆栈throw由 function 实现__cxa_throw。注意在一个多线程程序中__cxa_throw可以被多个线程同时调用,这样如果它把stacktrace捕获到一个全局数组中,那必须是thread_local.
您还可以使堆栈跟踪打印功能async-signal safe,以便您可以直接从您的SIGSEGV,SIGBUS信号处理程序(应该使用自己的堆栈以确保稳健性)调用它。获得函数名,源文件和行号利用 libdwfl从信号处理程序可能会失败,因为它不是异步信号安全或如果该进程的地址空间已经实质上损坏,但在实践中它成功的99%的时间(I避难所没有看到它失败)。
总而言之,用于自动堆栈跟踪输出的完整的生产就绪库应该:
throw到线程特定的存储中。SIGSEGV,SIGBUS,SIGFPE,等。ucontext_t信号函数参数(可能不包括向量寄存器),a-la Linux 内核 oops 日志消息打印故障点处所有 CPU 寄存器的值。小智 7
有没有办法在每次调用某个函数时在C或C++中正在运行的进程中转储调用堆栈?
您可以在特定函数中使用宏函数而不是return语句.
例如,而不是使用返回,
int foo(...)
{
if (error happened)
return -1;
... do something ...
return 0
}
Run Code Online (Sandbox Code Playgroud)
您可以使用宏功能.
#include "c-callstack.h"
int foo(...)
{
if (error happened)
NL_RETURN(-1);
... do something ...
NL_RETURN(0);
}
Run Code Online (Sandbox Code Playgroud)
每当函数中发生错误时,您将看到Java样式的调用堆栈,如下所示.
Error(code:-1) at : so_topless_ranking_server (sample.c:23)
Error(code:-1) at : nanolat_database (sample.c:31)
Error(code:-1) at : nanolat_message_queue (sample.c:39)
Error(code:-1) at : main (sample.c:47)
Run Code Online (Sandbox Code Playgroud)
完整源代码可在此处获得.
c-callstack在https://github.com/Nanolat
旧线程的另一个答案。
当我需要这样做时,我通常只使用system()和pstack
所以像这样:
#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <sstream>
#include <cstdlib>
void f()
{
pid_t myPid = getpid();
std::string pstackCommand = "pstack ";
std::stringstream ss;
ss << myPid;
pstackCommand += ss.str();
system(pstackCommand.c_str());
}
void g()
{
f();
}
void h()
{
g();
}
int main()
{
h();
}
Run Code Online (Sandbox Code Playgroud)
这输出
#0 0x00002aaaab62d61e in waitpid () from /lib64/libc.so.6
#1 0x00002aaaab5bf609 in do_system () from /lib64/libc.so.6
#2 0x0000000000400c3c in f() ()
#3 0x0000000000400cc5 in g() ()
#4 0x0000000000400cd1 in h() ()
#5 0x0000000000400cdd in main ()
Run Code Online (Sandbox Code Playgroud)
这应该适用于 Linux、FreeBSD 和 Solaris。我不认为 macOS 具有 pstack 或简单的等效项,但此线程似乎有替代.
如果您正在使用C,那么您将需要使用C字符串函数。
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
void f()
{
pid_t myPid = getpid();
/*
length of command 7 for 'pstack ', 7 for the PID, 1 for nul
*/
char pstackCommand[7+7+1];
sprintf(pstackCommand, "pstack %d", (int)myPid);
system(pstackCommand);
}
Run Code Online (Sandbox Code Playgroud)
根据这篇文章,我使用 7 作为 PID 中的最大位数。
您可以使用 Boost 库来打印当前的调用堆栈。
#include <boost/stacktrace.hpp>
// ... somewhere inside the `bar(int)` function that is called recursively:
std::cout << boost::stacktrace::stacktrace();
Run Code Online (Sandbox Code Playgroud)
人在这里:https ://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html
| 归档时间: |
|
| 查看次数: |
146057 次 |
| 最近记录: |