设为首页  
联系我们  
加入收藏  
网页制作 冲浪宝典 图形图像 操作系统 软件教学 编程开发 认证考试 安全技术 站长专区 文学驿站 娱乐天地 游戏天地 办公软件
文章搜索
您的位置: 首页 >> 文章首页 >> 编程开发 >> 其他开发语言 >> 使用GCC生成无格式二进制文件(plain binary files)
精品推荐
其他开发语言点击TOP10
·数字小键盘指法练习
·用C语言编通讯录程序(初学者级别的)
·Modem 常用AT指令集
·单片机模拟I2C总线及24C02(I2C EEPROM)读写实例(源代码)
·C++经典电子书下载
·Thinking in C++ 简体中文第二版
·debug和release的区别
·error LNK2001: unresolved external symbol __ftol2 错误解决
·C库函数手册
·一个简单的C语言编译器
编程开发点击TOP10
·数字小键盘指法练习
·ASP.NET 程序中常用的三十三种代码
·用C语言编通讯录程序(初学者级别的)
·我写的Java学生成绩管理系统源代码
·CHK文件恢复工具
·Modem 常用AT指令集
·java笔试题
·异常java.sql.SQLException: Io exception:The Network Adapter could not establish connection
·单片机模拟I2C总线及24C02(I2C EEPROM)读写实例(源代码)
·C++经典电子书下载
精选专题

使用GCC生成无格式二进制文件(plain binary files)

作者: 来源:网络文章 时间:2005-12-17 20:56:04

使用GCC生成无格式二进制文件(plain binary filesXML:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

我在互联网上搜索很久,只找到一些零星的关于这方面的资料。我想使用gcc开发一个自己使用的专用工具,结合自己的工作经验,写了这篇总结性的资料。

1.         软硬件环境

l          至少一台正在使用的80x86系列的32-bit电脑,越高档越好。

l          一套Linux的发行版,如RedhatMandrakeTurboLinux等。

l          GNU GCC编译器。该编译器在Linux下很常用。

l          Linux上的binutils

l          自己熟悉的文本编辑器,如vi等。

如果你不具备这些条件,就不要再往下看了。我的工作环境是,在一台赛扬433上安装了Redhat Linux8.0128M内存,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

   9:          b8 00 00 00 00                mov    $0x0,%eax

   e: 29 c4                      sub    %eax,%esp

  10:          c7 05 1c 00 00 00 78   movl   $0x12345678,0x1c

  17:          56 34 12

  1a: c9                        leave 

  1b:          c3                        ret 

正如我们看到的,数据段紧接着代码段。我们也可以明确的指定数据段的位置,试试下面的命令再进行编译:

gcc –c test.c

ld –Ttext 0x0 –Tdata 0x1234 –e main –N --oformat binary –o test.bin test.o

然后再使用objdump进行反汇编:

00000000 <.data>:

   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 34 12 00 00 78   movl   $0x12345678,0x1234

  17:          56 34 12

  1a: c9                        leave 

  1b:          c3                        ret   

现在,我们定义的全局变量被放到0x1234处了。通过给ld指定-Tdata参数,可以自由的定义数据段的地址,如果不指定,数据段在代码段后。

再看看直接给全局变量进行初始化的情况。

const int I=0x12345678;

int main()

{

}

仍然使用上面的方法进行编译、链接、反汇编,其结果如下:

00000000 <.data>:

   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   

  12:          00 00                      add    %al,(%eax)

  14:          78 56                      js     0x6c

  16:          34 12                      xor    $0x12,%al

代码以4Bytes对齐,全局变量被直接存储在代码段之后的数据段,ld直接将常数放到了全局变量的位置,一步到位。

使用如下命令可以看到更多细节:

objdump –D test.o

可以看到如下的结果:

test.o:     file format elf32-i386

 

Disassembly of section .text:

 

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   

Disassembly of section .data:

Disassembly of section .rodata:

 

00000000 <i>:

   0:          78 56                      js     58 <main+0x58>

   2:          34 12                      xor    $0x12,%al

我们可以更清楚地看到,在.c文件中定义的全局常量被放在了只读的数据段中了。再看下面的一段代码:

int I=0x12345678;

const int c=0x12345678;

int main()

{

}

还是使用上面的方法编译、链接、反汇编,可以到到如下结果:

test.o:     file format elf32-i386

 

Disassembly of section .text:

 

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   

Disassembly of section .data:

 

00000000 <i>:

   0:          78 56                      js     58 <main+0x58>

   2:          34 12                      xor    $0x12,%al

Disassembly of section .rodata:

 

00000000 <c>:

   0:          78 56                      js     58 <main+0x58>

   2:          34 12                      xor    $0x12,%al

可以看出,整数I被放在了普通的数据段中,常数c被放在了只读数据段中了。当使用全局变量(常量)时,ld会自动的使用合适的数据段存储他们。

5.         处理指针

使用如下代码来查看gcc处理指针变量的情况:

int main()

{

int I;

int* p;

p=&I;

*p=0x12345678;

}

使用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:         8d 45 fc                     lea    0xfffffffc(%ebp),%eax

  13:         89 45 f8                    mov    %eax,0xfffffff8(%ebp)

  16:         8b 45 f8                    mov    0xfffffff8(%ebp),%eax

  19:         c7 00 78 56 34 12    movl   $0x12345678,(%eax)

  1f: c9                        leave 

  20:          c3                        ret   

一开始,gcc已经为局部变量预分配了至少8Bytes的空间,并且使esp16Bytes边界对齐,如果还需要额外的空间,gcc将按照16Bytes为单位进行分配,而不是其他编译器所使用的以1Byte为单位进行分配。变量I位于ebp-4,变量p位于ebp-8lea指令将I的有效地址放入eax中,然后又被放入