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 ...
随机推荐
- Gitlab CI 集成 Kubernetes 集群部署 Spring Boot 项目
在上一篇博客中,我们成功将 Gitlab CI 部署到了 Docker 中去,成功创建了 Gitlab CI Pipline 来执行 CI/CD 任务.那么这篇文章我们更进一步,将它集成到 K8s 集 ...
- Bootstrap应用核心
Bootstrap是当前世界最受欢迎的响应式.移动设备优先的门户和应用前端框架.它不是单一的CSS或JavaScript框架,而是完整的HTML.CSS.JavaScript框架,你可以仅通过Boot ...
- CocosCreator中_worldMatrix到底是什么(下)
Cocos Creator 中 _worldMatrix 到底是什么(下) 1. 摘要 上篇介绍了矩阵的基本知识以及对应图形变换矩阵推倒.中篇具体介介绍了对应矩阵转换成cocos creator代码的 ...
- C#解析深浅拷贝
前言 前面我们学习完了设计模式,在其中我们有了解到原型模式.这里涉及到了克隆自身对象.那么也就是对对象进行拷贝.这里就涉及到了这么一个概念.深浅拷贝.何为深拷贝何为浅拷贝呢?我们一起来看看吧. 浅拷贝 ...
- Xbim.GLTF源码解析(四):轻量化处理
原创作者:flowell,转载请标明出处:https://www.cnblogs.com/flowell/p/10839433.html 在IFC标准中,由IfcRepresentationMap支持 ...
- Pandas | 17 缺失数据处理
数据丢失(缺失)在现实生活中总是一个问题. 机器学习和数据挖掘等领域由于数据缺失导致的数据质量差,在模型预测的准确性上面临着严重的问题. 在这些领域,缺失值处理是使模型更加准确和有效的重点. 使用重构 ...
- 认识JVM的内存分配
当我们在JVM中运行一段程序代码,JVM初始运行的时候都会分配好Method Area(方法区)和Heap(堆),而JVM每遇到一个线程,就为其分配一个Program Counter Register ...
- 消息队列 ActiveMQ 、RocketMQ 、RabbitMQ 和 Kafka 如何选择?
「 预计阅读 6 分钟 」 旁白:这是一篇拖更了N久的文章...0.0(看不见我~) 往期回顾 前端框架 jQuery 和 Vue 如何选择? 安全框架 Shiro 和 Spring Security ...
- Topshelf+Quartz在.Net Core框架下的实现
在我们日常开发工作中,经常会运用到Quartz+Topshelf组件的组合来开发一些定时任务.那么在.Net Core下如何去使用呢?我自己尝试搭建了一个测试项目,过程中遇到了以下一些问题: Quar ...
- H5 + WebGL 实现的楼宇自控 3D 可视化监控
前言 智慧楼宇和人们的生活息息相关,楼宇智能化程度的提高,会极大程度的改善人们的生活品质,在当前工业互联网大背景下受到很大关注.目前智慧楼宇可视化监控的主要优点包括: 智慧化 -- 智慧楼宇是一个生态 ...