【问题标题】:x86 assembler pushf causes program to exitx86 汇编器 pushf 导致程序退出
【发布时间】:2019-08-19 01:17:19
【问题描述】:

我认为我真正的问题是我不完全了解堆栈帧机制,因此我希望了解为什么以下代码会导致程序执行在应用程序结束时恢复。

这段代码是从一个有几个调用级别的 C 函数调用的,pushf 导致程序执行通过堆栈返回几个级别并完全退出程序。

由于我的工作按预期工作,我想知道为什么使用 pushf 指令似乎(我认为)会破坏堆栈。

在例程中,我通常设置和清理堆栈: 副 rsp,28 小时 ... 添加 rsp, 28h

但我注意到只有在汇编代码调用 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

【问题讨论】:

    标签: visual-studio assembly x86 masm


    【解决方案1】:

    您报告的行为没有真正的意义。 大多数情况下,这个答案只是提供一些背景而不是真正的答案,并建议出于性能原因首先不要使用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 Trap Flag: debug exception after every instruction。

    我们知道您处于 64 位模式,而不是 32 位兼容模式,否则 rsp 将不是有效的寄存器。在 32 位模式下运行 64 位机器代码也无法解释您的观察结果。

    我不确定这将如何解释 pushf 跳转到任何地方。 pushf本身不能出错也不能跳转,如果popf设置TF那么popf后面的指令就会导致调试异常。

    您确定要汇编 64 位机器代码在 64 位模式下运行它吗?如果 CPU 在 32 位模式下解码您的代码,唯一不同的是sub rsp, 28h 上的 REX 前缀,以及[x86flags] 上的 RIP 相对寻址模式绝对解码(这可能会出错)。所以我认为这不能解释你所看到的。

    您确定要使用调试器单步执行指令(不是源代码行或 C 语句)来测试吗?

    使用调试器单步查看机器代码。这看起来很奇怪。


    无论如何,使用pushf / popf 似乎是一个非常低性能的想法,而且使用 16 位操作数大小会创建错误的依赖关系。 p>

    例如您可以使用movzx ecx, word ptr [x86flags] / bt ecx, CF 设置 x86 CF。

    您可以使用setc cl捕获输出CF

    此外,如果您要对来宾内存中的字节执行多项操作,请将其加载到 x86 寄存器中。内存目标 RCR 和内存目标 ADD 与 load / rcr / ... / test reg,reg / store 相比,速度太慢了。

    LAHF/SAHF 可能有用,但在很多情况下您也可以不使用它们。 popf 非常慢(https://agner.org/optimize/),它强制在内存中进行往返。但是,在 x86 FLAGS 的低 8 位之外有一个条件代码:OF(有符号溢出)。 asm-source 与 8080 的兼容性在 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 默认与其他 push/pop 指令的宽度相同: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 编码,而不仅仅是 9C pushfq

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

    【讨论】:

    • @ Peter Cordes:我绝对是单步执行。我在推/弹时没有注意rsp的变化量。我会告诉你。感谢您的 bt 指令提示。我早该知道的!
    • 我无法通过 pushf 指令逐步完成程序集,因此我无法确定它对 rsp 做了什么。我在 64 位模式下运行!当我尝试跳过 pushf 指令时,发生了两件事之一。 1.它会立即退出程序和调试器! 2. 无论我尝试多少次,它都不会超过指令,唯一的副作用是 rax 寄存器每次都增加 1。
    • @Walter:你用的是什么调试器?另外,您看到pushf 的机器代码是什么?尝试在调试器中使用反汇编模式来单步执行机器代码,也许这会有所帮助。
    • @Peter:Visual Studio 2015。反汇编将不得不等待(几个小时),直到我回到系统。我确实简要地查看了反汇编,它向我显示了 pushf 和助记符的内存位置,但我无法识别操作码。给我一个小时来截屏!
    • @彼得:好的。在反汇编窗口中,您需要显式启用查看代码字节! pushf 编码为 66 9C ,它推送 8 个字节!!!我在几个地方都有这种逻辑,幸运的是没有失败,但我知道我会回去改变它。非常感谢!
    猜你喜欢
    • 1970-01-01
    • 2015-12-01
    • 2011-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-30
    相关资源
    最近更新 更多