我认为我的真正问题是我不完全了解堆栈框架机制,因此我想了解为什么以下代码导致程序在应用程序结尾处恢复执行。
此代码是从C函数调用的,该函数具有多个调用级别,而pushf导致程序执行通过堆栈返回几个级别并完全退出程序。
由于我的工作按预期进行,所以我想知道为什么使用pushf指令似乎会(我认为)破坏堆栈。
在例程中,我通常使用以下命令设置和清理堆栈: sub rsp,28小时 ... 添加rsp,28小时
但是我注意到只有在汇编代码调用C函数时才需要这样做。
所以我尝试从两个例程中删除它,但是没有区别。 SaveFlagsCmb是一个汇编函数,但很容易成为宏。
该代码表示一个模拟的6809 CPU Rora(右旋转寄存器A)。
PUBLIC Rora_I_A ; Op 46 - Rotate Right through Carry A reg
Rora_I_A PROC
sub rsp, 28h
; Restore Flags
mov cx, word ptr [x86flags]
push cx
popf
; Rotate Right the byte and save the FLAGS
rcr byte ptr [q_s+AREG], 1
; rcr only affects Carry. Save the Carry first in dx then
; add 0 to result to trigger Zero and Sign/Neg flags
pushf ; this causes jump to end of program ????
pop dx ; this line never reached
and dx, CF ; Save only Carry Flag
add [q_s+AREG], 0 ; trigger NZ flags
mov rcx, NF+ZF+CF ; Flag Mask NZ
Call SaveFlagsCmb ; NZ from the add and CF saved in dx
add rsp, 28h
ret
Rora_I_A ENDP
但是,如果我使用此代码,它将按预期工作:
PUBLIC Rora_I_A ; Op 46 - Rotate Right through Carry A reg
Rora_I_A PROC
; sub rsp, 28h ; works with or without this!!!
; Restore Flags
mov ah, byte ptr [x86flags+LSB]
sahf
; Rotate Right the byte and save the FLAGS
rcr byte ptr [q_s+AREG], 1
; rcr only affects Carry. Save the Carry first in dx then
; add 0 to result to trigger Zero and Sign/Neg flags
lahf
mov dl, ah
and dx, CF ; Save only Carry Flag
add [q_s+AREG], 0 ; trigger NZ flags
mov rcx, NF+ZF+CF ; Flag Mask NZ
Call SaveFlagsCmb ; NZ from the add and CF saved in dx
; add rsp, 28h ; works with or without this!!!
ret
Rora_I_A ENDP
答案 0 :(得分:2)
您举报的行为并没有任何意义。 大多数情况下,这个答案只是提供一些背景知识而不是真正的答案,并且出于性能原因,建议不要首先使用pushf
/ popf
。
请确保您的调试工具能够正常工作,并且不会被某种东西所欺骗而误将“跳转”显示到某个地方。 (然后完全跳到哪里?)
几乎没有理由弄乱16位操作数的大小,但这可能不是您的问题。
在Visual Studio / MASM中,apparently (according to OP's comment) pushf
组合为pushfw
,66 9C
,后者会推送2个字节。大概popf
也汇编为popfw
,仅将2个字节弹出到FLAGS中,而不是将正常的8个字节弹出到RFLAGS中。其他汇编程序是不同的。 1
因此您的代码应该可以工作。除非您不小心在FLAGS中设置了其他一些中断执行的位?有bits in EFLAGS/RFLAGS other than condition codes,包括单步TF陷阱标志:每条指令后的调试异常。
我们知道您处于64位模式,而不是32位兼容模式,否则rsp
将不是有效的寄存器。而且以32位模式运行64位机器代码也无法解释您的观察结果。
我不确定这将如何解释pushf
是一个跳转到任何地方的方法。 pushf
本身不会出错或跳转,如果popf
设置了TF,则popf
之后的指令将导致调试异常。
您确定要汇编64位计算机代码 并以64位模式运行它吗?如果CPU以32位模式解码您的代码,则唯一不同的应该是sub rsp, 28h
上的REX前缀和[x86flags]
上的RIP相对寻址模式解码为绝对值(这大概是绝对的)故障)。因此,我认为这无法解释您所看到的内容。
确定要使用调试器来测试指令(不是源代码行或C语句)吗?
使用调试器单步查看机器代码。这看起来真的很奇怪。
无论如何,完全使用pushf
/ popf
,并且使用16位操作数大小创建false似乎是一个非常低性能的想法依赖性。
例如您可以使用movzx ecx, word ptr [x86flags]
/ bt ecx, CF
设置x86 CF。
您可以使用setc cl
此外,如果您要对来宾存储器中的字节执行多项操作,请将其加载到x86寄存器中。相对于load / rcr / ... / test reg,reg
/ store,内存目标RCR和内存目标ADD不必要地变慢。
LAHF / SAHF可能有用,但是在许多情况下,也可以不用它们。 popf
的运行速度很慢(https://agner.org/optimize/),它强制通过内存进行往返。但是,在x86标志的低8之外有一个条件代码:OF
(有符号溢出)。与8080的asm源兼容性在2019年仍会损害x86:(
您可以使用add al, 127
从0/1整数恢复OF:如果AL最初是1
,它将溢出到0x80
,否则不会恢复。然后,您可以使用SAHF恢复其余条件代码。您可以使用seto al
提取OF。或者,您可以只使用pushf / popf。
; sub rsp, 28h ; works with or without this!!!
是的。您有一个不使用任何堆栈空间的叶子函数。
如果要从该函数调用另一个函数,则只需要再保留40个字节(对齐堆栈+ 32字节的阴影空间)即可。
在其他汇编程序中的pushf / popf :
在NASM中,pushf/popf
的默认宽度与其他推入/弹出指令的宽度相同:64位模式下为8字节。您将获得没有操作数大小前缀的普通编码。 (https://www.felixcloutier.com/x86/pushf:pushfd:pushfq)
就像整数寄存器一样,pushf / popf的16位和64位操作数大小都可以在64位模式下进行编码,但32位操作数大小则不能。
在NASM中,您的代码将被破坏,因为push cx
/ popf
会压入2个字节并弹出8,将您的返回地址的6个字节弹出到RFLAGS中。
但是,显然MASM不是那样的。无论如何都要使用显式操作数大小的说明符(例如如果使用pushfw
和popfw
)是一个好主意,以确保您获得66 9C
编码,而不仅仅是{{1 }}。
更好的是,像普通人一样使用9C pushfq
和pushfq
:仅在需要时才写入8或16位部分寄存器。