笔记
学习最新前沿软件逆向安全技术、游戏安全辅助脚本技术,B 站 小迪 xiaodi 老师
微信 i-xiaodi ,备用微信Binary_uncle_
作业:看完视频独立把实验程序练习完,分析流程
处理浮点数的运算
简单了解,不强制记忆,忘了就查笔记
1. FPU(Floating Point Unit,浮点运算单元)
- 定义: FPU 是处理器中的一个硬件模块,专门负责浮点运算。
- 功能: 处理浮点数的加减乘除、平方根、三角函数等复杂操作。
- 实现:
-
- 在早期 x86 处理器中,FPU 是一个独立的协处理器(如 Intel 8087)。
- 从 80486 开始,FPU 集成进了 CPU 内核中。
FPU 的组成
- 寄存器堆栈:
-
- 8 个 80 位宽的浮点寄存器(ST0-ST7),以堆栈形式组织。
- 通过
push
和pop
模式操作寄存器。
- 状态寄存器: 用于保存堆栈状态(如堆栈深度)和计算标志(如 C0-C3)。
- 控制寄存器: 配置 FPU 的工作模式(如精度、舍入模式等)。
2.x87 指令集
- 定义: x87 是专门为 FPU 设计的一套指令集,早期专用于处理浮点运算。
- 特点:
-
- 操作对象是 FPU 的堆栈寄存器(ST0-ST7)。
- 指令风格通常是
Fxxx
开头,比如FLD
(加载浮点数)、FADD
(浮点加法)。 - 支持扩展精度(80 位),比 SSE 的单精度和双精度浮点数更高。
- 代表指令:
-
FLD(load)
/FST
: 加载/存储浮点数。FADD
/FSUB
: 浮点加法/减法。FMUL
/FDIV
: 浮点乘法/除法。FCOM
/FUCOM
: 比较浮点数。
x87 的适用场景
- 高精度计算(如科学计算)。
- 需要 80 位扩展精度支持的场景。
3. SSE(Streaming SIMD Extensions)
- 定义: SSE 是 Intel 在 x86 处理器上引入的一套 SIMD(单指令多数据)扩展指令集。
- 特点:
-
- 采用 xmm 寄存器(每个 128 位宽)。
- 支持并行处理多个单精度(32 位)或双精度(64 位)浮点数。
- 指令风格是
xxxPS
(处理单精度矢量)或xxxSD
(处理双精度标量)。
- 优势:
-
- 更快的浮点计算(无需堆栈操作)。
- 并行处理多个浮点数,提高性能。
SSE 的适用场景
- 并行计算(如多媒体、游戏、音视频处理)。
- 性能要求较高但不需要扩展精度的场景。
总结关系
- FPU 是硬件模块,x87 是它的指令集:
-
- FPU 是处理器的物理单元。
- x87 是针对 FPU 提供的指令集。
- SSE 是独立于 FPU 的 SIMD 指令集:
-
- SSE 使用的是独立的
xmm
寄存器。 - 它不依赖 FPU,也不与
x87
堆栈共享资源。
- SSE 使用的是独立的
1. 浮点堆栈概念
x86 浮点指令通常操作的是浮点单元(FPU)的寄存器堆栈。
FPU(Floating Point Unit,浮点运算单元):
是计算机处理器中的一种专门用于处理浮点数运算的硬件模块。它主要负责执行涉及小数或非整数值的数学计算,例如加减乘除、平方根、三角函数等复杂操作。
- FPU 使用一个 8 级的堆栈架构(
ST(0)
到ST(7)
)。 - 堆栈顶通常表示为
ST(0)
,是 FPU 操作的默认目标。
在 x32dbg 中,FPU 浮点运算的反汇编指令 如下所示:
实验程序说明:3.1 + 4.3 单浮点小数
1. 浮点加载指令 (FLD)
FLD
指令的全称是 “Floating-Point Load”
常见情形:
- 从内存加载到堆栈顶:
FLD DWORD PTR ss:[ebp-4] ; 加载单精度浮点数
FLD QWORD PTR ss:[ebp-8] ; 加载双精度浮点数
FLD TBYTE PTR ss:[ebp-0C] ; 加载 10 字节扩展精度浮点数
- 从寄存器加载(堆栈的相对位置):
FLD ST(1) ; 将 ST(1) 的值复制到 ST(0)
- 反汇编形式补充
ST(0)
:
-
- x32dbg 可能显示为:
FLD ST(0), DWORD PTR ss:[ebp-4]
实际行为是 FLD DWORD PTR ss:[ebp-4]
,只是一种伪表示。
为什么存进去数据不一样了?
这是因为 FPU 的内部扩展机制
0x40466666
的含义:
-
- 这个值是按照 IEEE 754 单精度格式编码的。
- 它的二进制表示为:
符号位:0
指数部分:10000000 (偏移量 127,对应指数为 2)
尾数部分:0.10011001100110011001100
解码为:
FPU 的内部存储格式:
FLD
指令会将单精度浮点数转换为 80 位扩展精度格式(内部格式)。- 在扩展精度格式中,
3.1
的值表示为:
符号位:0
指数部分:16384 (偏移量 16383,对应指数为 2)
尾数部分:1.C6666666...
转换结果是:
这是扩展精度的表示方式。
mov dword ptr ss:[ebp-0x4], 0x40466666
将 IEEE 754 单精度浮点值3.1
的二进制表示存入内存。- 使用
FLD
加载这个值后,FPU 会将其扩展为 80 位浮点格式。 - 因此,
ST(0)
的值看似与0x40466666
不一致,但它实际上表示的是相同的浮点数3.1
。
2. 浮点存储指令 (FST/FSTP)
FST
:“Floating-Point Store”
- 功能:将浮点堆栈顶部(
ST(0)
)的值存储到指定的内存位置或寄存器中。 - 操作:只存储堆栈顶部的浮点数,并保持该值在堆栈顶部。
FSTP
:“Floating-Point Store and Pop”
- 功能:将浮点堆栈顶部(
ST(0)
)的值存储到指定的内存位置或寄存器中,并且弹出堆栈顶部的值,即将堆栈指针移动到下一个值。 - 操作:存储堆栈顶部的浮点数,并将堆栈指针向下移动,弹出该值。
常见情形:
- 将堆栈顶的值存储到内存:
FST DWORD PTR ss:[ebp-4] ; 将 ST(0) 的值存储为单精度浮点数
FSTP QWORD PTR ss:[ebp-8] ; 将 ST(0) 的值存储为双精度浮点数,并弹出堆栈
FSTP TBYTE PTR ss:[ebp-0C] ; 将 ST(0) 的值存储为扩展精度浮点数,并弹出堆栈
- 将堆栈顶的值存储到其他堆栈寄存器:
FST ST(1) ; 将 ST(0) 的值复制到 ST(1)
FSTP ST(2) ; 将 ST(0) 的值复制到 ST(2),并弹出堆栈
3. 浮点算术运算
常见情形:
- 基本运算:加减乘除
FADD DWORD PTR ss:[ebp-4] ; 将 [ebp-4] 加到 ST(0)
FSUB QWORD PTR ss:[ebp-8] ; 将 [ebp-8] 减去 ST(0)
FMUL ST(0), ST(1) ; 将 ST(0) 与 ST(1) 相乘
FDIV ST(1), ST(0) ; 将 ST(1) 除以 ST(0)
- 反汇编伪表示:
-
- x32dbg 有时将隐式堆栈寄存器显式显示:
FADD ST(0), QWORD PTR ss:[ebp-8]
4. 浮点比较
常见情形:
- 直接比较:
FCOM DWORD PTR ss:[ebp-4] ; 比较 ST(0) 与 [ebp-4]
FCOM ST(1) ; 比较 ST(0) 与 ST(1)
- 带弹出的比较:
FCOMP DWORD PTR ss:[ebp-4] ; 比较后弹出 ST(0)
FCOMP ST(1) ; 比较 ST(0) 和 ST(1),然后弹出 ST(0)
FSTSW
指令
功能:
FSTSW
用于将 FPU 的状态字(FPU Status Word
)复制到通用寄存器(AX)或内存中。
语法:
AX: 将状态字存储到通用寄存器 AX 中。
[mem]: 将状态字存储到内存地址 [mem]。
常见用法:
获取状态字到 AX:
FSTSW AX
将 FPU 状态字复制到通用寄存器 AX。
获取状态字到内存:
FSTSW [mem]
将状态字存储到内存地址 [mem]。
结合 FCOMP 使用: 一般与 FCOMP 一起使用,完成浮点数比较并获取比较结果:
FCOMP QWORD PTR [mem] ; 比较 ST(0) 和内存中的双精度数
FSTSW AX ; 将状态字加载到 AX
SAHF ; 将状态字的高 8 位加载到 FLAGS 寄存器
JZ equal_label ; 根据 ZF 判断是否相等
5. 浮点堆栈控制
常见情形:
- 交换堆栈寄存器:
FXCH ST(1) ; 交换 ST(0) 和 ST(1)
- 清除堆栈:
FINIT ; 初始化 FPU 状态,清空堆栈
6. 特殊指令
常见情形:
- 正弦/余弦/平方根:
FSQRT ; 计算 ST(0) 的平方根
FSIN ; 计算 ST(0) 的正弦值
FCOS ; 计算 ST(0) 的余弦值
- 加载常量:
FLD1 ; 加载 1.0 到 ST(0)
FLDZ ; 加载 0.0 到 ST(0)
fldpi ; 加载圆周率 π
- 取反:
FCHS ; 将 ST(0) 取反
扩展 SSE 指令集<———————–
在 x86 汇编中,xmm
寄存器属于 SSE(Streaming SIMD Extensions)指令集的一部分,它们用于处理矢量和标量浮点运算,支持更高效的并行计算。与 x87 的堆栈式 FPU 不同,SSE 使用寄存器直接寻址,寄存器名字如 xmm0
、xmm1
等。以下是对常见 xmm
寄存器操作指令及用法的详细讲解。
xmm
寄存器简介
- 每个
xmm
寄存器大小为 128 位,可以存储以下内容:
-
- 4 个单精度浮点数(32 位)
- 2 个双精度浮点数(64 位)
- 16 个字节(8 位)数据
- 其他整数或逻辑数据格式
- 常见指令可以操作标量(单个浮点数)或矢量(多个浮点数)。
常见指令及用法
1. MOVAPS
/ MOVUPS
功能: 将数据在内存和 xmm
寄存器之间传输。
指令 |
描述 |
|
复制对齐的单精度浮点数向量数据到寄存器 |
|
复制未对齐的单精度浮点数向量数据到寄存器 |
|
从对齐的内存地址加载单精度浮点数数据到 |
|
从未对齐的内存地址加载单精度浮点数数据到 |
- 对齐与未对齐:
-
- 对齐(
MOVAPS
): 数据必须位于内存地址的 16 字节边界上,否则会导致崩溃。 - 未对齐(
MOVUPS
): 无需满足 16 字节对齐,但性能稍差。
- 对齐(
什么是内存对齐?
- 对齐(Aligned): 数据的地址是其大小的倍数。例如:
-
- 4 字节的数据存储在地址是 4 的倍数的地方(如 0x1004)。
- 16 字节(128 位)的 SSE 寄存器数据存储在地址是 16 的倍数的地方(如 0x1010)。
- 未对齐(Unaligned): 数据的地址不是其大小的倍数。例如:
-
- 4 字节的数据存储在地址 0x1005。
- 16 字节的数据存储在地址 0x1013。
例子:
MOVAPS xmm0, [ebp-0x10] ; 加载 4 个对齐的浮点数到 xmm0。
MOVUPS xmm1, [ebp-0x20] ; 加载未对齐的浮点数到 xmm1。
MOVAPS [ebp-0x30], xmm0 ; 存储 xmm0 的数据到对齐的内存地址。
2. ADDPS
/ ADDSD
功能: 执行浮点加法。
ADDPS
: 对矢量(多个单精度浮点数)执行并行加法。ADDSD
: 对标量(单个双精度浮点数)执行加法。
指令 |
描述 |
|
对 |
|
将 |
例子:
结果:
xmm0[0] = xmm0[0] + xmm1[0] // 第1个元素相加
xmm0[1] = xmm0[1] + xmm1[1] // 第2个元素相加
xmm0[2] = xmm0[2] + xmm1[2] // 第3个元素相加
xmm0[3] = xmm0[3] + xmm1[3] // 第4个元素相加
计算后结果存储在 xmm0
中。
ADDPS xmm0, xmm1 ; xmm0[i] += xmm1[i], i = 0..3
ADDSD xmm0, xmm1 ; xmm0[0] += xmm1[0], 仅操作第一个双精度数
3. MULPS
/ MULSD
功能: 执行浮点乘法。
MULPS
: 对矢量进行并行乘法。MULSD
: 对标量进行单一乘法。
指令 |
描述 |
|
对 |
|
将 |
例子:
MULPS xmm0, xmm1 ; xmm0[i] *= xmm1[i], i = 0..3
MULSD xmm0, xmm1 ; xmm0[0] *= xmm1[0], 仅操作第一个双精度数
4. DIVPS
/ DIVSD
功能: 执行浮点除法。
DIVPS
: 对矢量进行并行除法。DIVSD
: 对标量进行单一除法。
指令 |
描述 |
|
对 |
|
将 |
5. CMPPS
/ CMPSD
功能: 比较浮点数。
CMPPS
: 对矢量进行并行比较。CMPSD
: 对标量进行单一比较。
指令 |
描述 |
|
按 |
|
按 |
imm8
是比较条件,例如:
-
- 0: 等于(
EQ
)。 - 1: 小于(
LT
)。 - 2: 小于等于(
LE
)。
- 0: 等于(
6. SQRTPS
/ SQRTSD
功能: 计算平方根。
SQRTPS
: 对矢量计算平方根。SQRTSD
: 对标量计算平方根。
指令 |
描述 |
|
计算 |
|
计算 |
7. MINPS
/ MAXPS
功能: 计算最小值或最大值。
MINPS
: 对矢量计算逐元素最小值。MAXPS
: 对矢量计算逐元素最大值。
x32dbg 中查看 xmm 寄存器
在 x32dbg 中,你可以通过以下步骤查看 xmm
寄存器的值:
- 在调试过程中,单步执行到包含
xmm
操作的指令。 - 打开寄存器窗口(通常默认显示在调试界面的右侧)。
- 找到
xmm0
~xmm15
的寄存器值。
- 32 位模式(x86):
- SSE 寄存器:XMM0-XMM7。
- 64 位模式(x64):
- 扩展 SSE 寄存器:XMM0-XMM15
- 观察每个寄存器的 128 位数据,通常以 16 字节的十六进制显示。
简单课外了解:AVX(Advanced Vector Extensions)指令集
以下是 YMM 寄存器常用的 AVX 指令,分为不同的类别列出,方便理解和参考:
1. 数据加载和存储
这些指令用于将数据加载到 YMM 寄存器或从 YMM 寄存器存储到内存。
指令 |
描述 |
VMOVAPS |
加载/存储对齐的 256 位单精度浮点数到 YMM。 |
VMOVUPS |
加载/存储未对齐的 256 位单精度浮点数到 YMM。 |
VMOVAPD |
加载/存储对齐的 256 位双精度浮点数到 YMM。 |
VMOVUPD |
加载/存储未对齐的 256 位双精度浮点数到 YMM。 |
VMOVDQA |
加载/存储对齐的 256 位整数数据到 YMM。 |
VMOVDQU |
加载/存储未对齐的 256 位整数数据到 YMM。 |
VBROADCASTSS |
将内存中的单个单精度浮点数广播到 YMM 的所有元素。 |
VBROADCASTSD |
将内存中的单个双精度浮点数广播到 YMM 的所有元素。 |
2. 算术运算
AVX 支持对 YMM 寄存器中的向量进行算术运算。
单精度浮点运算(32 位 × 8)
指令 |
描述 |
VADDPS |
对两个 YMM 寄存器中的 8 个单精度浮点数逐元素相加。 |
VSUBPS |
对两个 YMM 寄存器中的 8 个单精度浮点数逐元素相减。 |
VMULPS |
对两个 YMM 寄存器中的 8 个单精度浮点数逐元素相乘。 |
VDIVPS |
对两个 YMM 寄存器中的 8 个单精度浮点数逐元素相除。 |
VSQRTPS |
计算 YMM 寄存器中的 8 个单精度浮点数的平方根。 |
双精度浮点运算(64 位 × 4)
指令 |
描述 |
VADDPD |
对两个 YMM 寄存器中的 4 个双精度浮点数逐元素相加。 |
VSUBPD |
对两个 YMM 寄存器中的 4 个双精度浮点数逐元素相减。 |
VMULPD |
对两个 YMM 寄存器中的 4 个双精度浮点数逐元素相乘。 |
VDIVPD |
对两个 YMM 寄存器中的 4 个双精度浮点数逐元素相除。 |
VSQRTPD |
计算 YMM 寄存器中的 4 个双精度浮点数的平方根。 |
整数运算
指令 |
描述 |
VPADDD |
对两个 YMM 寄存器中的 8 个 32 位整数逐元素相加。 |
VPSUBD |
对两个 YMM 寄存器中的 8 个 32 位整数逐元素相减。 |
VPADDQ |
对两个 YMM 寄存器中的 4 个 64 位整数逐元素相加。 |
VPSUBQ |
对两个 YMM 寄存器中的 4 个 64 位整数逐元素相减。 |
3. 逻辑运算
这些指令执行按位逻辑操作。
指令 |
描述 |
VANDPS |
对两个 YMM 寄存器中的 8 个单精度浮点数按位与。 |
VORPS |
对两个 YMM 寄存器中的 8 个单精度浮点数按位或。 |
VXORPS |
对两个 YMM 寄存器中的 8 个单精度浮点数按位异或。 |
VANDPD |
对两个 YMM 寄存器中的 4 个双精度浮点数按位与。 |
VORPD |
对两个 YMM 寄存器中的 4 个双精度浮点数按位或。 |
VXORPD |
对两个 YMM 寄存器中的 4 个双精度浮点数按位异或。 |
4. 比较操作
比较指令可以用来逐元素比较向量中的值。
指令 |
描述 |
VCMPPS |
对两个 YMM 寄存器中的 8 个单精度浮点数逐元素比较。 |
VCMPPD |
对两个 YMM 寄存器中的 4 个双精度浮点数逐元素比较。 |
VPCMPGTD |
对两个 YMM 寄存器中的 8 个有符号整数逐元素比较。 |
VPCMPGTQ |
对两个 YMM 寄存器中的 4 个有符号整数逐元素比较。 |
5. 数据混合与选择
用于对数据向量进行混合或选择操作。
指令 |
描述 |
VBLENDPS |
按掩码从两个 YMM 寄存器中混合单精度浮点数。 |
VBLENDPD |
按掩码从两个 YMM 寄存器中混合双精度浮点数。 |
VSHUFPS |
按模式选择并混合单精度浮点数。 |
VSHUFPD |
按模式选择并混合双精度浮点数。 |
6. 数据转换
用于类型转换或压缩、扩展数据。
指令 |
描述 |
VCVTDQ2PS |
将 YMM 中的 8 个 32 位整数转换为单精度浮点数。 |
VCVTPS2DQ |
将 YMM 中的 8 个单精度浮点数转换为 32 位整数。 |
VCVTPS2PD |
将 8 个单精度浮点数转换为 4 个双精度浮点数。 |
VCVTPD2PS |
将 4 个双精度浮点数转换为 8 个单精度浮点数。 |
常见的 YMM 寄存器 AVX 指令用法
1. 数据加载与存储
用于从内存加载数据到 YMM 寄存器,或将寄存器中的数据存储回内存。
; 加载对齐数据
VMOVAPS YMM0, [dataAligned]
; 存储对齐数据
VMOVAPS [resultAligned], YMM0
; 加载未对齐数据
VMOVUPS YMM1, [dataUnaligned]
; 存储未对齐数据
VMOVUPS [resultUnaligned], YMM1
; 广播单精度值到所有元素
VBROADCASTSS YMM2, [singleValue]
; 广播双精度值到所有元素
VBROADCASTSD YMM3, [doubleValue]
2. 算术运算
YMM 寄存器中每 32 位(单精度浮点数)或 64 位(双精度浮点数)逐元素运算。
单精度浮点运算(8 个元素)
; 加法:YMM0 = YMM1 + YMM2
VADDPS YMM0, YMM1, YMM2
; 减法:YMM0 = YMM1 - YMM2
VSUBPS YMM0, YMM1, YMM2
; 乘法:YMM0 = YMM1 * YMM2
VMULPS YMM0, YMM1, YMM2
; 除法:YMM0 = YMM1 / YMM2
VDIVPS YMM0, YMM1, YMM2
双精度浮点运算(4 个元素)
; 加法:YMM0 = YMM1 + YMM2
VADDPD YMM0, YMM1, YMM2
; 减法:YMM0 = YMM1 - YMM2
VSUBPD YMM0, YMM1, YMM2
; 乘法:YMM0 = YMM1 * YMM2
VMULPD YMM0, YMM1, YMM2
; 平方根:YMM0 = sqrt(YMM1)
VSQRTPD YMM0, YMM1
3. 逻辑运算
对 YMM 寄存器中的每一位进行逻辑操作。
; 按位与:YMM0 = YMM1 & YMM2
VANDPS YMM0, YMM1, YMM2
; 按位或:YMM0 = YMM1 | YMM2
VORPS YMM0, YMM1, YMM2
; 按位异或:YMM0 = YMM1 ^ YMM2
VXORPS YMM0, YMM1, YMM2
; 按位与非:YMM0 = YMM1 & ~YMM2
VANDNPS YMM0, YMM1, YMM2
4. 比较运算
比较两个寄存器的数据,结果存储在目标寄存器中。
; 比较是否大于:YMM0 = (YMM1 > YMM2) ? 0xFFFFFFFF : 0x0
VCMPGTPS YMM0, YMM1, YMM2
; 比较是否小于:YMM0 = (YMM1 < YMM2) ? 0xFFFFFFFF : 0x0
VCMPLTPS YMM0, YMM1, YMM2
; 比较是否相等:YMM0 = (YMM1 == YMM2) ? 0xFFFFFFFF : 0x0
VCMPEQPS YMM0, YMM1, YMM2
5. 数据混合与重组
用于从多个寄存器中混合、选择或重新排列数据。
; 按掩码混合单精度浮点数:YMM0 = blend(YMM1, YMM2, mask)
VBLENDPS YMM0, YMM1, YMM2, 0b11001100
; 按模式混合单精度浮点数
VSHUFPS YMM0, YMM1, YMM2, 0b10110001
; 从两个寄存器中打包数据
VUNPCKLPS YMM0, YMM1, YMM2
VUNPCKHPS YMM0, YMM1, YMM2
6. 数据转换
将数据从一种类型转换为另一种类型。
; 整数转换为单精度浮点数
VCVTDQ2PS YMM0, YMM1
; 单精度浮点数转换为整数
VCVTPS2DQ YMM0, YMM1
; 单精度浮点数转双精度浮点数
VCVTPS2PD YMM0, XMM1
; 双精度浮点数转单精度浮点数
VCVTPD2PS YMM0, YMM1
7. 数据广播
广播内存中的单一值到整个寄存器。
; 单精度浮点数广播
VBROADCASTSS YMM0, [mem]
; 双精度浮点数广播
VBROADCASTSD YMM0, [mem]
8. 示例程序
将两个 8 元单精度浮点数组相加并存储结果:
section .data
array1: dd 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0
array2: dd 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0
result: times 8 dd 0.0
section .text
global _start
_start:
; 加载数据到寄存器
VMOVUPS YMM0, [array1]
VMOVUPS YMM1, [array2]
; 执行逐元素相加
VADDPS YMM0, YMM0, YMM1
; 存储结果到内存
VMOVUPS [result], YMM0
; 结束
ret
9. 小结
以上是 YMM 寄存器操作中最常见的指令,涵盖数据加载与存储、算术运算、逻辑运算、比较、混合、重组和数据类型转换等场景。通过这些指令,可以充分利用 AVX 指令集来优化 SIMD 运算。
没有回复内容