当前位置: 首页 > news >正文

CS:APP学习笔记之程序的机器级表示(三) - Invinc

本文记录《深入理解计算机系统》第3版中第3章程序的机器级表示的一些知识点。


第3章 程序的机器级表示

程序编码

两种抽象

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

第一种是由指令集体系结构或指令集架构( lnstruction Set Architecture , ISA )来定义机器级程序的格式和行为,它定义了处理器状态、指令的格式,以及每条指令对状态的影响。大多数ISA ,包括 x86-64 ,将程序的行为描述成好像每条指令都是按顺序执行的,一条指令结束后,下一条再开始。处理器的硬件远比描述的精细复杂,它们并发地执行许多指令,但是可以采取措施保证整体行为与 ISA 指定的顺序执行的行为完全一致。

第二种抽象是,机器级程序使用的内存地址是虚拟地址,提供的内存模型看上去是一个非常大的字节数组。存储器系统的实际实现是将多个硬件存储器和操作系统软件组合起来。

处理器状态

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

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

编译和调试命令

# 编译c文件,编译选项-Og高速编译器使用会生成符合源代码结构的机器代码的优化等级
gcc -Og -o p p1.c p2.c# 产生汇编文件mstore.s
gcc -Og -S mstore.c# 反汇编,查看机器代码文件的内容
objdump -d mstore.o# 产生Intel格式的汇编文件mstore.s
gcc -Og -S -masm=intel mstore.c

数据格式

C语言数据类型在x86-64中的大小

如图所示,大多数 GCC 生成的汇编代码指令都有一个字符的后缀,表明操作数的大小。例如,数据传送指令有四个变种: movb (传送字节)、movw (传送字)、 movl(传送双字)和 movq (传送四字)。后缀l用来表示双字,因为 32 位数被看成是“长字 (long word) ”。注意,汇编代码也使用后缀l 来表示 4 字节整数和 8 字节双精度浮点数。这不会产生歧义,因为浮点数使用的是一组完全不同的指令和寄存器。

访问信息

整数寄存器

整数寄存器

操作数指示符

大多数指令有一个或多个操作数,指示出执行一个操作中要使用的源数据值,以及放置结果的目的位置。 x86-64 支持多种操作数格式。源数据值可以以常数形式给出,或是从寄存器内存中读出。结果可以存放在寄存器内存中。因此,各种不同的操作数的可能性被分为三种类型。

第一种类型是立即数 (immediate) ,用来表示常数值。

第二种类型是寄存器( register ),它表示某个寄存器的内容。

第三类操作数是内存引用,它会根据计算出来的地址(通常称为有效地址)访问某个内存位置。

操作数格式

数据传送指令

源操作数指定的值是一个立即数,存储在寄存器中或者内存中。目的操作数指定一个位置,一个寄存器或者是一个内存地址。 x86-64 加了一条限制,传送指令的两个操作数不能都指向内存位置。

简单数据传送指令

图3-5 和图 3-6 记录的是两类数据移动指令,在将较小的源值复制到较大的目的时使用。所有这些指令都把数据从源(在寄存器或内存中)复制到目的寄存器。 MOVZ 类中的指令把目的中剩余的字节填充为 0 ,而 MOVS 类中的指令通过符号扩展来填充,把源操作的最高位进行复制。可以观察到,每条指令名字的最后两个字符都是大小指示符:第一个字符指定源的大小,而第二个指明目的的大小。

零扩展和符号扩展数据传送指令

压入和弹出栈数据

入栈和出栈指令

在x86-64 中,程序栈存放在内存中某个区域。如下图所示,栈向下增长,这样一来,栈顶元素的地址是所有栈中元素地址中最低的。栈指针%rsp保存着栈顶元素的地址。

栈操作说明

算术和逻辑操作

x86-64的一些整数和逻辑操作可分为四组:加载有效地址、一元操作、二元操作和移位。

整数算术操作

加载有效地址

leaq 是 x86-64 汇编中一个非常重要且"独特"的指令。它的全称是 Load Effective Address to Register(将有效地址加载到寄存器)。


核心概念:它不访问内存!

这是理解 leaq 最关键的一点:leaq 计算一个内存地址,但它并不去读取这个地址里的数据!它只是将计算出的地址值本身存入寄存器。

基本语法

leaq SRC, DEST
  • SRC:一个内存操作数(寻址模式)
  • DEST:一个寄存器
  • 效果DEST = address_of(SRC)
特性 leaq mov(内存到寄存器)
主要功能 计算地址 从内存加载数据
是否访问内存 ❌ 否 ✅ 是
实际用途 1. 地址计算
2. 整数运算
3. 指针运算
数据传输

简单记忆:leaq 是"披着内存操作外衣的算术指令"——它穿着内存寻址的"衣服"(语法),但干的是地址计算或普通算术的"活儿"。

这个指令的巧妙之处在于硬件设计者提供了一个强大的算术单元(用于地址计算),而软件开发者发现可以"借用"这个单元来做通用的数学运算,从而获得了性能优势。

一元和二元操作

一元操作只有一个操作数,既是源又是目的。这个操作数可以是一个寄存器,也可以是一个内存位置。比如说,指令incq (%rsp)会使栈顶的 8 字节元素加 1 。这种语法让人想起 C 语言中的加 1 运算符(++)和减 1 运算符(--)。

二元操作中第二个操作数既是源又是目的。这种语法让人想起 C 语言中的赋值运算符,例如 x -=y 。例如,指令subq %rax, %rdx使寄存器%rdx的值减去%rax中的值。(将指令解读成从 %rdx 中减去打 %rax 会有所帮助。)第一个操作数可以是立即数、寄存器或是内存位置。第二个操作数可以是寄存器或是内存位置。注意,当第二个操作数为内存地址时,处理器必须从内存读出值,执行操作,再把结果写回内存。

移位操作

移位操作先给出移位量,然后第二项给出的是要移位的数。可以进行算术和逻辑右移。移位量可以是一个立即数,或者放在单字节寄存器%cl中。(这些指令很特别,因为只允许以这个特定的寄存器作为操作数

控制

条件码寄存器

除了整数寄存器, CPU 还维护着一组单个位的条件码 (condition code) 寄存器,它们描述了最近的算术或逻辑操作的属性。可以检测这些寄存器来执行条件分支指令。最常用的条件码有:

  • CF :进位标志。最近的操作使最高位产生了进位。可用来检查无符号操作的溢出。
  • ZF:零标志。最近的操作得出的结果为 0 。
  • SF :符号标志。最近的操作得到的结果为负数。
  • OF :溢出标志。最近的操作导致一个补码溢出一一一正溢出或负溢出。

比较和测试指令

SET指令

跳转指令

跳转指令

条件控制的汇编

C语言中if-else语句的通用形式模板如下:

if(test-expr)then-statement
elseelse-statement

对应的汇编实现通常会使用下面的形式,这里用C语法来描述控制流:

	t = test-expr;if(!t)goto false;then-statementgoto done;
false:else-statement
done:

条件传送的汇编

条件传送一般只用于分支语句比较简单的情况。

为了理解如何通过条件数据传输来实现条件操作,考虑下面的条件表达式和赋值的通用形式:

v = test-expr ? then-expr : else-expr;

用条件控制转移的标准方法来编译这个表达式会得到如下形式:

	if(!test-expr)goto false;v = then-statementgoto done;
false:v = else-statement
done:

这段代码包含两个代码序列:一个对then-expr求值,另一个对else-expr求值。

基于条件传送的代码,会对then-exprelse-expr都求值,最终值的选择基于对test-expr的求值。可以用下面的抽象代码描述:

vt = then-expr;
ve = else-expr;
t = test-expr;
if(!t) v = ve;

这个序列中的最后一条语句是用条件传送实现的--只有当测试条件t满足时,vt 的值才会被复制到v中。

do-while的汇编

do-while语句的通用形式如下:

dobody-statementwhile(test-expr);

这种通用形式可以被翻译成如下所示的条件和goto语句:

loop:body-statementt = test-expr;if(t)goto loop;

while的汇编

while语句的通用形式如下:

while(test-expr)body-statement

GCC在代码翻译中使用两种方法,跳转到中间(jump to middle)和 guarded-do。

jump to middle

用以下模板表示这种方法:

	goto test;
loop:body-statement
test:t = test-expr;if(t)goto loop;
guarded-do

首先用条件分支,如果初始条件不成立就过循环,把代码变换为 do-while 循环。当使用较高优化等级编译时,例如使用命令行O1, GCC 会采用这种策略。可以用如下模板来表达这种方法,把通用的 while 循环翻译成do-while循环:

t = test-expr;
if(!t)goto done;
dobody-statement;while(test-expr);
done:

翻译成goto代码如下:

t = test-expr;
if(!t)goto done;
loop:body-statementt = test-expr;if(t)goto loop;
done:

for循环的汇编

for循环的通用形式如下:

for(init-expr; test-expr; update-expr)body-statement

可将其转换为while循环:

init-expr;
while(test-expr){body-statementupdate-expr;
}

GCC为for循环翻译的汇编代码是while循环的两种翻译之一,这取决于优化的等级。

过程

什么是过程

不同编程语言中,过程的形式多样:函数、方法、子例程、处理函数等,但是它们都有一些共有特性。

要提供对过程的机器级支持,必须处理不同属性。为讨论方便,假设过程P调用过程Q,Q执行后返回到P。这些动作包括下面一个或多个机制:

传递控制。在进入过程Q时,程序计数器必须被设置为Q的代码的起始地址,然后在返回时,要把程序计数器设置为P中调用Q后面那条指令的地址。

传递数据。P必须能够向Q提供一个或多个参数,Q必须能够向P返回一个值。

分配和释放内存。在开始时,Q可能需要为局部变量分配空间,而在返回前,又必须释放这些存储空间。

运行时栈

通用的栈帧结构

在x86 - 64架构的System V ABI规范下,对于超过6个的函数参数,压栈时是先压后面的参数,局部变量也是先压后面的参数。

在机器级程序中将控制与数据结合起来

对抗缓冲区溢出攻击的方法

栈随机化

栈随机化的思想使得栈的位置在程序每次运行时都有变化

在 Linux 系统中,栈随机化已经变成了标准行为。它是更大的一类技术中的一种,这类技术称为地址空间布局随机化 (Address-Space Layout Randomization) ,或者简称 ASLR。采用 ASLR ,每次运行时程序的不同部分,包括程序代码、库代码、栈、全局变量和堆数据,都会被加载到内存的不同区域。这就意味着在一台机器上运行一个程序,与在其他机器上运行同样的程序,它们的地址映射大相径庭。这样才能够对抗一些形式的攻击。

栈破坏检测

最近的 GCC 版本在产生的代码中加入了一种栈保护者( stack protector) 机制,来检测缓冲区越界。其思想是在栈帧中任何局部缓冲区与栈状态之间存储一个特殊的金丝雀值(canary)。这个金丝雀值,也称为哨兵值(guard value) ,是在程序每次运行时随机产生的,因此,攻击者没有简单的办法能够知道它是什么。在恢复寄存器状态和从函数返回之前,程序检查这个金丝雀值是否被该函数的某个操作或者该函数调用的某个函数的某个操作改变了。如果是的,那么程序异常中止。

栈保护很好地防止了缓冲区溢出攻击破坏存储在程序栈上的状态。它只会带来很小的能损失,特别是因为 GCC 只在函数中有局部 char 类型缓冲区的时候才插人这样的代码。

限制可执行代码区域

限制哪些内存区域能够存放可执行代码。在典型的程序中,只有保存编译器产生的代码的那部分内存才需要是可执行的。其他部分可以被限制为只允许读和写。


http://www.hskmm.com/?act=detail&tid=28141

相关文章:

  • EHOME视频平台EasyCVR构建全协议、全场景融合的视频监控中枢
  • GA/T 1400视图库平台EasyCVR平台GB28181与1400级联方式全解析
  • 2025 年玻璃钢水箱生产厂家最新推荐榜单:含 30 吨 / 订做 / 消防 / 方形 / 拼装式 / 屋顶 / 大型产品,从产能与服务双维度精选优质企业
  • linux 修改本地时区
  • crontab 定时执行python脚本失败,但手动执行却成功问题处理 - hello-*
  • 2025 年不锈钢水箱厂家最新推荐榜:优质厂家实力对比与选购指南,助您选到适配设备矩形/屋顶/定做方形不锈钢水箱厂家推荐
  • 实用指南:Java 后端面试技术文档(参考)
  • 2025 年钢结构厂家最新推荐榜:优质企业全面解析,助力客户精准选择可靠合作伙伴
  • SQL server 关于“DATEDIFF() ”日期差值计算函数的用法
  • 2025 年清洗机厂家最新推荐:高压清洗机 / 超声波清洗机 / 管道清洗机等多类型设备品牌榜单,助力企业精准选购优质产品
  • AI元人文决策范式的思维逻辑演进研究
  • 2025规划馆运营厂家 TOP 榜:苏州金梓树智能科技,专注场馆全周期服务,规划馆运维优质服务商推荐!
  • 2025 高温线缆厂家 TOP 榜:奇温线缆 (上海) 有限公司,专注特种高温领域,定制化高温线缆源头厂家推荐!
  • 2025 年最新推荐 RTO 蓄热炉厂商榜单:聚焦高浓度 VOCs 处理设备,权威解读行业标杆企业优势有机废气处理/RTO 蓄热炉/RTO蓄热炉专业废气处理设备厂商推荐
  • 时变和时不变(LTI)的区别
  • 实用指南:Python Tkinter构建交互式精灵表切割桌面应用程序:将精灵表分割成单个帧的功能
  • 题解:qoj7979 棋盘
  • 氧化铝
  • 2025 最新不锈钢管厂家推荐排行榜 权威发布:304/316L/2205 等材质焊管无缝管优质企业精选
  • 2025 年最新推荐微波干燥设备生产厂家排行榜,覆盖多行业高效干燥解决方案权威推荐黄粉虫/黑水虻/中药材/茶叶微波干燥设备厂家推荐
  • 控制台
  • 2025 年高强钢板厂家最新推荐排行榜:聚焦国内优质企业,助力采购者精准选品的权威榜单合金/HG785D/Q690D/S960QL/700L高强钢板厂家推荐
  • (数论大杂烩)古代猪文
  • 滥用ACL权限覆盖其他用户S3存储桶中的文件/视频
  • 2025 年最新三维扫描仪厂家权威排行榜:聚焦高精度与多场景适配,为企业与个人用户精选优质品牌推荐高精度/专业/手持激光/工业/便携式三维扫描仪厂家推荐
  • 后端基础-输入/输出件
  • 2025 年净化工程服务商最新权威推荐排行榜:医院净化工程 / 制药厂 / 化工厂 / 实验室 / 无尘车间优选净化工程设计安装施工公司
  • 2025 年最新推荐!国内优质充电桩厂家排行榜,涵盖多场景适配产品,助用户精准选靠谱品牌智能/新能源/电动车/重卡/电动车直流充电桩厂家推荐
  • 实用指南:【图像算法 - 28】基于YOLO与PyQt5的多路智能目标检测系统设计与实现
  • KingView 组态王 6.5下载地址与安装教程