@gain
2016-02-28T12:47:06.000000Z
字数 1689
阅读 1492
汇编
Linux
文章注明:干宇航 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
实验环境:Windows10 下Virtual Box Linux 64bit(Ubuntu 15.10) + putty
实验环境截图:
C代码
int g(int x)
{
return x + 12;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(6) + 5;
}
然后创建main.c
然后通过如下选项编译,
gcc –S –o main.s main.c -m32
得到main.s,并精简代码,得到。
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $12, %eax
popl %ebp
ret
f:
pushl %ebp
movl %esp, %ebp
pushl 8(%ebp)
call g
addl $4, %esp
leave
ret
main:
pushl %ebp
movl %esp, %ebp
pushl $6
call f
addl $4, %esp
addl $5, %eax
leave
ret
下面来分析这段汇编代码的工作过程中堆栈的变化
在分析的过程中,用这张图描述堆栈,每个一个单位之间相差8字节。
C语言,当然是先执行main函数喵:
main:
pushl %ebp
movl %esp, %ebp
pushl $6
call f
addl $4, %esp
addl $5, %eax
leave
ret
pushl %ebp
压栈操作,这里pushl %ebp
相当于做了两步
subl $4,%esp
(esp(栈底指向1))和movl %ebp,(%esp)
(esp所指向的内存单元存入ebp的地址)
于是,栈变成了这个样子,
movl %esp, %ebp
将ebp指向esp
pushl $6
esp压栈,并存储一个立即数6
call f
相当于 pushl %eip*
和 movl f ,%eip*
eip是指令寄存器,则将指令寄存器eip压入下一个内存单元,并将eip指向f
我们来看向f
f:
pushl %ebp
movl %esp, %ebp
pushl 8(%ebp)
call g
addl $4, %esp
leave
ret
依然是压栈,并将ebp指向esp
pushl %ebp
movl %esp, %ebp
pushl 8(%ebp)
变址寻址,将6存入esp中。这相当于函数的传值功能,将main函数内的6传到f函数中。
call g
将指令寄存器eip压入下一个内存单元,并将eip指向g
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $12, %eax
popl %ebp
ret
pushl %ebp
movl %esp, %ebp
依然压栈,esp中存入ebp的地址,ebp再指向esp,
movl 8(%ebp), %eax
addl $12, %eax
将5所指向的内存单元中的数值6(变址寻址)赋值给变量eax(函数传值)
notes:函数值得返回值默认使用eax寄存器存储返回给上一级函数
然后eax中的值加上12。
eax = 12+6=18
然后popl %ebp
相当于 movl (%esp),%ebp
(把esp中存储先前ebp的值传给ebp,意思是说ebp重新指向4,)和addl $4,%esp
(esp指向上一个内存单元)
g函数的最后return了
ret
这步相当于pop %eip*
,也就是movl (%esp),%eip
(eip指向f了现在,就是call g
之后的语句)和addl $4,%esp
addl $4,%esp
出栈
leave
相当于 movl %ebp,%esp
(esp指向ebp)并popl %ebp
(ebp 指向1,esp指向3)
ret
eip指向main函数中call f
之后的语句,esp指向2
addl $4, %esp
addl $5, %eax
leave
ret
addl $4, %esp
esp 指向1,addl $5, %eax
eax的值加上5,现在是23.
leave,esp指向ebp,都是1,然后popl,都指向0
ret可能后面还有栈。
总结,看着这张动图,整个指令流过程了然于胸。