原文链接:http://www.orlion.ga/989/
一、汇编程序的Hello world
x86 AT&T:
.data msg: .ascii "Hello world, hello AT&T asm! " len = . - msg .text .global _start _start: movl $len, %edx # 显示的字符数 movl $msg, %ecx # 缓冲区指针 movl $1, %ebx # 文件描述符 movl $4, %eax # 系统调用号,_write int $0x80 # 系统调用 movl $0, %ebx # 传给_exit的参数 movl $1, %eax # 系统调用号,_exit int $0x80 # 系统调用
然后汇编链接再执行:
这段汇编相当于:
#include <unistd.h> char msg[14] = "Hello,world! "; #define len 14 int main(void) { write(1, msg, len); _exit(0); }
.data段有一个标号msg,代表字符串“Hello,world! ”的首地址,相当于C程序的一个全局变量。在汇编指示.ascii定义的字符串末尾没有隐含的‘ ’。汇编程序中的len代表一个常量,它的值由当前地址减去符号msg所代表的地址得到,换句话说就是字符串“Hello,world! ”的长度。现在解释一下这行代码中的.,汇编器总是从前到后把汇编代码转换成目标文件,在这个过程中维护一个地址计数器,当处理到每个段的开头时把地址计数器置成0,然后每处理一条汇编指示或指令就把地址计数器增加相应的字节数,在汇编程序中用.可以取出当前地址计数器的值,是一个常量。
在_start中调了两个系统调用,第一个是write系统调用,第二个是以前讲过的_exit系统调用。在调write系统调用时,eax寄存器保存着write的系统调用号4,ebx、ecx、edx寄存器分别保存着write系统调用需要三个参数。ebx保存着文件描述符,进程中每个打开的文件都用一个编号来标识,成为文件描述符,文件描述符1表示标准输出,对应的C标准I/O库的stdout。ecx保存着输出缓冲区的首地址。edx保存着输出的字节数。write系统调用把从msg开始的len个字节写到标准输出。
C代码中的write函数是系统调用的包装函数,其内部实现就是把传进来的三个参数分别赋给ebx、ecx、edx寄存器,然后执行movl $4,%eax和int $0x80两指令。这个函数不可能完全用C代码写,因为任何C代码都不会编译生成int指令,所以这个函数有可能完全用汇编写的,也有可能是C用内联汇编写的,甚至是一个宏定义。_exite函数野史如此。