【问题标题】:C function profiling (address seem to be offseted)C函数分析(地址似乎偏移了)
【发布时间】:2013-11-08 10:40:08
【问题描述】:

我正在尝试使用 -finstrument-functions 选项分析函数调用。 基本上,我所做的是将以下内容写入任何已编译的源代码中:

static int __stepper=0;
void __cyg_profile_func_enter(void *this_fn, void *call_site)
                              __attribute__((no_instrument_function));
void __cyg_profile_func_enter(void *this_fn, void *call_site) {
  int i=0;
  for( ; i<__stepper; i++ ) printf(" ");
  printf("E: %p %p\n", this_fn, call_site);
  __stepper ++;
} /* __cyg_profile_func_enter */

void __cyg_profile_func_exit(void *this_fn, void *call_site)
                             __attribute__((no_instrument_function));
void __cyg_profile_func_exit(void *this_fn, void *call_site) {
  int i=0;
  __stepper --;
  for( ; i<__stepper; i++ ) printf(" ");
  printf("L:  %p %p\n", this_fn, call_site);
} /* __cyg_profile_func_enter */

并得到以下结果:

 E: 0xb7597ea0 0xb75987a8
  E: 0xb7597de0 0xb7597ef5
  L:  0xb7597de0 0xb7597ef5
 L:  0xb7597ea0 0xb75987a8

所有函数调用地址都在该区域附近(0xb7.......) 但是,如果我尝试使用“readelf -s”读取函数的符号,它会给出以下信息:

2157: 00101150   361 FUNC    LOCAL  DEFAULT   13 usb_audio_initfn
2158: 00100940   234 FUNC    LOCAL  DEFAULT   13 usb_audio_handle_reset
2159: 00100de0   867 FUNC    LOCAL  DEFAULT   13 usb_audio_handle_control

二进制所有函数的地址区域在0x00左右…… 所以,我无法从函数指针中获取函数名。 看起来像函数指针如何获得偏移量之类的。

有人知道吗?

【问题讨论】:

  • 你可以用 GDB 做很多事情。使用指针进行扩展解析是一个有用的功能。你试过了吗?

标签: c function profiling


【解决方案1】:

从问题看来,您正在分析一个库函数。

要了解正在测量的功能是什么,您有 2 个选择:

1 运行使用gdb 下的库的程序,并在main 处停止。此时,获取程序PID=...pid 并执行`cat /proc/$PID/maps'。在那里你应该看到这样的东西:

➜  ~  ps
  PID TTY          TIME CMD
18533 pts/4    00:00:00 zsh
18664 pts/4    00:00:00 ps
➜  ~  PID=18533
➜  ~  cat /proc/$PID/maps
00400000-004a2000 r-xp 00000000 08:01 3670052                            /bin/zsh5
006a1000-006a2000 r--p 000a1000 08:01 3670052                            /bin/zsh5
006a2000-006a8000 rw-p 000a2000 08:01 3670052                            /bin/zsh5
006a8000-006bc000 rw-p 00000000 00:00 0 
...
7fa174cc9000-7fa174ccd000 r-xp 00000000 08:01 528003                     /lib/x86_64-linux-gnu/libcap.so.2.22
7fa174ccd000-7fa174ecc000 ---p 00004000 08:01 528003                     /lib/x86_64-linux-gnu/libcap.so.2.22
7fa174ecc000-7fa174ecd000 r--p 00003000 08:01 528003                     /lib/x86_64-linux-gnu/libcap.so.2.22
7fa174ecd000-7fa174ece000 rw-p 00004000 08:01 528003                     /lib/x86_64-linux-gnu/libcap.so.2.22
...

这里的7fa174cc9000/lib/x86_64-linux-gnu/libcap.so.2.22 库的基地址。因此,您通过readelf -s 获得的所有地址都将被该值抵消。知道了基地址,您就可以计算出文件中的原始偏移量。

即如果你得到值7fa174206370 并且库的基地址是7fa1741cf000 那么偏移量是7fa174206370 - 7fa1741cf000 = 37370。在我的示例中,它是来自 GLIBC 的 sigsuspend

94: 0000000000037370   132 FUNC    WEAK   DEFAULT   12 sigsuspend@@GLIBC_2.2.5

2 在使用这些库的程序上运行gdb。它要么立即在内存中找到加载的库,要么需要指向库的.text 部分。

> gdb
(gdb) attach YOUR_PID
(a lot of output about symbols)
(gdb) x/i 0x00007fa174206386
=> 0x7fa174206386 <sigsuspend+22>:  cmp    $0xfffffffffffff000,%rax

这样你就知道0x7fa174206386sigsuspend里面。

如果gdb本身没有加载任何符号(附加后没有像Reading symbols from ... Loading symbols for ...这样的输出),你可以在选项1中查找库的基地址,然后添加.text 部分的偏移量

➜  ~  readelf -S /lib/x86_64-linux-gnu/libcap.so.2.22 | grep '.text.'
  [11] .text             PROGBITS         0000000000001620  00001620

十六进制的7fa174cc9000 + 0000000000001620 给出7FA174CCA620,然后你按上面的gdb 附加并执行

(gdb) add-symbol-file /lib/x86_64-linux-gnu/libcap.so.2.22 7FA174CCA620

即使gdb 不会自行加载符号,您也应该能够找到符号(通过x/i ADDRESS,如选项1 中的)。

如果有什么不清楚的地方请追问,我会尽力解释的。

澄清为什么会这样

观察到的行为是由于库被编译为Position-Independent Code。它使我们能够轻松地支持动态库。 PIC 本质上意味着库的 ELF 有 .plt.got 部分,可以在任何基地址加载。 PLT 是过程链接表,它包含对位于其他模块中的函数调用的陷阱,这些陷阱首先进入程序解释器以允许它重新定位被调用的函数,然后在第一次调用后跳转到该函数。它之所以起作用,是因为程序解释器更新了 GOT(全局偏移表),其中包含要调用的函数的地址。最初 GOT 被初始化,以便在第一次函数调用时跳转到程序解释器的函数,该函数执行当前调用函数的解析。

在 x86-64 上,PLT 条目通常如下所示:

0000000000001430 <free@plt>:
    1430:       ff 25 e2 2b 20 00       jmpq   *0x202be2(%rip)        # 204018 <_fini+0x201264>
    1436:       68 00 00 00 00          pushq  $0x0
    143b:       e9 e0 ff ff ff          jmpq   1420 <_init+0x28>

第一个jmpq是跳转地址,存储在GOT的%rip + 0x202be2位置:

  [20] .got              PROGBITS         0000000000203fd0  00003fd0
       0000000000000030  0000000000000008  WA       0     0     8

%rip + 0x202be2 将是0x204012,并将其添加到库的基地址以生成与实际加载库的位置相关的绝对地址。 IE。如果它被加载到0x7f66dfc03000,那么对应的GOT条目的结果地址将是0x7F66DFE07012。存储在该位置的地址是(在此示例中)free 函数的地址。它由程序解释器维护以指向libc 中的实际free

可以在here找到更多信息。

【讨论】:

    【解决方案2】:

    您需要的是这个dladdr 函数。如果您在调试模式中构建了定义相关函数的模块(您的主程序或共享库),那么通过调用dladdr 函数,您将获得该函数名称基于其地址以及加载模块(例如您的共享库)的基地址:

    #define _GNU_SOURCE
    #include <dlfcn.h>
    
    void find_func(void* pfnFuncAddr)
    {
        Dl_info info;
        memset(&info,0,sizeof(info));
        if(dladdr(pfnFuncAddr,&info) && info.dli_fname)
        {
                /*here: 'info.dli_fname' contains the function name */
                /*      'info.dli_fbase' contains Address at which shared library is loaded */
        }
        else
        {
               /* if we got here it means that the module was not built with debug
                  information or some other funny thing happened (e.g. we called function)
                  written purely in assembly) */ 
        }
    }
    

    链接时必须加上-ldl

    请记住:

    • 函数find_func 需要从您的分析进程中调用(读取:从您的__cyg_profile_func_enter__cyg_profile_func_exit 函数的某个位置)因为地址pfnFuncAddr 是实际的函数地址(读取:应该等于@987654328 @ 或 call_site 函数的 __cyg_* 参数)

    • 您将获得的函数名称可能是ma​​ngled(如果它是一个类的 c++ 函数/方法)。您可以使用名为 c++filt 的命令行工具对名称进行分解。如果您想从分析器代码中解构,那么您需要查看 bfd 库和函数,例如 bfd_read_minisymbols bfd_demangle 和朋友。如果您真的想要分析您的代码,那么稍后(在分析之后)对所有函数名称进行分解可能是一个好主意。

    • 您观察到的地址值差异正是相关函数的实际地址与加载包含该函数的模块的基地址之间的差异(阅读:info.dli_fbase)。

    希望对你有帮助。

    【讨论】:

    • 添加到源代码后,编译后出现如下错误:error: unknown type name ‘DL_info’。我正在使用 gcc 4.7.3
    • 对不起,我的错误:它应该是 'Dl_info' - 已在答案中更正 - 还添加了 '-ldl'
    • 它现在可以工作了,但是 dli_fname 中的值是二进制文件的文件名。 Google 建议使用 dli_sname,它是给定地址的最低符号名称,但在我的情况下,这个 dli_sname 为 NULL。
    • 我认为memset(&amp;info,0,sizeof(&amp;info)) 是错误的,应该是sizeof(info)。也就是说,info 对象的大小,而不是 指向 info 的指针对象。
    • @gnzlbg - 是的,你是对的 - 很好发现。我刚刚更正了它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-22
    • 1970-01-01
    • 2011-04-29
    • 2012-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多