1. 程式人生 > >Linux載入啟動可執行程式的過程(二)直譯器完成動態連結

Linux載入啟動可執行程式的過程(二)直譯器完成動態連結

接著上一篇部落格。前面的工作都是在核心完成的,接下來會回到使用者空間。

第一步,直譯器(也可以叫動態連結器)首先檢查可執行程式所依賴的共享庫,並在需要的時候對其進行載入。

ELF 檔案有一個特別的節區: .dynamic,它存放了和動態連結相關的很多資訊,例如動態連結器通過它找到該檔案使用的動態連結庫。不過,該資訊並未包含動態連結庫的絕對路徑,但直譯器通過 LD_LIBRARY_PATH 引數可以找到(它類似 Shell 直譯器中用於查詢可執行檔案的 PATH 環境變數,也是通過冒號分開指定了各個存放庫函式的路徑)該變數實際上也可以通過/etc/ld.so.conf 檔案來指定,一行對應一個路徑名。為了提高查詢和載入動態連結庫的效率,系統啟動後會通過 ldconfig 工具建立一個庫的快取 /etc/ld.so.cache 。如果使用者通過 /etc/ld.so.conf 加入了新的庫搜尋路徑或者是把新庫加到某個原有的庫目錄下,最好是執行一下 ldconfig 以便重新整理快取。

找到動態連結庫後,就可以將其載入到記憶體中。

第二步,直譯器對程式的外部引用進行重定位,並告訴程式其引用的外部變數/函式的地址,此地址位於共享庫被載入在記憶體的區間內。動態連結還有一個延遲定位的特性,即只有在“真正”需要引用符號時才重定位,這對提高程式執行效率有極大幫助。(如果設定了 LD_BIND_NOW 環境變數,這個動作就會直接進行)

下面具體說明符號重定位的過程。

首先了解幾個概念。符號,也就是可執行程式程式碼段中的變數名、函式名等。重定位是將符號引用與符號定義進行連結的過程,對符號的引用本質是對其在記憶體中具體地址的引用,所以本質上來說,符號重定位要解決的是當前編譯單元如何訪問「外部」符號這個問題。動態連結是在程式執行時對符號進行重定位,也叫執行時重定位(而靜態連結則是在編譯時進行,也叫連結時重定位)

現代作業系統中,二進位制映像的程式碼段不允許被修改,而資料段能被修改。

編寫如下程式碼

通過gcc編譯成.o檔案後,再通過objdump-d命令得到檔案的彙編指令,如下所示

call指令的運算元是fc ff ff ff,翻譯成16進位制數是0xfffffffc,看成有符號是-4。這裡應該存放printf函式的地址,但由於編譯階段無法知道printf函式的地址,所以預先放一個-4在這裡。所以程式為了正確執行,需要在連結時對其地址進行修正。這裡的原理對靜態連結和動態連結來說都是一樣的。

但對於動態連結來說,有兩個不同的地方:

(1)因為不允許對可執行檔案的程式碼段進行載入時符號重定位,因此如果可執行檔案引用了動態庫中的資料符號,則在該可執行檔案內對符號的重定位必須在連結階段完成,為做到這一點,連結器在構建可執行檔案的時候,會在當前可執行檔案的資料段裡分配出相應的空間來作為該符號真正的記憶體地址,等到執行時載入動態庫後,再在動態庫中對該符號的引用進行重定位:把對該符號的引用指向可執行檔案資料段裡相應的區域。

(2)ELF 檔案對呼叫動態庫中的函式採用了所謂的"延遲繫結"(lazy binding)策略, 只有當該函式在其第一次被呼叫發生時才最終被確認其真正的地址,因此我們不需要在呼叫動態庫函式的地方直接填上假的地址,而是使用了一些跳轉地址作為替換,這樣一來連修改動態庫和可執行程式中的相應程式碼都不需要進行了,當然延遲繫結的目的不是為了這個,具體先不細說。

可執行程式對符號的訪問又分為模組內和模組間的訪問,這裡只介紹模組間的訪問,也就是訪問動態連結庫中的符號。

通過gcc生成test可執行檔案,然後同樣用objdump-d得到可執行檔案的彙編指令,如下所示

可以看到這裡的call指令指向了80482e0地址處,也即是PLT。

PLT就是程式連結表(Procedure Link Table),屬於程式碼段。用於把位置獨立的函式呼叫重定向到絕對位置。每個動態連結的程式和共享庫都有一個PLT,PLT表的每一項都是一小段程式碼,從對應的GOT表項中讀取目標函式地址。程式對某個函式的第一次訪問都被調整為對 PLT入口也就是PLT0的訪問,也就是說所有的PLT首次執行時,最後都會跳轉到第一個PLT中執行。PLT0是一段訪問動態連結器的特殊程式碼,是動態連結做符號解析和重定位的公共入口。這樣做的好處是不用每個PLT表都有重複的一份指令,可以減少PLT指令條數。

PLT表結構如下圖所示

可以看到,PLT會先執行jmp指令跳轉到某一個地址,而這個地址就對應的GOT表項。

GOT就是全域性偏移表(Global Offset Table),屬於資料段。為了能使得程式碼段裡對資料及函式的引用與具體地址無關,只能再作一層跳轉,ELF 的做法是在動態庫的資料段中加一個表項,也就是GOT 。GOT表格中放的是資料全域性符號的地址,該表項在動態庫被載入後由動態載入器進行初始化,動態庫內所有對資料全域性符號的訪問都到該表中來取出相應的地址,即可做到與具體地址了,而該表作為動態庫的一部分,訪問起來與訪問模組內的資料是一樣的。

GOT表結構如下圖所示

GOT[0]對應本ELF動態段(.dynamic段)的裝載地址,GOT[1]對應本ELF的link_map資料結構描述符地址,GOT[2]:對應_dl_runtime_resolve動態連結器函式的地址。3個特殊項後面依次是每個動態庫函式的GOT表項

上面講到PLT通過jmp指令跳轉到GOT表中去取函式的真實地址,而符號所對應的表項開始是沒有這個地址的,而是存放了該PLT表項jmp指令的下一條指令地址,也就是push指令。回到了PLT表項對應的指令中繼續執行,最後一條jmp指令跳轉到了PLT0中執行。

PLT0對應的指令執行了下列過程:首先pushl把 804a004(GOT[1])這塊記憶體裡的qword入棧,這個qword是link_map的地址,根據這個地址可以找到動態庫的符號表。然後jmp跳轉到GOT表中的第三項,找到動態連結器的_dl_runtime_resolve函式地址,開始執行該函式。回想前面講到的核心中載入目標映像的過程,可執行檔案在Linux核心通過exeve裝載完成之後,不直接執行,而是先跳到動態連結器(ld-linux-XXX)執行。在ld-linux-XXX裡將link_map地址、_dl_runtime_resolve地址寫到GOT表項內。所以在此時,該GOT表項的不為空。(前面三個GOT表項都是這樣被寫入的)然後當程式載入其它動態庫的時候,會把動態庫的符號資訊插入link_map


_dl_runtime_resolve函式得到動態連結庫中函式的地址後(該過程以後再分析),寫回到對應的GOT表項中。

這就是函式第一次被呼叫時執行的過程。以後每次被呼叫直接從GOT表中取到函式地址就可以了。

總的來說,動態重定位的過程可以由下圖表示

相關推薦

Linux載入啟動執行程式過程直譯器完成動態連結

接著上一篇部落格。前面的工作都是在核心完成的,接下來會回到使用者空間。第一步,直譯器(也可以叫動態連結器)首先檢查可執行程式所依賴的共享庫,並在需要的時候對其進行載入。ELF 檔案有一個特別的節區: .dynamic,它存放了和動態連結相關的很多資訊,例如動態連結器通過它找到

Linux學習之多執行緒程式設計

言之者無罪,聞之者足以戒。 ——《詩序》 (二)、執行緒的基本控制 1、終止程序: 如果程序中的任意一個程序呼叫了exit、_exit、_Exit,那麼整個程序就會終止 普通的單個程序有以下3種退出方式,這樣不會終止程序: (1)從啟動例程中返回,返回值是執行緒的退

ASP.NET Core 3.x啟動執行非同步任務

這一篇是接著前一篇在寫的。如果沒有看過前一篇文章,建議先去看一下前一篇,這兒是傳送門   一、前言 前一篇文章,我們從應用啟動時非同步執行任務開始,說到了必要性,也說到了幾種解決方法,及各自的優缺點。最後,還提出了一個比較合理的解決方法:通過在Program.cs里加入程式碼,來實現IWebHost啟動

Python學習筆記Python程式碼轉換為.exe執行程式過程及注意事項

作者Python版本為3.6 一.  pyInstaller安裝配置 1,開啟網址:pyInstalller下載網址; 如圖: 2,下載並解壓後目錄如下:(該檔案版本為3.2.1版本,因後步驟需要加入-bac以示區別); 3,還需要下載一個pywin32,pywi

Linux執行本目錄的執行文件命令為什麽需要在文件名前加“./”

使用 當前 bin post 文件內容 sbin use usr 新增 一、PATH 是環境變量,裏面保存了執行文件路徑(通常會包含多個路徑,各路徑之間以冒號“:”進行間隔)。當執行一個可執行文件(命令)時,Linux 會優先到 PATH 環境變量中保存的路徑下進行查找。使

Linux 環境程式設計—執行程式結構與程序結構

Linux可執行檔案結構 在 Linux 下,程式是一個普通的可執行檔案,以下列出一個二進位制可執行檔案的基本情況: 可以看出,此可執行檔案在儲存時(沒有調入到記憶體前)分為程式碼區(text)、資料區(data)和未初始化資料區(bss)3 個部分。各段基本內容說明如下: 程式碼區:

Linux GCC生成執行程式的4個步驟——預處理、編譯、彙編、連結

一,預編譯 操作步驟:gcc -E hello.c -o hello.i 主要作用: 處理關於 “#” 的指令 【1】刪除#define,展開所有巨集定義。例#define portnumber 3333 【2】處理條件預編譯 #if, #ifdef, #if, #elif,#e

教你如何獲得執行程式執行目錄、執行程式名、執行程式字尾C\C++

很多童鞋可能遇見過這個問題。下面給大家講講哈,希望各位有用。 首先,我們應該明白main函式的引數是什麼意思。 int main(int argc, char **argv); 第一個引數說的是,argv中有多少個字串。第二個引數存放的是一些字串,這些字串是系統給出的。其

odroidc2中執行的fuchsiazircon kernel編譯過程基於2018年3月版本

zircon的啟動 zircon核心由uboot載入,本次試驗使用的uboot是odroid專門為fuchsia作業系統開發的,並沒有原始碼,zircon核心的啟動目錄內容為: XXXXXXXX:~/work/zircon-topic-odroidc2/odroidc

MFC獲取執行文件exe所在文件目錄

etl 獲取 markdown 可執行文件 for lena () break efi 可以應用函數GetModuleFileName(),舉一個例子: CString strexe; ::GetModuleFileName(NULL,strexe.GetBufferSet

linux執行緒入門互斥量

當多個執行緒訪問一個共享的變數的時候是非常危險的,可能會拿到錯誤的資料或者程式崩潰! 所以為了安全的使用執行緒引入了互斥量的做法 兩個互斥量的函式為 pthread_mutex_lock(pthread_mutex_lock* lock) pthread_mutex_unlock(p

hadoop啟動過程secondNameNode

  作用:定期將namenode的fsimage和edits合併(資料或者操作不多的時候可以關閉 ),可加速hdfs啟動(如果edits很多的話,開啟會很難)   SecondNameNode: 它會定期的和namenode就行通訊來完成整個的備份操作(????更

Linux學習之多執行緒程式設計

言之者無罪,聞之者足以戒。 ——《詩序》 三、Linux執行緒的高階控制 1、一次性初始化 有些事需要且只能執行一次(比如互斥量初始化)。通常當初始化應用程式時,可以比較容易地將其放在main函式中。但當你寫一個庫函式時,就不能在main裡面初始化了,你可以用靜態初始化

Linux學習之多執行緒程式設計

言之者無罪,聞之者足以戒。 ——《詩序》 4、執行緒私有屬性 應用程式設計中有必要提供一種變數,使得多個函式多個執行緒都可以訪問這個變數(看起來是個全域性變數),但是執行緒對這個變數的訪問都不會彼此產生影響(貌似不是全域性變數哦),但是你需要這樣的資料,比如errno。那

Linux上部署專案遇到的問題tomcat啟動成功,但是開啟專案失敗

這個可能是埠號不對。解決步驟如下: 1. 找到tomcat中server.xml 所在位置 find / -name server.xml 2. 使用cd 命令,轉換到server.xml的目錄下,再使用cat命令檢視server.xml cd /home/tomca

Linux 安裝Mono環境 執行ASP.NET

1、先看一下Linux環境下面請求的過程,(畫的不是很好,簡單的瞭解一下原理。) .NET跨平臺其實需要這三個關鍵:編譯器、CLR和基礎類庫。在.NET下我們編寫一個最簡單的“Hello World”都需要mscorlib.dll這個動態連結庫,因為.NET框架已經為我們提供了這些,因為在我們的計算機上安裝

elasticsearch原始碼分析之啟動過程

最近開始廣泛的使用elasticsearch,也開始寫一些java程式碼了,為了提高java程式碼能力,也為了更加深入一點了解elasticsearch的內部運作機制,所以開始看一些elasticsearch的原始碼了。對於這種廣受追捧的開源專案,細細品讀一定會受益匪淺,

windows系統下執行程式呼叫lib靜態庫和dll動態庫的方法

#include <stdio.h> #include <Windows.h>   int main() {    HINSTANCE h=LoadLibraryA("newdll.dll");     typedef int (* FunPtr)(int a,int b);//定義函

Docker在Linux執行NetCore系列把本地編譯好的映象釋出到線上阿里雲倉庫 Docker在Linux/Windows上執行NetCore文章系列

原文: Docker在Linux上執行NetCore系列(二)把本地編譯好的映象釋出到線上阿里雲倉庫 轉發請註明此文章作者與路徑,請尊重原著,違者必究。   系列文章:https://www.cnblogs.com/alunchen/p/10121379.html  

Eclipse 匯出執行Jar檔案工程包含第三方Jar包

背景:寫了一個小功能的專案,要匯出可執行的jar包,但是專案中匯入驅動資料庫的包;包含有第三方jar包;但是網上很多方法在打包之前先在工程目錄下建立一個名字為MANIFEST.MF;本人不愛寫,覺得太麻