如何使用 gprof 从 Windows DLL 获取分析信息?

how to get profiling information from a Windows DLL with gprof?

我使用 -pg 选项(编译和 link)创建了我的 DLL,并设法 运行 它的代码并在某个时候调用 exit

文件 gmon.out 已创建,目前一切顺利。

但是当我这样做的时候

gprof mydll.dll gmon.out

没有打印有用的信息。

当我对可执行文件执行相同的操作时,它可以正常工作,并且我可以正确获取计时和计数信息。

为什么我的 DLL 会出现这种情况?

(此问题已在 years/decades 前问过几次,但仍未得到回答)

实际上,gprof 可以 做到这一点。它遇到的问题是 DLL 中的地址与 gmon.out 文件中记录的地址不同。

在可执行文件上,(虚拟)地址是固定的,但在 DLL 上则不是。不要问我是因为 ASLR 还是其他原因,但它使 post-mortem 调试变得很复杂。

另外,gmon.out 文件格式没有记录,或者 记录的格式,但它与我们得到的不匹配。

但我们有点想通了...

有一个header,然后是很多零,最后是数据。我不知道很多数据,但我得到的知识足以将 gmon.out 文件转换为可用的文件。

首先,你必须在启动程序时打印你的DLL入口点符号的地址,并将其与nm

给出的静态值进行比较

假设您的入口点是 _entry。在您的程序中(例如 C)只需执行:

printf("entry: %p\n",&entry);

然后在DLL(必须有符号)上使用nm得到静态值。在 Windows:

nm mydll.dll | find "_entry"

假设静态值得到 0x1F000000,run-time(打印)值得到 0x6F000000。然后你有一个 0x50000000 偏移量,你必须应用(减去)到你的 gmon.out 二进制文件。

基本上格式很简单:

  • 2 个前 32 位字是 little-endian 开始和停止地址。
  • 后面的词是 little-endian mon 数据在这个文件中的偏移量
  • 在该偏移处,您将找到 12 个字节的块。同样,您有 2 个 32 位字用于开始和结束,然后是 4 个字节的数据。

在下面的 real-life 示例中,我突出显示了前 3 个长字:

  • 第一个长字是起始地址 (0x10121450)
  • 第二个长字是结束地址 (0x1357E590)
  • 第三个长字是分析数据偏移量 (0x1A2E8C0)

现在是数据的开始,注意偏移量匹配(在数据偏移量之前只有零,可能还有更多数据的空间)

现在我们必须在 运行 代码中应用偏移量 computed/printed,以便来自 gmon.out 的地址与来自 DLL

的地址相匹配

怎么做?使用一些 python 脚本非常容易。理念:

目的是对header个地址和chunk的所有地址加上地址移位,其余数据不变

脚本,一切都是硬编码的,但你明白了

import struct

with open("gmon.out","rb") as f:  # the file produced by the run
    contents = f.read()

start_address,end_address,data_offset = struct.unpack("<III",contents[:12])

profile_data = contents[data_offset:]
nb_records = len(profile_data)/12
records = []
for i in range(0,nb_records):
    offset=i*12
    extract = profile_data[offset:offset+12]
    s,e,data = struct.unpack("<III",extract)
    records.append((s,e,data))

# let's say 650000000 is the address that the program printed
# and 10120000 is the address that "nm" reports
shift = 0x65000000-0x10120000

with open("gmon2.out","wb") as f:   # the file that will work with that run

    f.write(struct.pack("<II",start_address+shift,end_address+shift))
    f.write(contents[8:data_offset])
    for s,e,d in records:
        f.write(struct.pack("<III",s+shift,e+shift,d))

现在:

gprof mydll.dll gmon2.out

这样做允许根据 DLL 解码 gmon 文件,因为地址现在已更正以匹配 DLL 中包含的静态地址。