在 x86 中调用 _start 是什么?

What does call _start in x86?

有一个 c 运行时库,根据 https://en.wikipedia.org/wiki/Crt0 在文件 ctr0.o 中被调用以在调用 main 之前初始化变量。我已经复制到这里了:

.text
    .globl _start
str : .asciz "abcd\n"
_start:
    xor %ebp, %ebp #basePointer == 0
    mov (%rsp), %edi #argc from stack
    lea 8(%rsp), %rsi #pointer to argv
    lea 16(%rsp,%rdi,8), %rdx #pointer to envp
    xor %eax, %eax
    call main
    mov %eax, %edi
    xor %eax, %eax
    call _exit

main:
    lea str(%rip), %rdi
    call puts

我对实施有一些疑问:

  1. 调用 _start 之前堆栈中的内容应该是 linker 的唯一条目?我问是因为有 mov (%rsp), %edi #argc from stack 等表达式,其中 _start 从堆栈中获取值,但 _start 不应该有任何 argc (只有 main 也没有 argvenvp。所有这些参数都是 main 函数的一部分,而不是 _start 入口点。那么 _start 之前的堆栈中有什么?

  2. 这应该设计为提供 initilization 来自 .data.bss 段的变量,但我没有看到这样的初始化他们在这里。它可能与堆栈有关,但我不知道如何。在初始化变量之前(应该在 ctr0.o 中),保持 initial 值和 linker reserve space for them (也来自 link)。在什么内存类型段,gcc对那些未初始化的变量持有space?

  3. 最后,如何在没有 stdlib 的情况下编译这个程序集,但需要它的一些功能(puts_exit)才能工作?我试过 cc -nostdlib foo.s

    /usr/bin/ld: /tmp/ccSKxoPY.o: in function `_start':
    (.text+0x21): undefined reference to `_exit'
    /usr/bin/ld: /tmp/ccSKxoPY.o: in function `main':
    (.text+0x2d): undefined reference to `puts'
    collect2: error: ld returned 1 exit status
    

(不能使用 stdlib 否则,会有 2 个 _start 入口点声明)。

首先,当使用相同的 CPU(例如 x86-64 CPU)时,不同的操作系统需要不同的 crt0.S 文件。

对于不是使用操作系统启动的程序(例如操作系统本身),您需要一个不同的 crt0.S

What is in stack before called _start which should be the only entry for linker?

这取决于操作系统。 Linux 会将 argc、参数 (argv[n]) 和环境 (environ[n]) 复制到堆栈的某处。

您问题中的文件适用于将 argc 置于 rsp+0 的操作系统,后跟参数和环境。

然而,我记得一个(32位)OS将argc放在esp+0x80而不是esp+0,所以这也是可能的...

据我所知,Windows 不会在堆栈上放置任何东西(至少不是正式的)。相应的crt0.S代码必须调用DLL文件中的函数来获取命令行参数。

对于在 CPU(微控制器)启动后立即启动的设备固件,crt0.S 代码甚至必须首先将堆栈指针设置为有效值。在这种情况下,内存(包括堆栈)通常是完全未初始化的。

不用说,堆栈在这种情况下不包含任何有用的值。

This should be designed to provide initilization of variables from .data ...

对于操作系统启动的软件,操作系统会初始化.data段。这意味着 crt0.S 代码不必这样做。

对于微控制器程序(设备固件),crt0.S 代码必须执行此操作。

因为您的文件显然是用于操作系统的,所以它不会初始化 .data 部分。

Finally, how to compile this assembly, without stdlib ...

如果您想使用问题中的 crt0.S 文件,您肯定需要 _exit() 函数。

如果您想在代码中使用函数 puts(),您还需要这个函数。

如果您不使用标准库,则必须自己编写这些函数:

    ...
main:
    lea str(%rip), %rdi
    call puts
    ret

_exit:
    ...

puts:
    ...

具体实现取决于您使用的操作系统。

puts() 实现起来有点棘手; write() 会更容易。

注:

也请不要忘记main()函数末尾的ret; (或者你可以 jmpputs() 而不是 call...)

  1. What is in stack before called _start which should be the only entry for linker?

这是由系统的 ABI 定义的。我假设您使用的是 Linux,它使用 System V ABI。在这种情况下,堆栈包含 argcargv 指针(以空值终止)、envp 指针(以空值终止)、辅助向量(以空值终止) , 最后是前面指针指向的值。

_start should not have any argc (only main does) nor argv and envp. All these arguments are part of main function, not _start entry point.

这是不对的。如果 _start 没有得到这些,那么 main 还能从哪里得到它们?

  1. This should be designed to provide initilization of variables from .data or .bss segments, but I do not see such initialization of them here.

内核在将进程映射到内存时会处理这个问题。唯一需要代码来初始化它们的情况就像在 C++ 中一样,如果您将变量初始化为不是编译时常量的东西。

In what section of memory type, does gcc hold space for those not-initialized variables?

这正是 .bss 的用途。

  1. Finally, how to compile this assembly, without stdlib, but requires some of its function (puts, _exit) in order to work?

如果你想使用libc函数,那么你需要使用libc。正确的方法是根据系统调用自己实现这些功能。 _exit 很简单:

_exit:
        movl    , %eax
        syscall

对于 puts 它会稍微复杂一些,因为您必须自己做 strlen(提示:repnz scasb),处理调用 write循环系统调用,并写一个尾随的换行符,但它应该仍然是完全可行的。

只是为了好玩,您可以尝试使用 -nostartfiles 而不是 -nostdlib,然后调用 libc 函数,但这可能会非常糟糕。自己编写函数绝对是更好的方法。