Obtaining the backtrace - libunwind
Sometimes when working on a large project, I find it useful to figure out all the places from which some function or method is called. Moreover, more often than not I don't just want the immediate caller, but the whole call stack. This is most useful in two scenarios - when debugging and when trying to figure out how some code works.
One possible solution is to use a debugger - run the program within a debugger, place a breakpoint in the interesting place, examine call stack when stopped. While this works and can sometimes be very useful, I personally prefer a more programmatic approach. I want to change the code in a way that will print out the call stack in every place I find interesting. Then I can use grepping and more sophisticated tools to analyze the call logs and thus gain a better understanding of the workings of some piece of code.
In this post, I want to present a relatively simple method to do this. It's aimed mainly at Linux, but should work with little modification on other Unixes (including OS X).
Obtaining the backtrace - libunwind
I'm aware of three reasonably well-known methods of accessing the call stack programmatically:
- The gcc builtin macro __builtin_return_address: very crude, low-level approach. This obtains the return address of the function on each frame on the stack. Note: just the address, not the function name. So extra processing is required to obtain the function name.
- glibc's backtrace and backtrace_symbols: can obtain the actual symbol names for the functions on the call stack.
- libunwind
Between the three, I strongly prefer libunwind, as it's the most modern, widespread and portable solution. It's also more flexible than backtrace, being able to provide extra information such as values of CPU registers at each stack frame.
Moreover, in the zoo of system programming, libunwind is the closest to the "official word" you can get these days. For example, gcc can use libunwind for implementing zero-cost C++ exceptions (which requires stack unwinding when an exception is actually thrown) [1]. LLVM also has a re-implementation of the libunwind interface in libc++, which is used for unwinding in LLVM toolchains based on this library.
Code sample
Here's a complete code sample for using libunwind to obtain the backtrace from an arbitrary point in the execution of a program. Refer to the libunwind documentation for more details about the API functions invoked here:
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include <stdio.h> // Call this function to get a backtrace.
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;
}
printf("0x%lx:", pc); char sym[256];
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");
}
}
} void foo() {
backtrace(); // <-------- backtrace here!
} void bar() {
foo();
} int main(int argc, char **argv) {
bar(); return 0;
}
libunwind is easy to install from source or as a package. I just built it from source with the usual configure, make and make install sequence and placed it into /usr/local/lib.
Once you have libunwind installed in a place the compiler can find [2], compile the code snippet with:
gcc -o libunwind_backtrace -Wall -g libunwind_backtrace.c -lunwind
Finally, run:
$ LD_LIBRARY_PATH=/usr/local/lib ./libunwind_backtrace
0x400958: (foo+0xe)
0x400968: (bar+0xe)
0x400983: (main+0x19)
0x7f6046b99ec5: (__libc_start_main+0xf5)
0x400779: (_start+0x29)
So we get the complete call stack at the point where backtrace is called. We can obtain the function symbol names and the address of the instruction where the call was made (more precisely, the return address which is the next instruction).
Sometimes, however, we want not only the caller's name, but also the call location (source file name + line number). This is useful when one function calls another from multiple locations and we want to pinpoint which one is actually part of a given call stack. libunwind gives us the call address, but nothing beyond. Fortunately, it's all in the DWARF information of the binary, and given the address we can extract the exact call location in a number of ways. The simplest is probably to call addr2line:
$ addr2line 0x400968 -e libunwind_backtrace
libunwind_backtrace.c:
We pass the PC address to the left of the bar frame to addr2line and get the file name and line number.
Alternatively, we can use the dwarf_decode_address example from pyelftools to obtain the same information:
$ python <path>/dwarf_decode_address.py 0x400968 libunwind_backtrace
Processing file: libunwind_backtrace
Function: bar
File: libunwind_backtrace.c
Line:
If printing out the exact locations is important for you during the backtrace call, you can also go fully programmatic by using libdwarf to open the executable and read this information from it, in the backtrace call. There's a section and a code sample about a very similar task in my blog post on debuggers.
C++ and mangled function names
The code sample above works well, but these days one is most likely writing C++ code and not C, so there's a slight problem. In C++, names of functions and methods are mangled. This is essential to make C++ features like function overloading, namespaces and templates work. Let's say the actual call sequence is:
namespace ns {
template <typename T, typename U>
void foo(T t, U u) {
backtrace(); // <-------- backtrace here!
}
} // namespace ns
template <typename T>
struct Klass {
T t;
void bar() {
ns::foo(t, true);
}
};
int main(int argc, char** argv) {
Klass<double> k;
k.bar();
return ;
}
The backtrace printed will then be:
0x400b3d: (_ZN2ns3fooIdbEEvT_T0_+0x17)
0x400b24: (_ZN5KlassIdE3barEv+0x26)
0x400af6: (main+0x1b)
0x7fc02c0c4ec5: (__libc_start_main+0xf5)
0x4008b9: (_start+0x29)
Oops, that's not nice. While some seasoned C++ veterans can usually make sense of simple mangled names (kinda like system programmers who can read text from hex ASCII), when the code is heavily templated this can get ugly very quickly.
One solution is to use a command-line tool - c++filt:
$ c++filt _ZN2ns3fooIdbEEvT_T0_
void ns::foo<double, bool>(double, bool)
However, it would be nicer if our backtrace dumper would print the demangled name directly. Luckily, this is pretty easy to do, using the cxxabi.h API that's part of libstdc++ (more precisely, libsupc++). libc++ also provides it in the low-level libc++abi. All we need to do is call abi::__cxa_demangle. Here's a complete example:
#define UNW_LOCAL_ONLY
#include <cxxabi.h>
#include <libunwind.h>
#include <cstdio>
#include <cstdlib> 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) > ) {
unw_word_t offset, pc;
unw_get_reg(&cursor, UNW_REG_IP, &pc);
if (pc == ) {
break;
}
std::printf("0x%lx:", pc); char sym[];
if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == ) {
char* nameptr = sym;
int status;
char* demangled = abi::__cxa_demangle(sym, nullptr, nullptr, &status);
if (status == ) {
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");
}
}
} namespace ns { template <typename T, typename U>
void foo(T t, U u) {
backtrace(); // <-------- backtrace here!
} } // namespace ns template <typename T>
struct Klass {
T t;
void bar() {
ns::foo(t, true);
}
}; int main(int argc, char** argv) {
Klass<double> k;
k.bar(); return ;
}
This time, the backtrace is printed with all names nicely demangled:
$ LD_LIBRARY_PATH=/usr/local/lib ./libunwind_backtrace_demangle
0x400b59: (void ns::foo<double, bool>(double, bool)+0x17)
0x400b40: (Klass<double>::bar()+0x26)
0x400b12: (main+0x1b)
0x7f6337475ec5: (__libc_start_main+0xf5)
0x4008b9: (_start+0x29)
Obtaining the backtrace - libunwind的更多相关文章
- 高效获得Linux函数调用栈/backtrace的方法【转】
转自:https://blog.csdn.net/littlefang/article/details/42295803 有四种方法可以获得Linux的函数调用堆栈,参见CALL STACK TRAC ...
- Obtaining Query Count Without executing a Query in Oracle D2k
Obtaining Query Count Without executing a Query in Oracle D2k Obtaining a count of records that will ...
- iOS 崩溃日志 Backtrace的符号化
iOS的崩溃日志配合dsym文件可以找到崩溃时的backtrace,这是解决崩溃的最重要的信息. 如果是在同一台mac上打包, 导入crash log时候会自动将backtrace符号化,可以看到方法 ...
- CGContextTranslateCTM: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
最近在测试的过程中, 发现了SpringBoar的一个问题: SpringBoard[53] <Error>: CGContextTranslateCTM: invalid context ...
- 利用backtrace和objdump进行分析挂掉的程序
转自:http://blog.csdn.net/hanchaoman/article/details/5583457 汇编不懂,先把方法记下来. glibc为我们提供了此类能够dump栈内容的函数簇, ...
- chromium的Backtrace记录
ffmpeg处理完视频流后,上层的webrtc调用错误,可以看出webrtc的调用过程: Backtrace: webrtc::RTPFragmentationHeader::CopyFrom [0x ...
- SQLSERVER:Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.
背景: 在最近开发中遇到一个问题,对一个数据库进行操作时,我采用64个并行的任务每个任务保证一个数据库连接对象:但是每个任务内部均包含有24个文件需要读取,在读取文件之后,我们需要快速将这24个文件批 ...
- 嵌入式 linux下利用backtrace追踪函数调用堆栈以及定位段错误
嵌入式 linux下利用backtrace追踪函数调用堆栈以及定位段错误 2015-05-27 14:19 184人阅读 评论(0) 收藏 举报 分类: 嵌入式(928) 一般察看函数运行时堆栈的 ...
- linux下利用backtrace追踪函数调用堆栈以及定位段错误
一般察看函数运行时堆栈的方法是使用GDB(bt命令)之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的. 在glibc ...
随机推荐
- 在idea中添加try/catch的快捷键
在idea中添加try/catch的快捷键ctrl+alt+t选中想被try/catch包围的语句,同时按下ctrl+alt+t,出现下图
- 百万年薪python之路 -- 网络通信原理
1. C/S B/S架构 C: Client 客户端 B: Browse 浏览器 S: Server 服务端 C/S架构: 基于客户端与服务端之间的通信 eg: QQ,微信,LOL,DNF等需要安装A ...
- Java学习笔记二--API课堂记录
JavaSE课堂记录(二) 第一节课 方法的重载:方法名相同,参数列表不同 方法的重写:方法名,参数列表相同 两同:方法名相同,参数列表相同 两小:访问权限小与等于父类,返回值类型小于等于父类(返回值 ...
- Leetcode Tags(1)Linked List
1.知识点回顾 https://www.cnblogs.com/BigJunOba/p/9174206.html https://www.cnblogs.com/BigJunOba/p/9174217 ...
- NetworkManager网络通讯_networkReader/Writer(六)
unet客户端和服务端进行消息发送时可以采用上一节中方法,也可以直接用networkReader/Writer类进行发送 (一)服务端/客户端注册消息 ; m_Server.RegisterHandl ...
- Spring框架学习笔记(5)——Spring Boot创建与使用
Spring Boot可以更为方便地搭建一个Web系统,之后服务器上部署也较为方便 创建Spring boot项目 1. 使用IDEA创建项目 2. 修改groupid和artifact 3. 一路n ...
- 虚拟环境:virtualenv与virtualenvwrapper
前言: 在使用 Python 开发的过程中,工程一多,难免会碰到不同的工程依赖不同版本的库的问题: 亦或者是在开发过程中不想让物理环境里充斥各种各样的库,引发未来的依赖灾难. 此时,我们需要对于不同的 ...
- 2019 年容器生态统计报告发布 | 云原生生态周报 Vol. 26
作者 | 酒祝.天元.元毅.心水.衷源 业界要闻 1.2019 年容器生态统计报告发布 据报告显示,Kubernetes 占据 77% 的容器编排产品份额,Docker 占据 79% 的容器引擎产品 ...
- 20191107-配置 pyqt5+pycharm 环境
因公司需要,今天配置了 pyqt5+pycharm 环境,准备试着写些 UI 界面. 参考资源: 1. Python3+PyQt5+PyCharm 桌面GUI开发环境搭建 https://www.cn ...
- Unity4-用户输入
Input是一个类,可以接收用户的输入 使用AddComponentMenu("Demo1/InputTest1"),将脚本加入到工程中. //例子: void Update() ...