(转)详解汇编系统调用过程(以printf为例)

来源:互联网 发布:崩坏学园2淘宝代充 编辑:程序博客网 时间:2024/04/30 14:16

(转)详解汇编系统调用过程(以printf为例)
本文以printf为例,详细解析一个简单的printf调用里头,系统究竟做了什么,各寄存器究竟如何变化。

环境:

linux + gnu as assembler + ld linker

如何在汇编调用glibc的函数?其实也很简单,根据c convention call的规则,参数反向压栈,call,然后结果保存在eax里头。注意,保存的是地址。

在汇编里头,一切皆地址。(别纠结这个,别告诉我还有立即数……主要是要有一切皆地址的思想)

例如这个printf,在C里头,我们用得很多

int printf(const char *format, …) 这里值得一提的是这个“…”是不定参数,也就是说后面有多少个参数,函数定义里头没有规定,感兴趣的可以google一下va_list相关的知识,这里就不展开了。

但是汇编怎么知道处理这个的呢?这里给个简单的解释,感兴趣的可以google一下“c convention call”了解更详细跟专业的解释。

例如当我们调用 result = printf( “%d %d”, 12, a )的时候,编译器默认是这样处理的(除非函数定义声明了pascal call)。

在栈里头,先一次push a的地址,还有12这个立即数,再push “%d %d”这个字符串的地址,内存模型如下,x86的esp是往下增长的。

(这里是buttom,往下增长的是top)

&a

12

address of “%d %d”

——————————————-(esp 指着这里 ,我们假设地址是4字节,12这个数也是4字节)

当call printf的时候,首先,push当前的eip入esp,解析esp+4所指的”%d %d”,因为%d这样的特定字符都定义了后面每个参数的大小,所以只要解析“%d %d”,我们就可以知道栈里头参数的情况,例如esp+4+4就是一个int,esp+4+4+4是另外一个int。

当返回的时候,先pop到eip,也就是把eip还原到call之后马上要执行的机器码,这时,esp就指着“%d %d”,esp+4指着12,esp+8指着a的地址。esp里头的内容怎么处理,看需要吧,你也可以pop出来,也可以不pop。但为了效率着想,如果空间够用,通常不pop,直接用mov指令把下一次要用的参数move进去。返回指储存在eax里头。

这也一定程度上解释了为什么c convention call是反向压栈,这样编译器处理起来方便,特别对于这些va_list,因为va_list后面不能继续跟参数,va_list一定出现在函数的末尾,如果是对printf这类的函数使用pascal call,也就是参数正向压栈,汇编级别处理起来就特别麻烦了。

眼见为实,下面就用汇编写一个调用printf的,并用gdb跟踪寄存器,看看是否是上述的一样。

文件:test_printf.s

.section .data
format: .asciz “%d\n”
.section .text
.global _start
_start:
pushl 12pushlformat
call printf
movl $0, (%esp)
call exit

使用如下命令编译,链接

$ as -g test_printf.s -o test_printf.o

$ ld -lc -I /lib/ld-linux.so.2 test_printf.o -o test_printf

as加入-g是要加入调试信息,ld的-lc是链接libc.a,-I是–dynamic-linker,/lib/ld-linux.so.2这个要看各人系统情况。链接libc跟ld库之后,生成test_printf

执行

$ ./test_printf

12

输出12,正常退出。

先用objdump看看test_printf里头的.text section

$ objdump -d test_printf

Disassembly of section .text:

080481c0 <_start>:
80481c0: 6a 0c push 0xc80481c2:68cc920408push0x80492cc
80481c7: e8 d4 ff ff ff call 80481a0

0 0
原创粉丝点击