如何使用 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 中包含的静态地址。
我使用 -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 中包含的静态地址。