【汇编】段寄存器相关
概述:寄存器相关整理
[!TIP] 相关参考
参考文章:参考书籍:
- 《INTEL 开发手册卷 3》
相关教程
- [_010 代码跨段跳转实验_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV11ktWezE9c?vd_source = 2d8da596010444663e6f18397a9e3505&spm_id_from = 333.788.player.switch&p = 10)
[toc]
寄存器的易失性和保存方式
下表说明了每种寄存器在整个函数调用过程中的使用方法:
展开表
| 注册 | 状态 | 使用 |
|---|---|---|
| RAX | 易失的 | 返回值寄存器 |
| RCX | 易失的 | 第一个整型自变量 |
| RDX | 易失的 | 第二个整型自变量 |
| R8 | 易失的 | 第三个整型自变量 |
| R9 | 易失的 | 第四个整型自变量 |
| R10: R11 | 易失的 | 必须根据需要由调用方保留;在 syscall/sysret 指令中使用 |
| R12: R15 | 非易失的 | 必须由被调用方保留 |
| RDI | 非易失的 | 必须由被调用方保留 |
| RSI | 非易失的 | 必须由被调用方保留 |
| RBX | 非易失的 | 必须由被调用方保留 |
| RBP | 非易失的 | 可用作帧指针;必须由被调用方保留 |
| RSP | 非易失的 | 堆栈指针 |
| XMM0、YMM0 | 易失的 | 第一个 FP 参数;使用 __vectorcall 时的第一个矢量类型参数 |
| XMM1、YMM1 | 易失的 | 第二个 FP 参数;使用 __vectorcall 时的第二个矢量类型参数 |
| XMM2、YMM2 | 易失的 | 第三个 FP 参数;使用 __vectorcall 时的第三个矢量类型参数 |
| XMM3、YMM3 | 易失的 | 第四个 FP 参数;使用 __vectorcall 时的第四个矢量类型参数 |
| XMM4、YMM4 | 易失的 | 必须根据调用方的需要保留;使用 __vectorcall 时的第五个矢量类型参数 |
| XMM5、YMM5 | 易失的 | 必须根据调用方的需要保留;使用 __vectorcall 时的第六个矢量类型参数 |
| XMM6: XMM15、YMM6: YMM15 | 非易失的 (XMM),易失的(YMM 的上半部分) | 必须由被调用方保留。 YMM 寄存器必须根据需要由调用方保留。 |
当函数进入和退出 C 运行时库调用和 Windows 系统调用时,CPU 标志寄存器的方向位标志将被清除。
段寄存器
[段寄存器](【汇编】【实验楼】汇编教程笔记 #8086 寄存器组),分别是 CS 代码段寄存器、DS 数据段寄存器、SS 堆栈段寄存器,还有另外三个数据段寄存器(ES 附加段寄存器、FS 和 GS)供进程使用。
在调试器中,段寄存器基本上如下所示:
段寄存器的结构
- Selector: 16 bits
- Attribute: 16 bits
- Base: 32 bits
- Limit: 32 bits
每个段寄存器都由“可见”部分和“不可见”部分组成。(有时,不可见部分也被称为“描述符缓存”或者“影子缓存器”)。当段选择符被加载到一个段寄存器的可见部分时,处理器也通过段选择符所指向的段描述符获取了这个段寄存器的不可见信息:段基址、段限长和访问权限。
段寄存器保存的信息(可见或不可见的)使得处理器在进行地址转换时,不需要花费额外的总线周期来从段描述符中获取段基址和段限长。 在一个允许多个进程访问同一个描述符表的系统中,当描述符表被改变后,软件应该重新载入段寄存器。如果不这么做,当存储位置信息(its memory-resident version)发生变化后,用到的将是缓存在段寄存器中的旧的描述符信息。
段寄存器成员简介
如下所示为测试环境中的一个可能情况(红色部分可变):
| 段寄存器 | Selector | Attribute | Base | Limit |
|---|---|---|---|---|
| ES | 0023 | 可读、可写 | 0 | 0xFFFFFFFF |
| CS | 001B | 可读、可执行 | 0 | 0xFFFFFFFF |
| SS | 0023 | 可读、可写 | 0 | 0xFFFFFFFF |
| DS | 0023 | 可读、可写 | 0 | 0xFFFFFFFF |
| FS | 003B | 可读、可写 | 0x7FFDE000 | 0xFFF |
| GS | - | - | - | - |
段描述符
CS
存放当前正在运行的程序代码所在段的段基址,表示当前使用的指令代码可以从该段寄存器指定的存储器段中取得,相应的偏移量则由 IP 提供。
SS
堆栈寄存器
DS
数据段寄存器
ES
附加段寄存器
FS
fs 是 80386 起增加的两个辅助段寄存器之一, 在这之前只有一个辅助段寄存器 ES FS 寄存器指向当前活动线程的 TEB 结构(线程结构)
| 偏移 | 说明 |
|---|---|
| 00 | 只想 SEH 链表指针 |
| 04 | 线程堆栈顶部(地址最小) |
| 08 | 线程堆栈底部(地址最大) |
| 0c | SubSystemTib |
| 10 | FiberData |
| 14 | ArbitraryUserPointer |
| 18 | FS 段寄存器在内存中的镜像 |
| 20 | 进程 PID |
| 24 | 线程 ID |
| 2c | 指向线程局部存储的指针 |
| 30 | PEB 结构地址(进程结构) |
| 34 | 上一个错误(LastError) |
GS
段寄存器的读写
段寄存器的读写指令只有几个,如下所示:
- MOV 指令:
MOV AX,ES,将 ES 寄存器拷贝到 AX 寄存器,只能读到 16 位的可见部分MOV ES,AX,将 AS 寄存器的值拷贝到 ES 段寄存器,拷贝 96 位。
- SLDT / LLDT 指令:读写 LDTR
- STR / LTR 指令: 读写 TR
段寄存器属性探测
前文提到段寄存器有 96 位,可见部分内容只有 16 位,那么剩下的 80 位内容如何查看?
根据前文段寄存器结构图,段寄存器中 Attribute 、Base、Limit 是不可见的。那么接下来操作下如何查看相关内容。
环境说明:
- WinXP SP3
- Visual Basic 6.0
探测 Attribute
新建控制台工程并输入以下代码:
1 | |
调试代码查看变量 a 的赋值情况,如下所示:
上述操作时将 cs 寄存器赋值给 ax 寄存器,之后又将常量 10 复制给段寄存器中的变量 a,再往下执行就会报错,因为段寄存器 cs 刻度不可写。原来的 ds 是可读可写的,但将 cs 通过 ax 赋值给 ds 的时候,ds 已经不是原来的 ds,而是 cs,所以会报异常。
探测 Base
修改代码如下所示:
1 | |
代码说明,主要是将 fs 寄存器通过 ax 赋值到 es 寄存器,然后获取 es 寄存器的 Base 再通过 eax 赋值给段寄存器中的变量 a。执行结果如下所示:
探测 Limit
修改代码如下所示:
1 | |
补充:x64 寄存器
| Register | 状态 | 请使用 |
|---|---|---|
| RAX | 易失的 | 返回值寄存器 |
| RCX | 易失的 | 第一个整型参数 |
| RDX | 易失的 | 第二个整型参数 |
| R8 | 易失的 | 第三个整型参数 |
| R9 | 易失的 | 第四个整型参数 |
| R10: R11 | 易失的 | 必须根据需要由调用方保留;在 syscall/sysret 指令中使用 |
| R12: R15 | 非易失的 | 必须由被调用方保留 |
| RDI | 非易失的 | 必须由被调用方保留 |
| RSI | 非易失的 | 必须由被调用方保留 |
| RBX | 非易失的 | 必须由被调用方保留 |
| RBP | 非易失的 | 可用作帧指针;必须由被调用方保留 |
| RSP | 非易失的 | 堆栈指针 |
| XMM0 | 易失的 | 第一个 FP 参数 |
| XMM1 | 易失的 | 第二个 FP 参数 |
| XMM2 | 易失的 | 第三个 FP 参数 |
| XMM3 | 易失的 | 第四个 FP 参数 |
| XMM4: XMM5 | 易失的 | 必须根据需要由调用方保留 |
| XMM6: XMM15 | 非易失的 | 必须根据需要由被调用方保留。 |