x86汇编程序pushf导致程序退出

时间:2019-08-19 01:17:19

标签: visual-studio assembly x86 masm

我认为我的真正问题是我不完全了解堆栈框架机制,因此我想了解为什么以下代码导致程序在应用程序结尾处恢复执行。

此代码是从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

1 个答案:

答案 0 :(得分:2)

您举报的行为并没有任何意义。 大多数情况下,这个答案只是提供一些背景知识而不是真正的答案,并且出于性能原因,建议不要首先使用pushf / popf

请确保您的调试工具能够正常工作,并且不会被某种东西所欺骗而误将“跳转”显示到某个地方。 (然后完全跳到哪里?)


几乎没有理由弄乱16位操作数的大小,但这可能不是您的问题。

在Visual Studio / MASM中,apparently (according to OP's comment) pushf组合为pushfw66 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

捕获输出CF

此外,如果您要对来宾存储器中的字节执行多项操作,请将其加载到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不是那样的。无论如何都要使用显式操作数大小的说明符(例如如果使用pushfwpopfw)是一个好主意,以确保您获得66 9C编码,而不仅仅是{{1 }}。

更好的是,像普通人一样使用9C pushfqpushfq:仅在需要时才写入8或16位部分寄存器。