4

我正在尝试掌握 FreeBSD 中的汇编程序。在手册的 UNIX 过滤器代码示例中,寄存器 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

这与文档上一页的示例不同:

 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 中这是必要的吗?我想象堆栈指针会自行处理。

4

1 回答 1

3

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 不会返回,它只是停止整个过程。因此,在这里恢复堆栈位置并不重要。

于 2017-06-17T08:34:28.083 回答