x86/Debian Linux/gcc。
在汇编中,一般具有如下的结构:
在汇编程序中,数据和附加段可选,但在汇编程序中经常包含有这两个段。代码段必含,是声明指令代码的地方。
每个段用”.section”或“.标识符”标识。如”.section.data”或”.data”就形象的表示一个数据段。其实对于对于系统来说,它只是一个段,以“.”标识。
当汇编代码被编译成为可执行程序的时候,连接器必须要知道程序入口点在哪里。在GNU汇编器中,_start被定义为汇编程序的入口标识符,_start标识符暗示程序从这里开始运行。
为了让这个入口标识符能被其它文件的汇编代码认识,提供 “.globl”指令实现,即用.globl _start语句声明程序入口标识符。
所以,一个AT&T汇编程序的结构大概如此:
.section .data < initialized data here>
.section .bss < uninitialized data here>
.section .text .globl _start _start: |
在代码段中,汇编代码依靠系统调用来完成一些操作,包括在程序退出之前的一些操作及程序退出操作。
.data msg: .ascii "hello, world\n" len= .-msg |
代码段主要是用系统调用来完成程序功能。包含“程序入口”、“功能代码”和“程序退出代码”。
.text .global _start _start: |
按照GNU AT&T汇编的格式,_start为汇编程序的入口,且由.global保证其它文件内的汇编代码可识别_start入口。
_start: movl $4, %eax #Linux system wirte call movl $1, %ebx #STDOUT movl $msg, %ecx #Give string start address to %ecx movl $len, %edx #Give string lenght to %edx int $0x80 #Software interrupt |
int指令中的立即数0x80是一个参数,在异常处理程序中要根据这个参数决定如何处理,在Linux内核中int $0x80这种异常称为系统调用(System Call)。内核提供了很多系统服务供用户程序使用,但这些系统服务不能像库函数(比如printf)那样调用,因为在执行用户程序时CPU处于用户模式,不能直接调用内核函数,所以需要通过系统调用切换CPU模式,经由异常处理程序进入内核,用户程序只能通过寄存器传几个参数,之后就要按内核设计好的代码路线走,而不能由用户程序随心所欲,想调哪个内核函数就调哪个内核函数,这样可以保证系统服务被安全地调用。在调用结束之后,CPU再切换回用户模式,继续执行int $0x80的下一条指令,在用户程序看来就像函数调用和返回一样。[Linux C 编程一站式学习]
在int $0x80引发的系统调用中,在eax寄存器内的值表示系统调用号,内核需要通过eax判断用户要调哪个系统调用。4代表linux write system call,1代表linux exits ystem call。ebx的值是传给_exit的参数,表示退出状态。大多数系统调用完成之后会返回用户空间继续执行后面的指令,而_exit系统调用比较特殊,它会终止掉当前进程,而不是返回用户空间继续执行。And so on。
在linux 写系统调用(eax值为4)下,其它的寄存器内所存值也会代表一些含义:
故而,在为写操作准备好所有的条件之后。int $0x80经执行引起的软件中断后,照eax内存的值就访问linux内核的一些函数,向控制台(ebx:1)输出字符串(ecx:msg)。
movl $1, %eax #Linux systemexit call movl $0, %ebx #Acceptprogram exit value int $0x80 #software interrupt |
Linux汇编程序的退出也是依靠系统调用来完成。此时寄存器eax中的值应该为1,ebx的值为程序退出后返回给shell的值。这几句汇编程序完成_exit()调用。
(1)、(2)这段汇编代码相当于以下这段C代码:
#include char msg[14] = "hello, world\n"; #define len 14 int main(void) { write(1, msg, len); _exit(0); } |
加入以上各部分组成的代码文件名为,learn.s,则编译执行过程为:
as -o learn.o learn.s [生成learn.o文件]
ld -o learn learn.o [生成learn可执行文件]
./learn [ 得程序执行结果]
hello, world
关于涉及到的AT&T汇编的语法:
初学汇编,在了解汇编程序框架后,如果不知代码段应该写什么内容,不明白要用哪一个寄存器来操作时。应该查查各寄存器在系统调用软件中断发生时(int $0x80)要发生的操作所对应的寄存器的值。这样,起码就能编写“hello world!”的入门代码了。
当然,要了解整个汇编代码的机制还得多编写,达到只需查询就可完成功能的目标。目前根据这个程序得到的经验是:汇编代码可以依靠系统调用软件中断来调用内核的函数,系统调用的类型由eax寄存器你的值。我们需要提前配置好eax及相关寄存器的值,从而实现咱的目标功能。一般是eax寄存器里面的值决定软件中断后linux系统调用的类型(eax:4linux write system call,eax:1 linux exit system call),然后其它的寄存器配合这种统调用。[好像总结得有点浅显]
我们用C标准I/O库函数最终也是通过系统调用把I/O操作从用户空间传给内核,然后让内核去做I/O操作。
ASNote Over。