前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【粉丝问答18】linux下查看函数被那些函数调用过?

【粉丝问答18】linux下查看函数被那些函数调用过?

作者头像
IOT物联网小镇
发布2021-05-13 11:25:59
1.6K0
发布2021-05-13 11:25:59
举报
文章被收录于专栏:IOT物联网小镇IOT物联网小镇

欢迎订阅粉丝问答专题,点击下面链接,然后点击订阅

粉丝问答

一、问题

有个打印log的函数,想知道该函数执行的时候,之前执行了哪些函数?

二、分析

在应用程序打印函数栈需要通过函数backtrace(),该函数对应头文件如下:

代码语言:javascript
复制
  #include <execinfo.h>

1、三个与打印调用栈相关的函数

打印函数栈需要使用到以下3个函数

代码语言:javascript
复制
int backtrace(void** buffer, int size);

函数功能:用于获取当前线程的调用堆栈。参数:buffer:它是一个指针数组,函数获取的当前线程的调用堆栈将会被存放在buffer中。在buffer中的指针实际是从堆栈中获取的返回地址,每一个堆栈 框架有一个返回地址。size:用来指定buffer中可以保存多少个void*元素。返回值:实际获取的指针个数,最大不超过size大小。

代码语言:javascript
复制
char** backtrace_symbols (void *const *buffer, int size);

函数功能:将从backtrace函数获取的信息转化为一个字符串数组。参数:buffer:从backtrace函数获取的数组指针。size:是该数组中的元素个数(backtrace函数的返回值)。返回值:是一个指向字符串数组的指针,它的大小同buffer相同。每个字符串包含了一个相对于buffer中对应元素的 可打印信息。它包括函数名,函数的偏移地址,和实际的返回地址。

注:

  • 1、只有使用ELF二进制格式的程序才能获取函数名称和偏移地址。在其他系统,只有16进制的返回地址能被获取。另外,需要传递相应的标志给链接器,以能支持函数名功能即编译选项-rdynamic。
  • 2、backtrace_symbols生成的字符串都是malloc出来的,最后需要free该块内存。
代码语言:javascript
复制
void backtrace_symbols_fd (void *const *buffer, int size, int fd)

功能:backtrace_symbols_fd与backtrace_symbols函数具有相同的功能,不同的是它不会给调用者返回字符串数组,而是将结果写入文件描述符为fd的文件中,每个函数对应一行.它不需要调用malloc函数,因此适用于有可能调用该函数会失败的情况。参数:fd:通常填写STDOUT_FILENO

2. 链接库

在编译的时候需要加上**-rdynamic**选项。

代码语言:javascript
复制
-rdynamic
     Pass the flag -export-dynamic to the ELF linker, on targets that support it. This instructs the linker to add all symbols, not only used ones, to the dynamic symbol table. This option is needed for some uses of "dlopen" or to allow obtaining backtraces from within a program.

该选项让链接器将所有符号添加到动态符号表中,这样才能将函数地址翻译成函数名,否则打印的结果是不会打印函数名的。

另外,这个选项不会处理static函数,所以,static函数的符号无法得到。

3. 举例

代码语言:javascript
复制
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
void fun1();
void fun2();
void fun3();
 
void print_stacktrace();
 
void print_stacktrace()
{
    int size = 16;
    void * array[100];
 
    int stack_num = backtrace(array, size);
 
 char ** stacktrace = backtrace_symbols(array, stack_num);
 
 backtrace_symbols_fd(array,size,STDOUT_FILENO);
 
#if 0
    char ** stacktrace = backtrace_symbols(array, stack_num);
 
    for (int i = 0; i < stack_num; ++i)
    {
        printf("%s\n", stacktrace[i]);
    }
    free(stacktrace);
#endif
}
void fun1()
{
    printf("stackstrace begin:\n");
    print_stacktrace();
}
void fun2()
{
    fun1();
}
void fun3()
{
    fun2();
}
int main()
{
    fun3();
}
 

编译运行gcc编译时加上-rdynamic参数,通知链接器支持函数名功能(不加-rdynamic参数则无函数名打印):

代码语言:javascript
复制
gcc 123.c -o run -rdynamic -g

执行结果:

4. 补充 address2line

同一个函数可以在代码中多个地方调用,如果我们只是知道函数,要想知道在哪里调用了该函数,可以通过address2line命令来完成,我们用第2步中编译出来的test2来做实验(address2line的-f选项可以打出函数名, -C选项也可以demangle):

address2line

三、内核代码中如何打印函数栈?

在Linux内核中提供了一个可以打印出内核调用堆栈的函数 dump_stack()。

该函数在我们调试内核的过程中可以打印出函数调用关系,该函数可以帮助我们进行内核调试,以及让我们了解内核的调用关系。

1. 头文件

该函数头文件为:

代码语言:javascript
复制
#include <asm/ptrace.h>

使用方式:

直接在想要查看的函数中添加

代码语言:javascript
复制
dump_stack();

2. 举例

测试代码如下:hello.c

代码语言:javascript
复制
  1 #include <linux/init.h>
  2 #include <linux/module.h>
  3 #include <asm/ptrace.h>
  4 
  6 MODULE_LICENSE("GPL");
  7 MODULE_AUTHOR("PD");
  8 void aaa(int a);
  9 void bbb(int b);
 10 void ccc(int c);
 11
 14 void ccc(int c)
 15 {
 16     printk(KERN_SOH"cccc \n");
 17     dump_stack();
 18     printk("c is %d\n",c);
 19 }
 20 void bbb(int b)
 21 {
 22     int c = b + 10;
 23     printk(KERN_SOH"bbbb \n");
 24     ccc(c);
 25 }
 26 void aaa(int a)
 27 {
 28     int b = a + 10;
 29     printk(KERN_SOH"aaaa \n");
 30     bbb(b);
 31 }
 32 
 34 static int hello_init(void)
 35 {
 36     int a = 10;                                                                
 37 
 38     aaa(a);
 39     printk(KERN_SOH"hello_init \n");
 40 
 41     return 0;
 42 }
 43 static void hello_exit(void)
 44 {
 45     printk("hello_exit \n");
 46     return;
 47 }
 48 
 49 module_init(hello_init); //insmod
 50 module_exit(hello_exit);//rmmod

Makefile

代码语言:javascript
复制
ifneq ($(KERNELRELEASE),)
obj-m:=hello.o
else
KDIR :=/lib/modules/$(shell uname -r)/build
PWD  :=$(shell pwd)
all:
 make -C $(KDIR) M=$(PWD) modules
clean:
 rm -f *.ko *.o *.mod.o *.symvers *.cmd  *.mod.c *.order
endif

编译安装模块

代码语言:javascript
复制
dmesg -c
make
insmod hello.ko

【注意】 都在root权限下操作

结果

可以看到在函数ccc中使用dump_stack()打印出了ccc的函数调用栈。

在内核开发中,我们可以使用dump_stack()来打印相关信息,同时在内核源码学习中也可以用来了解函数调用关系。

·················· END ··················

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IOT物联网小镇 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、问题
  • 二、分析
    • 1、三个与打印调用栈相关的函数
      • 2. 链接库
        • 3. 举例
          • 4. 补充 address2line
          • 三、内核代码中如何打印函数栈?
            • 1. 头文件
              • 2. 举例
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档