2

如何将参数传递给 Assembly 中的函数?我确实推动了最后一个参数,推动了第二个参数,推动了第一个参数..

但是我无法访问Meh函数中的参数。我正在做的事情使程序崩溃了。

format PE console                                ;Format PE OUT GUI 4.0
entry main

include 'macro/import32.inc'

section '.idata' import data readable           ;Import Section.
library msvcrt,'msvcrt.dll'
import msvcrt, printf, 'printf',\
exit,'exit', getchar, 'getchar'

section '.data' data readable writeable         ;Constants/Static Section.
InitialValue dd 0

section '.code' code readable executable
main:    
   push 67
   push 66
   push 65
   call MEH

   call [getchar]
   mov eax, 0
   ret 0

MEH:
   push ebx
   mov ebp, esp
   sub esp, 0

   mov eax, [ebp + 8]   ; Trying to print first parameter..
   push eax
   call [printf]
   add esp, eax

   mov esp, ebp
   pop ebx
ret
4

3 回答 3

5

小的附加说明。该过程的正确页眉/页脚使用 push/pop ebp:

MEH:
   push ebp
   mov ebp, esp

   mov esp, ebp
   pop ebp
   ret

原因是我们需要在将 ebp 寄存器用作指向参数和局部变量的指针之前保存/恢复它。

其次,调用者在过程返回后恢复堆栈指针的 CCALL 调用约定对于 C/C++ 语言很常见,但不适用于汇编编程。原因很明显 - 编译器可以正确计算有多少参数被压入堆栈。在手写汇编程序中,使用这种约定会使代码不清晰。

更好的方法是使用 STDCALL 调用约定:

MEH:
   push ebp
   mov  ebp, esp

   mov  esp, ebp
   pop  ebp
   retn 12   ; how many bytes to be automatically 
             ; removed from the stack after return.

更好的做法是使用一些宏来自动创建标准过程元素并为参数和局部变量提供人类可读的标签。例如,FreshLib库中提供的宏具有以下语法:

proc MEH, .arg1, .arg2, .arg3
; define local variables here, if needed.
begin
     ; place your code here without headers and footers
     return  ; will clean the stack automatically.
endp

; pushes the arguments in the stack and call MEH
stdcall MEH, 65, 66, 67   

FASM 包提供的标准宏库的语法略有不同,FASM 程序员手册对此进行了详细介绍。

于 2013-02-01T07:05:51.480 回答
3

让我们来看看...

假设你的 ESP 一开始是 0x00180078,然后在你有三个推动之后

00180078: 67
00180074: 66
00180070: 65

然后你调用 MEH,它会立即推送 ebx,所以现在你有堆栈

00180078: 67
00180074: 66
00180070: 65
0018006C: return address
00180068: ebx value

您现在使用 ESP = 00180068 加载 EBP

sub esp,0 does nothing

mov eax, [ebp+8] ~ 00180068 + 8 = 00180070 = 65 

所以不是第一个而是最后一个论点

   call [printf]

但是,您的问题来了:

   add esp, eax

这应该有什么好处?假设 printf 保留了传入的这个参数(偶然不需要这样做),为什么要将参数添加到堆栈指针?那肯定会搞砸你的回报。您要做的是将 esp 恢复为 ebp 的值并弹回保存的 ebx 值。

于 2013-01-24T22:13:53.930 回答
3

如果 for 的调用约定printf()是正确的(它适用于 Linux 上的 32 位 MinGW 和 32 位 gcc),那么您完全忽略了函数的预期,并且没有得到所需的输出也就不足为奇了。

该函数的原型是:

int printf(const char* format, ...);

format,第一个参数,是一个指向 ASCIIZ 字符串的指针,它包含要打印的文本和/或特殊标记,例如用对 .%d后面的可选参数的适当解释来替换format

所以,如果你想printf()打印'A',那么这就是你需要在 C 中做的事情:

printf("A");

或者

printf("%c", 'A');

以下是你在装配中如何做同样的事情:

myformatstring db "A", 0 ; this line goes into section .data

push myformatstring ; push address of the string
call [printf]
add esp, 4 ; remove all parameters from the stack

或者

myformatstring db "%c", 0 ; this line goes into section .data

push 'A'    
push myformatstring ; push address of the string
call [printf]
add esp, 2*4 ; remove all parameters from the stack
于 2013-01-25T04:38:51.273 回答