尽管接受的答案绰绰有余,但我想给出一个明确的答案,因为还有一些其他的答案可能会混淆。
最重要的(有关更多信息,请参见下面的示例):在 x86-64 中,命令行参数通过堆栈传递:
(%rsp) -> number of arguments
8(%rsp) -> address of the name of the executable
16(%rsp) -> address of the first command line argument (if exists)
... so on ...
不同于x86-64的函数参数传入,使用%rdi、%rsi等。
还有一件事:不应该从 C main 函数的逆向工程中推断出行为。 C 运行时提供入口点_start,包装命令行参数并将main 作为通用函数调用。要查看它,让我们考虑以下示例。
没有带有 -nostdlib 的 C 运行时/GCC
让我们检查一下这个简单的 x86-64 汇编程序,它什么都不做,只返回 42:
.section .text
.globl _start
_start:
movq $60, %rax #60 -> exit
movq $42, %rdi #return 42
syscall #run kernel
我们使用以下方式构建它:
as --64 exit64.s -o exit64.o
ld -m elf_x86_64 exit64.o -o exit64
或与
gcc -nostdlib exit64.s -o exit64
使用
在 gdb 中运行
./exit64 first second third
并在_start 处的断点处停止。让我们检查一下寄存器:
(gdb) info registers
...
rsi 0x0 0
rdi 0x0 0
...
那里什么都没有。堆栈呢?
(gdb) x/5g $sp
0x7fffffffde40: 4 140737488347650
0x7fffffffde50: 140737488347711 140737488347717
0x7fffffffde60: 140737488347724
所以堆栈上的第一个元素是4 - 预期的argc。接下来的 4 个值看起来很像指针。再来看看第二个指针:
(gdb) print (char[5])*(140737488347711)
$1 = "first"
正如预期的那样,它是第一个命令行参数。
因此有实验证据表明命令行参数是通过 x86-64 中的堆栈传递的。然而,只有通过阅读ABI(正如公认的答案所建议的那样),我们才能确定情况确实如此。
使用 C 运行时
我们必须稍微改变程序,将_start 重命名为main,因为入口点_start 是由C 运行时提供的。
.section .text
.globl main
main:
movq $60, %rax #60 -> exit
movq $42, %rdi #return 42
syscall #run kernel
我们使用(默认使用 C 运行时)构建它:
gcc exit64gcc.s -o exit64gcc
使用
在 gdb 中运行
./exit64gcc first second third
并在main 处的断点处停止。堆栈中有什么?
(gdb) x/5g $sp
0x7fffffffdd58: 0x00007ffff7a36f45 0x0000000000000000
0x7fffffffdd68: 0x00007fffffffde38 0x0000000400000000
0x7fffffffdd78: 0x00000000004004ed
看起来并不熟悉。并注册?
(gdb) info registers
...
rsi 0x7fffffffde38 140737488346680
rdi 0x4 4
...
我们可以看到rdi 包含argc 值。但是如果我们现在检查rsi 中的指针,就会发生奇怪的事情:
(gdb) print (char[5])*($rsi)
$1 = "\211\307???"
但是等等,C 中main 函数的第二个参数不是char *,而是char ** 也是:
(gdb) print (unsigned long long [4])*($rsi)
$8 = {140737488347644, 140737488347708, 140737488347714, 140737488347721}
(gdb) print (char[5])*(140737488347708)
$9 = "first"
现在我们找到了我们的参数,这些参数通过寄存器传递,就像 x86-64 中的普通函数一样。
结论:
正如我们所看到的,在使用 C 运行时的代码和不使用 C 运行时的代码之间传递命令行参数是不同的。