assembly - 为什么在 64 位模式下使用“DS:”段覆盖是非法的?
问题描述
我有几行非常简单的汇编代码,它围绕一些数据移动,如下所示:
mov rax,qword ptr gs:[60]
mov rcx,qword ptr ds:[rax+20]
mov rax,qword ptr gs:[60]
mov rcx,qword ptr ds:[rax+20]
mov rbx,qword ptr ds:[rcx+28]
但是,编译器返回
错误 A2202:非法使用段寄存器
我觉得这可能是我的编译器或版本的问题,任何指针都会有帮助
解决方案
cs
/// 64ds
位模式的机器码中允许es
段ss
覆盖前缀。但是一些组装商选择不允许它们。
但请注意,它们实际上对带有内存操作数的指令没有影响。这可能就是为什么 masm 显然选择不允许它,以使您免于在此类前缀上浪费空间的“错误”。其他汇编程序,如 NASM,确实允许这样做。
所有这些段都具有相同的固定基地址 = 0,甚至不更改会引发哪个异常。
英特尔手册 vol.1 3.3.7.1 规范寻址:
如果指令使用基址寄存器 RSP/RBP 并使用段覆盖前缀来指定非 SS 段,则规范错误会生成 #GP(而不是 #SS)。在 64 位模式下,只有 FS 和 GS 段覆盖适用于这种情况。其他段覆盖前缀(CS、DS、ES 和 SS)被忽略。请注意,这也意味着应用于“非堆栈”寄存器引用的 SS 段覆盖将被忽略。这样的序列仍然会为规范错误生成#GP(而不是#SS)。
例如,如果mov eax, ds:[rbp]
来自非规范地址的故障(高 16 位不是低 48 的符号扩展),它仍然是#SS 故障(因为基址寄存器是 RBP 或 RSP),而不是#GP。DS 前缀确实被忽略了。
您可以区分的唯一另一种方法是是否可以给 DS 一个段选择器,如果任何加载或存储尝试使用它,它将出错,然后运行mov eax, [ds:rbp]
. 但我不确定这是否可能。mov ds, eax
尝试在 Linux 中设置非零值时,我立即遇到了段错误。NULL 段选择器对 DS 和 ES “有效”。鉴于 NASM 和 FASM 的汇编器警告/行为,这些汇编器的开发人员至少认为段前缀确实没有意义。
fs
并且gs
是唯一在 64 位模式下执行任何操作的段覆盖。
它们可以具有非零基地址。
CS/DS/ES/SS 仅用于填充以使指令更长(出于对齐原因)。 在现代 x86 上可以使用哪些方法来有效地扩展指令长度?
请注意,这ds
已经是除rbp
/之外的所有基址寄存器的默认段,因此即使没有被忽略rsp
,显式ds
前缀也不会改变指令的含义。如果您的汇编程序拒绝接受mov rcx,qword ptr ds:[rax+20]
,那么mov ecx, dword ptr ds:[eax+20]
在 32 位模式下也拒绝接受是合理的。
对于某些汇编程序(例如 NASM),在 asm 源代码行中显式提及段会在指令上发出段前缀,即使它已经是默认值。 如果这就是 MASM 的工作方式,那么将其用于“文档”目的并不是一个好主意。
如果您确实想为填充发出 DS 前缀,只需显式编码它们:
mov rax, gs:[60]
db 3Eh ; ds prefix
mov rcx, [rax+20]
其他组装商(GAS、NASM)
有趣的是,在 GAS .intel_syntax noprefix
(类似于 MASM)中,ds:[rax+20]
组装时没有明确的前缀(因为它已经是默认设置),但ss:[rax+20]
确实发出了前缀。所以 GAS 不假设 cs/ds/es/ss 即使在 64 位代码中也是等价的。
NASM 警告:segments.asm:5: warning: ds segment base generated, but will be ignored in 64-bit mode
mov rax, [gs: 60] ; NASM requires the seg: to be
mov rcx, [ds: rax+20] ; inside the [] if present
mov rcx, [rax+20]
用 NASM 组装到这个(objdump -drwC -Mintel
输出):
4000b0: 65 48 8b 04 25 3c 00 00 00 mov rax,QWORD PTR gs:0x3c
4000b9: 3e 48 8b 48 14 mov rcx,QWORD PTR ds:[rax+0x14]
4000be: 48 8b 48 14 mov rcx,QWORD PTR [rax+0x14]