Assembly 中的 Linux x86-64 命令行参数来自 _start(不是 main)

问题描述 投票:0回答:3

此描述适用于 Linux 32 位: 当 Linux 程序开始时,所有指向命令行参数的指针都存储在堆栈中。参数数量存储在 0(%ebp) 处,程序名称存储在 4(%ebp) 处,参数从 8(%ebp) 处存储。

我需要 64 位的相同信息。

编辑: 我有工作代码示例,展示了如何使用来自

_start
的 argc、argv[0] 和 argv[1]:http://cubbi.com/fibonacci/asm.html

.globl _start
_start:
    popq    %rcx        # this is argc, must be 2 for one argument
    cmpq    $2,%rcx
    jne     usage_exit
    addq    $8,%rsp     # skip argv[0]
    popq    %rsi        # get argv[1]
    call ...
...
}

看起来参数在堆栈上。由于这段代码不清楚,所以我问这个问题。我猜我可以将

rsp
保留在
rbp
中,然后使用
0(%rbp)
8(%rbp)
16(%rbp)
等访问这些参数。这是正确的吗?


编者注:

_start
是进程入口点,不是函数:没有返回地址(
RSP%16 == 0
不是
8
),并且没有寄存器参数。
main
一个普通函数,参数按照标准调用约定传递给它;如果您链接到调用 main 的 glibc 启动文件,您将在 x86-64 System V ABI 的常用寄存器中找到
int argc
char *argv[]
(和
char *envp[]
)。

linux assembly command-line x86-64 att
3个回答
24
投票
尽管接受的答案已经足够了,但我想给出一个明确的答案,因为还有一些其他答案可能会令人困惑。

最重要(有关更多信息,请参阅下面的示例):在 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 运行时的代码之间的命令行参数传递存在差异。


11
投票
它看起来像第3.4节

进程初始化,特别是图3.9,在已经提到的System V AMD64 ABI中准确地描述了您想知道的内容。


-1
投票
© www.soinside.com 2019 - 2024. All rights reserved.