環(huán)境:lubuntu 13.04 kernel 3.8 gcc 4.7.3 作者: SA12226265 簡(jiǎn)介: 本文基于Linux?系統(tǒng)對(duì)進(jìn)程創(chuàng)建與加載進(jìn)行分析,文中實(shí)現(xiàn)了Linux庫函數(shù)fork,、exec,,剖析內(nèi)核態(tài)執(zhí)行過程,并進(jìn)一步展示進(jìn)程創(chuàng)建過程中進(jìn)程控制塊字段變化信息及ELF文件加載過程,。
一,、初識(shí)Linux進(jìn)程 進(jìn)程這個(gè)概念是針對(duì)系統(tǒng)而不是針對(duì)用戶的,對(duì)用戶來說,他面對(duì)的概念是程序,。當(dāng)用戶敲入命令執(zhí)行一個(gè)程序的時(shí)候,,對(duì)系統(tǒng)而言,它將啟動(dòng)一個(gè)進(jìn)程,。但和程序不同的是,,在這個(gè)進(jìn)程中,系統(tǒng)可能需要再啟動(dòng)一個(gè)或多個(gè)進(jìn)程來完成獨(dú)立的多個(gè)任務(wù),。簡(jiǎn)單介紹下進(jìn)程的結(jié)構(gòu),。 1.1 Linux下的進(jìn)程查看 我們可以使用$ps命令來查詢正在運(yùn)行的進(jìn)程,比如$ps -eo pid,comm,cmd,,下圖為執(zhí)行結(jié)果: (-e表示列出全部進(jìn)程,,-o pid,comm,cmd表示我們需要PID,COMMAND,,CMD信息)
每一行代表了一個(gè)進(jìn)程,。每一行又分為三列。第一列PID(process IDentity)是一個(gè)整數(shù),,每一個(gè)進(jìn)程都有一個(gè)唯一的PID來代表自己的身份,進(jìn)程也可以根據(jù)PID來識(shí)別其他的進(jìn)程,。第二列COMMAND是這個(gè)進(jìn)程的簡(jiǎn)稱,。第三列CMD是進(jìn)程所對(duì)應(yīng)的程序以及運(yùn)行時(shí)所帶的參數(shù)。 (第三列有一些由中括號(hào)[]括起來的,。它們是kernel的一部分功能,,顯示為進(jìn)程的樣子主要是為了方便操作系統(tǒng)管理。) 我們看第一行,,PID為1,,名字為init。這個(gè)進(jìn)程是執(zhí)行/bin/init這一文件(程序)生成的,。當(dāng)Linux啟動(dòng)的時(shí)候,,init是系統(tǒng)創(chuàng)建的第一個(gè)進(jìn)程,這一進(jìn)程會(huì)一直存在,,直到我們關(guān)閉計(jì)算機(jī),。 1.2 Linux下進(jìn)程的結(jié)構(gòu) Linux下一個(gè)進(jìn)程在內(nèi)存里有三部分的數(shù)據(jù),就是"代碼段",、"堆棧段"和"數(shù)據(jù)段",。其實(shí)學(xué)過匯編語言的人一定知道,一般的CPU都有上述三種段寄存器,,以方便操作系統(tǒng)的運(yùn)行,。這三個(gè)部分也是構(gòu)成一個(gè)完整的執(zhí)行序列的必要的部分。 "代碼段",,顧名思義,,就是存放了程序代碼的數(shù)據(jù),,假如機(jī)器中有數(shù)個(gè)進(jìn)程運(yùn)行相同的一個(gè)程序,那么它們就可以使用相同的代碼段,。"堆棧段"存放的就是子程序的返回地址,、子程序的參數(shù)以及程序的局部變量。而數(shù)據(jù)段則存放程序的全局變量,,常數(shù)以及動(dòng)態(tài)數(shù)據(jù)分配的數(shù)據(jù)空間(比如用malloc之類的函數(shù)取得的空間),。系統(tǒng)如果同時(shí)運(yùn)行數(shù)個(gè)相同的程序,它們之間就不能使用同一個(gè)堆棧段和數(shù)據(jù)段,。 1.3 Linux進(jìn)程描述符 在Linux中每一個(gè)進(jìn)程都由task_struct 數(shù)據(jù)結(jié)構(gòu)來定義.task_struct就是我們通常所說的PCB.它是對(duì)進(jìn)程控制的唯一手段也是最有效的手段. 當(dāng)我們調(diào)用fork() 時(shí),系統(tǒng)會(huì)為我們產(chǎn)生一個(gè)task_struct結(jié)構(gòu),。然后從父進(jìn)程,那里繼承一些數(shù)據(jù), 并把新的進(jìn)程插入到進(jìn)程樹中,以待進(jìn)行進(jìn)程管理。 以下是進(jìn)程描述符的源碼: 1 struct task_struct { 2 volatile long state; 3 unsigned long flags; 4 int sigpending; 5 mm_segment_taddr_limit; 6 volatile long need_resched; 7 int lock_depth; 8 long nice; 9 unsigned long policy; 10 struct mm_struct *mm; 11 int processor; 12 unsigned long cpus_runnable, cpus_allowed; 13 struct list_head run_list; 14 unsigned longsleep_time; 15 struct task_struct *next_task, *prev_task; 16 struct mm_struct *active_mm; 17 struct list_headlocal_pages; 18 unsigned int allocation_order, nr_local_pages; 19 struct linux_binfmt *binfmt; 20 int exit_code, exit_signal; 21 int pdeath_signal; 22 unsigned long personality; 23 int did_exec:1; 24 pid_t pid; 25 pid_t pgrp; 26 pid_t tty_old_pgrp; 27 pid_t session; 28 pid_t tgid; 29 int leader; 30 struct task_struct*p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr; 31 struct list_head thread_group; 32 struct task_struct *pid hash_next; 33 struct task_struct **pid hash_pprev; 34 wait_queue_head_t wait_chldexit; 35 struct completion *vfork_done; 36 unsigned long rt_priority; 37 unsigned long it_real_value, it_prof_value, it_virt_value; 38 unsigned long it_real_incr, it_prof_incr, it_virt_value; 39 struct timer_listreal_timer; 40 struct tmstimes; 41 unsigned long start_time; 42 long per_cpu_utime[NR_CPUS],per_cpu_stime[NR_CPUS]; 43 uid_t uid,euid,suid,fsuid; 44 gid_t gid,egid,sgid,fsgid; 45 int ngroups; 46 gid_t groups[NGROUPS]; 47 kernel_cap_tcap_effective, cap_inheritable, cap_permitted; 48 int keep_capabilities:1; 49 struct user_struct *user; 50 struct rlimit rlim[RLIM_NLIMITS]; 51 unsigned shortused_math; 52 charcomm[16]; 53 int link_count, total_link_count; 54 struct tty_struct*tty; 55 unsigned int locks; 56 struct sem_undo*semundo; 57 struct sem_queue *semsleeping; 58 struct thread_struct thread; 59 struct fs_struct *fs; 60 struct files_struct *files; 61 spinlock_t sigmask_lock; 62 struct signal_struct *sig; 63 sigset_t blocked; 64 struct sigpendingpending; 65 unsigned long sas_ss_sp; 66 size_t sas_ss_size; 67 int (*notifier)(void *priv); 68 void *notifier_data; 69 sigset_t *notifier_mask; 70 u32 parent_exec_id; 71 u32 self_exec_id; 72 spinlock_t alloc_lock; 73 void *journal_info; 74 }; 主要結(jié)構(gòu)分析: volatile long state; 說明了該進(jìn)程是否可以執(zhí)行,還是可中斷等信息 unsigned long flags; Flage 是進(jìn)程號(hào),在調(diào)用fork()時(shí)給出 int sigpending; 進(jìn)程上是否有待處理的信號(hào) mm_segment_taddr_limit; 進(jìn)程地址空間,區(qū)分內(nèi)核進(jìn)程與普通進(jìn)程在內(nèi)存存放的位置不同(0-0xBFFFFFFF foruser-thead 0-0xFFFFFFFF forkernel-thread) volatile long need_resched;調(diào)度標(biāo)志,表示該進(jìn)程是否需要重新調(diào)度,若非0,則當(dāng)從內(nèi)核態(tài)返回到用戶態(tài),會(huì)發(fā)生調(diào)度 struct mm_struct *mm; 進(jìn)程內(nèi)存管理信息 pid_tpid; 進(jìn)程標(biāo)識(shí)符,用來代表一個(gè)進(jìn)程 pid_tpgrp; 進(jìn)程組標(biāo)識(shí),表示進(jìn)程所屬的進(jìn)程組 task_struct的數(shù)據(jù)成員mm指向關(guān)于存儲(chǔ)管理的struct mm_struct結(jié)構(gòu),。它包含著進(jìn)程內(nèi)存管理的很多重要數(shù)據(jù),,如進(jìn)程代碼段、數(shù)據(jù)段,、未未初始化數(shù)據(jù)段,、調(diào)用參數(shù)區(qū)和進(jìn)程。
二,、 如何創(chuàng)建一個(gè)進(jìn)程
2.1 Linux下的進(jìn)程控制 在傳統(tǒng)的Linux環(huán)境下,,有兩個(gè)基本的操作用于創(chuàng)建和修改進(jìn)程:函數(shù)fork()用來創(chuàng)建一個(gè)新的進(jìn)程,該進(jìn)程幾乎是當(dāng)前進(jìn)程的一個(gè)完全拷貝,;函數(shù)族e(cuò)xec( )用來啟動(dòng)另外的進(jìn)程以取代當(dāng)前運(yùn)行的進(jìn)程,。 關(guān)于fork()與execl(),去年寫過一篇文章對(duì)部分源碼進(jìn)行過分析:system()和execv()函數(shù)使用詳解 2.2 fork() 一個(gè)進(jìn)程在運(yùn)行中,,如果使用了fork,,就產(chǎn)生了另一個(gè)進(jìn)程。下面就看看如何具體使用fork,,這段程序演示了使用fork的基本框架: #include <stdio.h> void main() { int i; if ( fork() == 0 ) { /* 子進(jìn)程程序 */ for ( i = 1; i <1000; i ++ ) printf("This is child process\n"); } else { /* 父進(jìn)程程序*/ for ( i = 1; i <1000; i ++ ) printf("This is origin process\n"); } } 運(yùn)行結(jié)果如下: 從上圖可以看出父進(jìn)程和子進(jìn)程并發(fā)運(yùn)行,,內(nèi)核能夠以任意方式交替運(yùn)行它們,這里是父進(jìn)程先運(yùn)行,,然后是子進(jìn)程,。但是在另外一個(gè)系統(tǒng)上運(yùn)行時(shí)不一定是這個(gè)順序。 使用fork函數(shù)創(chuàng)建的子進(jìn)程從父進(jìn)程的繼承了全部進(jìn)程的地址空間,,包括:進(jìn)程上下文,、進(jìn)程堆棧、內(nèi)存信息,、打開的文件描述符,、信號(hào)控制設(shè)置、進(jìn)程優(yōu)先級(jí)、進(jìn)程組號(hào),、當(dāng)前工作目錄,、根目錄、資源限制,、控制終端等,。 fork創(chuàng)建子進(jìn)程,首先調(diào)用int80中斷,,然后將系統(tǒng)調(diào)用號(hào)保存在eax寄存器中,,進(jìn)入內(nèi)核態(tài)后調(diào)用do_fork(),實(shí)際上是創(chuàng)建了一份父進(jìn)程的拷貝,,他們的內(nèi)存空間里包含了完全相同的內(nèi)容,,包括當(dāng)前打開的資源,數(shù)據(jù),,當(dāng)然也包含了程序運(yùn)行到的位置,,也就是說fork后子進(jìn)程也是從fork函數(shù)的位置開始往下執(zhí)行的,而不是從頭開始,。而為了判別當(dāng)前正在運(yùn)行的是哪個(gè)進(jìn)程,,fork函數(shù)返回了一個(gè)pid,在父進(jìn)程里標(biāo)識(shí)了子進(jìn)程的id,,在子進(jìn)程里其值為0,,在我們的程序里就根據(jù)這個(gè)值來分開父進(jìn)程的代碼和子進(jìn)程的代碼。 一旦使用fork創(chuàng)建子進(jìn)程,,則進(jìn)程地址空間中的任何有效地址都只能位于唯一的區(qū)域,,這些區(qū)域不能相互覆蓋,。編寫如下代碼進(jìn)行測(cè)試: 1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <unistd.h> 4 5 struct con { 6 int a; 7 }; 8 9 int main() { 10 pid_t pid; 11 struct con s; 12 s.a = 2; 13 struct con* sp = &s; 14 pid = fork(); 15 if (pid > 0) { 16 printf("parent show %p, %p, a = %d\n", sp, &sp->a, sp->a); 17 sp->a = 1; 18 sleep(10); 19 printf("parent show %p, %p, a = %d\n", sp, &sp->a, sp->a); 20 printf("parent exit\n"); 21 } 22 else { 23 printf("child show %p, %p, a = %d\n", sp, &sp->a, sp->a); 24 sp->a = -1; 25 printf("child change a to %d\n", sp->a); 26 } 27 return 0; 28 } 獲得結(jié)果如下: 從上面的分析可以看出進(jìn)程copy過程中,,fork就是基于寫時(shí)復(fù)制,只讀代碼段是可以同享的,,一般CPU都是以"頁"為單位來分配內(nèi)存空間的,,每一個(gè)頁都是實(shí)際物理內(nèi)存的一個(gè)映像,象INTEL的CPU,,其一頁在通常情況下是 4086字節(jié)大小,,而無論是數(shù)據(jù)段還是堆棧段都是由許多"頁"構(gòu)成的,fork函數(shù)復(fù)制這兩個(gè)段,,物理空間上兩個(gè)進(jìn)程的數(shù)據(jù)段和堆棧段都還是共享著的,,當(dāng)有一個(gè)進(jìn)程寫了某個(gè)數(shù)據(jù)時(shí),這時(shí)兩個(gè)進(jìn)程之間的數(shù)據(jù)才有了區(qū)別,,系統(tǒng)就將有區(qū)別的" 頁"從物理上也分開,。系統(tǒng)在空間上的開銷就可以達(dá)到最小。 2.3 exec( )函數(shù)族 下面我們來看看一個(gè)進(jìn)程如何來啟動(dòng)另一個(gè)程序的執(zhí)行。在Linux中要使用exec函數(shù)族,。系統(tǒng)調(diào)用execve()對(duì)當(dāng)前進(jìn)程進(jìn)行替換,,替換者為一個(gè)指定的程序,其參數(shù)包括文件名(filename),、參數(shù)列表(argv)以及環(huán)境變量(envp),。exec函數(shù)族當(dāng)然不止一個(gè),但它們大致相同,,在 Linux中,,它們分別是:execl,execlp,,execle,,execv,execve和execvp,,下面以execve為例,。 一個(gè)進(jìn)程一旦調(diào)用exec類函數(shù),它本身就"死亡"了,,execve首先調(diào)用int80中斷,,然后將系統(tǒng)調(diào)用號(hào)保存在eax寄存器中,調(diào)用sys_exec,,將可執(zhí)行程序加載到當(dāng)前進(jìn)程中,,系統(tǒng)把代碼段替換成新的程序的代碼,廢棄原有的數(shù)據(jù)段和堆棧段,,并為新程序分配新的數(shù)據(jù)段與堆棧段,,唯一留下的,就是進(jìn)程號(hào),,也就是說,,對(duì)系統(tǒng)而言,還是同一個(gè)進(jìn)程,,不過已經(jīng)是另一個(gè)程序了,。(不過exec類函數(shù)中有的還允許繼承環(huán)境變量之類的信息。) 那么如果我的程序想啟動(dòng)另一程序的執(zhí)行但自己仍想繼續(xù)運(yùn)行的話,,怎么辦呢,?那就是結(jié)合fork與exec的使用。下面一段代碼顯示如何啟動(dòng)運(yùn)行其它程序: 1 #include <stdio.h> 2 #include <unistd.h> 3 int main(){ 4 if(!fork()) 5 execve("./test",NULL,NULL); 6 else 7 printf("origin process!\n"); 8 return 0; 9 } 輸出結(jié)果如下:
原始進(jìn)程和execve創(chuàng)建的新進(jìn)程,,并發(fā)運(yùn)行,,exec函數(shù)在當(dāng)前進(jìn)程的上下文中加載并運(yùn)行一個(gè)新的程序,并且不返回創(chuàng)建進(jìn)程的函數(shù),。 接下來,,我們分析一下execve函數(shù)執(zhí)行過程中,,以及可執(zhí)行程序的加載過程,在內(nèi)核中execve()系統(tǒng)調(diào)用相應(yīng)的入口是sys_execve(),,函數(shù)首先通過 pt_regs參數(shù)檢查賦值在執(zhí)行該系統(tǒng)調(diào)用時(shí),,用戶態(tài)下的CPU寄存器在核心態(tài)的棧中的保存情況。通過這個(gè)參數(shù),,sys_execve可以獲得保存在用戶空間的以下信息:可執(zhí)行文件路徑的指針(regs.ebx中),、命令行參數(shù)的指針(regs.ecx中)和環(huán)境變量的指針(regs.edx中)。 struct pt_regs { long ebx; long ecx; long edx; long esi; long edi; long ebp; long eax; int xds; int xes; long orig_eax; long eip; int xcs; long eflags; long esp; int xss; } 然后調(diào)用do_execve函數(shù),,首先查找被執(zhí)行的文件,,讀取前128個(gè)字節(jié),確實(shí)加載的可執(zhí)行文件的類型,,然后調(diào)用search_binary_handle()搜索和匹配合適的可執(zhí)行文件裝載處理過程,,elf調(diào)用load_elf_binary(); struct linux_binprm{ char buf[BINPRM_BUF_SIZE]; //保存可執(zhí)行文件的頭128字節(jié) struct page *page[MAX_ARG_PAGES]; struct mm_struct *mm; load_elf_binary()加載過程如下: a.檢查ELF可執(zhí)行文件的有效性,,比如魔數(shù)(開頭四個(gè)字節(jié),,elf文件為0x7F),段“Segment”的數(shù)量,; b.尋找動(dòng)態(tài)鏈接.interp段,,設(shè)置動(dòng)態(tài)連接器的路徑; c.根據(jù)elf可執(zhí)行文件的程序頭表的描述,,對(duì)elf文件進(jìn)行映射,; d.初始化elf進(jìn)程環(huán)境,比如啟動(dòng)時(shí)候的edx寄存器地址是DT_FINI的地址,; e.將系統(tǒng)調(diào)用的返回地址修改為elf可執(zhí)行文件的入口點(diǎn),,就是e_entry所存的地址。對(duì)于動(dòng)態(tài)鏈接的elf可執(zhí)行文件就是動(dòng)態(tài)連接器,。
加載完成后返回do_execve返回到exeve(),從內(nèi)核態(tài)轉(zhuǎn)化為用戶態(tài)并返回e步所在更改的程序入口地址,。即eip存儲(chǔ)器直接跳轉(zhuǎn)到elf程序的入口地址,新進(jìn)程執(zhí)行,。
三,、 進(jìn)程虛擬地址空間與可執(zhí)行程序格式 從操作系統(tǒng)來看,,一進(jìn)程最關(guān)鍵的特征是它擁有獨(dú)立的虛擬地址空間,,一般情況下,創(chuàng)建過程如下: ?、賱?chuàng)建一個(gè)獨(dú)立的虛擬空間,。 ②讀取可執(zhí)行文件頭,,并且簡(jiǎn)歷虛擬空間與可執(zhí)行文件的映射關(guān)系,。 ?、蹖PU的指令寄存器設(shè)置成可執(zhí)行文件的入口地址,啟動(dòng)運(yùn)行,。 在討論地址空間,,進(jìn)程描述符以及ELF文件格式的之前,我們先介紹一點(diǎn)預(yù)備知識(shí),,由于第一節(jié)已經(jīng)介紹了進(jìn)程描述符的部分信息,,在這里介紹下ELF文件格式: 在第二節(jié)使用execve時(shí),我們使用了test可執(zhí)行程序進(jìn)行測(cè)試,,代碼如下: #include <stdio.h> int main(int argc, char const *argv[]) { printf("%s\n","execve the new process!"); return 0; } 描述“Segment”的結(jié)構(gòu)叫程序頭,,它描述了ELF文件該如何被操作系統(tǒng)映射到進(jìn)程的虛擬空間:
上圖共有5個(gè)Segment。從裝載的角度看,,我們只關(guān)心兩個(gè)LOAD和DYNAMIC,,其他Segment在裝載過程中只具有輔助作用,映射過程中,,根據(jù)讀寫執(zhí)行權(quán)限映射到不同的虛擬內(nèi)存區(qū)域 第四行LOAD表示代碼段,,具有可讀可執(zhí)行權(quán)限,被映射到虛擬地址0x08048000,,長度為0x005c4字節(jié)的虛擬存儲(chǔ)區(qū)域中,。 第五行LOAD表示長度為0x100個(gè)字節(jié)的數(shù)據(jù)段,具有可讀可寫權(quán)限,,被映射到開始于虛擬地址0x08049f08處,,長度為0x0011c字節(jié)的虛擬存儲(chǔ)區(qū)域中。 DYNAMIC字段表示的是動(dòng)態(tài)鏈接器所需要的基本信息,,具有可讀可寫權(quán)限,,被映射到開始于虛擬地址0x08049f14處,長度為0x000e8字節(jié)的虛擬存儲(chǔ)區(qū)域中,。
在第二節(jié)中執(zhí)行如下命令后,,ELF文件正式開始加載工作,執(zhí)行第二節(jié)中的加載過程: execve("./test",NULL,NULL); 文件在加載過程中是以elf可執(zhí)行文件的形式加載,,加載過程初始化時(shí),,根據(jù)elf段頭部表信息,初始化bss段,、代碼段和數(shù)據(jù)段的起始地址和終止地址,。 然后調(diào)用mm_release釋放掉當(dāng)前進(jìn)程所占用的內(nèi)存(old_mm),并且將當(dāng)前進(jìn)程的內(nèi)存空間替換成bprm->mm所指定的頁面,,而這塊空間,,便是新進(jìn)程在初始化時(shí)暫時(shí)向內(nèi)核借用的存儲(chǔ)空間,當(dāng)這段空間讀取到目前進(jìn)程的mm以后,,事實(shí)上也就完成了舊進(jìn)程到新進(jìn)程的替換,。這個(gè)時(shí)候bprm->mm這塊內(nèi)核空間也就完成了它的使命,,于是被置為NULL予以回收。(bprm為中保存了讀取128字節(jié)elf文件頭),。 mm指向關(guān)于存儲(chǔ)管理的struct mm_struct結(jié)構(gòu),,其包含在task_struct中。 然后加載段地址到虛擬內(nèi)存地址,,映射如下:
然后另一部分段映射到數(shù)據(jù)區(qū),,關(guān)系如下: 到這里,對(duì)于elf文件的載入(包括之前對(duì)可執(zhí)行文件運(yùn)行環(huán)境準(zhǔn)備工作)的分析基本上可以告一段落了,。
四,、進(jìn)程創(chuàng)建中動(dòng)態(tài)鏈接庫的表現(xiàn)形式
動(dòng)態(tài)鏈接的基本思想是把程序按照模塊拆分,運(yùn)行時(shí)才將它們鏈接在一起形成一個(gè)完整的程序,,而不是像靜態(tài)鏈接一樣把所有的程序模塊都鏈接成一個(gè)單獨(dú)的可執(zhí)行文件,。多個(gè)動(dòng)態(tài)鏈接庫均以ELF文件存儲(chǔ),執(zhí)行過程中以依賴樹的關(guān)系存在,,并以深度優(yōu)先的方式加載動(dòng)態(tài)鏈接庫,,最終將可執(zhí)行程序返回給用戶。 我們通過以下實(shí)例來測(cè)試動(dòng)態(tài)鏈接庫在虛擬地址及ELF文件的中表現(xiàn)形式:
/* Lib.c */ #include <stdio.h> void lab(int i){ printf("Printing from lib.so %d\n", i);
使用gcc編譯生成一個(gè)共享對(duì)象文件,,然后鏈接dyn.c程序,,生成可執(zhí)行文件dyn: gcc -fPIC -shared -o lib.so lib.c 運(yùn)行并查看進(jìn)程的虛擬地址空間分布:
整個(gè)進(jìn)程的虛擬地址空間中,多出了幾個(gè)文件的映射,。dyn與lib.so一樣,,都被系統(tǒng)映射到進(jìn)程的虛擬地址空間,地址與長度均不相同,。由第二節(jié)可知,,在映射完可執(zhí)行文件之后,操作系統(tǒng)會(huì)先啟動(dòng)一個(gè)動(dòng)態(tài)鏈接器,。 動(dòng)態(tài)鏈接器的的位置由ELF文件中的“.interp”段決定,,而段“.dynamic”為動(dòng)態(tài)鏈接提供了:依賴哪些共享對(duì)象、動(dòng)態(tài)鏈接符號(hào)表的位置,,動(dòng)態(tài)鏈接重定位表的位置,、共享對(duì)象初始化代碼的地址等??赏ㄟ^readelf查看".dynamic" 段的內(nèi)容: 動(dòng)態(tài)鏈接過程需要?jiǎng)討B(tài)符號(hào)表來確定函數(shù)的定義和引用關(guān)系,,還需要重定位表來修正導(dǎo)入符號(hào)的引用。初始化完成后堆棧中保存了動(dòng)態(tài)連接器所需要的一些輔助信息數(shù)組(其中包括程序入口地址,,程序表頭地址,,程序表頭項(xiàng)數(shù)及大?。?。動(dòng)態(tài)鏈接庫最后被映射到進(jìn)程地址空間的共享庫區(qū)域段,。 完成重定位和初始化后,所有準(zhǔn)備工作結(jié)束,,所需要的共享對(duì)象也都已經(jīng)裝載并且鏈接完成,。最后將進(jìn)程的控制權(quán)轉(zhuǎn)交給dyn程序的入口并開始執(zhí)行。
以上內(nèi)容均為個(gè)人理解,,由于能力有限,,可能會(huì)有諸多錯(cuò)誤,希望能夠和大家一起討論修正,。 |
|