如何在汇编程序 x86 函数调用中传递参数?
Posted
技术标签:
【中文标题】如何在汇编程序 x86 函数调用中传递参数?【英文标题】:How can I pass parameters in assembler x86 function call? 【发布时间】:2016-10-24 19:36:51 【问题描述】:看看这个汇编代码。它是为 32 位 x86 设计的,将由 nasm 编译
...
my_function:
pop %eax
...
ret
main:
push 0x08
call my_function
我很久以前就知道,我们可以使用堆栈在主程序和函数之间传递参数。
我希望 eax 包含 0x08,但这是错误的,我无法解释原因。
我应该如何获取我的函数参数?
【问题讨论】:
看看他的另一个answer。问题是 CALL 将返回地址放在堆栈上,这就是您将在 EAX 中使用您的代码找到的地址。 【参考方案1】:首先,如果您希望在您的平台上与其他语言或库进行交互,请务必阅读为该平台定义的接口。可以使用多种调用机制。
在您的情况下,call
指令将返回地址推入堆栈。您可以使用一些算术和esp
来访问您的参数。由于您使用的是eax
,因此我将假设使用 32 位代码(和 32 位堆栈宽度)。我使用的是 intel 语法,因为我可以在不查找任何内容的情况下编写它:
my_function:
mov eax, [esp+4] ; Move the contents of ESP+4 into EAX
; ESP should be pointing at the 32 bit RIP.
; ESP+4 should be the pushed parameter.
...
ret
main:
push 0x08
call my_function
在您的 cmets 中,您询问此答案是否表示内存泄漏。答案是不。”原因是 调用者 负责清理它添加到堆栈中的任何内容。基于已编写的其他 cmets 的更完整示例可能如下所示:
my_function:
push ebp ; Store the current stack frame
mov ebp, esp ; Preserve ESP into EBP for argument references
and esp, 0xfffffff0; Align the stack to allow library calls
mov eax, [ebp+8] ; Move the contents of EBP+8 into EAX
; [EBP] should be the saved 32 bit EBP.
; [EBP+4] should be the 32 bit EIP (return address).
; [EBP+8] should be the pushed parameter.
... ; Do lots of cool stuff
mov esp, ebp ; Restore the stack and ebp
pop ebp
ret
main:
push 0x08
call my_function
pop ebx ; Clean up the stack
请注意,当我们将堆栈(如果您不确定为什么会发生这种情况,您会在研究平台的调用标准时很快找到)对齐 16 字节边界时,我们甚至不会尝试弄清楚esp
发生了多少变化。由于ebp
将充当我们的“书签”,我们可以让esp
移动以进行对齐或局部变量分配,而无需再考虑。
在函数尾声中,我们将ebp
移回esp
,这会将esp
恢复为其调用函数时的原始值,从而清除已发生的任何本地分配和对齐操作。最后,我们将pop ebp
移出堆栈,将返回地址指针作为函数内堆栈上的最终值。我们现在回来了。
回来后,我们用砰的一声清理干净。
或者,可以通过返回指定要在堆栈上释放的字节数来清理堆栈(例如ret 4
)。这完全取决于您的调用标准是指定调用者清理还是被调用者清理。
【讨论】:
谢谢大卫,但在你的情况下,你永远不会打电话给流行音乐。你不觉得是内存泄漏吗? @Bob5421:通常由调用者(在call my_function
之后)执行pop
。这只是如何在函数内部访问堆栈参数的最短示例,它不是一个完整的示例。所以是的,它会“泄漏”。
我已经反汇编了一些 elf 可执行文件。有时我看到他们直接改变了 esp 值。我想它与 pop/push 相同(除了它们不获取或添加值)。我也看到了ebp。我想堆栈元素的真实内存地址在 ebp+esp ?
ESP 直接改成在栈上分配空间。 EBP 通常用于在调用函数时跟踪原始 EBP 值,以便轻松清理堆栈。 EBP 的值通常作为函数前导码的一部分存储到堆栈中,因此在正常使用中,您应该期望在 +8 处看到堆栈上的内容。至此,您已经描述了一个典型的“堆栈框架”。不过,您要知道的最重要的事情是,在堆栈上传递东西是不典型的。如今,大多数接口都喜欢使用寄存器传递,因为没有推送和弹出,所以速度更快......
...必需。这就是为什么我建议您查找您将要工作的平台的调用标准!【参考方案2】:
除了大卫的回答,这是另一个例子
push 0 ; fourth parameter
push 4 ; third parameter
push 4 ; second parameter
push [eax] ; first parameter
call printf
在 C 或 C++ 中与
somefunction(first,second,third,fourth);
【讨论】:
【参考方案3】:解释见下文:-
[BITS 32]
%include "nagoa+.inc"
%include "cfunctions.txt"
[SEGMENT .DATA USE32]
ret_value db "I am the Return Value",0
localvar db "My Local Variable Value",0
[SEGMENT .BSS USE32]
arg_param resb 160
[SEGMENT .TEXT USE32]
my_function:
;save arguments/parameters as this esp+ space will be destroyed by system function calls
mov eax,[esp+4]
;enjoy local variables for processing
;enter 4,0
mov dword [esp-4],localvar
call printf,`Argument/Parmeter=%s and Local Variable=%s`,eax,[esp-4]
add esp,12
;leave
;fill-up return values
mov eax,ret_value
ret
;ret 4
..start:
call puts,`Enter Argument/Parmeter`
add esp,4
call gets,arg_param
add esp,4
push arg_param
CALL my_function
call printf,`Return Value From Called Function=%s`,eax
add esp,4
call exit, 0
【讨论】:
存储到[esp-4]
是不安全的:它可以(至少在理论上)被异步破坏,除非您使用自定义系统/调用约定和 32 位的红色区域代码。此外,这是什么汇编程序,call
的额外操作数会神奇地为您推送? (还有从字符串文字为您创建的字符串常量?)另外,最后的 printf
有 2 个参数,但只有 add esp,4
没有 8 个。
@Peter Cordes:这是 uses the macro collection nagoa+.inc 的 NASM 源。 call_
宏(%define call call_
意味着大写的CALL
不会调用该宏)确实实现了将参数推送到调用目的地之后指定的堆栈,并从其地址被推送的字符串文字创建常量。
以上是关于如何在汇编程序 x86 函数调用中传递参数?的主要内容,如果未能解决你的问题,请参考以下文章