在MASM64中,如果我编写指令push 0
,它将在堆栈上立即推送64位(即RSP = RSP - 8)。
所以如果我只想按一个 16 位立即来设置 FLAGS,我不知道,只能写机器代码,例如:
.code
FlagFunction PROC
dd 00006866h; push a 16-bit immediate 0
popf
ret
FlagFunction ENDP
END
该程序有效,但我想知道 MASM64 中是否有实际说明。
64 位和 16 位(但不是 32 位)推送在 64 位模式下都是可能的。但通常您只需要 64 位堆栈操作。
MASM 支持 16 位推送的两种语法。 (我用jwasm -Zne
测试以禁用 MASM 不支持的扩展,因为我本身没有 MASM):
pushw 123 ; can assemble to 66 6a 7b push sign_extended_imm8
push word ptr 123 ; JWASM uses 66 68 7b 00 push imm16
在我看来,将ptr
用于即时似乎很疯狂;我本以为这是使用绝对寻址模式推送内存源操作数的语法,但这push word ptr [123]
. MASM 语法通常没有意义。
(使用word ptr
与pushw
可能是JWASM独有的,将其视为NASMpush strict word 123
的等价物。阿格纳雾的objconv -fmasm
拆解66 6A 7B
push word ptr 123
. 更喜欢pushw
因为 JWASM。
在 NASM 中它是push word 123
,在 GAS中.intel_syntax noprefix
它是pushw 123
. GAS Intel 语法类似于 MASM,也以相同的方式组装push word ptr 123
。 AT&T语法当然是pushw $0x1234
;操作数大小后缀是 AT&T 语法的标准,而具有隐式内存操作数的指令则是一种特殊情况。
设置标志/RFLAGS
如果您只需要修改 FLAGS 的低 8 位(OF
以外的条件代码),请使用mov eax, 0x00003400
/sahf
- 将 AH 存储到 Flags 中。 或者例如lahf
/or ah, 1
/sahf
以低效地模拟stc
(设置携带标志)。
要设置 RFLAGS,您需要push 0x1234
(符号扩展 imm32 的 qword 推送)/popfq
。 FLAGS是RFLAGS的低16位。 (https://en.wikipedia.org/wiki/FLAGS_register)。
堆栈操作将始终影响 RSP,而不是 ESP。
MASM/JWASM将popf
组装为16位流行音乐,而不是该模式的默认大小,因此您需要popfq
。 不幸的是,您甚至无法使用popfw
来明确它,您需要注释。 (或者使用更好的汇编程序,如 NASM,其中pushf
/popf
使用与push 123
相同的默认操作数大小。
如果你想避免在 FLAGS 的前 16 位中用零写入保留位和特殊位(即只修改 FLAGS 而不接触其余的 RFLAGS/EFLAG),你可以使用这种低效的方法(从包含最近狭窄存储的宽负载中存储转发停顿。 无论如何,popf
和popfq
都很慢,因为微码必须查看您是否正在设置/清除像 IF 这样的特殊标志。 (https://agner.org/optimize/,例如 Zen 3/4 上的 13 个周期,Skylake 上的 20 个周期。
pushfq ; qword push
mov word ptr [rsp], 0x1234 ; modify the low word
popfq ; qword pop
或者使用 16 位推送,如果临时堆栈未对齐是安全的(见下文)
pushw 0x1234
popf ; popfw
在英特尔 CPU 上解码时,push imm16
编码有一个 LCP 停滞。 如果无法避免在性能很重要的代码中使用popf
或popfq
,则可以考虑mov eax, 0x1234
/push ax
。 或者不是,因为 LCP 停滞仅在旧解码期间发生,而不是来自 uop 缓存。
Windows 使得即使暂时将 RSP 错位 8 也不安全?
Joshua 评论说,如果 RSP 未对齐(不是 8 的倍数),Windows 可能会随机使您的进程崩溃。 我不知道这样做的机制,但是如果可以在您的代码中提供SEH异常,也许可以交付SEH异常?
Joshua 建议,如果 2 字节push
需要增长堆栈,它可能会崩溃,因为您将在 RSP 未对齐的情况下进入堆栈增长处理程序。 并且可能还有其他可能的机制可能无法通过确保这不是堆栈有史以来增长的最深的机制来修复。
我们知道普通的 Windows 代码可以在函数序言/尾声中使用 8 字节push
/pop
,因为编译器这样做,因此它与函数调用约定所需的 16 对齐方式不同。
我似乎想起了一些关于堆栈展开元数据的事情,要求 Windows x64 函数在其序言和尾声期间只修改 RSP,但我认为 Windows 的 C 编译器确实支持alloca
所以这不能完全正确。 当然,alloca
将四舍五入堆栈调整以保持 RSP 对齐。 可能只有在不使用 RBP 作为帧指针时才适用在函数中间根本不移动 RSP 的要求。 如果有人有权威的东西,我可以链接:在Windows程序中使用RSP做什么是安全的,请告诉我。
如果 Linux 在向 RSP 未对齐 8 的线程传递信号时遇到任何问题,我会感到惊讶。 (或其他任何东西)。ABI保证涉及函数输入的RSP % 16 == 8
(Windows也是如此),因此信号堆栈处理必须重新对齐,因为信号可以在任何两个指令之间的任何点传递,并且代码绝对可以使用push-qword(Windows也可以)。我假设内核使用类似user_regs.rsp -= 128;
//保留红区user_regs.rsp &= -16;
//对齐
根据英特尔手册,这里: https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
允许在 64 位模式下推送 imm16。 使用 x86-64 程序集中的push imm16
指令将 16 位即时值推送到堆栈时,它不会为零扩展。push imm16
指令将 RSP 调整为 2 而不是 8,并且仅将 16 位值推送到堆栈上。 这是 CPU 使用的算法:
IF StackAddrSize = 64
THEN
IF OperandSize = 64
THEN
RSP := RSP – 8;
Memory[SS:RSP] := SRC; (* push quadword *)
ELSE IF OperandSize = 32
THEN
RSP := RSP – 4;
Memory[SS:RSP] := SRC; (* push dword *)
ELSE (* OperandSize = 16 *)
RSP := RSP – 2;
Memory[SS:RSP] := SRC; (* push word *)
FI
<</div> div class="one_answers">据我所知,PUSH和POP总是在x64中使用QWord