BSD堆栈清理

时间:2013-03-14 15:13:31

标签: assembly stack calling-convention

我正在学习汇编语言,并且对调用约定和堆栈清理有疑问。

由于我使用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

我确信这在一个小程序中并没有太大的区别,但是如果它永远不会被覆盖,那么它是不是浪费了空间呢?

2 个答案:

答案 0 :(得分:1)

  

但是,如果它永远不会被覆盖,那么它是不是浪费了空间?

不,这不是浪费空间。堆栈空间的16个字节将一遍又一遍地重复使用。

答案 1 :(得分:1)

是的,这种传递参数和清理它们的方法很好。但是,您的代码中存在一个问题:您只有三次推送但减去 16 字节。移至eax不会被视为推送,因为它不会更改esp

在这个小程序中可能并不重要,但会在任何体面大小的程序中崩溃。所以修复0x10到​​0x0C 添加push eaxsub 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来说是默认的,并导致相当可测量的代码膨胀。