函数调用栈打印

时间:2021-03-02 03:39:34

Linux打印函数调用栈

方法一:

#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
/* Obtain a backtrace and print it to stdout. */
void print_trace (void)
{
  void *array[10];
  size_t size;
  char **strings;
  size_t i;

  size = backtrace (array, 10);
  strings = backtrace_symbols (array, size);

  printf ("Obtained %zd stack frames.\n", size);

  for (i = 0; i < size; i++)
  printf ("%s\n", strings);

  free (strings);
}

int main()
{
  print_trace ();
}
编译:g++ stack.c -g -o stack  -rdynamic

方法二:

#include <unistd.h>
#include <stdio.h>
#include <execinfo.h>
#include <stdlib.h>
#include <string.h>
void backtrace()
{
        const int maxLevel = 200;
        void* buffer[maxLevel];
        int level = backtrace(buffer, maxLevel);
        const int SIZE = 1024;
        char cmd[SIZE] = "addr2line -C -f -e ";

        // let prog point to the end of "cmd"

        char* prog = cmd + strlen(cmd);

        int r = readlink("/proc/self/exe", prog, sizeof(cmd) - (prog-cmd)-1);

        FILE* fp = popen(cmd, "w");
        if (!fp)
        {
                perror("popen");
                return;
        }
        for (int i = 0; i < level; ++i)
        {
                fprintf(fp, "%p\n", buffer[i]);
        }

        fclose(fp);
}
void foo(int, char*)
{
        backtrace();
}
void bar(double)
{
        foo(0, NULL);
}

int main()
{
        bar(0.0);
        //A a;

        return 0;
}
编译:g++ stack1.c -g -o stack1 -rdynamic

注意:execinfo.h文件

Android 打印函数调用栈

为什么要打印函数调用堆栈?

打印调用堆栈可以直接把问题发生时的函数调用关系打出来,非常有利于理解函数调用关系。比如函数A可能被B/C/D调用,如果只看代码,B/C/D谁调用A都有可能,如果打印出调用堆栈,直接就把谁调的打出来了。

不仅如此,打印函数调用堆栈还有另一个好处。在Android代码里,函数命名很多雷同的,虚函数调用,几个类里的函数名相同等。如果用了堆栈打印,很容易看到函数调用逻辑。

那么一个问题来了,Android/kernel系统运行的境况下,打印出某个情形下的堆栈信息,这个对源代码逻辑研究很有帮助。

Linux Kernel

Kernel里最简单,直接有几现成的函数可以使用:

dump_stack() 这个函数打出当前堆栈和函数调用backtrace后接着运行。

> 需要包含的头文件:

#include <asm/ptrace.h>

> 在函数中调用:

dump_stack();

WARN_ON(x) 这个函数跟dump_stack很像,他需要满足一定的条件才把stack打出来。

打印出来的结果都在kernel log里面,一般使用dmesg命令就可以看到了

$ dmesg

Native C++

Android在新版(至少5.0, 6.0)里加入了CallStack类,这个类可以打印出当前的backtrace。用法很简单:

> 前面确保包含头文件#include <utils/CallStack.h>

> Android.mk的库依赖列表(LOCAL_SHARED_LIBRARIES)里包含libutils,一般都已经包含了。

> 然后在要打印堆栈处加入android::CallStack cs(“TAG”);

> “TAG”是logcat输出的TAG,这里可以自己定义。如果上下文已经在android namespace里,“android::”前缀就不必加了。

Native C++输出的log可以在logcat里看到。

 

注意,在网上的一些文档里说要这么用:

CallStack stack; 

stack.update(); 

stack.dump();

这样做已经不行了,在新版Android里编译不过。

 Native C

Andorid对C的堆栈打印支持不太好,过去一般推荐libcorkscrew.so,并加入大段代码来umwind_backtrace。新版Android上libcorkscrew已经被去掉,加载libcorkscrew库的方法自然就不能用了。 


一个简单方法是用C语言调用C++的函数,就是extern "C"。

> 先在项目里加入一个c++,如callstack.cpp。里面是:

#include <utils/CallStack.h>

extern "C" void dumping_callstack(void);

void dumping_callstack(void) {
      android::CallStack cs("Audio");
}
 

> 在项目里再加入一个c++的头文件,如callstack.h,里面是:

void dumping_callstack(void);

> 在将要添加函数调用栈的源文件的编译文件Android.mk中,源代码列表(LOCAL_SRC_FILES)里加入callstack.cpp,同时确保libutils在依赖列表里(LOCAL_SHARED_LIBRARIES)。

> 在native C里include callstack.h后,就可以直接调用dumping_callstack()打印函数调用栈了

#include "callstack.h"
...
dumping_callstack();
...

这个log可以在logcat里看到。

Java

Java最详细,它的backtrace最详细,连文件名和行号都打出来了:

> 在需要打印函数调用栈的位置添加如下代码:

Exception e = new Exception("Audio");

e.printStackTrace();

log在logcat里看以看到。