朱婷婷 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
计算机是怎么工作的?
我们知道计算机基本都是采用的冯诺依曼体系结构,它主要的核心思想就是存储程序计算机,指令和数据不加区别混合存储在同一个存储器中。CPU通过寄存器EIP指向内存从而获取内存中的数据。EIP总是指向内存的某一块区域(代码段),不断的指向下一条指令,每条指令的长度可能是不同的。我们可以用一个简单的图来描述下:
一、实验内容:通过反汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的
main.c
int g(int x)
{
return x+4;
}
int f(int y)
{
return g(y);
}
int main(void)
{
return f(2)+4;
}
保存后执行以下命令:
gcc -S -o main.s main.c -m32
其中gcc后面参数含义可以自行man以下,-S表示预处理后可直接对main.c文件编译生产汇编代码,如下所示:
shiyanlou:~/ $ cat main.s [21:59:59]
.file "main.c"
.text
.globl g
.type g, @function
g:
.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 $4, %eax
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size g, .-g
.globl f
.type f, @function
f:
.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 g
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE1:
.size f, .-f
.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 $2, (%esp)
call f
addl $4, %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
二、实验结果分析:
以上汇编代码中,以点(.)开头的都是用于链接的辅助信息,这些信息是不会被执行的。因此以下分析下除去辅助信息之外的汇编指令:
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $4, %eax
popl %ebp
ret
f:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
leave
ret
main:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl $2, (%esp)
call f
addl $4, %eax
leave
ret
程序从main函数开始,假设一开始是一个空栈,如下图所示(在32位环境中,int型数据占4个zi)
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl $2, (%esp)
call f
call f 的操作其实就相当于pushl %eip; movl * f, %eip; 从这步开始进入到f函数中的操作
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
以上操作相当于 eax = *(int32_t *) (ebx+4),因此eax = 2
movl %eax, (%esp)
call g
pushl %ebp
movl %esp, %ebp
·
movl 8(%ebp), %eax
addl $4, %eax
popl %ebp
ret
ret的操作就相当于pop eip(*),所以esp指针向上移一格,然后代码回到第15行的那条指令
leave
leave指令相当于movl %ebp,%esp; popl %ebp;
ret
回到第23行指令继续执行
addl $4, %eax
leave
ret
执行完以上所有代码,我们可以看到堆栈又回到了最初的状态。
三、总结
通过以上逐条指令的分析来理解C语言函数的执行过程,能够加深我们对计算机C语言等高级语言的理解,同时对以后学习用户空间虚拟地址空间打下了基础。
四、实验截图