张颜(111) 实验一
本文以一个简单的C程序为分析案例,分析其对应的.s汇编代码在CPU上的执行过程。
首先给出C程序代码:
int g(int x)
{
return x+3;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(8)+1;
}
用gcc将test.c分别生成.cpp,.s,.o和ELF可执行文件:
查看对应的汇编代码:
.file "test.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 $3, %eax
popl %ebp
.cfi_def_cfa 4, 4
.cfi_restore 5
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 $8, (%esp)
call f
addl $1, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE2:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
.section .note.GNU-stack,"",@progbits
汇编中部分指令注释:
我们对汇编代码在CPU上执行时,栈空间对应的变化,作如下分析:
首先在main函数中,栈空间的变化情况:
在call f 执行结束后,便进入到 f 函数中:
现在进入到g函数中:
ret执行后,执行流又返回到f中了:
现在执行流又回到main中了:
以上就是汇编指令在CPU上执行时,栈空间对应的变化。
小结:
通过观察汇编指令执行的整个过程中,栈空间的变化情况,我们可以得出单任务计算机的基本工作原理:
如上图所示,每次CPU执行都要先读取EIP寄存器的值,然后定位EIP指向的内存地址(CS段),并且读取汇编指令,最后执行。ESP始终指向栈顶,EBP指向栈底,当我们调用一个函数时,先将堆栈原先的基址(EBP)入栈,以保存之前任务的信息。然后将栈顶指针的值赋给EBP,将之前的栈顶作为新的基址(栈底),然后再这个基址上开辟相应的空间用作被调用函数的堆栈。函数返回后,从EBP中可取出之前的ESP值,使栈顶恢复函数调用前的位置;再从恢复后的栈顶可弹出之前的EBP值,因为这个值在函数调用前一步被压入堆栈。这样,EBP和ESP就都恢复了调用前的位置,堆栈恢复函数调用前的状态。
而多任务计算机的基本工作原理,则包括中断机制,时间片轮转进程调度,多线程,或者多核等。