我正在读一本描述装载机工作原理的教科书:
加载程序运行时,它会将可执行对象文件的块复制到代码和数据段中。接下来,加载程序跳转到程序的入口点,它始终是
_start
函数的地址。_start
函数调用系统启动函数__libc_start_main
从这个问题的答案开始?中,我们得到了关于执行流程的以下伪代码:
_start:
call __setup_for_c ; set up C environment
call __libc_start_main ; set up standard library
call _main ; call your main
call __libc_stop_main ; tear down standard library
call __teardown_for_c ; tear down C environment
jmp __exit ; return to OS
我的问题是:
objdump
检查程序的汇编代码,发现_start
只调用__libc_start_main
,如下图所示:
其他的函数如call __setup_for_c
、_main
等呢?特别是我的程序的main
函数,我看不出它是如何被调用的。那么关于执行流的伪代码是正确的吗?
__libc_start_main
设置标准库是什么意思?为什么需要设置标准库?在加载程序时,标准库不是只需要由动态链接器链接吗?发布于 2020-09-09 07:46:50
伪代码不是代码;) _libc_start_main()
可以调用应用程序的main()
,因为链接器将固定main()
的地址。编译器生成的代码进行初始化的顺序可能很有趣,但您不应该假设它在一个编译器到另一个编译器之间是相同的,甚至是从一个版本到另一个版本。如果你能避免的话,最好不要依赖于以一种特定的方式做的事情。
至于需要初始化什么--像glibc
这样的标准C库非常复杂,很多东西都需要初始化。举一个例子,必须设置内存分配器的块表,这样malloc()
就不会从内存分配的随机模式开始。
发布于 2020-09-09 07:45:39
_dl_start_user
),要么在__libc_start_main
中显式地。__libc_start_main
还负责调用用户的main
,这就是为什么在您的反汇编中没有看到它被调用的原因--但是它的地址是传递的(参见lea
,即callq
)。__libc_start_main
还负责程序退出,从不返回;这是hlt
在callq
之后出现的原因,如果函数返回,这将使程序崩溃。- some of its own relocation
- thread-local storage setup
- pthread setup
- destructor registration
- vDSO setup (on Linux)
- ctype initialisation
- copying the program name, arguments and environment to various library variables
等等,请参阅x86-64特定的sysdeps/x86_64/start.S
和泛型csu/libc-start.c
、csu/init-first.c
和misc/init-misc.c
等.
发布于 2020-09-09 07:48:34
其余的函数如call __setup_for_c、_main等呢?
这些只是虚构的、可读的名字,用在链接的答案中,更好地传递答案的含义。
它是如何被调用的
您的标准库实现没有提供名为__setup_for_c
或_main
的函数,因此它们不存在,因此不会被调用。每个实现都可以为函数选择不同的名称。
关于执行流的伪代码正确吗?
是的--你使用的“psuedo代码”一词可以推断出你知道它不是真正的代码。
__libc_start_main设置标准库是什么意思?
它的意思是一个名为__libc_start_main
的符号。__libc_start_main
是一个函数,它初始化所有标准库,并在glibc中运行main
。它初始化libc、p线程、atexit并最终运行main
。glibc是开源的,所以看看它吧。。
为什么需要设置标准库?
因为它是以依赖于它的方式写的。最简单的是,当你写的时候:
int var = 42; // variable with static storage duration
int main() {
return var == 42;
}
(假设优化器没有启动),那么42
值必须写入var
的内存中,然后才能执行 main
。因此,必须在main
之前执行某些操作,并将42
实际写入var
的内存中。这是为什么某些东西必须在main
之前执行的最简单的例子。许多地方都使用全局变量,所有这些变量都需要设置,例如,glibc中的一个名为名字的变量保存程序名称-- 所以有些代码需要实际上询问环境或内核程序的名称,并将字符串的值(并可能解析)存储到全局变量中(如果在退出时动态分配该字符串,也要记住该字符串的free()
)。有些代码“必须这样做”--而且该代码处于标准库初始化阶段。
还有更多的情况--在C++和其他语言中有构造函数,还有gcc GNU扩展__attribute__((__constructor__))
和.init
/.preinit
部分--它们都在main
之前执行。而且析构函数必须在exit
上执行,而不是在_exit
上执行--因此在atexit
之前初始化atexit
,并且根据实现的不同,所有析构函数都可以在其中注册。
环境需要初始化,可能需要堆栈和更多的东西。线程局部变量只需要分配给当前线程,这样当您pthread_create
另一个线程时,它们就不会被非线程局部变量复制。
当程序加载时,这个标准库不是只需要由动态链接器链接吗?
是的--当程序加载时,标准库就会被链接起来。编译器在生成程序时使用crt代码在程序中包含一些启动代码,例如调用__libc_start_main
。
https://stackoverflow.com/questions/63806608
复制相似问题