• 计算机内功修炼:程序的机器级表示(C与汇编)


    计算机执行机器代码,用字节序列编码低级的操作,包括处理数据、管理存储器、读写存储设备上的数据,以及利用网络通信。编译器基于编程语言的原则、目标机器的指令集和操作系统遵循的规则,经过一系列的阶段产生机器代码。

    GCC C语言编译器以汇编代码的形式产生输出,汇编代码是机器代码的文本表示,给出程序中的每一条指令。然后GCC调用汇编器和链接器,从而根据汇编代码生成可执行的机器代码。在本章中,我们会近距离地观察机器代码,以及人类可读的表示-——汇编代码。

    高级语言编程,机器会屏蔽程序实现的细节, 且我们使用起来不容易出错,为什么我们还需要花时间学习机器代码

    • 对于严谨的程序员来说,能够阅读和理解汇编代码仍是一项很重要的技能。以适当的命令行选项调用编译器,编译器就会产生一个以汇编代码形式表示的输出文件。通过阅读这些汇编代码,我们能够理解编译器的优化能力,并分析代码中隐含的低效率。试图最大化一段关键代码的性能的程序员,通常会尝试源代码的各种形式,每次编译并检查产生的汇编代码,从而了解程序将要运行的效率如何。
    • 高级语言提供的抽象层会隐藏我们想要了解的有关程序运行时行为的信息。例如,当用线程包写并发程序时,知道存储器保存不同的程序变量的区域是很重要的。这些信息在汇编代码级是可见的。
    • 另外再举一个例子,程序遭受攻击(使得蠕虫和病毒能够侵扰系统)的许多方式中,都涉及程序存储运行时控制信息方式的细节。许多攻击利用了系统程序中的漏洞重写信息,从而获得系统的控制权。了解这些漏洞是如何出现的以及如何防御它们,需要具备程序机器级表示的知识。

    程序员学习汇编代码的需求随着时间的推移也发生了变化,开始时只要求程序员能直接用汇编语言编写程序,现在则是要求他们能够阅读和理解编译器产生的代码。

    我们将详细学习两种特别的汇编语言: 了解如何将 C程序编译成这些形式的机器代码。阅读编译器产生的汇编代码,需要具备的技能不同于手工编写汇编代码。我们必须了解典型的编译器在将 C程序结构变换成机器代码时所做的转换。相对于C代码表示的计算操作,优化编译器能够重新排列执行顺序,消除不必要的计算,用快速操作替换慢速操作,甚至将递归计算变换成迭代计算。源代码与对应的汇编码的关系通常不太容易理解——就像要拼出的拼图与盒子上图片的设计不太一样。

    学习建议: 精通细节是理解更深合更基本概念的先决条件。 “理解一般规则,而不愿意劳神学习细节”实际上是自欺欺人。

    本文讲解事项点

    1. 基于两种机器语言 InterIA32 和 X86-64。前者是计算机主导语言,后者是64位机器上运行的扩展。
    2. 先浏览C语言,汇编语言和机器代码的关系,然后介绍IA32的细节,从数据的表示和处理以及控制的实现开始, 之后会讲到过程的实现,如果维护运行栈来支持过程间数据和控制的传递,以及局部空间的存储。紧接着会考虑在机器级别实现数组,结构和联合这样的数据结构,结尾,我们将给出GDB调试器检查机器级程序运行时行为的技巧。

    历史观点

    Linux使用了平坦寻址方式将整个存储空间看成一个大的字节数组。

    程序编码

    在这里插入图片描述
    编译选项 -01 告诉编译器使用第一级别的优化, 提高优化级别会使最终程序运行得更快,但编译时间更长,从得到的程序性能方面考虑,第二级别的优化 -02 是被认为较好的选择。

    实际上gcc命令调用了一系列程序,使得源代码转化为可执行的代码。

    • C预处理器扩展源代码,插入所有用 #include命令指定的文件,并扩展 #define声明指定的宏
    • 编译器产生两个源代码的汇编代码,名字为 p1.s, p2.s
    • 汇编器将汇编代码转换为二进制目标文件名为 p1.o和 p2.o, 目标文件是机器代码的一种形式,包含所有指令的二进制表示,但是还没有填入地址的全局值。
    • 最后链接器将两个目标代码文件与实现库函数的代码合并,并产生最终的可执行代码文件p。

    1. 机器级代码

    机器级编程,其中两种抽象尤为重要。

    1. 机器级别程序的格式和行为,定义为指令集体系结构(Instruction set archiecture, ISA) ,定义了处理器状态,指令的格式,以及每条指令对状态的影响。
    2. 机器级程序使用的存储器地址是虚拟地址,提供的存储器模型看上去是一个非常大的字节数组。

    在整个编译过程中,编译器会完成大部分的工作,将把用C语言提供的相对比较抽象的执行模型表示的程序转化成处理器执行的非常基本的指令。汇编代码有一个的主要特点,即它用可读性更好的文本格式来表示。能够理解汇编代码以及它与原始C代码的联系,是理解计算机如何执行程序的关键一步。

    IA32机器代码和原始C代码的差别非常大,一些通常对C语言程序员隐藏的处理器状态是可见的。

    • 程序计数器(在IA32中,通常称为“PC”,用 %eip表示)指示将要执行的下一条指令在存储器中的地址。
    • 整数寄存器文件包含8个命名的位置,分别存储32位的值。这些寄存器可以存储地址(对应于C语言的指针)或整数数据。有的寄存器被用来记录某些重要的程序状态,而其他的寄存器则用来保存临时数据,例如过程的局部变量和函数的返回值。
    • 条件码寄存器保存着最近执行的算术或逻辑指令的状态信息。它们用来实现控制或数据流中的条件变化,比如说用来实现if和while语句。
    • 一组浮点寄存器存放浮点数据。

    2. 代码示例

    int accum = 0;
    
    int sum(int x, int y) {
       
    	int t = x + y;
    	accum += t;
    	return t;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    命令上使用 -S 能得到C语言编译器产生的汇编代码 code.s

    gcc -O1 -S code.c
    
    • 1

    在这里插入图片描述
    以上代码中每个缩进去的行都对应一条机器指令。比如,pushl指令表示应该将寄存器 % ebp 的内容压入程序栈。这段代码中已经除去了所有关于局部变量名或数据类型的信息。我们还看到了一个对全局变量 accum 的引用,这是因为编译器还不能确定这个变量会放在存储器中的哪个位置。

    使用 -c 命令行选项, GCC会编译并汇编该代码 生成 code.o

    gcc -O1 -c code.c
    
    • 1

    在这里插入图片描述

    如何找到程序的字节表示? 可以利用反汇编器,根据目标代码生成类似汇编代码的格式。 在Linux中 带 -d 命令行标志的程序
    OBJDUMP可以充当这个角色。

    在这里插入图片描述
    我们看到按照前面的字节顺序排列的17个十六进制字节值,它们分成了几组,每组有1~6个字节。每组都是一条指令,右边是等价的汇编语言。
其中一些关于机器代码和它的反汇编表示的特性值得注意:

    • IA32指令长度从1到 15个字节不等。常用的指令以及操作数较少的指令所需的字节数少,而那些不太常用或操作数较多的指令所需字节数较多。
·
    • 设计指令格式的方式是,从某个给定位置开始,可以将字节唯一地解码成机器指令。例如,只有指令 pushl %ebp是以字节值55开头的。
    • 反汇编器只是基于机器代码文件中的字节序列来确定汇编代码。它不需要访问程序的源代码或汇编代码。
    • 反汇编器使用的指令命名规则与GCC生成的汇编代码使用的有些细微的差别。在我们的示例中,它省略了很多指令结尾的’i’。这些后缀是大小指示符,在大多数情况下可以忽略。

    生成实际可执行的代码需要对一组目标代码文件运行链接器, 这一组目标代码文件必须有一个main函数, 定义一个main.c文件,里面有这样的函数:

    int main() {
       
      return sum(1, 3);
    }
    
    gcc -O1 -o prog code.o main.c
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    文件prog变成了9123个字节,因为它不仅包含两个过程的代码,还包含了用来启动和终止程序的信息,以及用来与操作系统交互的信息。我们也可以反汇编prog文件∶

    在这里插入图片描述

    这段代码与code.c反汇编产生的代码几乎完全一样。

    • 其中一个主要的区别是左边列出的地址不同——链接器将代码的地址移到一段不同的地址范围中。
    • 第二个不同之处在于链接器确定了存储全局变量 accum的地址。在code.o反汇编代码的第6行,accum的地址还是0。在prog 的反汇编代码中,地址就设成了0x804a0
  • 相关阅读:
    client-go实战之九:手写一个kubernetes的controller
    spring如何解决循环依赖
    【机器学习300问】60、图像分类任务中,训练数据不足会带来什么问题?如何缓解图像数据不足带来的问题?
    【SpringBoot+MyBatisPlus】利用线程特性与ThreadLocal来解决公共字段自动填充问题
    【前端】学习笔记1.JavaScript书写位置、注释、结束符、输入输出、字面量
    使用FastReport导出Excel文件
    大气污染扩散模型Calpuff建模、数据后处理及应用
    【Midjourney入门教程2】Midjourney的基础操作和设置
    树莓派raspberry pi 4 SSH默认密码无法登录解决办法
    艾美捷内毒素纯化树脂说明书
  • 原文地址:https://blog.csdn.net/chongzi_daima/article/details/125450416