前面总结了一下普通寄存器的知识,这里绍下汇编程序的基本框架。
GAS语法框架
程序员学习语言都是从hello world
开始的,这样能快速展示一个能运行的完整程序,对语言有个直观的理解。首先说一下环境配置,对于linux程序,无需过多的配置,只需要 有gcc编译环境即可,调试时需要使用gdb,另外,由于AT&T汇编的跨平台性,在Windows系统下可以使用MinGW编译器,具备gcc 的全套功能,但是毕竟与纯粹的linux平台还是有些差别,为了使用AT&T和MASM两种语言方便切换,这里将使用MinGW编译器介绍,并且 将说明与Linux环境的差别。
下面看一下hello world
程序,因为GAS汇编文件以.s后缀,所以文件名定为hello.s:
.section .data
out_text:
.asciz "hello world\n"
.section .text
.globl _main
_main:
pushl $out_text
call _printf
pushl $0
call _exit
为了方便输出,忽略程序细节,其中引用了c函数库,可以使用 gcc hello.s -o hello
来编译此程序,运行hello后输出hello world字串。
至 此完成了最为简单的程序,从main开始,先调用printf输出out_text处的字串,之后调用exit程序结束程序,从代码调用的函数名可以很清 楚理解这个过程,但是汇编最需注重的是细节,任何一个细节都可能导致程序的崩溃。
GAS程序说明
下面我对以上涉及到的细节做一些说明:
函数入口
对于c来说,毫无疑问是main函数作为入口,但这也只是表象,之所以要指定入口,是因为连接器把目标文件组织成可执行文件时需要把入口代码放到代码 段最开始的位置,只要编译器告诉连接器把哪个部分放在入口处即可,所以入口仅仅是一个符号,一个程序可以完全不写main函数,直接修改连接器ld的连接 脚本或者参数,自定义一个入口点。
汇编程序也是如此,因为直接使用的gcc编译器,gcc把main
作为入口点,但其实gcc并不是编译汇编的主体,汇编编译器其实是as,它默认入口点是 start
。可以把以上程序_main
修改为_start
,使用as进行编译,编译成目标文件之后再使用ld进行链接,之所以这里用gcc编译,完全是为 了简单,它可以自动进行编译连接,并在连接时加入c库。
C库调用
另一个问题是main,printf
函数都有个下划线,问题便出来了,到底这个 是printf
还是_printf
呢?这里完全是Windows下的规则,Windows的标准c函数,在连接时符号都是加入下划线的,在vc里写入 printf
,编译时被修改为_printf
进行连接,这里由于是汇编,没有c编译器处理这些细节,只能由我们直接写出最终的连接符号。这里有必要一提的 是,对于stdcall类型函数,比如Win32 API,连接符号后会有“@参数总长度 ” 这样的后缀,比如上层的MessageBox
函数,在编译时便会转变成_MessageBoxA@16
,后面所加的A是使用的窄字符版本,这里并不是编译 器所作,而是Windows.h头文件中使用宏分离的不同字符集下的符号,作为汇编,一切工作都得由我们做了,可以看到使用GAS来处理Windows程 序甚为繁琐,因为没有很好的支持,后面可以看到MASM的一个封装,MASM32,作者本身定义了很多的宏以方便调用。当然,如果在linux下编程,便 可省去这烦人的下弧线了。
数据段和代码段
接下来该进入正题了,就是语法,由于最终程序是要分成若干段,比如代码段,数据段,这些c编译器可以帮忙指定,对于汇编,还是需要我们自己说明,这里的.section
指令便是指明段的属性
- .data —— 初始化数据段,其中的数据在编译期间便已经占用了指定了空间
- .bss —— 与.data对应,是未初始化数据段,也就是这里的数据是未初始化的,在程序运行时才分配的空间
- .rodata —— 只读数据段,相当于常量区,其中的数据不可修改
- .text —— 代码段,此段具有可执行属性,只读
标签
另外,out_text
为标签,因为gas汇编并没有变量名的概念,只能在内存地址的其实位置定义一个标签,方便以后引用此位置。.globl
导出_main
这个符号,毕竟在连接时需要使用此符号。
$符号
最后说明一$符号的使用,在gas汇编中,对于立即数,比如0,需要在数字之前加上$符号,来指明此为立即数,$0就是立即数0,另外,对于符号前的$,即为取地址,$out_text
含义便是取出out_text
的地址,因为printf
需要char *
指针传入。
如此,示例代码便很容易理解了。对于涉及到的一些汇编指令,今后将会做详细说明。
MASN 语法框架
对于MASM汇编,语法规则可能要繁琐,但是理解反而要简单,这可能是微软的一贯风格。 首先也来讲一下环境配置,其实编译MASM只需要在微软的开发包中提取出ml.exe和link.exe即可,有人为了方便大家使用,把所需要的一系列工具集成一个开发包,可以在这里下载 Masm32 。
安装好后,便可开始hello world
程序,这里使用记事本创建hello.asm
程序:
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.data
szText db 'hello world!',0
.code
start:
invoke MessageBox, NULL, offset szText, NULL, MB_OK
invoke ExitProcess, NULL
end start
这里编译的时候要注意,因为这里引用了头文件和lib文件,所以需要设置环境变量include和lib到masm32目录下的include和lib,或者直接在源文件里写绝对路径,最好设置masm32/bin到path变量。
ml /c /coff hello.asm
link /SUBSYSTEM:WINDOWS hello.obj
ml 为编译命令,/c
是仅编译不连接,/coff
是编译为coff格式的目标文件。
link 为连接命令,/SUBSYSTEM:WINDOWS
指定为窗口程序
先看开头这些命令:
.386
.model flat,stdcall
option casemap:none
.386
指定处理器构架在.386
以上,.model
指定编译模式为flat平坦模式,casemap:none
选项表明程序大小写敏感。
- .data 指定数据段 .code指定代码段。
- start 为程序开始指定一个符号
- end start 指明程序就此结束,并指定程序入口为start
这里给出框架仅仅为了能建立能运行的汇编环境,其中调用了一些API,也算作配合高级语言编程的一种方式,但汇编绝对不是调用几个API显示几个漂亮的窗口 就算是学会汇编,这些事情用C语言都能够更轻易的处理,一个语言擅长什么就该让它去做什么。没有能应对一切情况的语言,如同没有一个完美的人。