我正在学习汇编语言,并且对调用约定和堆栈清理有疑问。
由于我使用OSX,我需要使用BSD调用约定进行系统调用,如下所示。
SECTION .text
push StringLen ; Push string length
push MyString ; Push the string
push 0x1 ; Push the file descriptor (stdout)
mov eax, 4 ; Push the system call (sys_write)
int 0x80 ; Call kernel dispatcher
add esp, 0x10 ; Clean up stack 16 bytes for 4DWORDS
mov eax, 1
mov ebx, 0
int 0x80 ; System exit
我的问题是add esp, 0x10
被认为是清理堆栈的好习惯吗?我已经进行了实验,在清理之后,它会显示值仍在堆栈中,但是当按下另一个值时会被覆盖,例如。
add esp, 0x10 ; Clean up stack 16 bytes for 4DWORDS
push 0x1A ; New push overwrites the previous stack values
我确信这在一个小程序中并没有太大的区别,但是如果它永远不会被覆盖,那么它是不是浪费了空间呢?
答案 0 :(得分:1)
但是,如果它永远不会被覆盖,那么它是不是浪费了空间?
不,这不是浪费空间。堆栈空间的16个字节将一遍又一遍地重复使用。
答案 1 :(得分:1)
是的,这种传递参数和清理它们的方法很好。但是,您的代码中存在一个问题:您只有三次推送但减去 16 字节。移至eax
不会被视为推送,因为它不会更改esp
。
在这个小程序中可能并不重要,但会在任何体面大小的程序中崩溃。所以修复0x10到0x0C 添加push eax
或sub esp, 4
以使其平衡并匹配BSD调用约定的要求。
另一种方法是预先保留堆栈空间并使用mov
来设置参数而不是push
es。
编辑:根据FreeBSD calling convention修改代码(额外推送)。
SECTION .text
; we only need 12 bytes (3 args) but BSD ABI needs one extra stack slot
sub esp, 0x10
mov eax, StringLen
mov [esp-C], eax
mov eax, StringLen
mov [esp-8], eax
mov dword [esp-4], 0x1 ; file descriptor (stdout)
mov eax, 4 ; system call number (sys_write)
int 0x80 ; make the syscall
mov dword [esp-4], 0 ; exit code
mov eax, 1 ; system call number (sys_exit)
int 0x80 ; System exit
; unreachable in this case but necessary for returning functions
add esp, 0x10 ; restore the stack at the end of the function
正如您所看到的,它避免在函数体的大部分时间内更改ESP
,但它会使代码变得更大,更冗长。由于你不能mov
内存到内存,你必须使用临时寄存器来变量。
出于某种原因,这种传递参数的方式对于GCC来说是默认的,并导致相当可测量的代码膨胀。