在高级语言横行的现在,能看懂机器语言的程序员并不多。了解了寄存器,汇编等知识后,才能对进程,线程有更深的认识,而不仅仅只是一个Thread类。
寄存器
一个CPU包含一组8个存储32位值寄存器。这些寄存器用来保存整数数据和指针。访问寄存器的速度比访问内存的速度快。特别留意下%esp和%ebp这两个寄存器分别保存指向程序栈的栈顶位置的指针和指向程序栈栈底位置的指针。下面规定%eax表示图中%eax方法中的值,是个32位整数。其他寄存器同理。
函数的汇编实现
文中有这么一个例子:
这例子中有两个函数caller()和swap_add(),下面左边的图是caller()的栈帧,右边的图是调用swap_add()后,在caller()栈帧下建了swap_add的栈帧。
我们看下caller的汇编代码,看下它被调用时是如何建立自己的栈帧和swap_add的栈帧并为swap_add准备好参数的:
第2行:保存%ebp指向内存的值到栈顶
第3行:将%esp中的值赋给%ebp
第4行:将%esp-24,也就是将栈指针下移24个字节
第5行:将%ebp-4内存处赋值为534,也就是arg1
第6行:将%ebp-8内存处赋值为1057,也就是arg2
第7行,8:将arg2的地址(&arg1)赋值给%esp+4指向的内存
第9行,10:将arg1的地址(&arg2)赋值给%esp指向的内存,也就是栈顶
第11行:调用swap_add方法
经过这段汇编代码,就形成了上面的左图。再来看下建立swap_add栈帧的过程:
第2行:保存%ebp到栈顶
第3行:更新%ebp也指向栈顶,到这里新的栈帧已经建立
第4行:%ebx保存着caller函数中的值,在调用swap_add时可能覆盖%ebx,所以要先将%ebx里面的值保存到栈上,退出swap_add时要,恢复%ebx的值,这样才不会影响返回到caller后,caller正常执行。到这里就形成了上面右边的栈帧结构。
swap_add的栈帧建立好后,我们看下swap_add内部的汇编代码:
第5行:将参数&arg1保存到%edx
第6行:将参数&arg2保存到%ecx
第7行:将arg1保存到%ebx
第8行:将arg2保存到%eax
第9行:将%eax的值保存到%edx指向的内存处
第10行:将%ebx的值保存到%ecx指向的内存处,到这里实现了arg1和arg2值的交换
第11行:将%ebx加到%eax中,%eax规定为保存返回值的寄存器
这个函数中,我们交换了arg1和arg2的值,并且将它们的和保存在寄存器%eax中。
我们最后看下swap_add是如何返回到caller中的:
第12行:将上面保存在栈中的%ebx的值重新保存到%ebx中
第13行:%ebp指向caller的栈底
第14行:通知程序计数器回到caller中swap_add方法后面的那条指令,到这里又回到了caller的栈帧(上面左图的栈帧),好像swap_add没有调用过一样。
总结
熟悉了这个过程,我们对局部变量,指针,栈溢出这些名词肯定会更深刻的理解。下一节,我们看下虚拟存储器。
参考:《深入理解计算机系统》