運行庫到底做了什么?


對於程序加載講的挺淺顯清晰地,轉載下

轉自   http://mp.weixin.qq.com/s?__biz=MzI3NzA5MzUxNA==&mid=266460236

 

什么是運行庫?它們是在程序背后默默服務的團體,它們能夠使得程序正常地啟動,使得各種我們熟悉的函數發揮作用。

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注冊的函數,最后結束進程。

運行這些代碼的函數稱為 入口函數 ,實際上就是一個程序的初始化和結束部分,它往往是運行庫的一部分。

入口函數的實現

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++的異常處理的平台相關函數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM