为什么要在FreeBSD中重置堆栈指针寄存器?

时间:2017-06-16 13:25:27

标签: assembly x86 nasm freebsd stack-pointer

我试图掌握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中是否必要,其中参数不在堆栈上传递?我想象堆栈指针会照顾好自己。

1 个答案:

答案 0 :(得分:3)

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

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