查看 <_start>
和 <main>
我将我们的可执行程序 main.exe
反汇编后,看一下会汇编层面的可执行程序
命令 objdump -S main.exe
命令 objdump -S main.exe > main.s
:为了后续容易查询该反汇编代码,重定向放到 main.s
中
如图,main.s
文件中存储着可执行程序 main.exe
的反汇编程序
在可执行程序文件中,存储执行程序的区域是 .text
段
我们可以查看一下 .text
:
我们再查看一下该数据节 .text
的 main
部分:
<_start>
和 <main>
的区别
数据节 .text
中 <_start>
,这个才是 Linux
中程序执行的真正起始,而并不是从 main
函数开始执行,准确来说 ,main
函数是语言用户层面的程序执行起始点。
_start
是由编译器和链接器生成的一个特殊的入口点,负责设置程序的运行环境,然后调用 main
函数。这个过程通常被称为程序的初始化阶段。
_start
和 main
的关系
1、_start
入口点:
_start
是 ELF 可执行文件的真正入口点。- 它是由编译器和链接器生成的,通常位于 CRT(C Runtime)代码中。
_start
负责设置程序的运行环境,包括初始化全局变量、设置栈指针、传递命令行参数等。
2、main
函数:
main
函数是用户编写的程序的起点。- 在
_start
完成初始化后,控制权会传递给main
函数。
既然我们认识到,<_start>
程序才是一个可执行程序执行的入口起始位置,
那么 CPU
是如何知道我应该从 <_start>
开始执行该程序?同时又是如何开始进入并执行 <main>
函数的代码?
_start
函数中的初始化操作
在 _start
函数中,会执行一系列初始化操作,这些操作包括:
1、设置堆栈:为程序创建一个初始的堆栈环境。
2、初始化数据段:将程序的数据段(如全局变量和静态变量)从初始化数据段复制到相应的内存位置,并清零未初始化的数据段。
3、动态链接:这是关键的一步,_start
函数会调用动态链接器的代码来解析和加载程序所依赖的动态库(shared libraries)。动态链接器会处理所有的符号解析和重定位,确保程序中的函数调用和变量访问能够正确地映射到动态库中的实际地址。
动态链接器:
- 动态链接器(如
ld-linux.so
)负责在程序运行时加载动态库。- 当程序启动时,动态链接器会解析程序中的动态库依赖,并加载这些库到内存中。
环境变量和配置文件:
- Linux 系统通过环境变量(如
LD_LIBRARY_PATH
)和配置文件(如/etc/ld.so.conf
其子配置文件)来指定动态库的搜索路径。- 些路径会被动态链接器在加载动态库时搜索。
缓存文件:
- 为了提高动态库的加载效率,Linux 统会维护一个名为
/etc/ld.so.cache
的缓存文件。- 该文件包含了系统中所有已知动态库的路径和相关信息,动态链接器在加载动态库时会首先搜索这个缓存文件。
4、调用 __libc_start_main
:一旦动态链接完成,_start
函数会调用 __libc_start_main
,从而最终跳转到 main
函数开始执行程序的主要逻辑。
5、调用 main
数:最后,__libc_start_main
函数会调用程序的 main
函数,此时程序的执行控制权才正式交给用户编写的代码。
6、理 main
函数的返回值:当 main
数返回时,__libc_start_main
会负责处理这个返回值,并最终调用 _exit
函数来终止程序。
上述过程描述了 C/C++ 程序在 main
函数之前执行的一系列操作,但这些操作对于大多数程序员来说是透明的。程序员通常只需要关注 main
数中的代码,而不需要关心底层的初始化过程。然而,了解这些底层细节有助于更好地理解程序的执行流程和调试问题。
可执行程序的入口地址 Entry point address
前置知识:
1、可执行程序是一种 ELF
格式的文件,该格式文件的文件头 ELF Headers
中记录着一个属性: Entry point address
2、 Entry point address
指向汇编代码中 <_start>
的起始地址,说明该属性就存储着本程序的执行入口
3、CPU
中存在一个 PC
指针,该指针通过接收进程程序的地址,来识别并执行该指令,进而运行该程序
正文:
在可执行程序 main.exe
的 ELF
格式中的 Entry point address
,就代表该程序的真正入口!
指向汇编代码中 <_start>
的起始地址,因此也证明了 <_start>
才是一个可执行程序的真正入口。
在程序 <_start>
里面,会发现调用 main函数
的指令,这也证明了:一个程序在进入 main函数
执行语言层面的指令之前,会在程序 <_start>
里面,做一些初始化配置工作。
系统只需要将 Entry point address
的值给 CPU
的 PC
指针, CPU
就能从 PC
指针的值开始执行对对于的代码,真正开始运行一个程序。
另外,因为 Entry point address
记录的汇编代码中 <_start>
的起始地址,本质上也是虚拟地址,
因此 CPU 拿到并执行的 Entry point address
,也当然是虚拟地址!!