【汇编】段寄存器相关

概述:寄存器相关整理

[!TIP] 相关参考
参考文章:

参考书籍:

  • 《INTEL 开发手册卷 3》

相关教程

[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

fs80386 起增加的两个辅助段寄存器之一, 在这之前只有一个辅助段寄存器 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 位内容如何查看?

根据前文段寄存器结构图,段寄存器中 AttributeBaseLimit 是不可见的。那么接下来操作下如何查看相关内容。

环境说明:

  • WinXP SP3
  • Visual Basic 6.0

探测 Attribute

新建控制台工程并输入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "stdafx.h"

int a=0;
int main(int argc, char* argv[])
{
_asm
{
mov ax,cs;
mov dword ptr ds:[a],10;
mov ds,ax;
mov dword ptr ds:[a],20;
}
return 0;
}

调试代码查看变量 a 的赋值情况,如下所示:

上述操作时将 cs 寄存器赋值给 ax 寄存器,之后又将常量 10 复制给段寄存器中的变量 a,再往下执行就会报错,因为段寄存器 cs 刻度不可写。原来的 ds 是可读可写的,但将 cs 通过 ax 赋值给 ds 的时候,ds 已经不是原来的 ds,而是 cs,所以会报异常。

探测 Base

修改代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "stdafx.h"

int a=0;
int main(int argc, char* argv[])
{
_asm
{
mov ax,fs;
mov es,ax;
mov eax,es:[0]; //不要用DS,否则编译不过去
mov dword ptr ds:[a],eax;
}
return 0;
}

代码说明,主要是将 fs 寄存器通过 ax 赋值到 es 寄存器,然后获取 es 寄存器的 Base 再通过 eax 赋值给段寄存器中的变量 a。执行结果如下所示:

探测 Limit

修改代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
#include "stdafx.h"

int a=0;
int main(int argc, char* argv[])
{
_asm
{
mov eax,fs:[0x1000];
mov dword ptr ds:[a],eax;
}
return 0;
}

补充: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 非易失的 必须根据需要由被调用方保留。

【汇编】段寄存器相关
https://hodlyounger.github.io/2023/11/03/B_Code/汇编/【汇编】段寄存器/
作者
mingming
发布于
2023年11月3日
许可协议