装配通过堆栈传递变量
因此,在引入一些装配以获得乐趣之后,我现在停留在调用过程中。装配通过堆栈传递变量
...
_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
它打印出该程序的所有内容,然后赛格故障,而如果我取代“呼打印”具有打印功能的内容,它工作正常。
第一个POP
弹出返回地址,第二个POP
弹出地址msg
。如果你不搞乱int 80h
调用,你会在函数试图返回时至少出现分段错误。
相关值可以在返回地址的后面找到,这里是esp + 4和esp + 8。您可以直接使用ESP+xx
访问此地址。当你建立更复杂的过程,你可能想逃避到EBP
但ESP
去做的那一刻:
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
修复代码时,为什么在“call print”后没有清理堆栈?我在那里看到一条评论,';和堆栈“,但我不知道这意味着什么,我怀疑提问者也不会。 –
@CodyGray:以下SYS_EXIT也将“清理”堆栈。所以它是否被清理并不重要。我不知道OP为什么还没有清理堆栈。也许这是需要的目的不是张贴在这里。 – rkhb
如果我弹出退货地址并存储它,然后关闭休息,我可以推回去的地址,然后返回? – 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
问题是堆栈没有它应该指向。您必须记住堆栈的后进先出特性,并且还要考虑call
和ret
指令会影响堆栈指针。当你使用call
函数时,返回地址被压入堆栈,所以当你在print
内部执行pop
时,实际上是将返回值从堆栈中弹出,这不仅会给你错误的值,而且还会让你失望你后来才能到ret
。
检索传递给堆栈中函数的参数的正确方法是通过堆栈指针(ESP
)的偏移量。第一个参数将在ESP + 4
处找到(在压入堆栈的4字节返回地址后面call
)。有关更多信息,可以查找C代码常用的STDCALL和CDECL调用约定。
感谢Cody Gray的编辑:)。 – rcd
这是因为指令弹出ECX接收'EAX SYS_EXIT'而不是ret指令的地址,是你意识到调用也会将值推入堆栈? – rcd