办法当然是有的,那就是不使用.code16,而使用.code16gcc。.code16gcc和.code16不同的地方就在于它生成的汇 编代码在使用到call、ret、jump等指令时,都生成32位的机器码,相当于calll,retl,jumpl。这也是.code16gcc 叫.code16gcc的原因,因为它就是配合GCC生成的函数框架使用的。
下面再来修改代码,C语言代码修改很简单,只需要将.code16改成.code16gcc即可,如下图:
通过反汇编,可以看到它使用了32位的calll和retl,如下图:
汇编程序的修改主要是将.code16改为.code16gcc,然后手动将callw改成calll,将retw改成retl,如下图:
最后,编译连接,拷贝到freedos.img,运行虚拟机,查看运行效果,如下图:
大功告成,运行效果如上图。
总结:
编写运行于x86实模式下的16位代码是一个很复古的话题,编写能在DOS下运行的Plain Binary可执行文件是一个更复古的话题。以往,凡是需要使用x86的16位实模式的时候,作者都喜欢用NASM来编程。比如《30天自制操作系统》、 《Orange's 一个操作系统的实现》、《x86汇编语言——从实模式到保护模式》等书籍都以NASM汇编器和Intel汇编语法作为示例。而且他们都是在进入32位保护 模式后,才让汇编语言和C语言共同工作。
我用Linux操作系统,所以我就是想不管是写32位代码,还是16位代码,都能使用GCC和GNU AS。我还想即使是在16位模式下,也能尽量少用汇编语言,多用C语言。经过努力,有了上面的文章。使用GCC和GNU Binutils编写运行于x86实模式的16位代码的过程如下:
1. 如果只用汇编语言编写16位程序,请使用.code16指令,并保证只使用16位的指令和寄存器;如果要和C语言一起工作,请使用.code16gcc指 令,并且在函数框架中使用pushl,calll,retl,leavel,jmpl,使用0x8(%ebp)开始访问函数的参数;很显然,使用C语言和 汇编语言混编的程序可以在实模式下运行,但是不能在286之前的真实CPU上运行,因为286之前的CPU还没有pushl、calll、retl、 leavel、jmpl等指令。
2. 使用as时,请指定--32选项,使用gcc时,请指定-m32选项,使用ld时,请指定-m elf_i386选项。如果是反汇编16位代码,在使用objdump时,请使用-m i8086选项。
3. 在DOS中运行的.com文件会被加载到0x100处执行,所以使用ld连接时需指定-Ttext 0x100选项;引导扇区的代码会被加载到0x7c00处执行,所以使用ld连接时需指定-Ttext 0x7c00选项。
4. 使用gcc、as、ld生成的程序默认都是ELF格式,而在DOS下运行的.com程序是Plain Binary的,在引导扇区运行的代码也是Plain Binary的,所以需要使用objcopy将ELF文件中的代码段和数据段拷贝到一个Plain Binary文件中,使用-O binary选项; Plain Binary文件也可以反汇编,在使用objdump时需指定-b binary选项。