《深入理解计算机系统》-程序的机器级表示

在高级语言横行的现在,能看懂机器语言的程序员并不多。了解了寄存器,汇编等知识后,才能对进程,线程有更深的认识,而不仅仅只是一个Thread类。

寄存器

寄存器.png

一个CPU包含一组8个存储32位值寄存器。这些寄存器用来保存整数数据和指针。访问寄存器的速度比访问内存的速度快。特别留意下%esp和%ebp这两个寄存器分别保存指向程序栈的栈顶位置的指针和指向程序栈栈底位置的指针。下面规定%eax表示图中%eax方法中的值,是个32位整数。其他寄存器同理。

函数的汇编实现

文中有这么一个例子:

过程.png

这例子中有两个函数caller()和swap_add(),下面左边的图是caller()的栈帧,右边的图是调用swap_add()后,在caller()栈帧下建了swap_add的栈帧。
我们看下caller的汇编代码,看下它被调用时是如何建立自己的栈帧和swap_add的栈帧并为swap_add准备好参数的:

caller对应的汇编.png

第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栈帧的过程:

swap_add栈帧建立.png

第2行:保存%ebp到栈顶
第3行:更新%ebp也指向栈顶,到这里新的栈帧已经建立
第4行:%ebx保存着caller函数中的值,在调用swap_add时可能覆盖%ebx,所以要先将%ebx里面的值保存到栈上,退出swap_add时要,恢复%ebx的值,这样才不会影响返回到caller后,caller正常执行。到这里就形成了上面右边的栈帧结构。
swap_add的栈帧建立好后,我们看下swap_add内部的汇编代码:

swap_add内部实现.png

第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中的:

swap_add返回caller.png

第12行:将上面保存在栈中的%ebx的值重新保存到%ebx中
第13行:%ebp指向caller的栈底
第14行:通知程序计数器回到caller中swap_add方法后面的那条指令,到这里又回到了caller的栈帧(上面左图的栈帧),好像swap_add没有调用过一样。

总结

熟悉了这个过程,我们对局部变量,指针,栈溢出这些名词肯定会更深刻的理解。下一节,我们看下虚拟存储器。

参考:《深入理解计算机系统》

推荐阅读更多精彩内容

  • 精通细节是理解更深和更基本概念的先决条件,这一章节首先讲解了C代码、汇编代码与机器代码的关系,再次重申了汇编的承上...
    唐鱼的学习探索阅读 2,890评论 1 9
  • 程序编码 对于机器级编程来说,两种抽象比较重要,一种是机器级程序的格式和行为,为指令集体系结构(ISA),包括IA...
    fredal阅读 1,839评论 2 18
  • 阅读经典——《深入理解计算机系统》04 函数调用时的栈结构变化是一个很有趣的话题,本文就来详细剖析这个过程。 栈帧...
    金戈大王阅读 12,180评论 13 26
  • 原文地址:C语言函数调用栈(一)C语言函数调用栈(二) 0 引言 程序的执行过程可看作连续的函数调用。当一个函数执...
    小猪啊呜阅读 2,770评论 1 18
  • 在实际编程过程中,我们大多是使用高级语言如 Java 语言编程。多数时候,高级语言因其高度封装,使得程序易于编写,...
    棨帆阅读 304评论 1 1