用堆栈推入 nasm

push and pop in nasm with stack

我在做教程和一些我不明白的代码,也许有人能帮助我吗?我会感谢你的帮助。

首先会调用sprint函数,在'sprint'函数中将edx,ecx,ebx,eax逐级压入栈中,然后'slen'函数被调用,'ebx' 将再次被压入堆栈,我不明白这一步,ebx 已经在堆栈中,因为我知道 'ebx' 现在是堆栈中仅次于 eax 的倒数第二个在调用 sprint 函数之后。 我想知道这里是否有 2 个堆栈?或不 任何人都可以为我解释一下吗?我将非常感激。

此致

functions.asm

;------------------------------------------
; int slen(String message)
; String length calculation function
slen:
push    ebx
mov     ebx, eax

nextchar:
cmp     byte [eax], 0
jz      finished
inc     eax
jmp     nextchar

finished:
sub     eax, ebx
pop     ebx
ret


;------------------------------------------
; void sprint(String message)
; String printing function
sprint:
push    edx
push    ecx
push    ebx
push    eax
call    slen

mov     edx, eax
pop     eax

mov     ecx, eax
mov     ebx, 1
mov     eax, 4
int     80h

pop     ebx
pop     ecx
pop     edx
ret


;------------------------------------------
; void exit()
; Exit program and restore resources
quit:
mov     ebx, 0
mov     eax, 1
int     80h
ret '

Blockquote helloworld-inc.asm

; Hello World Program (External file include)
; Compile with: nasm -f elf helloworld-inc.asm
; Link with (64 bit systems require elf_i386 option): ld -m elf_i386 helloworld-inc.o -o helloworld-inc
; Run with: ./helloworld-inc

%include        'functions.asm'                             ; include our external file

SECTION .data
msg1    db      'Hello, brave new world!', 0Ah              ; our first message string
msg2    db      'This is how we recycle in NASM.', 0Ah      ; our second message string

SECTION .text
global  _start

_start:

mov     eax, msg1       ; move the address of our first message string into EAX
call    sprint          ; call our string printing function

mov     eax, msg2       ; move the address of our second message string into EAX
call    sprint          ; call our string printing function

call    quit            ; call our quit function

这两个函数是独立的(都是public API函数)。 sprint 在内部调用 slen 只是巧合,slen 不能假设它是从 sprint 调用的并且 ebx 已经被它保留了,它可以直接从用户代码调用,其中 ebx 可能不存储在堆栈中。

所以这两个函数都遵循调用约定(代码作者选择的那个,我从头上不知道它们,也不想猜测它是哪个,但是如果你在 linux并构建 elf32,它可能是标准的 linux 32b 调用约定(错误的猜测,看起来像 Irvine32 lib 调用约定,保留所有寄存器,但 eax 可能 return 值,感谢 Peter Cordes 的评论))。这意味着两个函数都必须独立地保留一些寄存器以符合约定,并且只有一些寄存器可以自由修改并 returned 处于修改状态。

as i know that 'ebx' is now the second last one on the stack after eax after sprint function is called

这也不是真的。 "stack" 只是普通的计算机内存,就像您的 .data.bss 部分一样。它只是为您的应用进程保留的另一个内存。使它有些特别的是寄存器 esp 中的值,它指向 "top of the stack".

现在当你执行 push ebx 时,指令会将寄存器 ebx 中的 32 位值写入地址 esp-4 的计算机内存中(32 位 = 4 字节,这就是为什么 push 在 32b 模式下将堆栈指针移动 -4),并且还将更新 esp 以指向该位置(即 esp -= 4)。

您的一个误解是 "ebx is stored",如果您重新阅读上面的描述,您会注意到堆栈内存中没有任何信息指出该值源自 ebx。事实上,如果您将执行下一条指令 pop eax,该值将毫无问题地恢复到寄存器 eax 中,而 esp += 4 会导致与 mov eax,ebx 完全相似的效果,但通过堆栈内存(比直接 mov 慢得多)。

另一个误解是"second last after eax"。 call 指令本身会将 return 地址压入堆栈,因此在 push ebx 之后的 slen 中,堆栈包含值:"ebx"、return 地址进入在 call slen、"eax"、"ebx".

之后的下一条指令处冲刺

不要犹豫,使用指令参考指南来验证特定指令的确切作用,例如:

即使对于经验丰富的 asm 程序员来说,验证关于特定指令的任何假设也是很常见的,尤其是当涉及标志时,或者像 mul/div/lods/stos/xlatb/... 这样的隐式寄存器使用。不要仅凭指令名称来猜测,其中一些比常识预期的要棘手得多。

此外,只需在调试器中触发此代码、单步执行指令并查看 esp 和堆栈内存内容如何演变(这将清除此答案的第二部分),会容易得多关于 push/pop 的工作原理)。