下面对以上代码进行简单解释:
1. GNU AS汇编器使用的汇编语言采用的是AT&T语法,该语法和Intel语法不同。我更喜欢AT&T的语法,原因有两个,一是AT&T语法是Linux世界中通用的标准,二是AT&T语法在某些概念方面确实理解起来更简单(比如内存寻址模式)。有汇编语言基础的人,AT&T语法学起来也很快,主要有以下几条:①汇编指令后面跟有操作数长度的后缀,比如mov指令,如果操作数是8位,则用movb,如果操作数是16位,则用movw,如果操作数是32位,则用movl,如果操作数是64位,则用movq,其余指令依此类推;②操作数的顺序是源操作数在前,目标操作数在后,比如movw %cs, %ax表示把cs寄存器中的数据移动到ax寄存器中,这个顺序和Intel汇编语法正好相反;③所有的寄存器使用%前缀,如%ax, %di, %esp等;④对于立即数,需要使用$前缀,比如 $4, $0x0c,而且如果一个数字是以0开头,则是8进制,以其它数字开头,是10进制,以0x开头则是16进制,标号当立即数使用时,需要$前缀,比如上面的pushw $message,而标号当函数名使用时,不需要$前缀,比如上面的callw display_str;⑤内存寻址方式,众所周知,x86寻址方式众多,什么直接寻址、间接寻址、基址寻址、基址变址寻址等等让人眼花缭乱,而AT&T语法对内存寻址方式做了一个很好的统一,其格式为section:displacement(base, index, scale),其中section是段地址,displacement是位移,base是基址寄存器,index是索引,scale是缩放因子,其计算方式为线性地址=section + displacement + base + index*scale,最重要的是,可以省略以上格式中的一个或多个部分,比如movw 4, %ax就是把内存地址4中的值移动到ax寄存器中,movw 4(%esp), %ax就是把esp+4指向的地址中的值移动到ax寄存器中,依此类推。我上面的介绍是不是全网络最简明的AT&T汇编语法教程?
2. 在以上代码中我全部使用的都是16位的指令,如movw、pushw、callw等,并且直接在代码中定义了字符串“Hello, world!”。
3. 在以上代码中使用了函数display_str,在调用display_str之前,我使用pushw $15和pushw $message将参数从右向左依次压栈,然后使用callw指令调用函数,这和C语言的函数调用约定是一样的。调用callw指令会自动将%ip寄存器压栈,而在函数开始时,我又用pushw %bp将%bp寄存器压栈,所以%esp又向下移动了4个字节,所以在函数中使用0x4(%esp)和0x6(%esp)可以访问到这两个参数。在32位代码中,由于调用函数时压栈的是%eip和%ebp,所以需要使用0x8(%esp)和0xc(%esp)来依次访问压栈的参数。关于汇编语言函数调用的细节,我这里有一本好书Linux汇编编程指南.pdf。这是一本免费的英文版电子书,其原名为《Programming from the ground up》。
Linux汇编编程指南.pdf 下载地址:
------------------------------------------分割线------------------------------------------
具体下载目录在 /2015年资料/3月/2日/Linux入门学习教程:使用GCC和GNU Binutils编写能在x86实模式运行的16位代码/
------------------------------------------分割线------------------------------------------
4. 以上代码使用BIOS中断int 0x10来输出字符串,使用DOS中断int 0x21来返回DOS系统。
5. 最重要的是,需要使用.code16指令让汇编器将程序汇编成16位的代码。
代码完成后,使用下面一串命令就可以把它进行汇编、链接,然后转换成DOS下的纯二进制格式(Plain Binary),最后复制到FreeDOS.img中,使用Qemu虚拟机执行FreeDOS,然后运行该16位实模式程序。这一串命令及其运行效果如下图:
这些命令中比较重要的选项我都特意标出来了。由于我用的是64位的环境,所以调用as命令的时候需要指定--32选项,调用ld命令的时候需要指定-m elf_i386选项。指定以上选项后,生成的是32位的ELF目标文件,否则默认会生成64位的ELF目标文件,如果目标文件是64位,以后和C语言生成的目标文件连接时会出问题。使用32位环境的朋友们不用特意指定这两个选项。由于DOS系统总是把Plain Binary文件载入到0x100地址处执行,所以调用ld命令时,需要指定-Ttext 0x100选项。ld命令执行完成后,生成的是ELF格式的可执行文件test.elf,最后需要调用objcopy生成纯二进制文件,-j .text选项的意思是只需要代码段,因为我把“Hello, world!”也是定义在代码段中的,-O binary选项指定输出格式为纯二进制文件,输出文件为test.com。最后,将freedos.img镜像文件mount到Ubuntu中,将test.com拷贝到其中,然后umount,然后运行虚拟机,在DOS中运行test,就可以看到效果了。
除了as和ld,GNU Binutils中的其它程序也是写程序和分析程序时的好帮手。可以使用readelf -S查看test.elf文件中的所有段,也可以使用objdump -s命令将test.elf中的数据以16进制形式输入,如下图:
当然,也可以使用objdump -d或者objdump -D将程序进行反汇编,查看是否真正生成了16位代码,如下图:(反汇编时一定要指定-m i8086选项)
也可以对纯二进制格式的文件进行反汇编,必须指定-b binary选项,如下图,对test.com进行反汇编: