8

我正在尝试使用 -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左右……所以,我无法从函数指针中获取函数名。看起来像函数指针如何获得偏移量或其他东西。

有人有什么想法吗?

4

2 回答 2

6

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

要了解正在测量的功能是什么,您有 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. 在我的示例中,它sigsuspend来自 GLIBC:

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

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

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

这样你就知道0x7fa174206386里面了sigsuspend

如果gdb自己不加载任何符号(附加后没有输出),您可以像选项1Reading symbols from ... Loading symbols for ...一样查找库的基地址,然后添加部分的偏移量.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

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

请询问是否有任何不清楚的地方,我会尽力解释。

澄清为什么会这样

观察到的行为是由于库被编译为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 的 location 中%rip + 0x202be2

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

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

更多信息可以在这里找到。

于 2013-11-04T23:19:46.490 回答
3

你需要的是这个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是实际的函数地址(读取:应该等于函数的this_fncall_site参数__cyg_*

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

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

我希望这会有所帮助。

于 2013-11-04T04:00:29.920 回答