如何在调用某个函数时打印堆栈跟踪
有没有办法在每次调用某个函数时转储 C 或 C++ 正在运行的进程中的调用堆栈?我的想法是这样的:
void foo()
{
print_stack_trace();
// foo's body
return
}
print_stack_trace
的工作方式类似于 < Perl 中的代码>调用者。
或者类似这样的内容:
int main (void)
{
// will print out debug info every time foo() is called
register_stack_trace_function(foo);
// etc...
}
其中 register_stack_trace_function
放置某种内部断点,每当调用 foo
时,都会导致打印堆栈跟踪。
标准 C 库中是否存在类似的东西?
我正在 Linux 上工作,使用 GCC。
背景
我有一个测试运行,它的行为基于一些不应该影响此行为的命令行开关。我的代码有一个伪随机数生成器,我假设根据这些开关以不同的方式调用它。我希望能够使用每组开关运行测试,并查看每组开关的随机数生成器调用是否不同。
Is there any way to dump the call stack in a running process in C or C++ every time a certain function is called? What I have in mind is something like this:
void foo()
{
print_stack_trace();
// foo's body
return
}
Where print_stack_trace
works similarly to caller
in Perl.
Or something like this:
int main (void)
{
// will print out debug info every time foo() is called
register_stack_trace_function(foo);
// etc...
}
where register_stack_trace_function
puts some sort of internal breakpoint that will cause a stack trace to be printed whenever foo
is called.
Does anything like this exist in some standard C library?
I am working on Linux, using GCC.
Background
I have a test run that behaves differently based on some commandline switches that shouldn't affect this behavior. My code has a pseudo-random number generator that I assume is being called differently based on these switches. I want to be able to run the test with each set of switches and see if the random number generator is called differently for each one.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(15)
C/C++ 回溯方法调查
在这个答案中,我将尝试对一堆解决方案运行一个基准测试,看看哪一个运行得更快,同时还考虑其他点,例如功能和可移植性。
GCC 12.1stacktrace()
backtrace_symbols_fd
-rdynamic
backtrace_symbols
-rdynamic< /code>
空单元格表示“TODO”,而不是“不”。
我们
:微秒行号:显示实际行号,而不仅仅是函数名称+内存地址。
通常可以在事后使用
addr2line
从地址手动恢复行号。但这是一种痛苦。重新编译:需要重新编译程序才能获取痕迹。不重新编译更好!
信号安全:对于“在发生段错误时获取堆栈跟踪”的重要用例至关重要:如何在程序崩溃时自动生成堆栈跟踪
作为字符串:您可以在程序本身中以字符串形式获取堆栈跟踪,而不是例如只是打印到标准输出。通常意味着信号不安全,因为我们事先不知道堆栈跟踪字符串的大小,因此需要非异步信号安全的 malloc。
C:它可以在纯 C 项目上运行吗(是的,那里仍然有可怜的灵魂),还是需要 C++?
测试设置
所有基准测试都将运行以下
main.cpp
此输入旨在测试自
my_func_1(int)
和my_func_1(float)
以来的 C++ 名称分解> 作为实现 C++ 的一种方式,必然会被破坏函数重载。我们通过使用不同的
-I
包含来指向print_stacktrace()
的不同实现来区分基准。每个基准测试都是通过以下形式的命令完成的:
针对每个实现调整迭代次数,以产生该基准测试的 1 秒左右的总运行时间。
除非另有说明,否则以下所有测试均使用
-O0
。堆栈跟踪可能会因某些优化而受到不可挽回的损害。尾部调用优化就是一个著名的例子:什么是尾部调用优化? 我们对此无能为力。C++23
此方法之前曾在以下位置提到过:https ://stackoverflow.com/a/69384663/895245请考虑投票赞成该答案。
这是最好的解决方案...它便携、快速、显示行号并分解 C++ 符号。一旦该选项变得更广泛可用,它就会取代所有其他替代方案,但 GDB 可能只是一次性的例外,无需或重新编译。
cpp20_stacktrace/mystacktrace.h
Ubuntu 22.04 中的 GCC 12.1.0 不支持编译,所以现在我按照以下方式从源代码构建它: 如何编辑和重新构建 GCC libstdc++ C++ 标准库源? 并设置 < code>--enable-libstdcxx-backtrace=yes,它成功了!
编译:
示例输出:
如果我们尝试使用 Ubuntu 22.04 中的 GCC 12.1.0:
它会失败:
检查构建选项:
不显示:
所以它没有被编译。 参考书目:
它不会在包含时失败,因为头文件:
具有功能检查:
Boost
stacktrace
库在 Ubuntu 22.04 周围发生了很大变化,因此请确保您的版本匹配: Boost 堆栈跟踪不显示函数名称和行号
该库几乎已被更可移植的 C++23 实现所取代,但对于那些不符合该标准的人来说仍然是一个非常好的选择版本还没有,但已经有“Boost 许可”。
记录于: https:// www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack
在Ubuntu 22.04,boost 1.74.0上测试,你应该这样做:
boost_stacktrace/mystacktrace.h
在 Ubuntu 19.10 boost 1.67.0 上,为了获取行号,我们必须改为:
这将调用
addr2line
可执行文件,并且比新的 Boost 版本慢 1000 倍。Ubuntu 16.04 上根本不存在
libboost-stacktrace-dev
包。本节的其余部分仅考虑 Ubuntu 22.04、boost 1.74 的行为。
编译:
示例输出:
请注意,各行相差一行。 评论中建议这是因为正在考虑以下指令地址。
仅 Boost
stacktrace
标头BOOST_STACKTRACE_LINK
的作用是在链接时需要-lboost_stacktrace_backtrace
,所以我们想象没有它它会起作用的。对于没有“Boost 许可”的开发人员来说,这将是一个不错的选择,可以一次性添加进行调试。TODO 不幸的是,它对我来说不太好:
then:
包含过短的输出:
我们甚至不能与
addr2line
一起使用。也许我们必须传递一些其他定义: https:// /www.boost.org/doc/libs/1_80_0/doc/html/stacktrace/configuration_and_build.html?在 Ubuntu 22.04 上测试。提升1.74。
Boost
boost::stacktrace::safe_dump_to
这是
boost::stacktrace::stacktrace
的一个有趣的替代方案,因为它将堆栈跟踪写入对文件的异步信号安全方式,这使得它成为自动转储段错误上的堆栈跟踪的好选择,这是一个超级常见的用例:如何在程序崩溃时自动生成堆栈跟踪记录于:https://www.boost.org/doc/libs/1_70_0/doc/html/boost/stacktrace /safe_dump_1_3_38_7_6_2_1_6.html
TODO 使其正常工作。我每次看到的都是一堆随机字节。我的尝试:
boost_stacktrace_safe/mystacktrace.h
示例输出:
每次都会发生巨大变化,表明它是随机内存地址。
在 Ubuntu 22.04、boost 1.74.0 上测试。
glibc
backtrace
这个方法非常便携,因为它是 glibc 本身自带的。记录于: https://www.gnu.org/software/libc/ Manual/html_node/Backtraces.html
在 Ubuntu 22.04、glibc 2.35 上测试。
glibc_backtrace_symbols_fd/mystacktrace.h
编译:
使用
-rdynamic
的示例输出:不使用
-rdynamic
的示例输出:要获取不使用
-rdynamic
的行号,我们可以使用addr2line
:不幸的是,当我们不使用
-rdynamic
时,addr2line
无法处理函数格式中的函数名+偏移量,例如_Z9my_func_2v+0xd
。然而,GDB 可以:
一个让它更容易忍受的助手:
用法:
glibc
backtrace_symbols
backtrace_symbols_fd
的一个版本,它返回一个字符串而不是打印到文件句柄。glibc_backtrace_symbols/mystacktrace.h
glibc
backtrace
with C++ demangling hack 1:-export-dynamic
+dladdr
我不能'找不到一种简单的方法来使用 glibc
backtrace
自动消除 C++ 符号的角度。改编自:https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01 d3
这是一个“黑客”,因为它需要使用
-export-dynamic
更改 ELF。glibc_ldl.cpp
编译并运行:
输出:
在 Ubuntu 18.04 上测试。
glibc
backtrace
with C++ demangled hack 2:解析回溯输出显示于:https://panthema.net/2008/0901-stacktrace-demangled/
这是一个 hack,因为它需要解析。
TODO 对其进行编译并在此处显示。
GDB 脚本
我们还可以使用 GDB 来执行此操作,而无需重新编译:当 GDB 中某个断点被击中时,如何执行特定操作?
我们为测试设置了一个空的回溯函数:
gdb/mystacktrace。 h
然后使用:
main.gdb
我们可以运行:
示例输出:
使用以下 Bash 函数可以使上面的内容更加有用:
用法:
我不知道如何使用
制作
没有临时文件: 使用 ex 命令从命令行添加断点时出现问题commands
-ex在 Ubuntu 22.04、GDB 12.0.90 中测试。
GDB 代码注入
TODO 这就是梦想!它可能允许两种类似编译的速度,但不需要重新编译!要么:
compile code
+ 其他选项之一,最好是 C++23
: 如何在 gdb 中调用程序集? 可能已经可以了。但是编译代码
非常古怪,所以我什至懒得尝试dprintf
动态printf的内置dbt
命令:如何当 GDB 中某个断点被击中时执行特定操作?libunwind
TODO 这比 glibc 回溯有什么优势吗?非常相似的输出,也需要修改构建命令,但不是 glibc 的一部分,因此需要额外的软件包安装。
代码改编自:https://eli。 thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
main.c
编译并运行:
#define _XOPEN_SOURCE 700
必须位于顶部,或者我们必须使用-std=gnu99
:运行:
输出:
and:
给出:
使用
-O0
:and:
给出:
在 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
编译并运行:
输出:
然后我们可以找到
my_func_2
和my_func_1(int)
的行:其中给出:
TODO:为什么这些行相差一位?
在 Ubuntu 18.04、GCC 7.4.0、libunwind 1.2.1 上测试。
Linux 内核
如何打印Linux内核中当前线程堆栈跟踪?
libdwfl
这最初是在:https://stackoverflow.com/a/60713161/895245 这可能是最好的方法,但我必须进行更多基准测试,但请对这个答案进行投票。
TODO:我试图将该答案中有效的代码最小化为单个函数,但它存在段错误,如果有人能找到原因,请告诉我。
dwfl.cpp:答案达到了 30k 个字符,这是最简单的削减: https://gist.github.com/ cirosantilli/f1dd3ee5d324b9d24e40f855723544ac
编译并运行:
我们还需要 libunwind,因为这使得结果更加正确。如果你没有它,它会运行,但你会发现有些行有点错误。
输出:
基准测试运行:
输出:
所以我们看到这个方法比 Boost 的堆栈跟踪快 10 倍,因此可能适用于更多用例。
在 Ubuntu 22.04 amd64、libdw-dev 0.186、libunwind 1.3.2 中测试。
libbacktrace
https://github.com/ianlancetaylor/libbacktrace
考虑 harcore 库作者,值得一试,也许就是The One。 TODO 检查一下。
另请参阅
cpptrace
Cpptrace 简单且可移植,支持 C++11和更新的。与其他解决方案不同,它支持所有主要平台和编译器,并且几乎完全独立。
示例输出:
除了一般的堆栈跟踪生成之外,它还包括一个跟踪的异常对象,该对象在抛出时生成跟踪,并允许生成可以稍后解析的原始堆栈跟踪。
更多信息可以在此处找到。
Survey of C/C++ backtrace methods
In this answer I will try to run a single benchmark for a bunch of solutions to see which one runs faster, while also considering other points such as features and portability.
<stacktrace>
GCC 12.1stacktrace()
stacktrace::safe_dump_to
backtrace_symbols_fd
-rdynamic
backtrace_symbols
-rdynamic
Empty cells mean "TODO", not "no".
us
: microsecondLine number: shows actual line number, not just function name + a memory address.
It is usually possible to recover the line number from an address manually after the fact with
addr2line
. But it is a pain.Recompile: requires recompiling the program to get your traces. Not recompiling is better!
Signal safe: crucial for the important uses case of "getting a stack trace in case of segfault": How to automatically generate a stacktrace when my program crashes
As string: you get the stack trace as a string in the program itself, as opposed to e.g. just printing to stdout. Usually implies not signal safe, as we don't know the size of the stack trace string size in advance, and therefore requires malloc which is not async signal safe.
C: does it work on a plain-C project (yes, there are still poor souls out there), or is C++ required?
Test setup
All benchmarks will run the following
main.cpp
This input is designed to test C++ name demangling since
my_func_1(int)
andmy_func_1(float)
are necessarily mangled as a way to implement C++ function overload.We differentiate between the benchmarks by using different
-I
includes to point to different implementations ofprint_stacktrace()
.Each benchmark is done with a command of form:
The number of iterations is adjusted for each implementation to produce a total runtime of the order of 1s for that benchmark.
-O0
is used on all tests below unless noted. Stack traces may be irreparably mutilated by certain optimizations. Tail call optimization is a notable example of that: What is tail call optimization? There's nothing we can do about it.C++23
<stacktrace>
This method was previously mentioned at: https://stackoverflow.com/a/69384663/895245 please consider upvoting that answer.
This is the best solution... it's portable, fast, shows line numbers and demangles C++ symbols. This option will displace every other alternative as soon as it becomes more widely available, with the exception perhaps only of GDB for one-offs without the need or recompilation.
cpp20_stacktrace/mystacktrace.h
GCC 12.1.0 from Ubuntu 22.04 does not have support compiled in, so for now I built it from source as per: How to edit and re-build the GCC libstdc++ C++ standard library source? and set
--enable-libstdcxx-backtrace=yes
, and it worked!Compile with:
Sample output:
If we try to use GCC 12.1.0 from Ubuntu 22.04:
It fails with:
Checking build options with:
does not show:
so it wasn't compiled in. Bibliography:
It does not fail on the include because the header file:
has a feature check:
Boost
stacktrace
The library has changed quite a lot around Ubuntu 22.04, so make sure your version matches: Boost stack-trace not showing function names and line numbers
The library is pretty much superseded by the more portable C++23 implementation, but remains a very good option for those that are not at that standard version yet, but already have a "Boost clearance".
Documented at: https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack
Tested on Ubuntu 22.04, boost 1.74.0, you should do:
boost_stacktrace/mystacktrace.h
On Ubuntu 19.10 boost 1.67.0 to get the line numbers we had to instead:
which would call out to the
addr2line
executable and be 1000x slower than the newer Boost version.The package
libboost-stacktrace-dev
did not exist at all on Ubuntu 16.04.The rest of this section considers only the Ubuntu 22.04, boost 1.74 behaviour.
Compile:
Sample output:
Note that the lines are off by one line. It was suggested in the comments that this is because the following instruction address is being considered.
Boost
stacktrace
header onlyWhat the
BOOST_STACKTRACE_LINK
does is to require-lboost_stacktrace_backtrace
at link time, so we imagine without that it will just work. This would be a good option for devs who don't have the "Boost clearance" to just add as one offs to debug.TODO unfortunately it didn't so well for me:
then:
contains the overly short output:
which we can't even use with
addr2line
. Maybe we have to pass some other define from: https://www.boost.org/doc/libs/1_80_0/doc/html/stacktrace/configuration_and_build.html ?Tested on Ubuntu 22.04. boost 1.74.
Boost
boost::stacktrace::safe_dump_to
This is an interesting alternative to
boost::stacktrace::stacktrace
as it writes the stack trace in a async signal safe manner to a file, which makes it a good option for automatically dumping stack traces on segfaults which is a super common use case: How to automatically generate a stacktrace when my program crashesDocumented at: https://www.boost.org/doc/libs/1_70_0/doc/html/boost/stacktrace/safe_dump_1_3_38_7_6_2_1_6.html
TODO get it to work. All I see each time is a bunch of random bytes. My attempt:
boost_stacktrace_safe/mystacktrace.h
Sample output:
Changes drastically each time, suggesting it is random memory addresses.
Tested on Ubuntu 22.04, boost 1.74.0.
glibc
backtrace
This method is quite portable as it comes with glibc itself. Documented at: https://www.gnu.org/software/libc/manual/html_node/Backtraces.html
Tested on Ubuntu 22.04, glibc 2.35.
glibc_backtrace_symbols_fd/mystacktrace.h
Compile with:
Sample output with
-rdynamic
:Sample output without
-rdynamic
:To get the line numbers without
-rdynamic
we can useaddr2line
:addr2line
cannot unfortunately handle the function name + offset in function format of when we are not using-rdynamic
, e.g._Z9my_func_2v+0xd
.GDB can however:
A helper to make it more bearable:
Usage:
glibc
backtrace_symbols
A version of
backtrace_symbols_fd
that returns a string rather than printing to a file handle.glibc_backtrace_symbols/mystacktrace.h
glibc
backtrace
with C++ demangling hack 1:-export-dynamic
+dladdr
I couldn't find a simple way to automatically demangle C++ symbols with glibc
backtrace
.Adapted from: https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
This is a "hack" because it requires changing the ELF with
-export-dynamic
.glibc_ldl.cpp
Compile and run:
output:
Tested on Ubuntu 18.04.
glibc
backtrace
with C++ demangling hack 2: parse backtrace outputShown at: https://panthema.net/2008/0901-stacktrace-demangled/
This is a hack because it requires parsing.
TODO get it to compile and show it here.
GDB scripting
We can also do this with GDB without recompiling by using: How to do an specific action when a certain breakpoint is hit in GDB?
We setup an empty backtrace function for our testing:
gdb/mystacktrace.h
and then with:
main.gdb
we can run:
Sample output:
The above can be made more usable with the following Bash function:
Usage:
I don't know how to make
commands
with-ex
without the temporary file: Problems adding a breakpoint with commands from command line with ex commandTested in Ubuntu 22.04, GDB 12.0.90.
GDB code injection
TODO this is the dream! It might allow for both compiled-liked speeds, but without the need to recompile! Either:
compile code
+ one of the other options, ideally C++23<stacktrace>
: How to call assembly in gdb? Might already be possible. Butcompile code
is mega-quirky so I'm lazy to even trydbt
command analogous todprintf
dynamic printf: How to do an specific action when a certain breakpoint is hit in GDB?libunwind
TODO does this have any advantage over glibc backtrace? Very similar output, also requires modifying the build command, but not part of glibc so requires an extra package installation.
Code adapted from: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
main.c
Compile and run:
Either
#define _XOPEN_SOURCE 700
must be on top, or we must use-std=gnu99
:Run:
Output:
and:
gives:
With
-O0
:and:
gives:
Tested on Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.
libunwind with C++ name demangling
Code adapted from: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
unwind.cpp
Compile and run:
Output:
and then we can find the lines of
my_func_2
andmy_func_1(int)
with:which gives:
TODO: why are the lines off by one?
Tested on Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1.
Linux kernel
How to print the current thread stack trace inside the Linux kernel?
libdwfl
This was originally mentioned at: https://stackoverflow.com/a/60713161/895245 and it might be the best method, but I have to benchmark a bit more, but please go upvote that answer.
TODO: I tried to minimize the code in that answer, which was working, to a single function, but it is segfaulting, let me know if anyone can find why.
dwfl.cpp: answer reached 30k chars and this was the easiest cut: https://gist.github.com/cirosantilli/f1dd3ee5d324b9d24e40f855723544ac
Compile and run:
We also need libunwind as that makes results more correct. If you do without it, it runs, but you will see that some of the lines are a bit wrong.
Output:
Benchmark run:
Output:
So we see that this method is 10x faster than Boost's stacktrace, and might therefore be applicable to more use cases.
Tested in Ubuntu 22.04 amd64, libdw-dev 0.186, libunwind 1.3.2.
libbacktrace
https://github.com/ianlancetaylor/libbacktrace
Considering the harcore library author, it is worth trying this out, maybe it is The One. TODO check it out.
See also
cpptrace
Cpptrace is simple and portable supporting C++11 and newer. Unlike other solutions it supports all major platforms and compilers and is almost entirely self-contained.
Sample output:
In addition to general stacktrace generation it includes a traced exception object that generates a trace when thrown and allows for generation of raw stack traces that can be resolved later.
More information can be found here.
对于仅限 Linux 的解决方案,您可以使用 backtrace( 3) 简单地返回一个
void *
数组(事实上,每个数组都指向相应堆栈帧的返回地址)。要将这些转换为有用的东西,可以使用 backtrace_symbols(3)。注意 backtrace 中的 注释部分( 3):
For a linux-only solution you can use backtrace(3) that simply returns an array of
void *
(in fact each of these point to the return address from the corresponding stack frame). To translate these to something of use, there's backtrace_symbols(3).Pay attention to the notes section in backtrace(3):
在C++23中,会有
,然后你可以这样做:更多细节:
• https://en.cppreference.com/w/cpp/header/堆栈跟踪
• https://en.cppreference.com/w/cpp/实用程序/basic_stacktrace/operator_ltlt
In C++23, there will be
<stacktrace>
, and then you can do:Further details:
• https://en.cppreference.com/w/cpp/header/stacktrace
• https://en.cppreference.com/w/cpp/utility/basic_stacktrace/operator_ltlt
对旧线程的另一个答案。
当我需要这样做时,我通常只使用
system()
和pstack
所以像这样:
这个输出
这应该在 Linux、FreeBSD 和 Solaris 上工作(在 FreeBSD 上“bstack”端口比“pstack”工作得更好)。我不认为 macOS 有 pstack 或简单的等效项,但是这个 线程似乎有一个替代方案。
如果您使用的是
C
,那么您将需要使用C
字符串函数。我使用 7 作为 PID 中的最大位数,基于 这篇文章。
Another answer to an old thread.
When I need to do this, I usually just use
system()
andpstack
So something like this:
This outputs
This should work on Linux, FreeBSD and Solaris (on FreeBSD the "bstack" port works better than "pstack"). I don't think that macOS has pstack or a simple equivalent, but this thread seems to have an alternative.
If you are using
C
, then you will need to useC
string functions.I've used 7 for the max number of digits in the PID, based on this post.
Linux 特定,TLDR:
backtrace
-lunwind 时,glibc
中的 > 才会生成准确的堆栈跟踪(未记录的特定于平台的功能)。#include
(该库仅在其头文件中记录)。backtrace_symbols
和backtrace_symbolsd_fd
提供的信息最少。在现代 Linux 上,您可以使用函数
backtrace< 获取堆栈跟踪地址/代码>
。使
backtrace
在流行平台上生成更准确地址的未记录方法是链接-lunwind
(Ubuntu 18.04 上的libunwind-dev
)(请参阅下面的示例输出)。backtrace
使用函数_Unwind_Backtrace
,默认情况下后者来自libgcc_s.so.1
,并且该实现是最可移植的。当链接-lunwind
时,它提供了更准确的_Unwind_Backtrace
版本,但该库的可移植性较差(请参阅libunwind/src
)。不幸的是,配套的
backtrace_symbolsd
和backtrace_symbols_fd
函数大约十年来一直无法将堆栈跟踪地址解析为具有源文件名和行号的函数名称(请参阅示例输出如下)。但是,还有另一种方法可以将地址解析为符号,它会生成最有用的跟踪,其中包含函数名称、源文件和行号。方法是
#include
并与-ldw
链接(Ubuntu 18.04 上为libdw-dev
)。工作 C++ 示例 (
test.cc
):在 Ubuntu 18.04.4 LTS 上使用 gcc-8.3 编译:
输出:
当没有链接
-lunwind
时,它会产生不太准确的堆栈跟踪:为了进行比较,同一堆栈跟踪的
backtrace_symbols_fd
输出信息量最少:在生产版本(以及 C 语言版本)中,您可能希望通过替换
boost: 来使此代码更加健壮: core::demangle
、std::string
和std::cout
及其底层调用。您还可以重写 __cxa_throw 来捕获抛出异常时的堆栈跟踪,并在捕获异常时打印它。当它进入
catch
块时,堆栈已经展开,因此调用backtrace
为时已晚,这就是为什么必须在throw上捕获堆栈的原因
由函数__cxa_throw
。请注意,在多线程程序中,__cxa_throw
可以由多个线程同时调用,因此,如果它将堆栈跟踪捕获到全局数组中,则该数组必须是thread_local
。您还可以使堆栈跟踪打印功能异步信号安全< /a>,以便您可以直接从
SIGSEGV
、SIGBUS
信号处理程序(应使用自己的堆栈以保证稳健性)调用它。使用libdwfl
从信号处理程序获取函数名称、源文件和行号可能会失败,因为它不是异步的-信号安全或者进程的地址空间已被严重损坏,但实际上它在 99% 的时间内成功(我还没有看到它失败)。总而言之,用于自动堆栈跟踪输出的完整生产就绪库应该:
扔
上的堆栈跟踪捕获到线程特定的存储中。SIGSEGV
、SIGBUS
、SIGFPE
等的信号处理程序。Linux specific, TLDR:
backtrace
inglibc
produces accurate stacktraces only when-lunwind
is linked (undocumented platform-specific feature).#include <elfutils/libdwfl.h>
(this library is documented only in its header file).backtrace_symbols
andbacktrace_symbolsd_fd
are least informative.On modern Linux your can get the stacktrace addresses using function
backtrace
. The undocumented way to makebacktrace
produce more accurate addresses on popular platforms is to link with-lunwind
(libunwind-dev
on Ubuntu 18.04) (see the example output below).backtrace
uses function_Unwind_Backtrace
and by default the latter comes fromlibgcc_s.so.1
and that implementation is most portable. When-lunwind
is linked it provides a more accurate version of_Unwind_Backtrace
but this library is less portable (see supported architectures inlibunwind/src
).Unfortunately, the companion
backtrace_symbolsd
andbacktrace_symbols_fd
functions have not been able to resolve the stacktrace addresses to function names with source file name and line number for probably a decade now (see the example output below).However, there is another method to resolve addresses to symbols and it produces the most useful traces with function name, source file and line number. The method is to
#include <elfutils/libdwfl.h>
and link with-ldw
(libdw-dev
on Ubuntu 18.04).Working C++ example (
test.cc
):Compiled on Ubuntu 18.04.4 LTS with gcc-8.3:
Outputs:
When no
-lunwind
is linked, it produces a less accurate stacktrace:For comparison,
backtrace_symbols_fd
output for the same stacktrace is least informative:In a production version (as well as C language version) you may like to make this code extra robust by replacing
boost::core::demangle
,std::string
andstd::cout
with their underlying calls.You can also override
__cxa_throw
to capture the stacktrace when an exception is thrown and print it when the exception is caught. By the time it enterscatch
block the stack has been unwound, so it is too late to callbacktrace
, and this is why the stack must be captured onthrow
which is implemented by function__cxa_throw
. Note that in a multi-threaded program__cxa_throw
can be called simultaneously by multiple threads, so that if it captures the stacktrace into a global array that must bethread_local
.You can also make the stack trace printing function async-signal safe, so that you can invoke it directly from your
SIGSEGV
,SIGBUS
signal handlers (which should use their own stacks for robustness). Obtaining function name, source file and line number usinglibdwfl
from a signal handler may fail because it is not async-signal safe or if the address space of the process has been substantially corrupted, but in practice it succeeds 99% of the time (I haven't seen it fail).To summarize, a complete production-ready library for automatic stacktrace output should:
throw
into thread-specific storage.SIGSEGV
,SIGBUS
,SIGFPE
, etc..ucontext_t
signal function argument (may be excluding vector registers), a-la Linux kernel oops log messages.您可以在特定函数中使用宏函数来代替 return 语句。
例如,
您可以使用宏函数,而不是使用 return。
每当函数发生错误时,您都会看到 Java 风格的调用堆栈,如下所示。
完整的源代码可在此处获取。
c-callstack 位于 https://github.com/Nanolat
You can use a macro function instead of return statement in the specific function.
For example, instead of using return,
You can use a macro function.
Whenever an error happens in a function, you will see Java-style call stack as shown below.
Full source code is available here.
c-callstack at https://github.com/Nanolat
没有标准化的方法可以做到这一点。对于 Windows,该功能在 DbgHelp 库
There is no standardized way to do that. For windows the functionality is provided in the DbgHelp library
您可以使用 Boost 库来打印当前的调用堆栈。
人在这里: https://www.boost.org/doc/ libs/1_65_1/doc/html/stacktrace.html
You can use the Boost libraries to print the current callstack.
Man here: https://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html
我知道这个线程很旧,但我认为它对其他人可能有用。如果您使用 gcc,则可以使用其仪器功能(-finstrument-functions 选项)来记录任何函数调用(进入和退出)。查看此内容以获取更多信息: http://hacktalks.blogspot .fr/2013/08/gcc-instrument-functions.html
因此,您可以将每个调用推送和弹出到堆栈中,当您想要打印它时,您只需查看堆栈中的内容即可。
我已经测试过它,它工作完美并且非常方便
更新:您还可以在有关仪器选项的 GCC 文档中找到有关 -finstrument-functions 编译选项的信息:https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
I know this thread is old, but I think it can be useful for other people. If you are using gcc, you can use its instrument features (-finstrument-functions option) to log any function call (entry and exit). Have a look at this for more information: http://hacktalks.blogspot.fr/2013/08/gcc-instrument-functions.html
You can thus for instance push and pop every calls into a stack, and when you want to print it, you just look at what you have in your stack.
I've tested it, it works perfectly and is very handy
UPDATE: you can also find information about the -finstrument-functions compile option in the GCC doc concerning the Instrumentation options: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
您可以自己实现该功能:
使用全局(字符串)堆栈,并在每个函数开始时将函数名称和其他值(例如参数)压入该堆栈;在函数退出时再次弹出它。
编写一个函数,在调用时打印出堆栈内容,并在要查看调用堆栈的函数中使用它。
这听起来可能需要做很多工作,但非常有用。
You can implement the functionality yourself:
Use a global (string)stack and at start of each function push the function name and such other values (eg parameters) onto this stack; at exit of function pop it again.
Write a function that will printout the stack content when it is called, and use this in the function where you want to see the callstack.
This may sound like a lot of work but is quite useful.
当然,下一个问题是:这足够了吗?
堆栈跟踪的主要缺点是,为什么您拥有被调用的精确函数,您没有其他任何东西,例如其参数的值,这对于调试非常有用。
如果您可以访问 gcc 和 gdb,我建议使用
assert
检查特定条件,如果不满足则生成内存转储。当然,这意味着该过程将停止,但您将获得完整的报告,而不仅仅是堆栈跟踪。如果您希望采用一种不那么唐突的方式,您始终可以使用日志记录。有非常高效的日志记录工具,例如 Pantheios。这再次可以让您更准确地了解正在发生的事情。
Of course the next question is: will this be enough ?
The main disadvantage of stack-traces is that why you have the precise function being called you do not have anything else, like the value of its arguments, which is very useful for debugging.
If you have access to gcc and gdb, I would suggest using
assert
to check for a specific condition, and produce a memory dump if it is not met. Of course this means the process will stop, but you'll have a full fledged report instead of a mere stack-trace.If you wish for a less obtrusive way, you can always use logging. There are very efficient logging facilities out there, like Pantheios for example. Which once again could give you a much more accurate image of what is going on.
您可以使用 Poppy 来实现此目的。它通常用于在崩溃期间收集堆栈跟踪,但它也可以为正在运行的程序输出它。
现在这是好的部分:它可以输出堆栈上每个函数的实际参数值,甚至局部变量、循环计数器等。
You can use Poppy for this. It is normally used to gather the stack trace during a crash but it can also output it for a running program as well.
Now here's the good part: it can output the actual parameter values for each function on the stack, and even local variables, loop counters, etc.
您可以使用 GNU 分析器。它还显示了调用图!命令是 gprof,您需要使用某些选项来编译代码。
You can use the GNU profiler. It shows the call-graph as well! the command is
gprof
and you need to compile your code with some option.对于 QT,这是一种在输出中打印内容的选择性方法。
ccallstack.h
`
ccallstack.cpp
用法:
使用以下方法注册一个函数:
或者
然后只需调用:
输出:
简单而有用...
With QT, this is a selective approach what to print in output.
ccallstack.h
`
ccallstack.cpp
usage:
register a function using:
or
Then just call like:
outputs:
Simple and usefull...
不,不存在,尽管可能存在依赖于平台的解决方案。
No there is not, although platform-dependent solutions might exist.