X.86 X64 汇编器中的正确堆栈操作

Posted

技术标签:

【中文标题】X.86 X64 汇编器中的正确堆栈操作【英文标题】:Correct Stack Manipulation in X.86 X64 assembler 【发布时间】:2018-11-11 03:36:00 【问题描述】:

所以在阅读了 x64 架构快速入门指南后,我编写了一些汇编程序。

https://software.intel.com/en-us/articles/introduction-to-x64-assembly

从 C 调用汇编器函数。反过来,汇编器调用 C 函数。

我不确定堆栈机制是如何工作的,因为我似乎多次破坏堆栈。

以下代码演示:

PUBLIC Lbra_R_A ; Op 16 - Long Branch Always
Lbra_R_A PROC
    sub rsp, 28h
    push rbx ; must preserve rbx
    ; Calc destination branch address by Adding the two Bytes at [PC+1] (high byte) and [PC+2] (low byte) with PC+2 reg
    ; Get first byte high byte
    movzx rcx, word ptr [pc_s]
    mov rbx, rcx ; save pc_s into temp
    inc bx ; inc temp pc_s
    call MemRead8_s ; returns byte in ax (al)
    push ax ; save high byte
    ; Get second byte low byte @ pc_s
    mov rcx, rbx
    inc bx ; inc temp pc_s
    call MemRead8_s ; returns byte in ax (al) - this call destroys saved high byte???
    ; combine low and high bytes to make 16 bit 2 complements offset
    pop dx ; get saved high byte - wrong value
    mov ah, dl ; move the high byte to high position ; ax now contains 16 bit offset
    add bx, ax ; bx now contains pc_s(+2) + offset
    mov word ptr [pc_s], bx
    pop rbx ; must restore rbx - wrong value???
    add rsp, 28h
    ret
Lbra_R_A ENDP

我用 sub rsp, 28h 设置堆栈,但我不知道为什么,我不知道我可以在那个 28h 字节区域做什么!!!是给我的还是保留的。但是没有这个我的代码甚至不能运行!!!

接下来我保留 rbx 寄存器,因为它被认为是非易失性的。但是最后当我恢复 rbx 时它和我保存的不一样???

在调用名为 MemRead8_s(由我提供)的 C 函数之前,我保存/推送 ax 寄存器。但是,当我调用该函数时,现在存储在堆栈中的 ax 的值被覆盖了,所以当我稍后尝试恢复它的几条指令时,它是错误的!!!这个调用前后的rsp值好像是一样的,那么调用这个函数对栈做了什么?

谁能解释一下正确的堆栈设置协议是什么,并可能解释为什么我的堆栈保存会损坏?

【问题讨论】:

您的函数拥有低于 RSP 初始值(在函数入口处)和高于 RSP 当前值的堆栈空间。所以sub rsp, 28h 分配了 0x28 字节的堆栈空间(并将堆栈对齐 16,您使用 16 位推送来打破它。不要使用 16 位推送/弹出;保存/恢复完整的 64 位寄存器。@ 987654325@ 可以假设 RSP 在为其推送返回地址的call 之前是 16 字节对齐的。) 16 位还是字节?我认为推 AX 是 16 位推!!!为什么我的 push rbx 出错了? 是的,push ax 推送 2 个字节,打破了 sub rsp, 28h 创建的 16-byte 堆栈对齐。 所以如果我想保留 rbx reg 和 ax 可以/应该将它们存储在我保留的 28h 字节区域中吗?我可以安全地使用这个区域吗? 是的,这将是一个好计划。或者 push rbx 在函数的开头(在 sub rsp, 20h 之前)和最后的 pop 也会很有效。你不需要保存/恢复ax;您的函数被允许破坏该寄存器。查找您正在使用的调用约定,以找出哪些 reg 是调用保留的与调用破坏的。实际上,您不需要使用 16 位寄存器。在 32 或 64 位代码中使用 32 位 eax。有关文档的链接,请参阅 ***.com/tags/x86/info。 【参考方案1】:

您的函数拥有低于 RSP 初始值(在函数入口处)和高于 RSP 当前值的堆栈空间。

在 Windows 上,您的函数还拥有高于返回地址的 32 个字节(影子空间)。确保在调用另一个函数之前保留此空间,除非它是不使用影子空间的私有辅助函数。

在 Linux 和其他非 Windows 上,x86-64 System V ABI 表示您的函数拥有低于当前 RSP(红色区域)的 128 个字节。显然push 或函数调用会踩到该空间,因此在叶函数中避免 RSP 的 sub/add 最有用。但它不会被信号处理程序破坏。


所以sub rsp, 28h 分配了 0x28 字节的堆栈空间(并将堆栈对齐 16 个字节,因为它在您的调用者中的 call 推送返回地址之前是 16 字节对齐的。(您用 16 位(2 字节)推送打破这种对齐,这很糟糕)。ret 基本上是pop rip,这就是为什么在运行ret 之前必须将堆栈恢复到其原始值的原因。

不要使用 16 位推送/弹出;保存/恢复完整的 64 位寄存器。 MemRead8_s 被允许假设 RSP 在为其推送返回地址的调用之前是 16 字节对齐的,你很幸运它碰巧不依赖堆栈对齐。)

【讨论】:

以上是关于X.86 X64 汇编器中的正确堆栈操作的主要内容,如果未能解决你的问题,请参考以下文章

64位的汇编怎么搞

如何在 x86-64 汇编中使用堆栈?

x64 汇编中的递归阶乘子例程导致堆栈溢出

如何在 JCL/汇编器中为 QSAM put-locate 正确声明 DCB

x86平台转x64平台关于内联汇编不再支持的解决

x86_64 汇编器的 gcc 错误(操作码 0x83 cmp m64/imm8)