1

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

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

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

4

2 回答 2

1

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

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

于 2013-03-14T15:16:59.067 回答
1

是的,这种传递参数并清理它们的方式很好。但是,您的代码中存在一个问题:您只有3次推送,但减去了16个字节。移动到eax不计为推送,因为它不会更改esp.

在这个小程序中可能无关紧要,但在任何体面大小的程序中都会崩溃。所以修复 0x10 到 0x0C添加一个push eaxorsub esp, 4使其平衡并匹配 BSD 调用约定的要求。

另一种方法是预先保留堆栈空间并使用movs 而不是pushes 来设置参数。

编辑:根据FreeBSD 调用约定(额外推送)修复代码。

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 的默认方式,并且会导致相当大的代码膨胀。

于 2013-03-14T15:36:05.563 回答