乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      淺析Linux計(jì)算機(jī)進(jìn)程地址空間與內(nèi)核裝載ELF

       鳳凰苑兇真 2016-07-24

      環(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)程描述符的源碼:

      復(fù)制代碼
       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 };
      復(fù)制代碼

      主要結(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的基本框架:

      復(fù)制代碼
      #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");
          }
      }
      復(fù)制代碼

        運(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è)試:

      復(fù)制代碼
       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 }
      復(fù)制代碼

      獲得結(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)行其它程序:

      復(fù)制代碼
      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 }
      復(fù)制代碼

        輸出結(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中)。

      復(fù)制代碼
      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;
      }
      復(fù)制代碼

        然后調(diào)用do_execve函數(shù),首先查找被執(zhí)行的文件,讀取前128個(gè)字節(jié),確實(shí)加載的可執(zhí)行文件的類型,然后調(diào)用search_binary_handle()搜索和匹配合適的可執(zhí)行文件裝載處理過程,elf調(diào)用load_elf_binary();

      復(fù)制代碼
      struct linux_binprm{     
           char buf[BINPRM_BUF_SIZE]; //保存可執(zhí)行文件的頭128字節(jié)               struct page *page[MAX_ARG_PAGES];
           struct mm_struct *mm;
      unsigned long p; //當(dāng)前內(nèi)存頁最高地址 int sh_bang; struct file * file; //要執(zhí)行的文件 int e_uid, e_gid; //要執(zhí)行的進(jìn)程的有效用戶ID和有效組ID kernel_cap_t cap_inheritable, cap_permitted, cap_effective;
      void *security; int argc, envc; //命令行參數(shù)和環(huán)境變量數(shù)目 char * filename; //要執(zhí)行的文件的名稱 char * interp; //要執(zhí)行的文件的真實(shí)名稱,通常和filename相同 unsigned interp_flags; unsigned interp_data; unsigned long loader, exec; };
      復(fù)制代碼

        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ú)立的虛擬空間。

       ?、谧x取可執(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è)試,代碼如下:

      復(fù)制代碼
      #include <stdio.h>
      
      int main(int argc, char const *argv[])
      {
          printf("%s\n","execve the new process!");
          return 0;
      }
      復(fù)制代碼

        描述“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,長(zhǎng)度為0x005c4字節(jié)的虛擬存儲(chǔ)區(qū)域中。

          第五行LOAD表示長(zhǎng)度為0x100個(gè)字節(jié)的數(shù)據(jù)段,具有可讀可寫權(quán)限,被映射到開始于虛擬地址0x08049f08處,長(zhǎng)度為0x0011c字節(jié)的虛擬存儲(chǔ)區(qū)域中。

          DYNAMIC字段表示的是動(dòng)態(tài)鏈接器所需要的基本信息,具有可讀可寫權(quán)限,被映射到開始于虛擬地址0x08049f14處,長(zhǎng)度為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)形式:

      復(fù)制代碼
      /* Lib.h */
      #ifndef LIB_H
      #define LIB_H
      
      void lab(int i)
      
      #endif
      復(fù)制代碼

       

      復(fù)制代碼
      /* Lib.c */
      #include <stdio.h>
      
      void lab(int i){
          printf("Printing from lib.so %d\n", i);
        sleep(-1); }
      復(fù)制代碼

       

      復(fù)制代碼
      /* dyn.c*/
      #include "lib.h"
      
      int main(){
          lab(1);
          return 0;
      }
      復(fù)制代碼

        使用gcc編譯生成一個(gè)共享對(duì)象文件,然后鏈接dyn.c程序,生成可執(zhí)行文件dyn:

      gcc -fPIC -shared -o lib.so lib.c
      gcc -o dyn dyn.c ./lib.so

        運(yùn)行并查看進(jìn)程的虛擬地址空間分布:

       

        整個(gè)進(jìn)程的虛擬地址空間中,多出了幾個(gè)文件的映射。dyn與lib.so一樣,都被系統(tǒng)映射到進(jìn)程的虛擬地址空間,地址與長(zhǎng)度均不相同。由第二節(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ò)誤,希望能夠和大家一起討論修正。

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多