装配通过堆栈传递变量

问题描述:

因此,在引入一些装配以获得乐趣之后,我现在停留在调用过程中。装配通过堆栈传递变量

... 
_start: 
    push dword len 
    push dword msg 

    call print 

    mov eax, SYS_EXIT 
    mov ebx, 0 
    int 80h 

print: ; *char (message), int (len) -> push len, then message 
    mov eax, SYS_WRITE 
    mov ebx, STDOUT 
    pop ecx 
    pop edx 
    int 80h 
    ret 

当我运行这片组装

nasm -f elf program.asm && ld -m elf_i386 -o program program.o && ./program 

它打印出该程序的所有内容,然后赛格故障,而如果我取代“呼打印”具有打印功能的内容,它工作正常。

+0

这是因为指令弹出ECX接收'EAX SYS_EXIT'而不是ret指令的地址,是你意识到调用也会将值推入堆栈? – rcd

第一个POP弹出返回地址,第二个POP弹出地址msg。如果你不搞乱int 80h调用,你会在函数试图返回时至少出现分段错误。

相关值可以在返回地址的后面找到,这里是esp + 4和esp + 8。您可以直接使用ESP+xx访问此地址。当你建立更复杂的过程,你可能想逃避到EBPESP去做的那一刻:

SYS_EXIT equ 1 
SYS_WRITE equ 4 
STDOUT equ 1 

segment .data 

    msg db `Hello world!\n`  ; Backspaces for C-like escape-sequences ("\n") 
    len equ $- msg 

section .text 

    global _start 

_start: 
    push dword len 
    push dword msg 

    call print 
    ; and the stack? 

    mov eax, SYS_EXIT 
    mov ebx, 0 
    int 80h 

print: ; *char (message), int (len) -> push len, then message 
    mov eax, SYS_WRITE 
    mov ebx, STDOUT 
    mov ecx, [esp+4] 
    mov edx, [esp+8] 
    int 80h 
    ret 
+0

修复代码时,为什么在“call print”后没有清理堆栈?我在那里看到一条评论,';和堆栈“,但我不知道这意味着什么,我怀疑提问者也不会。 –

+0

@CodyGray:以下SYS_EXIT也将“清理”堆栈。所以它是否被清理并不重要。我不知道OP为什么还没有清理堆栈。也许这是需要的目的不是张贴在这里。 – rkhb

+0

如果我弹出退货地址并存储它,然后关闭休息,我可以推回去的地址,然后返回? – Jackywathy

下面的代码是你应该写的方式:

_start: 
    push dword len 
    push dword msg 
    call print 
    add esp, 8 ; equivalent to 2 32-bit POP instructions 
       ; (effectively "undoes" the above PUSH instructions 
       ; to restore the stack to its original state) 

    mov eax, SYS_EXIT 
    mov ebx, 0 
    int 80h 

print: ; *char (message), int (len) -> push len, then message 
    mov eax, SYS_WRITE 
    mov ebx, STDOUT 
    mov ecx, [esp+4] ; load "msg" from stack using an offset from ESP 
    mov edx, [esp+8] ; load "length" from stack using an offset from ESP 
    int 80h 
    ret 

问题是堆栈没有它应该指向。您必须记住堆栈的后进先出特性,并且还要考虑callret指令会影响堆栈指针。当你使用call函数时,返回地址被压入堆栈,所以当你在print内部执行pop时,实际上是将返回值从堆栈中弹出,这不仅会给你错误的值,而且还会让你失望你后来才能到ret

检索传递给堆栈中函数的参数的正确方法是通过堆栈指针(ESP)的偏移量。第一个参数将在ESP + 4处找到(在压入堆栈的4字节返回地址后面call)。有关更多信息,可以查找C代码常用的STDCALL和CDECL调用约定。

+0

感谢Cody Gray的编辑:)。 – rcd