读书笔记系列深入理解计算机系统之程序的机器级表示

前言

计算机执行机器代码,汇编代码是机器代码的文本表示,具有易读性,和一条机器码一对一关系,给出程序中每一条指令,通过汇编器和链接器,根据汇编代码生成可执行机器代码。 而汇编是与特定机器密切相关的,不同CPU都有不同指令集。 能够阅读和理解汇编是一项很重要的技能,通过阅读汇编代码,能够理解编译器的优化能力,分析代码中隐含的低效率

x86-64一直是intel的机器语言首选,其前身是IA32,32位机器的随机访问存储器只有4GB的空间,这对于现在是完全不够的。

intel处理器复杂性年鉴 此处输入图片的描述

摩尔定律:芯片上的晶体管数量每年都会翻一番。

对于机器级编程来说,有两种抽象很重要,其一是指令集体系结构或指令集架构来定义机器级程序的格式和行为,定义了处理器状态,指令格式,每条指令对状态的影响;其二是使用的内存地址是虚拟地址

以x86-64的机器代码为例子

机器代码和反汇编的一些特性

指令的类型

intel用字表示16位数据类型,32位为双字,64位为四字 此处输入图片的描述

访问信息

一个x86-64中央处理单元包含一组16个存储64位值的通用目的寄存器,这些寄存器用来存储整数数据和指针。如图: 此处输入图片的描述

寄存器由多个触发器或锁存器组成简单电路,N位代表N个触发或锁存器

寄存器分类

操作数指示符

大多数指令有一个或多个操作数,指示执行一个操作中要使用的源数据,以及放置结果的目的位置。源数据值可以以常数给出,或从寄存器,内存中读出,结果存放在寄存器或内存中。各种不同操作数可能性被分为三种类型,第一种是立即数,ATT格式中用$表示;第二种是寄存器,分别对应上面所说的8,16,32,64位寄存器作为一个操作数,用r表示任意寄存器;第三种是内存引用,他会根据计算出来的地址访问某个内存地址。用符号Mb[addr]表示从地质addr的b个字节引用。

数据传送指令

最频繁使用的指令是将数据从一个位置复制到另一个位置的指令。

此处输入图片的描述 上述数据传送指令,源操作数是一个立即数,存储在寄存器或内存中;目的操作数是一个位置(寄存器或内存地址)。寄存器大小根据指令最后一个字符与上述寄存器匹配相同大小,mov指令只会更新目的操作数指定寄存器字节或内存位置。唯一例外是movl指令会把寄存器高位4字节设置为0.

将较小的源值复制到较大的目的时使用指令:

此处输入图片的描述

TODO 压栈,出栈详解

程序执行时,CPU根据PC寄存器里的地址,从内存取出需要执行的指令放到指令寄存器中,顺序执行每一条指令。

当遇到条件码指令时,PC寄存器的指针会指向之后或之前的指令地址。

当遇到方法函数调用时,和条件码一样jump指令替换成了call指令,指令指向下一个地址。那么时怎么实现调用的呢? 函数内联:通过把调用的函数指令,直接插入在调用函数的地方。该方法缺点,如果函数调用地方很多,造成内存浪费,以及类似于A,B互相调用代码无限调用的问题。 如果使用寄存器存下每一个函数返回地址呢,当需要记录多个地址时,寄存器的数量是远远不够的。 综其所述,使用栈帧结构保存调用返回后的地址是最好的方式。

真实程序里,压栈的不止是函数调用后返回地址,还有一些寄存器不够用时所需的参数

一个完整的C语言程序执行过程:编译-》汇编-》链接-》装载。

只有通过链接的把目标文件和函数库链接起来,才能得到可执行文件

linux环境下可执行文件使用ELF(可执行与可链接文件格式)文件格式,ELF除了存放汇编指令,还有函数名称,全局变量存放在符号表

ELF文件通过一个个section保存各种信息。

链接过程 链接器扫描所有输入目标文件,把所有符号表信息收集起来,构成全局符号表;再根据重定位表,把所有不确定要跳转地址,根据符号表里地址,进行修正;最后,把所有目标文件对应段进行一次合并,得到可执行代码(此时文件里函数调用地址都是正确的)。

Windows下可执行文件格式是PE,所以正常情况下,Windows下和linux下文件是不能互相解析和加载的,除非有相应的装载器。