GPROF 如何与程序挂钩?

逆向工程 部件 x64
2021-06-27 10:59:38

我编译了一个简单的helloworld并查看了使用objdump.

一开始有_init

0000000000400600 <_init>:
  400600:   48 83 ec 08             sub    rsp,0x8
  400604:   48 8b 05 ed 09 20 00    mov    rax,QWORD PTR [rip+0x2009ed]        # 600ff8 <_DYNAMIC+0x1e0>
  40060b:   48 85 c0                test   rax,rax
  40060e:   74 05                   je     400615 <_init+0x15>
  400610:   e8 1b 00 00 00          call   400630 <__gmon_start__@plt>
  400615:   48 83 c4 08             add    rsp,0x8
  400619:   c3                      ret    

什么是_DYNAMIC使用-x我可以看到部分详细信息:

Dynamic Section:
  NEEDED               libstdc++.so.6
  NEEDED               libc.so.6
  INIT                 0x0000000000400600
  FINI                 0x0000000000400864
  INIT_ARRAY           0x0000000000600df8
  INIT_ARRAYSZ         0x0000000000000010
  FINI_ARRAY           0x0000000000600e08
  FINI_ARRAYSZ         0x0000000000000008
  GNU_HASH             0x0000000000400298
  STRTAB               0x00000000004003c8
  SYMTAB               0x00000000004002c0
  STRSZ                0x000000000000011c
  SYMENT               0x0000000000000018
  DEBUG                0x0000000000000000
  PLTGOT               0x0000000000601000
  PLTRELSZ             0x0000000000000090
  PLTREL               0x0000000000000007
  JMPREL               0x0000000000400570
  RELA                 0x0000000000400540
  RELASZ               0x0000000000000030
  RELAENT              0x0000000000000018
  VERNEED              0x0000000000400500
  VERNEEDNUM           0x0000000000000002
  VERSYM               0x00000000004004e4

但是,我不确定哪个条目rip+0x2009ed指的是。考虑到下一行是对 的调用gmon,它与GPROF钩子有什么关系吗?

1个回答

这是初始化代码使用的一个技巧,用于在编译时支持监视,并在未编译时省略它。

当我使用 编译一个小测试程序gcc -pg,然后调用objdump -Mintel -d它时,我得到:

00000000004004c0 <_init>:
  4004c0:   48 83 ec 08             sub    rsp,0x8
  4004c4:   48 8d 05 c5 00 00 00    lea    rax,[rip+0xc5]        # 400590 <__gmon_start__>
  4004cb:   48 85 c0                test   rax,rax
  4004ce:   74 05                   je     4004d5 <_init+0x15>
  4004d0:   e8 bb 00 00 00          call   400590 <__gmon_start__>
  4004d5:   48 83 c4 08             add    rsp,0x8
  4004d9:   c3                      ret    

如果我-pg在编译时省略了,这将更改为:

0000000000400418 <_init>:
  400418:   48 83 ec 08             sub    rsp,0x8
  40041c:   48 8b 05 d5 0b 20 00    mov    rax,QWORD PTR [rip+0x200bd5]        # 600ff8 <_DYNAMIC+0x1d0>
  400423:   48 85 c0                test   rax,rax
  400426:   74 05                   je     40042d <_init+0x15>
  400428:   e8 43 00 00 00          call   400470 <__gmon_start__@plt>
  40042d:   48 83 c4 08             add    rsp,0x8
  400431:   c3                      ret    

所以你看到,在启用监控的情况下,代码__gmon_start__在调用函数之前检查是否为空。在禁用监控的情况下,它会检查一些变量,如果它是 0,它会跳过对 的调用__gmon_start__

可是等等。为什么我们lea在第一种情况下有 a ,而mov在第二种情况下有 a ?为什么名称以 结尾@plt?因为,即使您的程序没有编译分析,也许您的某些动态库有,而且您可能正在针对启用分析的 C 库版本运行。因此,C 库可能会提供 的动态版本__gmon_start__,并且它会提供一个标志来标记是否这样做。该函数和标志在GOT和 GOTPLT 部分中定义

事实上,如果您记下所使用的地址600FF8,并objdump -x从您的部分详细信息中向下滚动输出,您将看到:

 21 .got          00000008  0000000000600ff8  0000000000600ff8  00000ff8  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 22 .got.plt      00000038  0000000000601000  0000000000601000  00001000  2**3
                  CONTENTS, ALLOC, LOAD, DATA

你会看到代码访问了一个 GOT 表条目(对于一个小型测试程序,它是 GOT 中唯一的条目,这就是为什么 GOT 的大小只有 8 个字节)。