使用GCC生成无格式二进制文件(plain binary files)
我在互联网上搜索很久,只找到一些零星的关于这方面的资料。我想使用gcc开发一个自己使用的专用工具,结合自己的工作经验,写了这篇总结性的资料。
1. 软硬件环境
l 至少一台正在使用的80x86系列的32-bit电脑,越高档越好。
l 一套Linux的发行版,如Redhat、Mandrake、TurboLinux等。
l GNU GCC编译器。该编译器在Linux下很常用。
l Linux上的binutils。
l 自己熟悉的文本编辑器,如vi等。
如果你不具备这些条件,就不要再往下看了。我的工作环境是,在一台赛扬433上安装了Redhat Linux8.0,128M内存,gcc是默认的,版本为3.2.2。可以使用如下命令查看gcc的版本:
gcc --version |
2. 使用C语言生成一个二进制文件
使用自己喜欢的文本编辑器写一个test.c:
int main() { } |
再使用如下命令编译:
gcc –c test.c ld –o test –Ttext 0x0 –e main test.o objcopy –R .note –R .comment –S –O binary test test.bin |
最后生成的二进制文件是test.bin,可以使用你喜欢的反汇编工具看看这个文件里到底是什么。我使用Linux下的objdump进行反汇编:
objdump –D –b binary –a i386 test.bin |
结果如下:
00000000 <main>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 08 sub $0x8,%esp 6: 83 e4 f0 and $0xfffffff0,%esp 9: b8 00 00 00 00 mov $0x0,%eax e: 29 c4 sub %eax,%esp 10: c9 leave 11: c3 ret |
其中第一列是指令的内存地址;第二列是指令的机器码;第三列是汇编指令。相信你的结果与此同。如果你的gcc与我的不一样,例如2.7.x版本的gcc,你的结果很可能会有所不同,缺少如下的四条指令,这是正常的,这两个版本的gcc所使用的堆栈框架不同(下面介绍的例子也会因为编译器版本的不同造成其结果有别):
3: 83 ec 08 sub $0x8,%esp 6: 83 e4 f0 and $0xfffffff0,%esp #堆栈对齐,以16Bytes为单位分配局部变量空间 9: b8 00 00 00 00 mov $0x0,%eax e: 29 c4 sub %eax,%esp |
上述代码都是32-bit代码,你需要在像Linux这样的 32-bit环境下运行,并且是保护模式。也可以只用下面的指令直接生成test.bin:
gcc –c test.c ld –Ttext 0x0 –e main --oformat binary –o test.bin test.o |
上面的test.c中只有一个函数,而且还只是个框架。其反汇编代码也没什么难理解的。
3. 编写带局部变量的程序
再创建一个新的test.c,看看gcc是如何处理局部变量的。
int main() { int i; i=0x12345678; } |
使用上述两种方法的人一种编译,生成test.bin。然后使用objdump进行反汇编:
00000000 <main>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 08 sub $0x8,%esp 6: 83 e4 f0 and $0xfffffff0,%esp 9: b8 00 00 00 00 mov $0x0,%eax e: 29 c4 sub %eax,%esp 10: c7 45 fc 78 56 34 12 movl $0x12345678,0xfffffffc(%ebp) 17: c9 leave 18: c3 ret |
与第一个例子相比,开头的六条指令和最后的两条指令完全相同,仅有一条指令不同。这条语句是给局部变量赋值,其空间的分配在前面已经进行了。在gcc中,堆栈中的局部变量空间按16字节为单位进行分配,而不是通常的1字节为单位。如果将
int i; i=0x12345678; 改为 int i=0x12345678; |
其结果没有区别。但是,如果是全局变量,就不一样了。
4. 编写带全局变量的程序
将test.c改为:
int i; int main() { i=0x12345678; } |
使用同样的方法编译,然后再进行反汇编:
00000000 <main>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 08 sub $0x8,%esp 6: 83 e4 f0 and $0xfffffff0,%esp 9: b8 00 00 00 00 mov $0x0,%eax e: 29 c4 sub %eax,%esp 10: c7 05 1c 10 00 00 78 movl $0x12345678,0x101c 17: 56 34 12 1a: c9 leave 1b: c3 ret |
我们定义的全局变量被放到了0x101c处,这是gcc默认以page-align对齐数据段的结果,此处的page与页式内存管理中的page没有关系。在使用ld链接时,使用-N参数可以关闭对齐效果。
00000000 <main>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 08 sub $0x8,%esp 6: 83 e4 f0 and $0xfffffff0,%esp |