运行库到底做了些什么?

来源:互联网 发布:淘宝客微信助手软件 编辑:程序博客网 时间:2024/05/18 22:47

//这是我今天看到的一篇文章,里面的内容进行了一下补充。

1、入口函数与程序初始化


main真的是程序的起始吗?


我们编写每一个C程序都需要编写main函数,之前也一直都说main函数是程序的开始,但是真的是这样吗?其实程序执行到main函数的第一行时候,很多事情都已经完成,比如全局变量的初始化,比如命令行的参数传递,比如堆和栈的初始化,更比如一些系统I/O的初始化(可以放心使用printf、malloc等函数)都已经完成了,这些都说明了main并不是我们执行一个程序的真正开始,那么main之前都做了什么呢?

首先了解一下atexit函数,这个函数的调用时机是在main结束后,它以一个函数指针作为参数,保证在程序正常退出(从main函数返回或者调用exit函数)时,函数指针指向的函数会被调用


可以看到结果是首先执行了第二句的end of main,然后在main函数返回后执行了atexit注册的foo函数。

于是推断真正的过程是:
OS装载程序后,首先运行的是别的代码,它们负责准备好main函数执行所需要的环境,并负责调用main函数,在main返回后,其会记录main函数的返回值,调用atexit注册的函数,最后结束进程。

运行这些代码的函数称为入口函数,实际上就是一个程序的初始化和结束部分,它往往是运行库的一部分。

<span style="color:#cc33cc;">C语言规定main函数的参数只能有两个,习惯上这两个参数写为argc和argv。因此,main函数的函数头可写为: main (argc,argv)C语言还规定<u>argc(第一个形参)必须是<strong>整型变量</strong>,argv( 第二个形参)必须是<strong>指向字符串的指针数组</strong></u>。加上形参说明后,main函数的函数头应写为:</span>
<span style="color:#cc33cc;">main (argc,argv)int argv;char *argv[];或写成:main (int argc,char *argv[])</span>
<span style="color:#cc33cc;">由于main函数不能被其它函数调用, 因此不可能在程序内部取得实际值。那么,在何处把实参值赋予main函数的形参呢? 实际上,</span>
<span style="color:#cc33cc;">main函数的参数值是从操作系统命令行上获得的。main的形参只有二个,而命令行中的参数个数原则上未加限制<strong>。argc参数</strong>表示了命令行中<strong>参数的个数</strong>(注意:文件名本身也 算一个参数),argc的值是在输入命令行时由系统按实际参数的个数自动赋予的<strong>。argv参数是字符串指针数组</strong>,其各元素值为命令行中各字符串(参数均按字符串处 理)的<strong>首地址</strong>。 <u>指针数组的长度即为参数个数</u>。数组元素初值由系统自动赋予。</span>
<span style="color:#cc33cc;"></span>


入口函数的实现


glibc的真正程序入口为_start,需要下载glibc的源码才能够看到,下载完成并解压后在其目录下的sysdeps目录下有着各种CPU型号的实现,由于在32位环境下进行实验,因此进入i386目录下在start.S文件中可以看到_start的具体实现。


图中就是具体实现的步骤,为了简单起见,这里删除了一些共享情况下的代码,这些是静态链接情况下的_start的具体实现,主要分为两部分,即图中的线分隔开的两部分内容。
其实这部分的道理很简单,我们来一句一句看


这是第一部分的功能,可以看到其实功能就是ebp清0表示最外层函数,esi中存放argc,ecx指向argv(这里还要注意到其实ecx也指向了环境变量,毕竟环境变量就在命令行参数后面)

下面来看第二个部分,不过第二个部分实际上就是压参数然后调函数,先了解这个函数的名称及参数
具体的文件位于glibc目录下的csu/libc-start.c文件中


可以看到,总计7个参数,这里我们按照之前讲过的调用惯例,从右向左压入参数来对比下面的具体的第二部分代码,对具体每一句的功能进行解释


这样第二部分的功能就很清晰了

那么接下来看__libc_start_main的具体工作,由于代码较多所以直接列出主要的部分


最后简单看一下exit的实现


调用_exit后进程会直接结束。程序结束一般有两种情况,一个是main函数正常返回,另一个是程序中exit退出。实际上可以看到不管是哪种最后都会使用exit退出的情况,因此exit是进程正常退出的必经之路

实际上glibc的入口函数写得不是很直观,我们没有从glibc的入口函数中了解多少内容,而在Windows下则能看得比较清楚。将Windows下Visual Studio中的默认的入口函数mainCRTStartup的功能进行一个简单的概括,主要流程就是:
1、初始化和操作系统版本有关的全局变量
2、初始化堆
3、初始化I/O
4、获取命令行参数和环境变量
5、初始化C库的一些数据
6、调用main并记录返回值
7、检查错误并将main的返回值返回
这也是一个入口函数的实现上比较清晰的思路。


2、C语言运行库


任何一个C程序,背后都有一套庞大的代码对其支持,让其能够正常运行。前面了解的入口函数以及一些初始化所要使用的函数都必须包含在这个代码集合中,这样的代码集合叫做运行时库,而C语言的运行库,称为C运行库简称CRT

一个C语言运行库大致包含以下功能:



glibc


glibc即GNU C Library,是GNU下的C标准库,关于其的一些版本细节什么的就不说了,主要介绍下几个除了C标准库之外的辅助程序运行的运行库,它们是/usr/lib/crt1.o、/usr/lib/crti.o和/usr/lib/crtn.o

首先是crt1.o,它包含了程序的入口函数_start,负责调用__libc_start_main初始化libc并且调用main函数进入真正的程序主体。
另外crti.o和crtn.o这两个目标文件中包含的代码实际上是_init( )函数和_finit( )函数的开始和结尾部分,因此将这两个文件和其他目标文件链接起来以后就刚好形成了_init( )和_finit( )两个完整的函数,实际上也就是最终输出文件中的.init和.fini两个段。
crti.o反汇编代码


crtn.o反汇编代码


可以看到两个目标文件中都包含了.init和.fini的代码,分别构成了两段的头和尾部

在链接时除上面的三个文件以外还有着其他文件,但是它们实际上不属于glibc,是GCC的一部分都位于gcc的安装目录下


其中crtbeginT.o以及crtend.o是用于实现C++全局构造和析构的目标文件,实际上glibc只是一个C语言运行库,它对C++的实现并不了解,gcc才是C++的真正实现者,于是由它提供了这两个目标文件配合glibc实现C++的全局构造和析构。
GCC是支持诸多平台的,而处理不同平台间的差异性也是其的任务,而libgcc.a就是用来正确处理不同平台的差异的,主要包括整数运算、浮点数运算,最后libgcc_eh.a包含了支持C++的异常处理的平台相关函数。


0 0
原创粉丝点击