【问题标题】:Why reset the stack pointer register in FreeBSD?为什么要在 FreeBSD 中重置堆栈指针寄存器?
【发布时间】:2017-06-16 13:25:26
【问题描述】:

我正在尝试掌握 FreeBSD 中的汇编程序。在handbook's code example for a UNIX filter 中,寄存器 esp 在每次系统调用后都会重置。有问题的代码是:

%include    'system.inc'

section .data
hex db  '0123456789ABCDEF'
buffer  db  0, 0, ' '

section .text
global  _start
_start:
    ; read a byte from stdin
    push    dword 1
    push    dword buffer
    push    dword stdin
    sys.read
    add esp, byte 12        ; <--------- Is this necessary?
    or  eax, eax
    je  .done

    ; convert it to hex
    movzx   eax, byte [buffer]
    mov edx, eax
    shr dl, 4
    mov dl, [hex+edx]
    mov [buffer], dl
    and al, 0Fh
    mov al, [hex+eax]
    mov [buffer+1], al

    ; print it
    push    dword 3
    push    dword buffer
    push    dword stdout
    sys.write
    add esp, byte 12        ; <--------- Is this necessary?
    jmp short _start

.done:
    push    dword 0
    sys.exit

这与previous page of the documentation 上的示例不同:

 1: %include    'system.inc'
 2:
 3: section .data
 4: hello   db  'Hello, World!', 0Ah
 5: hbytes  equ $-hello
 6:
 7: section .text
 8: global  _start
 9: _start:
10: push    dword hbytes
11: push    dword hello
12: push    dword stdout
13: sys.write               ; <--------- ESP not adjusted after this. Why?
14:
15: push    dword 0
16: sys.exit

为什么这两个例子不同?为什么需要add esp, byte 12 之类的东西?系统调用没有弹出值吗?在没有在堆栈上传递参数的 64 位 FreeBSD 中这是必要的吗?我以为堆栈指针会自行处理。

【问题讨论】:

  • 能否请您引用您感到困惑的代码,或者至少提供一个链接?
  • “寄存器 esp 已重置”是什么意思?你如何“重置”一个寄存器?您的意思是保存并恢复原始值吗?你的意思是一个值被添加到ESP,就像你清理堆栈一样?还是别的什么?
  • 在 32 位代码中,int 0x80 用于系统调用。 int 0x80 的调用约定要求将参数压入堆栈,int 0x80 要求调用者自己恢复堆栈。如果您将参数推送到int 0x80 的堆栈上,则必须在之后回收空间。在 64 位代码中,系统调用是通过 syscall 指令完成的。在这种情况下,系统调用参数都在寄存器中传递。在这种情况下,无需在 syscall 指令之后调整 RSP
  • 我查看了 FreeBSD 文档的上一页。我知道您对他们显示的代码的困惑在哪里。现实情况是,之后未调整 ESP 的代码可能被认为是正确的,但通常会养成一个坏习惯。它还使他们的文档不一致。在这些简单的情况下,add esp, byte xxx 不是必需的,因为它们在完成时不会返回到调用函数。他们正在执行一个 sys.exit 系统调用,这将终止该过程。系统退出调用后的代码将永远不会返回,因此堆栈的状态无关紧要。
  • @MichaelPetch:感谢您的确认。我还认为,出于一致性原因,他们也应该将其添加到第一个示例中。

标签: assembly x86 nasm freebsd stack-pointer


【解决方案1】:

FreeBSD 使用调用者在另一个函数调用后清除堆栈的调用约定。调用后,堆栈包含所有函数参数。通过在函数调用后立即调整其位置来从函数参数中清除堆栈的请求是实现适当堆栈维护的最简单方法。但这不是唯一的方法。例如,你可以写:

; print a first thing
push    dword len1
push    dword buffer1
push    dword stdout
sys.write
; here, 12 bytes in stack aren't retired yet
; print a second thing
push    dword len2
push    dword buffer2
push    dword stdout
sys.write
add esp, byte 24 ; clean stack after _both_ write() calls

这种优化确实用在了,例如在 GCC 中。

如果您从函数中返回,则应在所有函数调用后恢复堆栈位置。但是,如果所有堆栈操作都是正确的,那么它究竟是如何完成的,完全取决于您。

那么,最后一个示例(使用 sys.exit)有什么特别之处?特殊之处在于您不会从中返回。 Sys.exit 不会返回,它只是停止整个过程。所以,这里恢复堆栈位置并不重要。

【讨论】:

  • 或者不用push堆积更多的args,你(或gcc)可以使用mov存储到已经保留的空间。
猜你喜欢
  • 2021-06-10
  • 1970-01-01
  • 2019-12-23
  • 2012-12-10
  • 2023-01-31
  • 2014-03-02
  • 1970-01-01
  • 2013-06-30
  • 1970-01-01
相关资源
最近更新 更多