在相同权限级别从兼容模式切换到 64 位模式时,是否会发生隐式堆栈切换?

Will an implicit stack switch occur when switching from compatibility mode to 64-bit mode at the same privilege level?

通过far call从兼容模式切换到同权限级别的64位模式时,段寄存器中的BASE或LIMIT等字段将被忽略,64位指针寄存器都可用。但是,如果忽略 BASE 字段,堆栈基地址将隐式变为 0x0,这意味着将引发隐式堆栈切换,对吗? 如果我的理解是正确的,CPU在通过ret指令返回时如何切换回原始堆栈?

请注意,在 x86 术语中,“堆栈切换”具有特定的技术含义,涉及从 TSS 加载新的 SS:[ER]SP,例如当 user-space 运行 int 指令时。你说的不是,只是 ss.base + esp 可能不同于 0 + rsp


是的,我认为这是正确的,在不寻常的情况下,您的 32 位代码有 SS.base != 0.

所有主流操作系统都使用 CS/DS/ES/SS 基数全为零(且限制=-1)的平面内存模型,因此这是 non-issue.

顺便说一句,Windows x64 实际上在其 DLL 中执行此 user-space 模式切换,作为其 WoW64 的一部分(即 Windows(32 位)在 Windows64). user-space 不是使用 sysenter 或任何直接调用内核的方法,而是使用 sycall.

生成远 call 到 64 位的代码

如果 32 位代码是 运行 non-zero SS 基础,那将不那么方便。 (我不确定 CS:EIP return 地址是否会被推送到 ss.base + ESP[RSP];如果是后者,64 位仍然是可能的如果在 rspss.base + rsp 处分配了内存,则实际 return 的代码。)

Will an implicit stack switch occur when switching from compatibility mode to 64-bit mode at the same privilege level?

在相同权限级别从兼容模式切换到64位模式时不会发生隐式堆栈切换;除非它是通过使用 IST 机制的中断来完成的。

However, if the BASE field is ignored, the stack base address will become 0x0 implicitly, which means an implicit stack switch will be raised, am I right?

对于 80x86;通常,线性地址是通过将段内的偏移量添加到段的基址来计算的,其中该段的基址存储在段寄存器的“隐藏”部分中。例如,在 32 位代码中,如果您执行 mov eax,[esp],则 CPU 计算“linear_address = SS.base + ESP”。

因为大多数操作系统使用“平面内存模型”,其中段被有效禁用(通过将所有段基数设置为零并将所有段限制设置为“最大”); CPUs 优化了这种特殊情况,如果已知段基数为零,则跳过添加(例如,mov eax,[esp] CPU 可能作弊并执行“linear_address = ESP " 如果它已经知道 SS.base 为零)。

对于64位代码,不包括FS和GS段寄存器,段寄存器的“隐藏”部分(用于段基址)中的值无论是否为零都假定为零;并且 CPU 知道段基数总是“假设为零”并且总是优化地址计算(通过不添加段基数)。

在相同权限级别从兼容模式切换到64位模式时;有两种可能性:

a) SS 的段基址在兼容模式下为零,在 64 位中变为“假定为零”,因此堆栈地址不变(见注释)。

b) SS 的段基址在兼容模式下是 non-zero,在 64 位中变为“假定为零”,因此堆栈地址确实发生了变化。

后一种可能性(non-zero 兼容模式下的 SS 段基数)是我强烈避免的;因为它对程序员来说非常混乱,并且具有 quirks/errata 的“高于正常”风险(例如,未来 CPU 以稍微不同的顺序做事并将 return 信息存储在“ss.base + esp" 而不是 "0 + esp").

注意:在 64 位代码中,任何仅更新寄存器下半部分的内容都会导致 64 位寄存器的上半部分被清零(例如,将值 0x9ABCDEF0 加载到 ESP 中将导致 RSP 被设置到 0x000000009ABCDEF0)。我认为这不会发生在兼容模式下(或者至少,我不认为它一定会发生)。这可能会造成这样一种情况,即当您切换回 64 位时,在切换到兼容模式之前留在 RSP 上半部分的“垃圾”仍然存在(例如,可能导致堆栈地址从 0x9ABCDEF0 更改为 0x123456789ABCDEF0)。

SDM 确实使用术语“隐式堆栈切换”来描述此行为,但仅限于使用调用门执行调用时。

... when using a call gate to perform a far call to a segment at the same privilege level, an implicit stack switch occurs as a result of entering 64-bit mode. The SS selector is unchanged, but stack segment accesses use a segment base of 0x0, the limit is ignored, and the default stack size is 64 bits. The full value of RSP is used for the offset, of which the upper 32 bits are undefined.

尽管 SDM 没有描述在不使用调用门的情况下执行调用时的行为,但我观察到的行为是相同的。 return 地址 (cs:rip) 被推送到新的堆栈(忽略 ss.base),并且对 return 的 32 位代码工作。当然,32 位代码继续像往常一样使用 ss.base。

(我测试RSP的高位在进入兼容模式前为0,调用回64位模式时一直为0。)

这是 运行 执行以下操作的程序后的内存转储:
ss.base = 0x80
esp = 8385f118
推 32323232
推 esp
远调用 64 位代码(return 地址是 20:776bb1e2)
推 64646464
推送响应
远调用 32 位代码(return 地址是 38:776bb208)
推 30303030
推 esp
添加 esp, 8
远距离
添加响应,10
远距离
推 31313131
推 esp

8385f0f0: 00000000 00000000 8385f100 00000000
8385f100: 64646464 00000000 776bb1e2 00000020
8385f110: 00000000 00000000 00000000 00000000
8385f120: 00000000 00000000 00000000 00000000

8385f160: 00000000 00000000 8385f0ec 30303030
8385f170: 776bb208 00000038 00000000 00000000
8385f180: 00000000 00000000 8385f10c 31313131
8385f190: 8385f114 32323232 00000000 00000000