在 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
我对实施有一些疑问:
调用 _start
之前堆栈中的内容应该是 linker 的唯一条目?我问是因为有 mov (%rsp), %edi #argc from stack
等表达式,其中 _start
从堆栈中获取值,但 _start
不应该有任何 argc
(只有 main
也没有 argv
和 envp
。所有这些参数都是 main
函数的一部分,而不是 _start
入口点。那么 _start
之前的堆栈中有什么?
这应该设计为提供 initilization 来自 .data
或 .bss
段的变量,但我没有看到这样的初始化他们在这里。它可能与堆栈有关,但我不知道如何。在初始化变量之前(应该在 ctr0.o
中),保持 initial 值和 linker reserve space for them (也来自 link)。在什么内存类型段,gcc对那些未初始化的变量持有space?
最后,如何在没有 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
; (或者你可以 jmp
到 puts()
而不是 call
...)
- What is in stack before called
_start
which should be the only entry for linker?
这是由系统的 ABI 定义的。我假设您使用的是 Linux,它使用 System V ABI。在这种情况下,堆栈包含 argc
、argv
指针(以空值终止)、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
还能从哪里得到它们?
- 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
的用途。
- 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 函数,但这可能会非常糟糕。自己编写函数绝对是更好的方法。
有一个 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
我对实施有一些疑问:
调用
_start
之前堆栈中的内容应该是 linker 的唯一条目?我问是因为有mov (%rsp), %edi #argc from stack
等表达式,其中_start
从堆栈中获取值,但_start
不应该有任何argc
(只有main
也没有argv
和envp
。所有这些参数都是main
函数的一部分,而不是_start
入口点。那么_start
之前的堆栈中有什么?这应该设计为提供 initilization 来自
.data
或.bss
段的变量,但我没有看到这样的初始化他们在这里。它可能与堆栈有关,但我不知道如何。在初始化变量之前(应该在ctr0.o
中),保持 initial 值和 linker reserve space for them (也来自 link)。在什么内存类型段,gcc对那些未初始化的变量持有space?最后,如何在没有 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
; (或者你可以 jmp
到 puts()
而不是 call
...)
- What is in stack before called
_start
which should be the only entry for linker?
这是由系统的 ABI 定义的。我假设您使用的是 Linux,它使用 System V ABI。在这种情况下,堆栈包含 argc
、argv
指针(以空值终止)、envp
指针(以空值终止)、辅助向量(以空值终止) , 最后是前面指针指向的值。
_start
should not have anyargc
(onlymain
does) norargv
andenvp
. All these arguments are part ofmain
function, not_start
entry point.
这是不对的。如果 _start
没有得到这些,那么 main
还能从哪里得到它们?
- 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
的用途。
- 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 函数,但这可能会非常糟糕。自己编写函数绝对是更好的方法。