我试图掌握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中是否必要,其中参数不在堆栈上传递?我想象堆栈指针会照顾好自己。
答案 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不会返回,它只是停止整个过程。因此,恢复堆栈位置在这里并不重要。