Call - Call Procedure


简介

Intel® 64 和 IA-32 架构软件开发者手册,第2卷,第3章节中的调用程序部分。手工翻译,如有疏漏错误,务必以原卷为主。

阅读需要有Windows内核基础,即汇编基础,段机制基础。

Call - Call Procedure ( 调用--调用程序 )

操作码 指令 op/en 64 位模式 Compat/Leg 模式 说明
E8 cw call rel16 D N.S. 有效 近程调用、相对调用、相对于下一个指令的位移调用
E8 cd call rel32 D 有效 有效 近程调用、相对、相对于下一个指令的位移。在 64 位模式下,32 位的位移符号扩展为 64 位。
FF /2 call r/m16 M N.E. 有效 调用 r/m16 中给出的近程绝对间接地址。
FF /2 call r/m32 M N.E. 有效 调用近程绝对间接地址,地址以 r/m32 表示。
FF /2 call r/m64 M 有效 N.E. 调用 r/m64 中给出的近程绝对间接地址。
9A cd call ptr16:16 D 无效 有效 调用操作数中给出的远端绝对地址。
9A cp call ptr16:32 D 无效 有效 调用操作数中给出的远端绝对地址。
FF /3 call m16:16 M 有效 有效 调用 m16:16 中给出的远端绝对间接地址。在 32 位模式下:如果选择子指向一个门,则 RIP = 从门中获取的 32 位零扩展位移;否则 RIP = 从指令引用的远指针获取的 16 位零扩展偏移。
FF /3 call m16:32 M 有效 有效 在 64 位模式下:如果选择子指向一个门,则 RIP = 从门中获取的 64 位位移;否则 RIP = 指令中引用的远指针的零扩展 32 位偏移。
REX.W FF /3 call m16:64 M 有效 N.E. 在 64 位模式下:如果选择子指向一个门,则 RIP = 从门中获取的 64 位位移;否则 RIP = 从指令引用的远指针获取的 64 位偏移。

指令操作数编码

操作/执行 操作数 1 操作数 2 操作数 3 操作数 4
D 偏移 NA NA NA
M ModRM:r/m (r) NA NA NA

说明

将过程链接信息保存到栈中,并跳转到通过目标操作数指定的被调用过程。目标操作数指定了被调用过程中第一条指令的地址。该操作数可以是立即数、通用寄存器或内存位置。该指令可用于执行四种类型的调用:

近调用(Near Call)--调用当前代码段(CS 寄存器当前指向的代码段)中的过程,有时也称为段内调用。

远调用(Far Call)--调用与当前代码段不同的代码段中的过程,有时也称为跨代码段调用。

跨权限远程调用 -- 远程调用与当前执行程序或过程的权限级别不同的程序段中的过程。

任务切换 -- 调用位于不同任务中的存储过程。

后两种调用类型(权限间调用和任务切换)只能在保护模式下执行。有关近程、远程和权限间调用更多信息,请参见《英特尔® 64 和 IA-32 体系结构软件开发人员手册》第 1 卷第 6 章 "使用 Call 和 RET 调用程序"。有关使用 CALL 指令执行任务切换的信息,请参见《英特尔® 64 和 IA-32 体系结构软件开发人员手册》第 3A 卷)第 7 章 "任务管理"。

近调用

近调用。在执行近调用时,处理器会将 EIP 寄存器的值(其中包含 CALL 指令后的指令偏移量)压入到堆栈上(用作以后的返回指令指针)。然后,处理器分支到目标操作数指定的当前代码段中的地址。目标操作数可以指定代码段中的绝对偏移量(从代码段底部开始的偏移量)或相对偏移量(相对于 EIP 寄存器中指令指针当前值的带符号位移;该值指向 CALL 指令之后的指令)。近程调用时,CS 寄存器不会发生变化。

对于近程调用绝对偏移,在通用寄存器或内存位置(r/m16r/m32 或 r/m64)中间接指定绝对偏移。操作数大小属性决定目标操作数的大小(16、32 或 64 位)。在 64 位模式下,近调用(和所有近程跳转)的操作数大小强制为 64 位。绝对偏移量直接载入 EIP(RIP) 寄存器。如果操作数大小属性为 16,EIP 寄存器的上高两个字节将被清零,从而导致指令指针的最大大小为 16 位。使用堆栈指针 [ESP] 作为基准寄存器间接访问绝对偏移量时,使用的基准值是指令执行前 ESP 的值。

相对偏移(rel16rel32)通常在汇编代码中指定为一个标签。但在机器码层面,它被编码为带符号的 16 位或 32 位立即值。该值会与 EIP(RIP) 寄存器中的值相加。在 64 位模式下,相对偏移始终是一个 32 位立即值,在将其添加到 RIP 寄存器中的值之前,先将其符号扩展到 64 位,然后再进行目标计算。与绝对偏移一样,操作数大小属性决定目标操作数的大小(16、32 或 64 位)。在 64 位模式下,目标操作数始终为 64 位,因为操作数大小在近程跳转时强制为 64 位。

实地址或虚拟-8086 模式下的远程调用

实地址或虚拟-8086 模式下的远程调用。在实地址或虚拟 8086 模式下执行远调用时,处理器会将 CS 和 EIP 寄存器的当前值推入堆栈,用作返回指令指针。然后,处理器执行一个“远程跳转”,跳转到由目标操作数指定的代码段和偏移量,进入被调用的过程。目标操作数直接用指针(ptr16:16ptr16:32)或间接用内存位置(m16:16m16:32)指定绝对远端地址。使用指针方法时,通过在指令中使用 4 字节(16 位操作数大小)或 6 字节(32 位操作数大小)的远地址立即数来编码被调用过程的段和偏移。使用间接方法时,目标操作数指定一个包含 4 字节(16 位操作数大小)或 6 字节(32 位操作数大小)远地址的内存位置。操作数大小属性决定了远地址中偏移量的大小(16 或 32 位)。远地址直接加载到 CS 和 EIP 寄存器中。如果操作数大小属性为 16,EIP 寄存器的上的高两个字节将被清零。

保护模式下的远程调用

保护模式下的远程调用当处理器在保护模式下运行时,CALL 指令可用于执行以下类型的远程调用:

• 远程调用到相同特权级别

• 远程调用到不同特权级别(权限级别间调用)

• 任务切换(远程调用另一项任务)

在保护模式下,处理器始终使用远地址中的段选择子部分来访问 GDT(全局描述符表)或 LDT(局部描述符表)中的相应描述符。描述符类型(代码段、调用门、任务门或 TSS)和访问权限决定了将执行哪种类型的调用操作。

如果所选描述符是代码段的描述符,则会执行对相同特权级别代码段的远距离调用。(如果所选代码段的权限级别不同,且是非一致性(non-comforming)代码段,则会产生一般保护异常)。在保护模式下对同一权限级别的远程调用与在实地址或虚拟-8086 模式下的远程调用非常相似。目标操作数通过指针(ptr16:16ptr16:32)直接指定绝对远地址,或通过内存位置(m16:16m16:32)间接指定绝对远地址。操作数大小属性决定远地址中偏移量的大小(16 位或 32 位)。新的代码段选择子及其描述符被加载到 CS 寄存器;指令中的偏移量被加载到 EIP 寄存器中。

调用门(将在下一段中介绍)也可用于执行对同一权限级别代码段的远距离调用。使用这种机制可提供额外的间接级别,是在 16 位和 32 位代码段之间进行调用的首选方法。

在执行特权级别间(也就是特权级别不同)的远距离调用时,必须通过调用门访问被调用过程的代码段。目标操作数指定的代码段选择子用于识别调用门。目标操作数可以直接用指针(ptr16:16ptr16:32)或间接用内存位置(m16:16m16:32)指定调用门的段选择子。处理器从调用门描述符中获取新代码段的段选择子和新的指令指针(偏移量)。(使用调用门时,目标操作数的偏移量将被忽略)。

新的堆栈段的段选择子在当前任务的 TSS 中指定。堆栈切换发生在跳转到新的代码段之前。(注意,使用调用门执行同一特权级别的远程调用时,不会发生堆栈切换。)在新的堆栈上,处理器会压入调用过程堆栈的段选择子和堆栈指针,来自调用过程堆栈的可选参数集,以及调用过程代码段的段选择子和指令指针。(在调用门描述符中有一个值,决定了要复制到新堆栈中的参数数量。)最后,处理器会跳转到被调用过程在新代码段中的地址。

通过 CALL 指令执行任务切换与通过调用门执行远程调用类似。目标操作数指定任务切换所激活的新任务的任务门段选择子(目标操作数中的偏移量被忽略)。任务门指向新的任务的 TSS,该 TSS 包含任务代码段和堆栈段的段选择子。注意,TSS 还包含调用任务暂停之前,下一条要执行的指令的 EIP 值。这个指令指针值被加载到 EIP 寄存器中,用以重新启动调用任务。

CALL 指令还可以直接指定 TSS 的段选择子,这样可以省去任务门的间接过程。有关任务切换的具体机制,请参见《英特尔® 64 和 IA-32 体系结构软件开发人员手册》第 3A 卷第 7 章 "任务管理"。

使用 CALL 指令执行任务切换时,EFLAGS 寄存器中的 NT(嵌套任务)标志被设置,新的 TSSprevious task link 字段被加载为旧任务的 TSS 选择子。代码预计会通过执行 IRET 指令暂停该嵌套任务,因为 NT 标志被设置,IRET 指令会自动使用 previous task link 返回到调用任务。(有关嵌套任务的信息,请参见《英特尔® 64 和 IA-32 体系结构软件开发人员手册》第 3A 卷*第 7 章 "任务链接"部分)。在这方面,使用 CALL 指令进行切换任务与 JMP 指令不同。JMP 不设置 NT 标志,因此不需要 IRET 指令来暂停任务。

16 位和 32 位调用混用

16 位和 32 位调用混用。在 16 位和 32 位代码段之间进行远程调用时,应使用调用门。如果远程调用是从 32 位代码段到 16 位代码段,则应该从 32 位代码段的前 64 KBytes 部分进行调用。这是因为 CALL 指令的操作数大小属性被设置为 16,因此只能保存 16 位的返回地址偏移。此外,应使用 16 位调用门进行调用,以便将 16 位值推入堆栈。更多信息,请参见《英特尔® 64 和 IA-32 体系结构软件开发人员手册》第 3B 卷第 21 章 "混合使用 16 位 32 位代码"。

兼容模式下的远程调用

兼容模式下的远程调用当处理器在兼容模式下运行时,CALL 指令可用于执行以下类型的远程调用:

• 远程调用到相同的特权级别,保持在兼容模式下

• 远程调用到相同的特权级别,过渡到 64 位模式

• 远程调用到不同的特权级别(特权级别间调用),过渡到 64 位模式

请注意,由于 IA-32e 模式不支持任务切换,因此在兼容模式下不能使用 CALL 指令进行任务切换。

在兼容模式下,处理器始终使用远地址中的段选择子部分来访问 GDT(全局描述符表)或 LDT(局部描述符表)中的相应描述符。描述符类型(代码段、调用门)和访问权限决定了将执行的调用操作类型。

如果所选描述符是代码段的描述符,则会执行到相同特权级别的代码段的远程调用。(如果所选代码段的特权级别不同,且代码段是非一致性的,则会产生一般保护异常)。在兼容模式下对相同特权级别的远程调用与在保护模式下的远程调用非常相似。目标操作数通过指针(ptr16:16ptr16:32)直接指定绝对远地址,或通过内存位置(m16:16m16:32)间接指定绝对远地址。操作数大小属性决定远地址中偏移量(16 或 32 位)的大小。新代码段选择子及其描述符被载入 CS 寄存器,指令偏移量被载入 EIP 寄存器。不同之处在于,可以进入 64 位模式。这由新代码段描述符中的 L 位指定。

请注意,64 位调用门(将在下一段中介绍)也可用于执行对相同权限级别代码段的远调用。不过,使用这种机制需要目标代码段描述符设置 L 位,从而进入 64 位模式。

执行特权级别间的远距离调用时,必须通过 64 位调用门访问被调用过程的代码段。目标操作数指定的代码段选择子标识了调用门。目标操作数可以直接用指针(ptr16:16ptr16:32)或间接用内存位置(m16:16m16:32)指定调用门代码段选择子。处理器从 16 字节调用门描述符中获取新代码段的段选择子和新指令指针(偏移量)。(使用调用门时,目标操作数的偏移量将被忽略)。

在特权级间调用时,处理器会切换到被调用过程的权限级别堆栈。新的堆栈段选择子设置为 NULL。新的堆栈指针在当前运行任务的 TSS 中指定。堆栈切换发生在跳转到新的代码段之前。(请注意,在使用调用门对同一权限级别的代码段执行远调用时,由于进入 64 位模式,会发生隐式堆栈切换。SS 选择子保持不变,但堆栈段访问使用基址为 0x0 的段,忽略限制,并且默认堆栈大小为 64 位。偏移量使用 RSP 的完整值,其中高 32 位是未定义的)。在新的堆栈中,处理器会压入调用过程堆栈的段选择子和堆栈指针,以及调用过程代码段的段选择子和指令指针。(在 IA-32e 模式下不支持参数复制。)最后,处理器会跳转到新代码段中被调用过程的地址。

64 位模式下的近 /(远)调用

64 位模式下的近 /(远)调用。当处理器在 64 位模式下运行时,CALL 指令可用于执行以下类型的远程调用:

• 远调用相同权限级别,过渡到兼容模式

• 远调用相同权限级别,保持 64 位模式

• 远调用不同权限级别(权限级别间调用),保持 64 位模式

请注意,在这种模式下,CALL 指令不能用于在 64 位模式下进行任务切换,因为 IA-32e 模式不支持任务切换。

在 64 位模式下,处理器总是使用远地址的段选择子部分来访问 GDT 或 LDT 中的相应描述符。描述符类型(代码段、调用门)和访问权限决定了要执行的调用操作类型。

如果所选描述符是代码段的描述符,则会执行对相同权限级别代码段的远距离调用。(如果所选代码段的权限级别不同,且代码段是非一致性代码段,则会产生一般保护异常)。在 64 位模式下,对相同特权级别的远程调用与在兼容模式下执行的远程调用非常相似。目标操作数通过内存位置(m16:16、m16:32m16:64)间接指定一个绝对远端地址。在 64 位模式下,CALL 指令的直接指定绝对远地址的形式是未定义的。操作数大小属性决定远地址中偏移量的大小(16、32 或 64 位)。新代码段选择子及其描述符被载入 CS 寄存器;偏移量从指令中加载到 EIP 寄存器中。新的代码段可能会根据 L 位值指定进入兼容模式或 64 位模式。

64 位调用门(将在下一段中介绍)也可用于执行对相同权限级别代码段的远调用。不过,使用这种机制需要设置目标代码段描述符的 L 位。

执行特权级间远程调用时,必须通过 64 位调用门访问被调用过程的代码段。目标操作数指定的段选择子可标识调用门。目标操作数只能通过内存位置(m16:16、m16:32m16:64)间接指定调用门的段选择子。处理器从 16 字节调用门描述符中获取新代码段的段选择子和新的指令指针(偏移量)。(使用调用门时,目标操作数的偏移量将被忽略)。

在特权级间调用时,处理器会切换到被调用过程的特权级堆栈。新的堆栈段选择子设置为 NULL。新的堆栈指针在当前任务的 TSS 中指定。堆栈切换发生在跳转到新的代码段之前。

请注意,在使用调用门对同一权限级别的段执行远调用时,由于进入 64 位模式,会发生隐式堆栈切换。SS 选择子保持不变,但堆栈段访问使用基址为 0x0 的段,忽略限制,且默认堆栈大小为 64 位。(RSP 的完整值用于偏移量。)在新的堆栈上,处理器会压入调用过程堆栈的段选择子和堆栈指针,以及调用过程代码段的段选择子和指令指针。(在 IA-32e 模式下不支持参数复制。)最后,处理器会跳转到新代码段中被调用过程的地址。

请参考《Intel® 64 和 IA-32 架构软件开发者手册,第1卷》的第6章“过程调用、中断与异常”以及第18章“控制流保护技术(CET)”中的相关内容以获取CET的详细信息。

指令排序

指令排序。远程调用后面的指令可能会在前面的指令执行完毕之前从内存中获取,但在远程调用之前的所有指令执行完毕之前,这些指令(指远程调用之后的指令)不会执行(即使是推测执行)(后面的指令可能会在前面指令存储的数据全局可见之前执行)。

某些情况下,近间接 CALL 指令后的下一个顺序指令可能会被推测执行。如果软件需要阻止这种情况发生(例如,为了防止推测执行侧信道攻击),则可以在近间接 CALL 指令之后插入 LFENCE 指令操作码,以阻止推测执行。

运行

近程调用(Near Call)

该伪代码描述了不同操作数大小(64位、32位和16位)下的近程调用逻辑。具体分为相对调用和绝对调用两部分。


1. 近程相对调用(Near Relative Call)

操作数大小为 64位:
  • 步骤
    1. DEST(相对偏移量,假设是rel32)符号扩展为64位,存储到tempDEST
    2. 计算新的目标地址 tempRIP = RIP + tempDEST
    3. 如果返回地址空间不足以存放8字节的返回地址,则触发#SS(0)(栈空间不足异常)。
    4. 将当前的RIP(返回地址)压入栈中。
    5. 如果启用了影子栈并且DEST不为0,推入影子栈(8字节)。
    6. RIP更新为tempRIP,继续执行跳转。
操作数大小为 32位:
  • 步骤
    1. 计算新的目标地址 tempEIP = EIP + DESTDESTrel32的相对偏移)。
    2. 如果tempEIP超出代码段限制,则触发#GP(0)(一般保护异常)。
    3. 如果返回地址空间不足以存放4字节的返回地址,则触发#SS(0)
    4. 将当前的EIP压入栈中。
    5. 如果启用了影子栈并且DEST不为0,推入影子栈(4字节)。
    6. EIP更新为tempEIP,执行跳转。
操作数大小为 16位:
  • 步骤
    1. 计算新的目标地址 tempEIP = (EIP + DEST) & 0xFFFFDESTrel16的相对偏移,进行16位掩码操作)。
    2. 如果tempEIP超出代码段限制,则触发#GP(0)
    3. 如果返回地址空间不足以存放2字节的返回地址,则触发#SS(0)
    4. 将当前的IP压入栈中。
    5. 如果启用了影子栈并且DEST不为0,将IP(零扩展为32位)推入影子栈。
    6. EIP更新为tempEIP,执行跳转。

2. 近程绝对调用(Near Absolute Call)

操作数大小为 64位:
  • 步骤
    1. 直接将DEST(假设是r/m64的绝对地址)赋值给tempRIP
    2. 如果返回地址空间不足以存放8字节的返回地址,则触发#SS(0)
    3. 将当前的RIP压入栈中。
    4. 如果启用了影子栈,推入影子栈(8字节)。
    5. RIP更新为tempRIP,继续执行跳转。
操作数大小为 32位:
  • 步骤
    1. DEST(假设是r/m32的绝对地址)赋值给tempEIP
    2. 如果tempEIP超出代码段限制,则触发#GP(0)
    3. 如果返回地址空间不足以存放4字节的返回地址,则触发#SS(0)
    4. 将当前的EIP压入栈中。
    5. 如果启用了影子栈,推入影子栈(4字节)。
    6. EIP更新为tempEIP,执行跳转。
操作数大小为 16位:
  • 步骤
    1. DEST(假设是r/m16的绝对地址)与0xFFFF进行掩码操作,得到tempEIP
    2. 如果tempEIP超出代码段限制,则触发#GP(0)
    3. 如果返回地址空间不足以存放2字节的返回地址,则触发#SS(0)
    4. 将当前的IP压入栈中。
    5. 如果启用了影子栈,推入影子栈(将IP零扩展为32位)。
    6. EIP更新为tempEIP,执行跳转。

3. 近程间接调用(Near Indirect Call)

在处理近程间接调用时,执行以下操作:

  • 检查是否启用了 Endbranch(结束分支)并且未被抑制
    • 如果当前特权级为3(用户模式):
      • 如果没有3EH前缀或IA32_U_CET.NO_TRACK_EN为0,则将IA32_U_CET.TRACKER设置为WAIT_FOR_ENDBRANCH
    • 否则(特权级为0,内核模式):
      • 如果没有3EH前缀或IA32_S_CET.NO_TRACK_EN为0,则将IA32_S_CET.TRACKER设置为WAIT_FOR_ENDBRANCH

该部分的目的是确保在执行某些特定的指令(如CALL)时,跟踪和保护控制流的变化,防止恶意攻击(例如,控制流劫持)。

远程调用(Far Call)

此伪代码描述了在实地址模式(Real-address mode)或虚拟8086模式(Virtual 8086 mode)下,执行远程调用的行为。具体步骤根据操作数大小(32位或16位)进行处理。


远程调用(Far Call)处理逻辑:

条件:
  • 远程调用(Far Call)时,PE标志为0(实模式)或(PE标志为1且VM标志为1,表示虚拟8086模式)。在这两种模式下,进行远程调用的操作。

操作数大小为32位
  1. 检查栈空间是否足够存放6字节的返回地址
    • 如果栈空间不足以存放6字节的返回地址,触发#SS(0)异常(栈溢出异常)。
  2. 检查目标地址的高16位
    • 如果DEST[31:16]DEST的高16位)不为零,触发#GP(0)异常(一般保护异常)。这通常是因为远程调用中不允许存在高位非零的指针。
  3. 将当前的CS(代码段寄存器)和EIP(指令指针寄存器)压入栈中
    • 先将CS压入栈(CS会被填充高16位,以适应栈的对齐要求)。
    • 然后将EIP(当前指令指针)压入栈。
  4. 更新CS和EIP
    • CS被更新为DEST[47:32](即DEST的高16位,指向目标代码段)。
    • EIP被更新为DEST[31:0](即DEST的低32位,指向目标地址的偏移量)。

操作数大小为16位
  1. 检查栈空间是否足够存放4字节的返回地址
    • 如果栈空间不足以存放4字节的返回地址,触发#SS(0)异常。
  2. 将当前的CS(代码段寄存器)和IP(指令指针寄存器)压入栈中
    • CS压入栈。
    • 然后将IP(当前指令指针)压入栈。
  3. 更新CS和EIP
    • CS被更新为DEST[31:16](即DEST的高16位,指向目标代码段)。
    • EIP被更新为DEST[15:0](即DEST的低16位,指向目标地址的偏移量,清除高16位)。

总结:
  • 远程调用处理过程中,根据操作数大小(32位或16位),分别将返回地址和指令指针压入栈中,然后将目标代码段和目标地址更新到CSEIP寄存器中,执行跳转。

远程调用(Far Call)在保护模式或IA-32e模式下的处理

此伪代码描述了在保护模式(Protected mode)或IA-32e模式(64位扩展模式)下执行远程调用时,处理目标段选择器和访问权限的步骤。


远程调用(Far Call)处理逻辑:

条件:
  • 保护模式或IA-32e模式(即PE = 1VM = 0,表示不是虚拟8086模式)。
  • 根据目标操作数中的段选择器,执行以下步骤。

步骤:
  1. 检查目标操作数中的段选择器是否为空:
    • 如果目标操作数中的段选择器为NULL(即选择器无效),触发#GP(0)异常(一般保护异常)。
  2. 检查段选择器的索引是否在描述符表的有效范围内:
    • 如果段选择器的索引不在描述符表的有效范围内,触发#GP(新代码段选择器)异常。
  3. 读取选择的段描述符的类型和访问权限:
    • 读取从段选择器中获取的段描述符的类型(如代码段、数据段等)和访问权限。
  4. 检查IA32_EFER.LMA标志(IA32扩展功能启用寄存器中的LMA位):
    • 如果IA32_EFER.LMA = 0(表示当前在兼容模式下运行):
      • 如果段的类型不是符合要求的代码段类型(如符合型代码段非符合型代码段),或者是调用门(Call Gate)、任务门(Task Gate)或任务状态段(TSS),则触发#GP(段选择器)异常。
    • 如果IA32_EFER.LMA = 1(表示当前在64位模式下运行):
      • 如果段的类型不是符合要求的代码段类型,或者不是64位调用门(64-bit Call Gate),则触发#GP(段选择器)异常。
  5. 根据段类型和访问权限,转到相应的代码段处理:
    • 根据段的类型和访问权限,决定程序的执行路径:
      • 符合型代码段(Conforming Code Segment)
      • 非符合型代码段(Non-conforming Code Segment)
      • 调用门(Call Gate)
      • 任务门(Task Gate)
      • 任务状态段(Task-State Segment)

总结:

保护模式IA-32e模式下,远程调用的处理涉及到以下几个关键步骤: - 验证目标操作数中的段选择器是否有效; - 根据段选择器的索引检查是否超出有效范围; - 根据段的类型和访问权限进行检查,并确保段符合规定的类型要求(如符合型或非符合型代码段、64位调用门等); - 如果不符合要求,触发相关的异常(如#GP异常); - 如果合法,则根据段类型执行相应的代码处理路径。

一致性代码段处理逻辑(Conforming Code Segment)

此伪代码描述了在处理一致性代码段(Conforming Code Segment)时,执行的一系列检查和操作。这些检查包括段的有效性、权限检查、堆栈大小检查、目标地址的范围检查,以及是否启用影子堆栈等。


步骤:

1. 检查段的有效性和权限:
  • L位 = 1 且 D位 = 1 且 IA32_EFER.LMA = 1
    • 如果这些条件成立,则触发#GP(新代码段选择器)异常(一般保护异常)。
  • DPL > CPL:
    • 如果段的DPL(描述符权限级别)大于CPL(当前特权级别),则触发#GP(新代码段选择器)异常。
  • 段未加载(Segment not present):
    • 如果目标段没有被加载,触发#NP(新代码段选择器)异常(段不存在异常)。
  • 堆栈不够大:
    • 如果堆栈空间不足以存储返回地址,触发#SS(0)异常(堆栈段异常)。
2. 设置目标地址:
  • 计算目标地址tempEIP := DEST(Offset)),并根据目标模式(兼容模式)进行调整:
    • 如果目标模式为兼容模式(Compatibility mode),则将tempEIP0xFFFFFFFF00000000进行与操作(清除高32位)。
  • 调整tempEIP大小:
    • 如果操作数大小为16位,则将tempEIP的高16位清零。
  • 检查目标地址是否超出代码段的有效范围:
    • 如果IA32_EFER.LMA = 0或者目标模式为兼容模式,并且tempEIP超出代码段限制,则触发#GP(0)异常。
  • 检查非规范地址:
    • 如果tempEIP是非规范的地址,触发#GP(0)异常。
3. 启用影子堆栈检查:
  • 如果启用影子堆栈(Shadow Stack Enabled):
    • 根据操作数大小(16/32/64位)计算并保存tempPushLIPtempPushCS,并将这些值推送到堆栈。
4. 根据操作数大小执行不同的推送操作:
  • 如果操作数大小为32位:
    • 推送CS(段寄存器)和EIP(指令指针)。
    • 更新CS为目标代码段选择器,并将CS(RPL)设置为当前特权级别(CPL)。
    • 设置EIP为目标地址。
  • 如果操作数大小为16位:
    • 推送CS(段寄存器)和IP(指令指针)。
    • 更新CS为目标代码段选择器,并将CS(RPL)设置为当前特权级别(CPL)。
    • 设置EIP为目标地址。
  • 如果操作数大小为64位:
    • 推送CS(段寄存器)和RIP(指令指针)。
    • 更新CS为目标代码段选择器,并将CS(RPL)设置为当前特权级别(CPL)。
    • 设置RIP为目标地址。
5. 影子堆栈操作:
  • 如果启用影子堆栈并且目标段为传统或兼容模式:
    • 如果影子堆栈指针(SSP)不在低4GB内,触发#GP(0)异常。
    • SSP进行对齐(对齐到8字节边界),如果未对齐则进行对齐。
    • 将返回地址等信息存储到影子堆栈中。
6. 结束分支处理:
  • 如果启用结束分支追踪(Endbranch Enabled):
    • 如果当前特权级(CPL)为3,则更新用户模式CET追踪器并允许结束分支追踪。
    • 否则,更新系统模式CET追踪器并允许结束分支追踪。

总结:

一致性代码段的处理过程中,以下操作是关键:

  1. 验证段的有效性和访问权限:确保目标段是有效的,且符合当前特权级别(CPL)的要求。
  2. 计算并调整目标地址:处理目标地址,并确保其在代码段的有效范围内。
  3. 影子堆栈支持:如果启用了影子堆栈,则保存堆栈信息,并确保堆栈指针符合要求。
  4. 根据操作数大小推送返回地址和指令指针:根据操作数的位数(16位、32位或64位),将返回地址、指令指针和段寄存器推送到堆栈。
  5. 结束分支追踪:根据CPL和模式设置,更新CET追踪器的状态。

非一致性代码段处理逻辑(Nonconforming Code Segment)

此伪代码描述了在处理非一致性代码段(Nonconforming Code Segment)时的一系列步骤,包括对段的有效性检查、目标地址的范围检查、堆栈大小检查以及是否启用影子堆栈等。


步骤:

1. 检查段的有效性和权限:
  • L位 = 1 且 D位 = 1 且 IA32_EFER.LMA = 1
    • 如果这些条件成立,则触发#GP(新代码段选择器)异常(一般保护异常)。
  • RPL > CPL 或 DPL ≠ CPL:
    • 如果目标段选择子的RPL(请求者特权级)大于CPL(当前特权级别)或DPL(描述符权限级别)不等于CPL,则触发#GP(新代码段选择器)异常。
  • 段未加载(Segment not present):
    • 如果目标段没有被加载,触发#NP(新代码段选择器)异常(段不存在异常)。
  • 堆栈不够大:
    • 如果堆栈空间不足以存储返回地址,触发#SS(0)异常(堆栈段异常)。
2. 计算目标地址:
  • 计算目标地址tempEIP := DEST(Offset)),并根据目标模式(兼容模式)进行调整:
    • 如果目标模式为兼容模式(Compatibility mode),则将tempEIP0xFFFFFFFF00000000进行与操作(清除高32位)。
  • 调整tempEIP大小:
    • 如果操作数大小为16位,则将tempEIP的高16位清零。
  • 检查目标地址是否超出代码段的有效范围:
    • 如果IA32_EFER.LMA = 0或目标模式为兼容模式,并且tempEIP超出代码段限制,则触发#GP(0)异常。
  • 检查非规范地址:
    • 如果tempEIP是非规范的地址,触发#GP(0)异常。
3. 启用影子堆栈检查:
  • 如果启用影子堆栈(Shadow Stack Enabled):
    • 根据IA32_EFER.LMACS.L的值,选择计算tempPushLIP的方式。如果IA32_EFER.LMACS.L为1,则tempPushLIPRIP,否则计算为CSBASE + EIP

    • 设置tempPushCS为当前的CS寄存器值。

4. 根据操作数大小执行不同的推送操作:
  • 如果操作数大小为32位:
    • 推送CS(段寄存器)和EIP(指令指针)。
    • 更新CS为目标代码段选择器,并将CS(RPL)设置为当前特权级别(CPL)。
    • 设置EIP为目标地址。
  • 如果操作数大小为16位:
    • 推送CS(段寄存器)和IP(指令指针)。
    • 更新CS为目标代码段选择器,并将CS(RPL)设置为当前特权级别(CPL)。
    • 设置EIP为目标地址。
  • 如果操作数大小为64位:
    • 推送CS(段寄存器)和RIP(指令指针)。
    • 更新CS为目标代码段选择器,并将CS(RPL)设置为当前特权级别(CPL)。
    • 设置RIP为目标地址。
5. 影子堆栈操作:
  • 如果启用影子堆栈并且目标段为传统或兼容模式:
    • 如果影子堆栈指针(SSP)不在低4GB内,触发#GP(0)异常。
    • SSP进行对齐(对齐到8字节边界),如果未对齐则进行对齐。
    • 将返回地址等信息存储到影子堆栈中。
6. 结束分支处理:
  • 如果启用结束分支追踪(Endbranch Enabled):
    • 如果当前特权级(CPL)为3,则更新用户模式CET追踪器并允许结束分支追踪。
    • 否则,更新系统模式CET追踪器并允许结束分支追踪。

总结:

非一致性代码段的处理过程中,以下操作是关键:

  1. 验证段的有效性和访问权限:确保目标段是有效的,且符合当前特权级别(CPL)的要求。
  2. 计算并调整目标地址:处理目标地址,并确保其在代码段的有效范围内。
  3. 影子堆栈支持:如果启用了影子堆栈,则保存堆栈信息,并确保堆栈指针符合要求。
  4. 根据操作数大小推送返回地址和指令指针:根据操作数的位数(16位、32位或64位),将返回地址、指令指针和段寄存器推送到堆栈。
  5. 结束分支追踪:根据CPL和模式设置,更新CET追踪器的状态。

调用门(CALL-GATE)处理逻辑

此伪代码描述了如何处理调用门(Call Gate)的相关操作,主要包括对调用门选择子的验证、代码段描述符的检查、堆栈空间的检查等。以下是详细的执行步骤和逻辑分析:


步骤:

1. 检查调用门的权限:
  • 调用门权限检查:
    • 如果调用门的DPL小于CPL,或者RPL大于DPL,则触发#GP(call-gate selector)异常(一般保护异常)。
2. 检查调用门是否存在:
  • 调用门不存在:
    • 如果调用门不存在,则触发#NP(call-gate selector)异常(段不存在异常)。
3. 检查调用门的代码段选择器:
  • 调用门代码段选择器为NULL:
    • 如果调用门代码段选择器为NULL,则触发#GP(0)异常。
  • 调用门代码段选择器索引超出描述符表的范围:
    • 如果调用门代码段选择器的索引超出了描述符表的有效范围,则触发#GP(call-gate code-segment selector)异常。
4. 读取并验证调用门的代码段描述符:
  • 读取调用门的代码段描述符:
    • 获取调用门的代码段描述符。
  • 检查调用门代码段描述符是否有效:
    • 如果调用门的代码段描述符不是有效的代码段,或者调用门代码段描述符的DPL大于当前特权级(CPL),则触发#GP(call-gate code-segment selector)异常。
5. 检查调用门代码段的属性:
  • 调用门代码段描述符类型检查:
    • 如果IA32_EFER.LMA = 1(启用了长模式),并且调用门代码段描述符不是64位代码段,或者调用门代码段描述符同时设置了L位和D位,则触发#GP(call-gate code-segment selector)异常。
  • 调用门代码段是否存在:
    • 如果调用门代码段不在内存中,则触发#NP(call-gate code-segment selector)异常。
6. 检查调用门代码段的符合性和权限:
  • 调用门代码段不符合要求且DPL小于CPL:
    • 如果调用门代码段是非符合型代码段DPL小于CPL,则跳转到MORE-PRIVILEGE步骤,进一步处理。
  • 调用门代码段符合要求或者权限足够:
    • 否则,跳转到SAME-PRIVILEGE步骤,继续处理。

总结:

在处理调用门(Call Gate)时,以下操作是关键:

  1. 验证调用门选择子的权限:确保调用门的DPL、RPL、CPL之间的权限关系正确。
  2. 检查调用门的存在性:确认调用门是否存在,并且其代码段选择器是有效的。
  3. 读取并验证代码段描述符:确保调用门代码段描述符是有效的,且符合特定的权限和模式要求。
  4. 检查调用门代码段的属性:验证调用门的代码段是否符合长模式要求,是否存在,并且是否符合调用门的标准。
  5. 处理权限相关的跳转:根据调用门的权限、符合性以及特权级别的比较,跳转到不同的处理步骤(MORE-PRIVILEGESAME-PRIVILEGE)。

MORE-PRIVILEGE 处理逻辑

此部分伪代码描述了在调用门处理过程中,调用特权级(CPL)发生变化时,如何根据当前的TSS(任务状态段)处理堆栈和寄存器,并执行相应的操作。主要关注的是根据调用门的参数调整堆栈、段描述符、权限等,并处理影子栈结束分支跟踪等高级特性。以下是详细分析。


步骤:

1. 根据当前TSS类型处理堆栈
  • 32位TSS:
    • 计算新的堆栈地址TSSstackAddress,根据新的代码段DPL(描述符权限级)和TSS的栈深度来计算偏移。
    • 检查堆栈是否超出当前TSS限制:
      • 如果TSSstackAddress + 5超出当前TSS的限制,则触发#TS(current TSS selector)异常。
    • TSS base加载NewSS(堆栈段选择子)和NewESP(堆栈指针)。
  • 16位TSS:
    • 计算新的堆栈地址TSSstackAddress,根据新的代码段DPL和TSS栈深度来计算偏移。
    • 检查堆栈是否超出当前TSS限制:
      • 如果TSSstackAddress + 3超出当前TSS的限制,则触发#TS(current TSS selector)异常。
    • TSS base加载NewSSNewESP
  • 64位TSS:
    • 计算新的堆栈地址TSSstackAddress,根据新的代码段DPL和TSS的栈深度来计算偏移。
    • 检查堆栈是否超出当前TSS限制:
      • 如果TSSstackAddress + 7超出当前TSS的限制,则触发#TS(current TSS selector)异常。
    • TSS base加载NewSS(堆栈段选择子)和NewRSP(堆栈指针)。
2. 检查和设置新的堆栈段
  • 如果IA32_EFER.LMA = 0NewSS为NULL:
    • 如果新堆栈段为空(即选择子为NULL),则触发#TS(NewSS)异常。
  • 读取新的堆栈段描述符:
    • 读取新的堆栈段描述符信息。
  • 检查堆栈段的合法性:
    • 如果IA32_EFER.LMA = 0且新的堆栈段选择子的RPL与新的代码段DPL不匹配,或者新的堆栈段DPL与新的代码段DPL不一致,或者新的堆栈段不是可写数据段,则触发#TS(NewSS)异常。
    • 如果IA32_EFER.LMA = 0且新的堆栈段不存在,则触发#SS(NewSS)异常。
  • 检查堆栈是否足够:
    • 如果调用门大小是32位,检查新的堆栈是否有足够的空间存放参数和返回地址(至少16字节)。
    • 如果调用门大小是16位,检查新的堆栈是否有足够的空间存放参数和返回地址(至少8字节)。
3. 处理调用门的指针和栈操作
  • 32位调用门:
    • 检查调用门指针是否在新的代码段内
      • 如果调用门的指令指针不在新的代码段限制内,则触发#GP(0)异常。
    • 更新堆栈和寄存器:
      • 将新的堆栈段SS和堆栈指针ESP加载为newSSnewESP
      • 将指令指针CS:EIP设置为调用门中的CS:InstructionPointer
  • 16位调用门:
    • 检查调用门指针是否在新的代码段内
      • 如果调用门的指令指针(低16位)不在新的代码段限制内,则触发#GP(0)异常。
    • 更新堆栈和寄存器:
      • 将新的堆栈段SS和堆栈指针ESP加载为newSSnewESP
      • 将指令指针CS:IP设置为调用门中的CS:InstructionPointer
  • 64位调用门:
    • 检查调用门指针是否有效
      • 如果推送32字节时会导致非规范地址,则触发#SS(NewSS)异常。
      • 如果调用门的指令指针为非规范地址,则触发#GP(0)异常。
    • 更新堆栈和寄存器:
      • 更新堆栈段SS和堆栈指针RSP
      • 将指令指针CS:IP设置为调用门中的CS:InstructionPointer
4. 影子栈相关操作
  • 如果影子栈启用且CPL为3(用户模式):
    • 处理影子栈状态:
      • 根据IA32_EFER.LMA的值调整IA32_PL3_SSP,以确保影子栈指针符合要求。
      • 进行shadow_stack_lock_cmpxchg8b检查,以确保影子栈状态一致性。
  • 更新堆栈和寄存器:
    • 如果影子栈启用且CPL为3,则进行影子栈的推送操作,推送包括oldCSoldRIPSSP
5. 结束分支跟踪
  • 如果启用结束分支(Endbranch)
    • 设置相应的跟踪器状态IA32_S_CET.TRACKER = WAIT_FOR_ENDBRANCH,并清除SUPPRESS标志。

总结:

此部分伪代码在MORE-PRIVILEGE中详细描述了调用门处理过程中与堆栈、TSS和影子栈相关的操作。主要操作包括:

  1. 根据当前TSS类型调整堆栈(32位、16位、64位)。
  2. 读取和验证堆栈段描述符,并检查堆栈是否足够。
  3. 处理调用门指令指针,并根据调用门大小(16位、32位、64位)更新堆栈和寄存器。
  4. 影子栈相关操作,确保影子栈指针一致,并进行必要的锁定操作。
  5. 结束分支跟踪,设置相应的跟踪器状态以等待结束分支事件。

这些步骤确保了在调用门过程中,特权级别的变化、堆栈操作和指令指针跳转都符合架构要求,且在多个层次上进行安全检查。

SAME-PRIVILEGE 处理逻辑

这段伪代码描述了当调用门(Call Gate)的权限级别不变(即特权级别保持不变,CPL与DPL相等)时,如何对堆栈、指令指针(IP/EIP/RIP)和影子栈进行操作,确保调用门能够正确处理。


步骤:

1. 根据调用门大小(CallGateSize)处理堆栈和指令指针
  • 32位调用门(CallGateSize = 32):
    • 检查堆栈空间是否足够:
      • 如果堆栈空间不足以存放8字节,则触发#SS(0)异常。
    • 检查调用门指令指针是否在代码段限制内:
      • 如果调用门的指令指针不在代码段限制内,则触发#GP(0)异常。
    • 更新堆栈和寄存器:
      • CS:EIP设置为调用门中的CS:EIP(更新段描述符信息)。
      • 将返回地址(oldCS:oldEIP)压入堆栈。
  • 16位调用门(CallGateSize = 16):
    • 检查堆栈空间是否足够:
      • 如果堆栈空间不足以存放4字节,则触发#SS(0)异常。
    • 检查调用门指令指针是否在代码段限制内:
      • 如果调用门的指令指针不在代码段限制内,则触发#GP(0)异常。
    • 更新堆栈和寄存器:
      • CS:IP设置为调用门中的CS:InstructionPointer(更新段描述符信息)。
      • 将返回地址(oldCS:oldIP)压入堆栈。
  • 64位调用门(CallGateSize = 64):
    • 检查堆栈空间是否足够:
      • 如果压栈16字节时会触及非规范地址,则触发#SS(0)异常。
    • 检查RIP是否为非规范地址:
      • 如果RIP为非规范地址,则触发#GP(0)异常。
    • 更新堆栈和寄存器:
      • CS:IP设置为调用门中的CS:InstructionPointer(更新段描述符信息)。
      • 将返回地址(oldCS:oldIP)压入堆栈。
2. 影子栈相关操作
  • 如果影子栈启用且CPL为当前级别:
    • 对影子栈进行对齐处理:
      • 将堆栈指针SSP对齐到8字节边界。
      • 将4字节0值存储到SSP - 4的位置。
      • 更新SSP以确保对齐(通过掩码操作对齐到8字节边界)。
    • 将寄存器和堆栈状态推送到影子栈:
      • oldCSoldRIP(补充为32位LIP)和SSP压入影子栈。
3. 结束分支跟踪
  • 如果启用结束分支(Endbranch)跟踪:
    • 根据CPL值设置结束分支状态:
      • 如果CPL为3(用户模式),设置IA32_U_CET.TRACKER = WAIT_FOR_ENDBRANCH,并清除SUPPRESS标志。
      • 如果CPL不为3(特权模式),则设置IA32_S_CET.TRACKER = WAIT_FOR_ENDBRANCH,并清除SUPPRESS标志。

总结:

SAME-PRIVILEGE部分,伪代码描述了在特权级别不变化时,如何处理不同大小的调用门(32位、16位、64位)。关键操作包括:

  1. 检查堆栈空间是否足够存放参数或返回地址。
  2. 验证调用门的指令指针是否在代码段的限制内。
  3. 根据调用门的大小更新堆栈、指令指针和返回地址。
  4. 影子栈的对齐与压栈,确保影子栈指针正确,并将状态信息推送到影子栈中。
  5. 结束分支跟踪,设置相应的跟踪器状态以等待结束分支事件。

这些步骤确保了在调用门过程中的堆栈操作和指令指针跳转安全有效,同时支持影子栈和结束分支跟踪等高级特性。

TASK-GATE 处理逻辑

这段伪代码描述了处理任务门(Task Gate)的过程,确保任务切换(task switch)操作的合法性。它检查任务门的权限级别、任务状态段描述符,并进行相关的错误处理。


步骤:

1. 权限检查
  • 检查任务门的权限级别(DPL)是否小于当前特权级别(CPL)或请求特权级别(RPL):
    • 如果是,触发#GP(task gate selector)异常。
  • 检查任务门是否存在:
    • 如果任务门不存在,触发#NP(task gate selector)异常。
2. 读取任务门描述符
  • 读取任务门描述符中的TSS(任务状态段)选择子:
    • 检查TSS选择子的本地/全局位是否设置为本地,或者索引是否超出了全局描述符表(GDT)的限制:
      • 如果是,触发#GP(TSS selector)异常。
3. 访问TSS描述符
  • 在GDT中访问TSS描述符:
    • 如果描述符不是TSS段,触发#GP(TSS selector)异常。
    • 如果TSS描述符指定TSS正在使用(busy),触发#GP(TSS selector)异常。
    • 如果TSS段不存在,触发#NP(TSS selector)异常。
4. 任务切换
  • 执行任务切换(Switching tasks)操作,并处理嵌套切换:
    • 检查EIP(指令指针)是否在代码段的有效范围内:
      • 如果EIP超出代码段的限制,触发#GP(0)异常。

总结:

TASK-GATE部分,伪代码确保了任务门的有效性和合法性,主要检查以下几个方面:

  1. 权限检查:确保任务门的权限级别合适,并且任务门存在。
  2. TSS选择子验证:验证TSS选择子是否有效,是否在正确的GDT范围内。
  3. TSS描述符检查:确保TSS段是有效的,且没有处于忙碌状态。
  4. 任务切换操作:在切换任务时,确保EIP在代码段的有效范围内,避免跳转到无效位置。

这些操作确保了任务门切换的安全性,防止了潜在的无效或非法任务切换。

TASK-STATE-SEGMENT 处理逻辑

这段伪代码描述了处理任务状态段(Task State Segment, TSS)的过程,确保任务切换的合法性和正确性。它检查TSS的权限、存在性以及切换到TSS后的EIP是否在有效范围内。


步骤:

1. 权限检查
  • 检查TSS的权限级别(DPL)是否小于当前特权级别(CPL)或请求特权级别(RPL):
    • 如果是,触发#GP(TSS selector)异常。
  • 检查TSS描述符是否指示TSS不可用:
    • 如果不可用,触发#GP(TSS selector)异常。
2. 检查TSS是否存在
  • 检查TSS是否存在:
    • 如果TSS不存在,触发#NP(TSS selector)异常。
3. 任务切换
  • 执行任务切换(Switching tasks)操作,并处理嵌套切换:
    • 切换到TSS时,执行任务切换,确保在切换过程中不发生错误。
4. EIP检查
  • 检查EIP(指令指针)是否在代码段的有效范围内:
    • 如果EIP超出代码段的限制,触发#GP(0)异常。

总结:

TASK-STATE-SEGMENT部分,伪代码确保了任务状态段切换的合法性,主要检查以下几个方面:

  1. 权限检查:确保TSS的权限级别合适。
  2. TSS的可用性和存在性:确保TSS存在且没有标记为不可用。
  3. 任务切换操作:在切换到TSS时,确保任务状态正确,切换过程中不会出错。
  4. EIP检查:确保EIP在代码段的有效范围内,避免跳转到无效的指令位置。

这些操作确保了TSS切换的安全性和有效性,防止了非法任务切换导致的系统崩溃或错误。

受影响的标志

如果发生任务切换,则所有标志都会受到影响;如果没有发生任务切换,则标志不受影响。

保护模式异常

#GP(0)  
如果目标偏移量在目标操作数中超出了新的代码段限制。  
如果目标操作数中的段选择子为NULL。  
如果门中的代码段选择子为NULL。  
如果内存操作数的有效地址超出了CS、DS、ES、FS或GS段限制。  
如果使用DS、ES、FS或GS寄存器访问内存,且该寄存器包含NULL段选择子。  
如果目标模式是兼容模式,且SSP不在低4GB范围内。  
如果IA32_PLi_SSP中的SSP(其中i是新的CPL)没有8字节对齐。  
如果新的影子栈上的“监督影子栈”标记为忙碌状态。  
如果目标模式是32位或兼容模式,但“监督影子栈”标记中的SSP地址超出了4GB。  
如果“监督影子栈”标记中的SSP地址与IA32_PLi_SSP中的SSP地址(其中i是新的CPL)不匹配。  

#GP(selector)  
如果代码段、门或TSS选择子的索引超出了描述符表的限制。  
如果目标操作数中的段选择子指向的段描述符不是符合代码段、非符合代码段、调用门、任务门或任务状态段。  
如果非符合代码段的DPL不等于CPL,或者段选择子的RPL大于CPL。  
如果符合代码段的DPL大于CPL。  
如果来自调用门、任务门或TSS段描述符的DPL小于CPL,或者小于调用门、任务门或TSS段选择子的RPL。  
如果来自调用门的段选择子的段描述符未指示它是代码段。  
如果来自调用门的段选择子超出了描述符表的限制。  
如果从调用门获得的代码段的DPL大于CPL。  
如果TSS的段选择子的本地/全局位被设置为本地。  
如果TSS段描述符指定TSS为忙碌或不可用。  

#SS(0)  
如果在没有发生堆栈切换的情况下,向堆栈压入返回地址、参数或堆栈段指针时,超出了堆栈段的边界。  
如果内存操作数的有效地址超出了SS段的限制。  

#SS(selector)  
如果在发生堆栈切换时,向堆栈压入返回地址、参数或堆栈段指针时,超出了堆栈段的边界。  
如果SS寄存器作为堆栈切换的一部分被加载,并且该段被标记为不可用。  
如果堆栈切换发生时,堆栈段没有足够的空间来容纳返回地址、参数或堆栈段指针。  

#NP(selector)  
如果代码段、数据段、调用门、任务门或TSS不可用。  

#TS(selector)  
如果新的堆栈段选择子和ESP超出了TSS的末尾。  
如果新的堆栈段选择子为NULL。  
如果TSS中的新堆栈段选择子的RPL不等于正在访问的代码段的DPL。  
如果新堆栈段的堆栈段描述符的DPL不等于代码段描述符的DPL。  
如果新的堆栈段不是可写的数据段。  
如果堆栈段的段选择子索引超出了描述符表的限制。  

#PF(fault-code)  
如果发生页面故障。  

#AC(0)  
如果启用了对齐检查,并且在当前特权级为3时发生未对齐的内存引用。  

#UD  
如果使用了LOCK前缀。  

实地址模式异常

#GP  
如果内存操作数的有效地址超出了CS、DS、ES、FS或GS段的限制。  
如果目标偏移量超出了代码段的限制。  

#UD  
如果使用了LOCK前缀。

虚拟8086模式异常

#GP(0)  
如果内存操作数的有效地址超出了CS、DS、ES、FS或GS段的限制。  
如果目标偏移量超出了代码段的限制。  

#PF(fault-code)  
如果发生页面故障。  

#AC(0)  
如果启用了对齐检查,并且发生了未对齐的内存引用。  

#UD  
如果使用了LOCK前缀。  

### 兼容模式异常

与保护模式中的异常相同。

#GP(selector)  
如果通过选择子访问的内存地址位于非规范空间。  

#GP(0)  
如果目标操作数中的目标偏移量是非规范的。  

### 64位模式异常

#GP(0)  
如果内存地址是非规范的。  
如果目标操作数中的目标偏移量是非规范的。  
如果目标操作数中的段选择子为NULL。  
如果64位门中的代码段选择子为NULL。  
如果目标模式是兼容模式且SSP不在低4GB范围内。  
如果IA32_PLi_SSP中的SSP(其中i是新的CPL)没有8字节对齐。  
如果新的影子栈上的“监督影子栈”标记为忙碌状态。  
如果目标模式是32位模式或兼容模式,但“监督影子栈”标记中的SSP地址超出了4GB。  
如果“监督影子栈”标记中的SSP地址与IA32_PLi_SSP中的SSP地址(其中i是新的CPL)不匹配。  

#GP(selector)  
如果代码段或64位调用门超出了描述符表的限制。  
如果代码段或64位调用门重叠了非规范空间。  
如果目标操作数中的段选择子指向的段描述符不是符合代码段、非符合代码段或64位调用门。  
如果目标操作数中的段选择子指向的段描述符是代码段,并且同时设置了D位和L位。  
如果非符合代码段的DPL不等于CPL,或者段选择子的RPL大于CPL。  
如果符合代码段的DPL大于CPL。  
如果来自64位调用门的DPL小于CPL或小于64位调用门的RPL。  
如果64位调用门的上部类型字段不是0x0。  
如果64位调用门的段选择子超出了描述符表的限制。  
如果从64位调用门获得的代码段的DPL大于CPL。  
如果64位门中选择子指向的代码段描述符未设置L位且清除了D位。  
如果从64位调用门的段选择子指向的段描述符未指示它是代码段。  

#SS(0)  
如果在没有发生堆栈切换的情况下,向堆栈压入返回偏移量或CS选择子时超出了堆栈段的边界。  
如果内存操作数的有效地址超出了SS段的限制。  
如果堆栈地址是非规范形式。  

#SS(selector)  
如果在发生堆栈切换时,向堆栈压入SS选择子、堆栈指针、EFLAGS、CS选择子、偏移量或错误代码的旧值时违反了规范边界。  

#NP(selector)  
如果代码段或64位调用门不可用。  

#TS(selector)  
如果加载新的RSP超出了TSS的限制。  

#UD (仅限64位模式)  
如果远程调用直接跳转到内存中的绝对地址。  
如果使用了LOCK前缀。  

#PF(fault-code)  
如果发生页面故障。  

#AC(0)  
如果启用了对齐检查,并且在当前特权级为3时发生了未对齐的内存引用。

文章作者: 曙光
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 曙光 !
  目录