内容说明
本次的内容,是一次 MOOC 课程的作业。具体的,是在 Linux 下对一段简单的 C 代码生成的汇编代码进行分析,进而了解计算机、CPU 的工作机制。
作业声明
qianyizhou17 + 原创作品转载请注明出处 + 《Linux 内核分析》MOOC 课程
http://mooc.study.163.com/course/USTC-1000029000
实验准备
int func_a(int x)
{
return x + 1000;
}
int func_b(int x)
{
return func_a(x);
}
int main(void)
{
return func_b(8) + 1;
}
$ gcc –S –o main.s main.c -m32
.file "main.c"
.text
.globl func_a
.type func_a, @function
func_a:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
movl 8(%ebp), %eax
addl $1000, %eax
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size func_a, .-func_a
.globl func_b
.type func_b, @function
func_b:
.LFB1:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call func_a
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE1:
.size func_b, .-func_b
.globl main
.type main, @function
main:
.LFB2:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $4, %esp
movl $8, (%esp)
call func_b
addl $1, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE2:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
其中以 “.” 开头的为辅助信息,在分析代码时可以删除,整理后的汇编信息如下:
func_a:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $1000, %eax
popl %ebp
ret
func_b:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call func_a
leave
ret
main:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl $8, (%esp)
call func_b
addl $1, %eax
leave
ret
代码分析前提
代码分析
首先代码从 17 main 开始执行
执行 18、19 行,即执行 call 指令,保存上一次的栈信息:将上次的栈底入栈,并将栈底、栈指针重新指向下一地址,完成当前函数栈的初始化:
执行前
| |
ebp -> --------- 5000
. | |
.
.
| |
esp -> --------- 4000
执行后
| |
--------- 5000
. | |
.
.
| |
--------- 4000
| ebp 5000|
esp/ebp-> --------- 3996
执行 20、21 行,将立即数 8 存储到 esp 所指向的空间中
| |
--------- 5000
. | |
.
.
| |
--------- 4000
| ebp 5000|
ebp-> --------- 3996
| 8 |
esp-> --------- 3992
执行 22 行:call func_b
| |
--------- 5000
. | |
.
.
| |
--------- 4000
| ebp 5000|
ebp-> --------- 3996
| 8 |
--------- 3992
| eip 23 |
esp-> --------- 3988
同时 eip 指向了 func_b 的地址:8。继续执行时,将从 func_b 的下一个地址 9 开始执行。
执行第 9、10 行后:
| |
--------- 5000
. | |
.
.
| |
--------- 4000
| ebp 5000|
--------- 3996
| 8 |
--------- 3992
| eip 23 |
--------- 3988
| ebp 3996|
ebp/esp-> --------- 3984
以此类推,调用 call func_a,执行 func_a,第 5 行 addl $1000, %eax 执行后,eax 中存储的值变为 1008。
执行 6 行,popl %ebp
执行前
eip 1
eax 1008
| |
--------- 5000
. | |
.
.
| |
--------- 4000
| ebp 5000|
--------- 3996
| 8 |
--------- 3992
| eip 23 |
--------- 3988
| ebp 3996|
--------- 3984
| 8 |
--------- 3980
| eip 15 |
--------- 3976
| ebp 3984|
ebp/esp-> --------- 3972
执行 popl %ebp 后
eip 1
eax 1008
| |
--------- 5000
. | |
.
.
| |
--------- 4000
| ebp 5000|
--------- 3996
| 8 |
--------- 3992
| eip 23 |
--------- 3988
| ebp 3996|
ebp -> --------- 3984
| 8 |
--------- 3980
| eip 15 |
esp -> --------- 3976
| ebp 3984|
--------- 3972
执行 ret 之后
eip 15
eax 1008
| |
--------- 5000
. | |
.
.
| |
--------- 4000
| ebp 5000|
--------- 3996
| 8 |
--------- 3992
| eip 23 |
--------- 3988
| ebp 3996|
ebp -> --------- 3984
| 8 |
esp -> --------- 3980
| eip 15 |
--------- 3976
| ebp 3984|
--------- 3972
注意,此时 eip 的值为 15,因此继续执行的指令为 func_b 的 leave 指令
执行后
eip 15
eax 1008
| |
--------- 5000
. | |
.
.
| |
--------- 4000
| ebp 5000|
ebp -> --------- 3996
| 8 |
--------- 3992
| eip 23 |
esp -> --------- 3988
| ebp 3996|
--------- 3984
| 8 |
--------- 3980
| eip 15 |
--------- 3976
| ebp 3984|
--------- 3972
继续执行 ret 后
eip 23
eax 1008
| |
--------- 5000
. | |
.
.
| |
--------- 4000
| ebp 5000|
ebp -> --------- 3996
| 8 |
esp -> --------- 3992
| eip 23 |
--------- 3988
| ebp 3996|
--------- 3984
| 8 |
--------- 3980
| eip 15 |
--------- 3976
| ebp 3984|
--------- 3972
addl $1, %eax 之后,eax 中存储 1009。
继续执行 leave,将当前的栈信息恢复。
before leave after leave, before ret
eip 23 eip 23
eax 1009 eax 1009
| | | |
--------- 5000 ebp -> --------- 5000
. | | . | |
. .
. .
| | | |
--------- 4000 esp -> --------- 4000
| ebp 5000| | ebp 5000|
ebp -> --------- 3996 --------- 3996
| 8 | | 8 |
esp -> --------- 3992 --------- 3992
| eip 23 | | eip 23 |
--------- 3988 --------- 3988
| ebp 3996| | ebp 3996|
--------- 3984 --------- 3984
| 8 | | 8 |
--------- 3980 --------- 3980
| eip 15 | | eip 15 |
--------- 3976 --------- 3976
| ebp 3984| | ebp 3984|
--------- 3972 --------- 3972
调用 ret,恢复 eip:将 esp 所指向的指令值写入 eip,main 函数结束。
pushl %ebp; movl %esp, %ebp
来进行栈信息的保存,包括 1)将上次的栈底信息压入栈; 2)将栈底、栈指针重置为下一个栈地址。 --------- 4000
| ebp DATA|
--------- XXXX
| |
| |
| |
--------- XXXX
| eip DATA|
--------- 39XX