机器代码如何访问子程序调用的参数?

时间:2011-12-05 23:24:16

标签: x86 assembly machine-code

运行程序时,您可以传递参数,例如

$ myProgram par1 par2 par3

在C中,您可以通过查看argv

来访问这些参数
int main (int argc, char *argv[]) 
{
     char* aParameter = argv[1];  // Not sure if this is 100% right but you get the idea...
}

如何在assembly / x86机器代码中进行转换?你会如何访问给你的变量?系统如何为您提供这些变量?

我对组装非常新,它接缝只能访问寄存器和绝对地址。我很困惑你如何访问参数。系统是否会将参数预加载到特殊寄存器中?

3 个答案:

答案 0 :(得分:9)

函数调用

参数通常在堆栈上传递,堆栈是esp指向的内存的一部分。操作系统负责为堆栈保留一些内存,然后在将控制权交给程序之前正确设置esp

正常的函数调用看起来像这样:

main:
  push 456
  push 123
  call MyFunction
  add esp, 8
  ret

MyFunction:
   ; [esp+0] will hold the return address
   ; [esp+4] will hold the first parameter (123)
   ; [esp+8] will hold the second parameter (456)
   ;
   ; To return from here, we usually execute a 'ret' instruction,
   ; which is actually equivalent to:
   ;
   ; add esp, 4
   ; jmp [esp-4]

   ret

在调用函数和被调用的函数之间存在不同的职责分离,关于它们如何保证寄存器。这些规则称为 calling conventions

上面的示例使用 cdecl 调用约定,这意味着参数以相反的顺序被压入堆栈,并且调用函数负责将esp恢复回指向的位置在那些参数被推入堆栈之前。这就是add esp, 8所做的。

主要功能

通常,您在程序集中编写main函数并将其组装到目标文件中。然后,将此目标文件传递给链接器以生成可执行文件。

链接器负责生成启动代码,在控制传递给main函数之前正确设置堆栈,这样您的函数就可以像使用两个参数(argc / argv)一样调用它。也就是说,你的main函数不是真正的入口点,但启动代码在设置argc / argv参数后跳转到那里。

启动代码

那么这个“启动代码”看起来如何?链接器将为我们生成它,但知道它是如何工作的总是很有趣。

这是特定于平台的,但我将描述Linux上的典型案例。 This article,虽然过时,但解释了i386程序启动时Linux上的堆栈布局。堆栈将如下所示:

esp+00h: argc
esp+04h: argv[0]
esp+08h: argv[1]
esp+1Ch: argv[2]
...

因此,启动代码可以从堆栈中获取argc / argv值,然后使用两个参数调用main(...)

; This is very incomplete startup code, but it illustrates the point

mov eax, [esp]        ; eax = argc
lea edx, [esp+0x04]   ; edx = argv

; push argv, and argc onto the stack (note the reverse order)
push edx
push eax
call main
;
; When main returns, use its return value (eax)
; to set an exit status
;
...

答案 1 :(得分:4)

以同样的方式执行;你只需要手动完成。

在调用函数之前,函数的参数存储在各种寄存器/存储器段中。在程序集中调用函数时,必须在调用之前手动设置堆栈。调用约定决定了这些变量的去向,它们的排序方式以及它们的访问方式。

例如,argcargv将被创建并压入堆栈。他们指出的数据也已经被创建了。当调用该函数时,它知道根据调用约定,参数1..n将被放置在内存的某个部分中。

Here is a quick rundown on calling conventions以及一些关于如何在调用函数之前设置堆栈的示例。

另一方面,在调用main之前必须完成一些工作,这对你来说是隐藏的。这是件好事;每次我们开始一个新项目时,我们都不想写一堆引导代码。

答案 2 :(得分:4)

C-runtime正在为你做一些工作 - 它从操作系统中获取程序参数,并在必要时解析它们,然后再使用main函数。在asemmbler中,您必须获取命令参数并自己解析它们。如何获得程序参数是特定于操作系统的。

相关问题