5

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

$ 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机器代码?你将如何访问给你的变量?系统将如何为您提供这些变量?

我对汇编很陌生,它使您只能访问寄存器和绝对地址。我很困惑如何访问参数。系统是否为您将参数预加载到专用寄存器中?

4

3 回答 3

9

Function calls

Parameters are usually passed on the stack, which is a part of memory that is pointed to by esp. The operating system is responsible for reserving some memory for the stack and then setting up esp properly before passing control to your program.

A normal function call could look something like this:

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

There are different responsibilities split between the calling function and the function that is being called, with regards to how they promise to preserve registers. These rules are referred to as calling conventions.

The example above uses the cdecl calling convention, which means that parameters are pushed onto the stack in reverse order, and the calling function is responsible for restoring esp back to where it pointed before those parameters were pushed to the stack. That's what add esp, 8 does.

Main function

Typically, you write a main function in assembly and assemble it into an object file. You then pass this object file to a linker to produce an executable.

The linker is responsible for producing startup code that sets up the stack properly before control is passed to your main function, so that your function can act as if it were called with two arguments (argc/argv). That is, your main function is not the real entry point, but the startup code jumps there after it has set up the argc/argv arguments.

Startup code

So how does this "startup code" look? The linker will produce it for us, but it's always interesting to know how stuff works.

This is platform specific, but I'll describe a typical case on Linux. This article, while dated, explains the stack layout on Linux when an i386 program starts. The stack will look like this:

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

So the startup code can get the argc/argv values from the stack and then call main(...) with two parameters:

; 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
;
...
于 2011-12-06T00:16:44.783 回答
4

以与您的程序相同的方式;你只需要手动完成。

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

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

以下是调用约定的简要说明,其中包含一些示例,说明在调用函数之前如何设置堆栈。

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

于 2011-12-05T23:26:16.930 回答
4

mainC 运行时在这里为您做一些工作 - 它从操作系统获取程序参数并在调用您的函数之前在必要时解析它们。在汇编程序中,您必须获取命令参数并自己解析它们。获取程序参数的方式是特定于操作系统的。

于 2011-12-05T23:36:41.107 回答