购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

5.3 通过目标码工具分析编译器的输出

尽管许多编译器提供生成汇编语言输出代码而非目标码的选项,但还有大量的编译器没有这种功能—它们只能生成包含二进制机器代码的目标码文件。因此,分析这类编译器的输出,工作量就要大一些,并需要专门的工具。如果编译器生成供链接器使用的目标代码文件如PE/COFF、ELF文件,可以找一个“目标码转储”工具来帮助分析编译器的输出。举例来说,微软的 dumpbin.exe 堪当此任;FSF/GNU的 dumpobj 程序也提供了类似功能,适用于Linux等操作系统下的ELF文件。随后各小节将探讨使用这两种工具分析编译器输出的方法。

工作于目标文件的好处之一是,目标文件通常含有符号信息。也就是说,除了二进制机器码,目标文件还含有源文件中说明标识符名字的字符串,而这些信息一般不会出现在可执行文件中。对于以符号引用相关内存位置的机器指令,目标码工具通常能显示源代码里的符号名。尽管目标码转储工具不会自动将高级语言源代码对应到机器指令,但我们可以利用符号信息研究其输出—毕竟“JumpTable”之类的名字比内存地址$401_1000要容易理解得多。

5.3.1 微软的 dumpbin.exe 工具

微软提供了命令行工具 dumpbin.exe ,用它可查看微软的PE/COFF格式文件内容 [1] 。通过下列命令格式来运行程序:

filename 是要检查的目标文件 .obj 名,而参数 options 则为一套命令行选项,指定要显示的信息类型。每个选项都以“/”打头。下面列出了允许的选项,可通过命令行选项“/?”得到:

dumpbin主要用来查看编译器生成的目标码,其实它还能显示PE/COFF文件的许多有趣信息。关于dumpbin这么多命令行选项的含义,可以参看4.6节和4.7节。

下列几小节将说明dumpbin的若干命令行选项,并给出C语言小程序“Hello World”的示例输出。

5.3.1.1 /all

命令行选项“/all”吩咐dumpbin把所有可能的信息显示出来,但不包括对目标文件的反汇编信息。这种方法的问题是 .exe 文件囊括了语言标准库(如C标准库)内的所有例程,链接器已经把标准库融入了应用程序。在分析编译器的输出以改进应用程序代码时,在这么多额外信息中找出自己的代码将非常不便。幸好有一个减少不必要信息的简单办法—可对目标文件( .obj )而非可执行文件(. exe )运行dumpbin。下面是对“Hello World”示例执行dumpbin命令产生的输出:

这个例子删去了输出的大部分原始数据,以免让你不得不阅读太多内容。可以尝试用/all选项的命令来看看输出有多大量的信息。不过,通常要慎用这个命令选项。

5.3.1.2 /disasm

“/disasm”是最有用的命令行选项之一,它能够生成目标文件的反汇编清单。和选项“/all”一样,不应用dumpbin程序反汇编. exe 文件。这样得到的反汇编清单相当长,其中应用程序调用的那些库例程占了相当大的比重。例如,简单的“Hello World”应用程序居然会生成5000余行反汇编代码,而绝大部分语句其实都对应于库例程。要在如此浩瀚的代码中查找自己需要的东西,会让多数人望而却步。

不过,如果是反汇编 hw.obj 文件而非可执行文件,就可得到如下的典型输出:

仔细查看反汇编所得的代码就会发现,对目标文件(而非可执行文件)反汇编时存在一个大问题—代码中的多数地址都可重定位,这些地址在目标代码中以$00000000的形式出现。结果,要想推敲出各条汇编语句干什么,颇须费一番心思。比如, hw.obj 的反汇编清单中有下面两条语句:

lea指令操作码是3字节序列48 8D 0D,它包含了REX操作码前缀字节。"Hello World"字符串地址并未位于操作码后面的4个字节00 00 00 00。事实上,此地址是可重定位的,链接器/系统后面会填写该地址。如果对 hw.obj 运行带参数“/all”的dumpbin,我们就会发现文件里有两个重定位项:

Offset列告诉我们重定位项要用于文件何处,以字节偏移量标识。注意,在上面的反汇编代码中,lea指令从偏移量$d开始,因此实际的立即数位于偏移量$10。类似地,call指令从偏移量$14开始,所以实际例程的地址需要往后修正一个字节,即偏移量为$15。从dumpbin输出的重定位信息可以看到它们所关联的符号—$SG4247是C编译器为字符串"Hello World"生成的内部符号,而printf显然是与C语言printf()函数有关的符号名。

按照重定位清单找出每个函数调用和内存引用的过程也许充满痛苦,但至少还有符号名相伴。

假如对 hw.exe 文件使用选项“/disasm”,我们只看反汇编代码的前几行:

这时会发现,链接器将相对于文件调入地址的偏移量$SG4247和标号print处都填入了内容。看起来似乎方便了,但请注意这些标号,特别是printf将不会出现在文件中。阅读这些反汇编输出时,没有标号会使得辨识机器指令对应哪条高级语言语句变得异常困难。这是我们应当对目标文件而非可执行文件运行dumpbin的另一个原因。

如果你觉得阅读dumpbin工具的反汇编输出实在麻烦,不要着急。从优化角度来看,比起搞清每条机器指令在干什么,我们通常对两种版本的高级语言程序的输出有何差异更感兴趣。因此,通过将dumpbin运行于两个不同版本的目标文件—一种方法是对修改前的高级语言代码运行dumpbin,另一种方法是对修改后的高级语言代码运行dumpbin,我们可轻而易举地找出对代码进行的修改影响到了哪些机器指令。例如,对“Hello World”程序做下列修改:

下面是dumpbin对 hw.obj 生成的反汇编输出:

手工比对此输出与前一个汇编输出,或者运行诸如基于UNIX的diff工具来比对,就可以看到高级语言源程序所做的修改对生成机器码的影响。

注意: 5.8节将探讨手工比对、diff工具比对各自的优点。

5.3.1.3 /headers

选项“/headers”要求dumpbin显示COFF文件头及区域头信息。选项“/all”同样会给出这些信息,但“/headers” 专门 显示这些信息,而隐去对其他信息的显示。下面是对“Hello World”可执行文件的示例输出:

回头看看第4章里对目标文件格式的讨论(参看4.6节),就能看懂dumpbin在指定选项“/headers”时输出的信息。

5.3.1.4 /imports

dumpbin带选项“/imports”时,将列出操作系统将程序调入内存时必须提供的动态链接符号。这些信息在分析高级语言语句的代码输出时并没有多大用处,所以本章只打算对此选项说这么多。

5.3.1.5 /relocations

带选项“/relocations”的dumpbin将显示文件中所有的重定位项。这一选项很有用处,因为它能列出程序中全部符号及其在反汇编清单中的使用位置。不用说,选项“/all”也有这一功能,但“/relocations”专门显示这些信息,而隐去了其他信息。

5.3.1.6 dumpbin的其余命令行选项

除了本章提到的上述命令行选项,dumpbin工具还支持很多命令行选项。可以在运行dumpbin时指定“/?”,以得到一个清单,其中列出了所有可能的选项。我们也可以通过网址链接11查到更多信息。

5.3.2 FSF/GNU的objdump工具

倘若在Linux、Mac、BSD等操作系统中运行GNU工具包,我们就可以通过FSF/GNU的objdump工具来查看GCC之类兼容GNU的工具所生成的目标文件。下面是objdump支持的命令行选项:

假如有下列 m.hla 源代码段:

其目标文件在80x86上的反汇编输出可通过Linux命令行“objdump -S m”创建,摘录如下:

以上只是整个代码的一部分,所以有些标号没给出。不过,这个简短示例说明我们能对有疑问的代码段反汇编,所以objdump在分析编译器输出时有其用武之地。

和微软的dumpbin一样,FSF/GNU的objdump工具除了显示反汇编机器码,还能给出其他信息,这将有助于分析编译器输出。不过在大多数场合,GCC的“-S”(输出汇编语言代码)是最有用的选项。下面是用objdump工具对某些C代码反汇编的示例。我们首先来看C语言源代码:

GCC对上述源程序的Gas输出(在x86-64上)如下:

下面是objdump对main()函数的反汇编输出:

不难看出,汇编代码输出比objdump的输出更容易看懂。 50uA/FhE2N+FcVEV7aYivDZUauebZbBiLFhxdSAq6+ICMo2JqePR0exl31UrUF+F

点击中间区域
呼出菜单
上一章
目录
下一章
×