找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1898

积分

0

好友

257

主题
发表于 昨天 09:44 | 查看: 6| 回复: 0

1 汇编语言和本地代码是一一对应的

计算机 CPU 能直接解释运行的只有本地代码(也称为机器语言)程序。用高级语言编写的源代码,必须通过编译器编译,才能转换成本地代码。

通过研究本地代码的内容,我们能了解程序最终是以何种形式运行的。但如果直接打开本地代码文件,看到的只是一堆难以理解的数值罗列。

为了解决这个问题,人们想出了为本地代码附上表示其功能的英语单词缩写。例如,在加法运算的本地代码旁加上add(addition的缩写),在比较运算旁加上cmp(compare的缩写)。这些缩写被称为助记符,而使用助记符的编程语言就称为汇编语言

即便如此,用汇编语言编写的源代码最终也必须转换成本地代码才能运行。负责这项转换工作的程序称为汇编器,转换过程本身称为汇编。反过来,本地代码也可以转换成汇编语言的源代码。具备这种逆转换功能的程序称为反汇编程序,逆转换过程称为反汇编

汇编源代码与对应本地代码对比

2 通过编译器输出汇编语言的源代码

获取汇编语言源代码,除了对本地代码进行反汇编,还有另一种常用方法。大多数 C 语言编译器都支持将 C 源代码直接转换成汇编语言源代码,而不是一步到位生成本地代码。这对于学习计算机基础知识,理解高级语言如何映射到底层操作非常有帮助。

C语言函数源代码示例

下面是通过编译器生成的汇编语言源代码片段(部分省略,彩色部分是转换成的 C 语言注释)。

C语言代码对应的汇编语言源代码1

C语言代码对应的汇编语言源代码2

3 不会转换成本地代码的伪指令

汇编语言的源代码由两部分构成:一是可以转换成本地代码的指令(即操作码),二是针对汇编器的伪指令

伪指令的作用是将程序的构造及汇编方法指示给汇编器,但它本身不会被汇编成本地代码。

例如,下面是从示例代码中摘出的伪指令部分:

汇编语言中的段定义伪指令

汇编语言中的过程定义伪指令

由伪指令 segmentends 围起来的部分,被称为段定义。它给构成程序的命令或数据的集合体加上一个名字。

  • _TEXT 是指令的段定义。
  • _DATA 是已初始化(有初始值)的数据的段定义。
  • _BSS 是尚未初始化数据的段定义。
  • group 伪指令表示将 _BSS_DATA 这两个段定义汇总为名为 DGROUP 的组。
  • _AddNum proc_AddNum endp 围起来的部分,以及 _MyFunc proc_MyFunc endp 围起来的部分,分别表示 AddNum 函数和 MyFunc 函数的范围。用伪指令 procendp 围起来的部分表示一个过程(procedure)的范围。

4 汇编语言的语法是“操作码 + 操作数”

在汇编语言中,一行通常对应 CPU 的一个指令。其语法结构是操作码 + 操作数(也存在只有操作码的指令)。操作码表示指令要执行的动作,而操作数则表示指令操作的对象。

常见汇编操作码功能表

本地代码必须加载到内存中才能运行。内存里存储着构成本地代码的指令和数据。程序运行时,CPU 会从内存中读取指令和数据,然后将它们存储在 CPU 内部的寄存器中进行处理。

CPU与内存交互示意图

寄存器是 CPU 中的存储区域,但它不仅有存储功能,还具备运算能力。在汇编语言中,操作数通过名称来指定寄存器。这与内存用地址编号来区分不同,CPU 内的寄存器是用 eaxebx 这类名称来区分的

x86 CPU主要寄存器功能表

5 最常用的 mov 指令

指令中最常用的当属 mov 指令,它负责在寄存器和内存之间移动数据。mov 指令的两个操作数,分别指定数据的存储目的地读取源

操作数可以指定为寄存器、常数、标签(附加在地址前),或者用方括号 [] 括起来的上述内容。如果指定了没用方括号括起来的内容,表示直接处理该值;如果指定了用方括号括起来的内容,则方括号内的值会被解释为内存地址,进而对该地址的值进行读写。

mov指令操作数示例

mov ebp, esp 中,esp 寄存器中的值被直接存储到 ebp 寄存器中。如果 esp 的值是 100,那么 ebp 的值也会变成 100。
而在 mov eax, dword ptr [ebp+8] 中,ebp 的值加上 8 后会被解释为内存地址。如果 ebp 的值是 100,那么 eax 寄存器将存储内存地址 108 处的数据。dword ptr(double word pointer)表示从指定内存地址读取 4 字节的数据。

6 对栈进行 push 和 pop

程序运行时,会在内存上分配一个称为的数据空间。正如其名,数据存储时从内存的高地址向低地址堆积(即“向下生长”),读取时则按照从低地址到高地址的顺序进行。

栈的Push和Pop操作示意图

栈是存储临时数据的区域,通过 push 指令和 pop 指令进行数据的存入和读出。 存入数据称为“入栈”,读出数据称为“出栈”。在 32 位 x86 系列 CPU 中,一次 pushpop 可以处理 32 位(4 字节)的数据。

栈的读写地址由 esp 寄存器(栈指针)管理。 pushpop 指令执行后,esp 寄存器的值会自动更新(push 时减 4,pop 时加 4),因此程序员无需手动指定内存地址。

7 函数调用机制

在高级语言中,函数调用看起来很简单,但在汇编层面,它涉及一系列精心设计的栈操作。以下是函数调用的汇编语言代码示例。

函数调用的汇编语言代码

整个过程伴随着栈空间的动态变化,下图展示了关键节点的栈状态。

函数调用过程中的栈状态变化

8 函数内部的处理

函数被调用后,其内部同样需要处理参数、局部变量并返回结果。下面的代码展示了一个加法函数内部的汇编实现。

函数内部的汇编处理代码

这印证了一个重要约定:“函数的参数通过栈传递,而返回值通常通过寄存器(如 eax)返回。”

函数内部执行时,栈空间会进一步被用于保存现场和分配局部变量,其状态变化如下图所示。

AddNum函数内部的栈状态变化

9 始终确保全局变量用的内存空间

在 C 语言中,在函数外部定义的变量称为全局变量,在函数内部定义的变量称为局部变量。了解它们在汇编层面的实现,是深入理解内存管理的关键。

使用全局变量和局部变量的C语言源代码

C语言源代码续

将上述 C 代码转换成汇编语言后,全局变量的定义和初始化会体现在特定的段中。

全局变量在汇编中的段定义

而函数中对这些变量的操作,则转换为对固定内存地址的读写指令。如果你对这类底层交互的细节感兴趣,可以在云栈社区找到更多深入的讨论和资源。

函数中操作全局变量的汇编代码

以下是上述及后续代码中出现的一些汇编指令功能说明。

汇编语言指令功能表1

汇编语言指令功能表2

10 临时确保局部变量用的内存空间

与全局变量在程序启动时就分配好内存不同,局部变量的内存空间是临时的,通常在函数被调用时在栈上分配,函数返回时释放。下图清晰地展示了这个过程。

栈空间用于局部变量的分配与释放

通过调整栈指针 esp,函数为自己预留出一块空间。这块空间随后被分割,用于存放各个局部变量。

栈内存空间分割用于局部变量

11 循环处理的实现方法

高级语言中的循环结构,在汇编层面是通过标签跳转和条件判断指令组合实现的。例如,一个简单的 for 循环会被转换成以下形式。

循环处理的C源代码及对应汇编代码

12 条件分支的实现方法

同样,if-else 这类条件分支语句,在汇编中依赖 cmp(比较)指令和一系列条件跳转指令(如 jlejgejmp)来完成逻辑判断和流程控制。这属于后端其他语言实现中常见的控制流转换模式。

条件分支的C语言源代码

下面的汇编代码展示了条件分支的具体实现方式。

条件分支的汇编语言实现1

条件分支的汇编语言实现2




上一篇:政企网络安全架构解析:防火墙、IDS、IPS与零信任的分层防御逻辑
下一篇:信息收集常见误区:90%渗透测试员过度依赖自动化工具
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 18:03 , Processed in 0.241142 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表