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. 使用JRebel插件实现SpringBoot应用代码热加载

    前言 在实际的开发过程中,我们经常修改代码之后,手动的重启项目,查看修改效果.那么有没有一种方式能够快速的.自动的帮我们将修改代码自动更新,避免手动重启,从而提高开发效率呢?是有的,在我之前的文章里面 ...

  2. Java源码 HashMap.roundUpToPowerOf2原理

    int rounded = number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (rounded = Integer.highestOneBit(nu ...

  3. 基于 HTML5 + Canvas 实现的 PID 可视化系统

    前言 随着工业物联网和互联网技术的普及和发展,人工填料的方式已经逐渐被机械设备取代.工业厂商减小误操作.提升设备安全以及追求高效率等制造特点对设备的要求愈加高标准.严要求.同时机械生产以后还需遵从整个 ...

  4. 利用X-Forwarded-For伪造客户端IP漏洞成因及防范

    内容转载自叉叉哥https://blog.csdn.net/xiao__gui/article/details/83054462 问题背景 在Web应用开发中,经常会需要获取客户端IP地址.一个典型的 ...

  5. python:枚举类型

    1.什么是枚举类型? 枚举类型可以看做是一系列常量的集合,通常用于表示某些有限且固定的集合,例如月份(一年有12个月).星期(一星期有七天).季节(一年四个季节)等. 2.枚举的定义 定义枚举首先要导 ...

  6. AutoCad 二次开发 .net 之相同块的自动编号

    主要步骤: 一.获取一个块的id: 其中oId就是了. 二.通过次oId获取块引用blkRef: 三.通过它获取所有相同的块引用的id集合: 四.通过步骤三的集合得到所有的块引用得到集合listBr: ...

  7. 等距结点下的Newton插值多项式系数计算(向前差分)

    插值多项式的牛顿法 1.为何需要牛顿法? ​ 使用Lagrange插值法不具备继承性.当求好经过\(({x_0},{y_0})-({x_n},{y_n})\)共n+1个点的插值曲线时候,如果再增加一个 ...

  8. CSPS_103

    被sdfz踩爆了! %%%kai586123 %%%Gekoo %%%sdfz_yrt T1 我以为是水题!一直在肝! 而且为什么每次我的考场暴力都是考后才调出来啊!! 先记录一下正解的大神做法: 按 ...

  9. NOIP模拟 13

    我终于又厚颜无耻地赖着没走 ...... T1 矩阵游戏 用了30hmin找规律,然后发现貌似具有交换律,然后发现貌似有通项公式,然后发现貌似每次操作对通项的影响是相同的,然后发现貌似跟N没啥关系.. ...

  10. 卖饲料——单调队列优化dp

    题目描述 约翰开车来到镇上,他要带K吨饲料回家.运送饲料是需要花钱的,如果他的车上有X吨饲料,每公里就要花费X^2元,开车D公里就需要D* X^2元.约翰可以从N家商店购买饲料,所有商店都在一个坐标轴 ...