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

X86 机器代码如何访问子程序调用的参数?,x86,assembly,machine-code,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... } 这将如何在汇编/x86机器代码中转换?您将如何访问提供给您的变量?系统将如何为您提供这些变量 我对

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

$ 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...
}
这将如何在汇编/x86机器代码中转换?您将如何访问提供给您的变量?系统将如何为您提供这些变量


我对汇编非常陌生,它告诉我你们只能访问寄存器和绝对地址。我不明白你怎么能访问参数。系统是否为您将参数预加载到特殊寄存器中?

与您的程序一样;你只需要手动操作

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

例如,
argc
argv
将被创建并推送到堆栈上。他们指向的数据也已经创建好了。调用函数时,它知道参数1..n将根据调用约定放置在内存的某个部分中

并举例说明在调用函数之前如何设置堆栈


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

C-runtime在这里为您做一些工作-它从操作系统获取程序参数,并在调用
main
函数之前对其进行必要的解析。在asembler中,您必须获取命令参数并自己解析它们。如何获取程序参数是特定于操作系统的

函数调用 参数通常在堆栈上传递,堆栈是
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
esp+00h: argc
esp+04h: argv[0]
esp+08h: argv[1]
esp+1Ch: argv[2]
...
在调用函数和被调用函数之间有不同的职责,它们承诺如何保留寄存器。这些规则称为

上面的示例使用cdecl调用约定,这意味着参数按相反顺序推送到堆栈上,调用函数负责将
esp
恢复到将这些参数推送到堆栈之前的位置。这就是添加esp,8的作用

主要功能 通常,您在汇编中编写一个
main
函数,并将其汇编到一个对象文件中。然后将此对象文件传递给链接器以生成可执行文件

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

启动码 那么这个“启动代码”看起来怎么样?链接器会为我们制作它,但知道这些东西是如何工作的总是很有趣的

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

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
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
;
...

这是运行时的责任吗?我不知道C规范中有任何规定如何调用程序的入口点,或者在调用
main
之前如何执行各种内务管理职责(打开std in、out等)。当然,它可以用这种方式实现,但也可以用许多其他方式执行,no?main通常与任何其他函数类似,您会看到argc和argv参数是根据编译器和处理器的调用约定输入的。机器代码如何访问子例程调用的参数?好的,我更改了标题it,谢谢。这是一个非常好的回答,非常清楚,完全符合我的要求。谢谢回答得真不错。关于从目标代码创建可执行文件,您提到了一件事。。。目标代码对任何x86平台都是通用的,但将其链接成可执行文件会使其特定于某个平台,例如Linux吗?@Robert:我将把这个问题留给更有资格的人来回答。我相信对象文件不是特定于操作系统的,但它可能特定于创建它的编译器/链接器(我想到了c++名称混乱)