简介
用户在编写程序时都要定义一个main()函数作为程序运行的入口。程序开始 执行时就从这个函数开始。当这个函数返回时就表明程序运行结束了。可是用户编写的 程序要能正确运行远不是这么简单。比如,我们不禁要问main()是由谁调用 的呢?当从main()返回后又运行到哪里去了呢?C++程序中定义的全局对象 是如何构造的呢?又是如何析构的呢?如果程序是动态链接的,它所依赖的共享库是 如何加载进内存的?更复杂的是,共享对象中的全局对象是如何构造的和析构的呢? 要回答这些问题,就不得不弄清程序加载、运行和终止的整个流程,从中也可以知道 系统软件(包括操作系统、动态链接器、链接编辑器和编译器)为了支持用户程序的 正确运行做了多么复杂的工作。
为了支持用户程序的正确运行需要解决以下几个重要问题:
- 加载用户程序以及它所依赖的所有共享对象;
- 对用户程序和共享对象进行符号解析和重定 位;
- 向用户程序传递环境变量和命令行参数。
- 根据C++标准的规定,全局对象(包括用户程序和共享库中定义的)必须 在main()执行前初始化,并在程序结束时以相反的顺序析构。
为了理清这些问题,下面我们来分析Linux系统下程序的运行流程。
术语
-
程序头(Program Header)
-
程序头在[gabi]的
Program Header
一节中定义,是ELF文件执行视图的重 要部分。它规定了ELF文件中的哪些部分段需要加载以及加载的地址以及是否需要动 态链接器等信息。若需要动态链接器,程序头中的 PT_INTERP指定了动态 链接器的路径 初始化代码和终止代码(Initialization and Termination code)
-
每个可执行文件和共享对象都有初始化代码和终止代码。初始化代码在用户程 序开始执行前执行。所有的共享对象的初始化代码在可执行文件获得控制权之前执 行。终止代码则在进程退出时执行,顺序与初始化代码执行的顺序相反。共享对象 的初始化代码和终止代码由动态连接器负责执行。(
Initialization and Termination Functions
, [gabi]) 加载时重定位(Load-time Relocation)和运行时重定位(Run-time Relocation)
- 加载时重定位指在动态链接器加载对象文件后就进行的重定位,而运行时重定位 是指在用户程序已开始运行后在需要的情况下进行的重定位。PLT表的重定位就属于 运行时重定位。在P