C函数分析(地址似乎已经过时)

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

有人有什么想法吗?

从问题看起来你正在分析库函数。

要了解被测量的function,您有两个选择:

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 : cmp $0xfffffffffffff000,%rax 

这样你知道0x7fa174206386sigsuspend里面。

如果gdb本身没有加载任何符号(没有输出像Reading symbols from ... Loading symbols for ...在附加后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 

以hex表示的7fa174cc9000 + 0000000000001620给出7FA174CCA620 ,然后按上面的方式附加gdb并执行

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

然后你应该能够找到符号(通过选项1中的 x/i ADDRESS ),即使gdb没有自己加载它们。

请问是否有任何不清楚的地方,我会试着解释一下。

澄清为什么会这样

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

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

 0000000000001430 : 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 ,并将其添加到库的基址以生成与实际加载库的位置相关的绝对地址。 即如果它被加载到0x7f66dfc03000 ,那么相应GOT条目的结果地址将是0x7F66DFE07012 。 存储在该位置的地址是(在该示例中) freefunction的地址。 它由程序解释器维护,指向libc实际free

有关这方面的更多信息,请点击此处 。

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

 #define _GNU_SOURCE #include  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 (eg we called function) written purely in assembly) */ } } 

您必须在链接时添加-ldl

请记住:

  • 函数find_func需要从配置文件进程中调用(读取:从__cyg_profile_func_enter__cyg_profile_func_exit函数的某处),因为地址pfnFuncAddr是实际的函数地址(读取:应该等于__cyg_*函数的this_fncall_site参数)
  • 您将获得的函数名称可能会被破坏 (如果它是类的c ++函数/方法)。 您可以使用名为c ++ filt的命令行工具对名称进行解码 。 如果你想从你的探查器代码中解码,那么你需要查看bfd库和函数,如bfd_read_minisymbols bfd_demangle和朋友。 如果你真的想要对你的代码进行分析,那么稍后(在分析之后)解析所有函数名称可能是一个好主意。

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

我希望有所帮助。