分享
 
 
 

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

王朝other·作者佚名  2006-01-30
窄屏简体版  字體: |||超大  

使用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

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的空间,并且使esp以16Bytes边界对齐,如果还需要额外的空间,gcc将按照16Bytes为单位进行分配,而不是其他编译器所使用的以1Byte为单位进行分配。变量I位于ebp-4,变量p位于ebp-8,lea指令将I的有效地址放入eax中,然后又被放入p中。最后,将0x12345678赋给p指向的变量I。

6. 关于函数调用 看如下代码:

void func();

int main()

{

func();

}

void func()

{

}

再看生成的二进制代码:

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: e8 03 00 00 00 call 0x18

15: c9 leave

16: c3 ret

17: 90 nop

18: 55 push %ebp

19: 89 e5 mov %esp,%ebp

1b: c9 leave

1c: c3 ret

主函数main通过call指令调用了空函数func,该函数与main大同小异。为ld指定-Map开关输出map文件,可以得到更详细的信息。

.text 0x00000000 0x1d

*(.text .stub .text.* .gnu.linkonce.t.*)

.text 0x00000000 0x1d test.o

0x00000000 main

0x00000018 func

第一列是段名,这里是.text;第二列是起始位置,第三列是段长度,最后一列是附加信息,如函数名、所出自的目标文件等。可以看到,.text段从0x0开始,长度为0x1d;函数func从0x18开始。

7. 函数的返回值 看下面的代码,主函数main返回一个整型值:

int main()

{

return 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: b8 78 56 34 12 mov $0x12345678,%eax

15: c9 leave

16: c3 ret

你已经看到了,gcc使用eax传递返回值。因为返回值就是eax寄存器的值,所以你可以隐含的返回,甚至什么都不返回。因为返回值保存在寄存器中,进行函数调用时,经常忽略返回值。例如,我们经常这样调用函数:

printf(…);

该函数是有返回值的。如果函数返回的数据大于4Bytes,就不能再使用这种方法返回数据了。再看下面的例子:

typedef struct mydef{

int a,b,c,d;

int array[10];

}mydef;

mydef func();

int main()

{

mydef d;

d=func();

}

mydef func()

{

mydef d;

return d;

}

接着看反汇编的代码:

00000000 <.data>:

0: 55 push %ebp

1: 89 e5 mov %esp,%ebp

3: 83 ec 48 sub $0x48,%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 b8 lea 0xffffffb8(%ebp),%eax

13: 83 ec 0c sub $0xc,%esp

16: 50 push %eax

17: e8 06 00 00 00 call 0x22

1c: 83 c4 0c add $0xc,%esp

1f: c9 leave

20: c3 ret

21: 90 nop

22: 55 push %ebp

23: 89 e5 mov %esp,%ebp

25: 57 push %edi

26: 56 push %esi

27: 83 ec 40 sub $0x40,%esp

2a: 8b 7d 08 mov 0x8(%ebp),%edi

2d: 8d 75 b8 lea 0xffffffb8(%ebp),%esi

30: fc cld

31: b8 0e 00 00 00 mov $0xe,%eax

36: 89 c1 mov %eax,%ecx

38: f3 a5 repz movsl %ds:(%esi),%es:(%edi)

3a: 8b 45 08 mov 0x8(%ebp),%eax

3d: 83 c4 40 add $0x40,%esp

40: 5e pop %esi

41: 5f pop %edi

42: c9 leave

43: c2 04 00 ret $0x4

我们自定义的结构为0x38Bytes,gcc为了保持堆栈的16Bytes对齐,分配了0x40Bytes的空间。函数func并没有参数,但是在调用时,却将变量d的指针传了进去。然后利用这个指针,使用指令movsl直接对d进行赋值。再看下面的例子:

typedef struct mydef{

int a,b,c,d;

int array[10];

}mydef;

mydef func();

int main()

{

func();

}

mydef func()

{

mydef d;

return d;

}

再看反汇编的结果:

00000000 <.data>:

0: 55 push %ebp

1: 89 e5 mov %esp,%ebp

3: 83 ec 48 sub $0x48,%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 b8 lea 0xffffffb8(%ebp),%eax

13: 83 ec 0c sub $0xc,%esp

16: 50 push %eax

17: e8 06 00 00 00 call 0x22

1c: 83 c4 0c add $0xc,%esp

1f: c9 leave

20: c3 ret

21: 90 nop

22: 55 push %ebp

23: 89 e5 mov %esp,%ebp

25: 57 push %edi

26: 56 push %esi

27: 83 ec 40 sub $0x40,%esp

2a: 8b 7d 08 mov 0x8(%ebp),%edi

2d: 8d 75 b8 lea 0xffffffb8(%ebp),%esi

30: fc cld

31: b8 0e 00 00 00 mov $0xe,%eax

36: 89 c1 mov %eax,%ecx

38: f3 a5 repz movsl %ds:(%esi),%es:(%edi)

3a: 8b 45 08 mov 0x8(%ebp),%eax

3d: 83 c4 40 add $0x40,%esp

40: 5e pop %esi

41: 5f pop %edi

42: c9 leave

43: c2 04 00 ret $0x4

可以说,与上面的结果一字不差!我们没有在main函数中声明变量存储func返回的结果,但是gcc替我们做了。它仍然为函数func传递了一个指针,并将结果传了出来,尽管我们对返回值不感兴趣,但编译器对我们的兴趣好像也没有兴趣,依然我行我素。(如果使用了优化选项,结果很可能有所相同)。

8. 给函数传递参数 gcc遵循一般的c语言标准,包括参数传递方式。看看下面的例子:

char res;

char func(char a,char b);

int main()

{

res=func(0x02,0x03);

}

char func(char a,char b)

{

return a+b;

}

再看看他的反汇编代码:

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: 83 ec 08 sub $0x8,%esp

13: 6a 03 push $0x3

15: 6a 02 push $0x2

17: e8 0a 00 00 00 call 0x26

1c: 83 c4 10 add $0x10,%esp

1f: a2 44 00 00 00 mov %al,0x44

24: c9 leave

25: c3 ret

26: 55 push %ebp

27: 89 e5 mov %esp,%ebp

29: 83 ec 04 sub $0x4,%esp

2c: 8b 45 08 mov 0x8(%ebp),%eax

2f: 8b 55 0c mov 0xc(%ebp),%edx

32: 88 45 ff mov %al,0xffffffff(%ebp)

35: 88 55 fe mov %dl,0xfffffffe(%ebp)

38: 8a 45 fe mov 0xfffffffe(%ebp),%al

3b: 02 45 ff add 0xffffffff(%ebp),%al

3e: 0f be c0 movsbl %al,%eax

41: c9 leave

42: c3 ret

如果你精通汇编语言,看完这段代码,恐怕你已经口吐鲜血并晕倒在地了!gcc居然生成了这么啰嗦的代码!但是,我们还是先说说C语言的函数调用规范吧。

我们已经看到了,参数从右到左依次入栈。下面的说明全部以32Bytes代码为准,其规范具体可罗列以下几条:

l 调用者负责将参数压入堆栈,顺序为从右到左依次入栈。也就是左边的最后入栈。

l 调用者使用near call指令将控制权传给被调用者。

l 被调用者得到控制权,一般需要创建堆栈框架(这不是必需的,通常都是这么做)。首先,将ebp压入堆栈保存,再将esp放入ebp,使ebp成为访问参数的基址指针。

l 被调用者通过ebp访问参数。因为ebp已经先行压入堆栈,所以[ebp+4]就是被call指令自动压入堆栈的返回地址,显然,从[ebp+8]开始,就是参数。由于函数最左边的参数最后被压入堆栈,所以[ebp+8]就是该参数,其他参数以此类推。像printf这样的函数,具有个数不确定的参数,但是参数入栈顺序的规则,说明被调用者通过[ebp+8]就能够找到第一个参数,其他参数的类型和数目,则需要由第一个参数给出。

l 被调用者减小esp的值为堆栈中的临时变量分配空间,然后使用ebp和一个负的偏移访问。

l 被调用者使用al,ax,eax返回大小不同的值。浮点数可以通过ST0寄存器返回。

l 被调用者完成处理后,使用事先建立的堆栈框架,恢复esp,sbp的值,并使用ret指令返回调用者。

l 调用者重新得到控制权,通过给esp加上一个立即数清空堆栈(尽量不要使用多次pop指令清空堆栈)。如果因为使用了错误的函数原型通过堆栈多传递了或者少传递了参数,调用者仍然能够将堆栈恢复到正确的状态,因为调用者知道自己向堆栈压了几个字节的数据。

结合C语言的函数调用规则,上面的代码不难理解。

从80386开始,push指令的操作数可以是8-bit,16-bit,32-bit,但是C语言统统按32-bit整型数处理,被调用者也按32-bit进行处理。这一点很重要,特别是汇编语言和C语言混合编程时。

9. 基本的数据类型间的转换 gcc处理三类基本数据类型:

l signed char , unsigned char , 1 Byte

l signed short , unsigned short , 2 Bytes

l signed int , unsigned int , 4 Bytes

各种数据类型间的转换,遵循一般C语言的规则,具体可以参考IA-32的标准。这里只举一例说明:

int main()

{

char ch=’a’;

int x=2;

int y=-4;

}

使用同样的方法进行编译及反汇编:

00000000 <main>:

0: 55 push %ebp

1: 89 e5 mov %esp,%ebp

3: 83 ec 18 sub $0x18,%esp

6: 83 e4 f0 and $0xfffffff0,%esp

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

e: 29 c4 sub %eax,%esp

10: c6 45 ff 61 movb $0x61,0xffffffff(%ebp)

14: c7 45 f8 02 00 00 00 movl $0x2,0xfffffff8(%ebp)

1b: c7 45 f4 fc ff ff ff movl $0xfffffffc,0xfffffff4(%ebp)

22: c9 leave

23: c3 ret

10. gcc编译代码的基本运行环境 这一部分,我查了很多的文档,都没有这方面的介绍。又请教了很多的高手,大致情况如下,我实在无法保证这里所说的都是正确的,并且将来也是正确的,仅供参考:

l 32-bit的保护模式下运行。

l 段寄存器cs,ds,es,fs,gs,ss必须指向同一段内存区域。

l 没有初始化的全局变量被放在BSS的段内,该区域在代码段之后。但是,如果你生成的文件是二进制文件,BSS段不是该文件的一部分,你需要自己小心使用。初始化的全局变量在DATA段内,它是二进制文件的一部分,并且位于代码段之后。被声明为const的全局变量被放在RODATA段内,它也是二进制文件的一部分,并放在代码段之后。

l 确保堆栈没有溢出,小心代码段和全局数据不要被破坏。

我也查了Intel提供的帮助文档“Intel Architecture Software Developer’s Manual”,一共有三卷之多!参考了其中关于内存组织(Volume 1:Memory Organization)中的说法(建议你去好好研究)。总之,使cs,ds,ss总是指向同一内存区域应该可以使代码正确运行。如果运行环境不是这样,我就不知道结果了。

11. 访问外部的全局变量 看看在非C语言程序中如何访问C语言程序中的全局变量。如果你想使用其他程序加载C程序,例如汇编语言写的程序,这部分很有用,特别是在核心开发时经常用到。

int myVal=0x5;

int main()

{

}

编译这段代码:

gcc –c test.c

ld –Ttext 0x0 –e main –N –oformat binary –Map memmap.txt –o test.bin test.o

objdump –D –b binrary –m i386 test.bin

得到如下结果:

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: 05 .byte 0x5

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

全局变量myVal存储在0x14。刚才已经使用-Map开关使ld生成了内存映像文件memmap.txt,应该能够找到:

.data 0x00000014 0x4

*(.data .data.* .gnu.linkonce.d.*)

.data 0x00000014 0x4 test.o

0x00000014 myVal

说明myVal位于test.o模块的0x00000014位置。使用地址作为偏移量,就可以直接在其他语言中访问myVal变量了。另为也可以通过memmap.txt查到BSS段的大小:

cat memmap.txt | grep ‘\.bss’ | grep ‘0x’ | sed ‘s/.*0x/0x/’

本例子,BSS的大小是0x0。

无法直接访问C程序中的使用static修饰的全局变量。因为这样的变量是静态的,map文件中没有列出他们的地址。也许你可以使用其他办法做到,但是,最好不要这样做。

12. 生成其他格式的二进制文件的选项 生成不同格式的二进制文件是一件相当麻烦的事。它需要使用很多不常使用的选项,并且有些在man的帮助信息中没有被列出。

首先是gcc的选项:-nostdinc。很显然,使用该选项后,gcc就不搜索默认的include路径了,通常是/usr/include。如果需要使用的自定义的头文件,可以使用-I选项添加搜索路径。

然后是ld的选项。第一个是-nostdlib,就是忽略标准库。如果需要,可以使用-L选项指定库的搜索路径。第二个是-Ttext,就是指定代码段的地址,如果没有继续指定其他段的地址,则他们将自动的一次被放在代码段之后。第三个是-e,就是指定代码的入口地址,默认的是_start,如果代码不是以其开头,就应该指定入口点。第四个是—oformat binary,就是输出的文件是原始的二进制文件,而是如文件可以使系统支持的任何文件。但是,中间模块文件不能是原始的二进制文件,因为还需要很多符号和重定位信息。可以使用—iformat选项指定输入文件的格式,但通常很少使用。第五个是-static,如果使用了其他库,用该使用静态链接方式,除非你的程序支持动态链接。

另外还有代码指示伪指令。汇编器可以编译16-bit代码,也可以编译32-bit代码。但是,gcc总是生成32-bit的汇编代码。通过在C代码中使用asm()伪指令可以让gcc生成16-bit汇编代码。

第一个是.code16,即生成在16-bit段中运行的16-bit代码;

第二个是.code32,即生成在32-bit段中运行的32-bit代码,默认情况下gcc总是这么做;

第三个是.code16gcc,gcc将根据需要决定生成在16-bit段下运行的16-bit或32-bit代码。GAS将会加上必要的前缀,指示32-bit的指令或寄存器等。这个选项是很有用的,它允许我们使用C语言写在16-bit环境下运行的代码,不论是实模式还是保护模式。

现在可以在一个C模块中既有16-bit代码,又有32-bit代码,但是此时需要注意不同部分代码的地址空间问题。

例如,我们想使用gcc生成在DOS下运行的.com程序和启动引导程序。

首先,DOS中的.com文件是在实模式下运行的原始的二进制文件,其起始地址为0x100。要使用gcc生成.com文件,在每一个.c文件的开头加上如下伪指令:

__asm__(“code16gcc\n”);

如果需要引用其他库文件,则这些库文件也需要按这种方式生成。在链接时,加上如下选项:

-Ttext 0x100 –static –oformat binary

如果程序中包含嵌入的汇编代码,需要将其转换为AT&T格式。

如果要写引导程序,只需要在链接时使用0x7C00代替0x100!另外,最终生成的二进制代码必须小于446个字节!

13. 参考资料 l Intel Architecture Software Developer’s Manual

l Manual Pages in Linux

l Redhat GNUPro Toolkit

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有