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:

  1. 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.
  2. glibc's backtrace and backtrace_symbols: can obtain the actual symbol names for the functions on the call stack.
  3. 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 configuremake 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的更多相关文章

  1. 高效获得Linux函数调用栈/backtrace的方法【转】

    转自:https://blog.csdn.net/littlefang/article/details/42295803 有四种方法可以获得Linux的函数调用堆栈,参见CALL STACK TRAC ...

  2. 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 ...

  3. iOS 崩溃日志 Backtrace的符号化

    iOS的崩溃日志配合dsym文件可以找到崩溃时的backtrace,这是解决崩溃的最重要的信息. 如果是在同一台mac上打包, 导入crash log时候会自动将backtrace符号化,可以看到方法 ...

  4. 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 ...

  5. 利用backtrace和objdump进行分析挂掉的程序

    转自:http://blog.csdn.net/hanchaoman/article/details/5583457 汇编不懂,先把方法记下来. glibc为我们提供了此类能够dump栈内容的函数簇, ...

  6. chromium的Backtrace记录

    ffmpeg处理完视频流后,上层的webrtc调用错误,可以看出webrtc的调用过程: Backtrace: webrtc::RTPFragmentationHeader::CopyFrom [0x ...

  7. 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个文件批 ...

  8. 嵌入式 linux下利用backtrace追踪函数调用堆栈以及定位段错误

    嵌入式 linux下利用backtrace追踪函数调用堆栈以及定位段错误 2015-05-27 14:19 184人阅读 评论(0) 收藏 举报  分类: 嵌入式(928)  一般察看函数运行时堆栈的 ...

  9. linux下利用backtrace追踪函数调用堆栈以及定位段错误

    一般察看函数运行时堆栈的方法是使用GDB(bt命令)之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的. 在glibc ...

随机推荐

  1. ESP8266开发之旅 网络篇⑨ HttpClient——ESP8266HTTPClient库的使用

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  2. 整洁的 Table View 代码

    Table view 是 iOS 应用程序中非常通用的组件.许多代码和 table view 都有直接或间接的关系,随便举几个例子,比如提供数据.更新 table view,控制它的行为以及响应选择事 ...

  3. mybatis 使用redis实现二级缓存(spring boot)

    mybatis 自定义redis做二级缓存 前言 如果关注功能实现,可以直接看功能实现部分 何时使用二级缓存 一个宗旨---不常变的稳定而常用的 一级是默认开启的sqlsession级别的. 只在单表 ...

  4. Java基础(三十五)Math、Random类和数字格式化(String.format方法)

    一.Math类 Math类常用的方法: public static long abs (double a) 返回a的绝对值 public static double max (double a,dou ...

  5. quartus使用串口IP模块

    在quartus平台中使用串口模块的IP,需要使用到platform designer软件来实现. 1.在quartus界面调出IP Catalog界面. 2.在IP catalog中搜索UART,找 ...

  6. Mycat分布式数据库架构解决方案--Linux安装运行Mycat

    echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加echo微信(微信号:t2421499075)交流学习. 百战不败,依不自称常胜,百败不颓,依能奋力前行.--这才是真正的堪称强大!!! Myc ...

  7. 热门开源网关的性能对比:Goku > Kong > Tyk

    不多说,先展示最后的性能测试结果 我们将Goku与市场上的其他同类热门产品进行比较,使用相同的环境和条件,测试以下产品:Goku.Kong.Tyk.简单介绍下, Goku API Gateway (中 ...

  8. 一:XMind

  9. Access教程 Access学习 Access培训 Access QQ交流集中地

    Access教程 Access学习 Access培训 Access QQ交流集中地 http://www.office-cn.net/plugin.php?id=zstm_qqgroup:index ...

  10. java常用类Time

    LocalDate:IOS格式(yyyy-MM-dd)日期 LocalTime:表示一个时间 LocalDateTime:表示时间日期 Instant 时间线上的瞬时点,可以用来记录应用程序中的时间时 ...