2016-04-07
張超《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000#/info
一、理解編譯鏈接的過程和ELF可執行文件格式

我給出了一個例子:
第一步:先編輯一個hello.c,如下
vi hello.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main() 5 { 6 printf("Hello World!\n"); 7 return 0; 8 }
第二步:生成預處理文件hello.cpp(預處理負責把include的文件包含進來及宏替換等工作)
gcc -E -o hello.cpp hello.c -m32
vi hello.cpp (查看一下hello.cpp的內容,但是太多,這里就不顯示了)
第三步:編譯成匯編代碼hello.s
gcc -x cpp-output -S -o hello.s hello.cpp -m32
vi hello.s (查看一下hello.s的內容,如下所示)
1 .file "hello.c" 2 .section .rodata 3 .LC0: 4 .string "Hello World!" 5 .text 6 .globl main 7 .type main, @function 8 main: 9 .LFB2: 10 .cfi_startproc 11 pushl %ebp 12 .cfi_def_cfa_offset 8 13 .cfi_offset 5, -8 14 movl %esp, %ebp 15 .cfi_def_cfa_register 5 16 andl $-16, %esp 17 subl $16, %esp 18 movl $.LC0, (%esp) 19 call puts 20 movl $0, %eax 21 leave 22 .cfi_restore 5 23 .cfi_def_cfa 4, 4 24 ret 25 .cfi_endproc 26 .LFE2: 27 .size main, .-main 28 .ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4" 29 .section .note.GNU-stack,"",@progbits
第四步:編譯成目標代碼,得到二進制文件hello.o,(該文件中有機器指令,但不能在機器上直接運行)
gcc -x assembler -c hello.s -o hello.o -m32
vi hello.o (查看一下hello.o的內容,結果是亂碼)
第五步:鏈接成可執行文件hello,(它是二進制文件)
gcc -o hello hello.o -m32
vi hello(查看一下hello,發現除了幾個英文標示符可見,其他都是亂碼文件,不過開頭有ELF)
第六步:運行一下
./hello

其實:我們可以用一個命令直接只生成可執行文件hello,並運行
gcc -o hello hello.c
這里,hello和hello.o都是ELF格式的文件。
而這樣編譯出來的hello是使用的共享庫。
我們也可以靜態編譯,(是完全把所有需要執行所依賴的東西放到程序內部)
gcc -o hello.static hello.o -m32 -static
hello.static 也是ELF格式文件
運行一下hello.static
./hello.static

ls -l (查看一下各個文件)

發現hello.static (733298)比 hello (7336)大的多。
靜態鏈接於動態鏈接:
- 靜態鏈接方式於動態鏈接方式
- 靜態鏈接方式:在程序運行之前完成所有的組裝工作,生成一個可執行的目標文件
- 動態鏈接方式:在程序已經為了執行被裝載入內存之后完成鏈接工作,並且在內存中一般只保留該編譯單元的一份拷貝
2.靜態鏈接庫(.a(Linux下) .lib(Windows下))與動態鏈接庫(.so(Linux下) .dll(Windows下))
靜態鏈接庫與動態鏈接庫都是共享代碼的方式。如果采用靜態鏈接庫,則程序把所有執行需要所依賴的東西都加載到可執行文件中了。但若是使用動態庫,則不必被包含在最終執行的文件中,可執行文件執行時可以“動態”的引用和卸載這個與可執行程序獨立的動態庫文件。
簡單的說,靜態庫和應用程序編譯在一起了,在任何情況下都能運行,而動態庫是動態鏈接,顧名思義就是在應用程序啟動的時候再會鏈接的,所以,當用戶的系統上沒有該動態庫時,應用程序就會運行失敗。
3.靜態庫於動態庫的特點
靜態庫:代碼的裝載速度快,執行速度也快,因為編譯時它只會把你所需要的那部分鏈接進去,應用程序相對較大。但是如果沒有多個應用程序的話,會被裝載多次,浪費內存。
動態庫:
共享:多個應用程序可以使用同一個動態庫,啟動多個應用程序的時候,只需要將動態庫加載到內存一次就好。
開發模塊好:要求設計者對功能划分的比較好。
4.認識動態鏈接庫
動態鏈接庫是相對靜態鏈接庫而言的。所謂的靜態鏈接庫只把要調用的函數或過程鏈接到可執行文件中,成為可執行文件的一部分。換句話說,函數和過程的代碼就在程序的可執行文件中,該文件包含了運行時所需的全部代碼。當多個程序都調用相同函數時,內存中就會存在這個函數的多個拷貝,這樣就浪費了寶貴的內存資源,而動態鏈接所調用的函數代碼並沒有被拷貝到應用程序的可執行文件中去,而是僅僅在其中加入了所調用函數的描述信息(往往是一些重定位信息)。僅當應用程序被裝入內存開始運行時,在系統的管理下,才在應用程序與相應的動態庫之間建立鏈接關系。當要執行所調用的動態鏈接庫中的函數時,根據鏈接產生的重定位信息,系統下轉去執行狀態鏈接庫中相應的函數代碼。一般情況下,如果一個應用程序使用了動態鏈接庫,系統保證內存只有動態庫的一份復制品。
動態鏈接庫的兩種鏈接方法:
1)裝載時動態鏈接(Load-time Dynamic Linking):這種方法的前提是在編譯之前已經明確知道要調用的動態庫的哪些函數,編譯時在目標文件中只保留必要的鏈接信息,而不含動態庫函數代碼;當程序執行時,調用函數的時候利用鏈接信息加載動態庫函數代碼並在內存中將其鏈接入調用程序的執行空間中(全部函數加載進內存),其主要目的是便於代碼共享。(動態加載程序,處在加載階段,主要為了共享代碼,共享代碼內存)
2)運行時動態鏈接(Run-time Dynamic Linking):這種方式是指在編譯之前並不知道將會調用哪些動態庫函數,完全是在運行過程中根據需要決定應調用哪個函數,將其加載到內存中(只加載調用的函數進內存);並標識內存地址,其他程序也可以使用該程序,並獲得動態庫函數的入口地址。(動態庫在內存中只存在一份,處在運行階段)
要想理解一個進程是怎樣執行的,一個可執行程序是如何加載成進程的?我們這時需要知道可執行文件的內部是什么?
ELF目標文件格式:ELF(Executable And Linkable Format)
- ELF文件格式--(中文翻譯版本)
ELF Header1 #define EI_NIDENT 16 2 3 typedef struct { 4 unsigned char e_ident[EI_NIDENT]; 5 Elf32_Half e_type; 6 Elf32_Half e_machine; 7 Elf32_Word e_version; 8 Elf32_Addr e_entry; 9 Elf32_Off e_phoff; 10 Elf32_Off e_shoff; 11 Elf32_Word e_flags; 12 Elf32_Half e_ehsize; 13 Elf32_Half e_phentsize; 14 Elf32_Half e_phnum; 15 Elf32_Half e_shentsize; 16 Elf32_Half e_shnum; 17 Elf32_Half e_shstrndx; 18 } Elf32_Ehdr;
- 查看ELF文件的頭部(hello)
readlf -h hello

- 查看該ELF所依賴的共享庫(過程及結果如下圖所示)
ldd hello
readelf -d (也可以看依賴的so文件)

常見的目標文件的格式有如下:
A.out 是最古老的目標文件格式。 現在用的最多的PE(Windows下用的最多的)、ELF(Linux下用的最多的)
目標文件我們一般也叫ABI(應用程序二進制接口)
實際上在目標文件中它已經是二進制兼容(指目標文件已經是適應到某一個CPU體系上的二進制指令)
比如x86上的可執行文件鏈接成ARM上的可執行文件肯定是不行的
ELF格式的文件中有三種主要的目標文件
- 可重定位(relocatable)文件保存着代碼和適當的數據,用來和其他的object文件一起來創建一個可執行文件或是一個共享文件(主要是 .o 文件)
- 一個可執行(executable)文件保存着一個用來執行的程序;該文件指出了exec(BA_OS)如何來創建序進程映像。
- 一個共享object文件保存這代碼和合適的數據,用來被下面的兩個鏈接器鏈接。第一個連接編譯器(請查看ld(SD_CMD)),也可以和其他可重定位和共享object文件來創建他的object。第二是動態鏈接器,聯合一個可執行文件和其他的共享object文件來創建一個進程映像。(主要是 .so 文件)
靜態鏈接的ELF可執行文件於進程的地址空間:

對於32位的x86來講,進程是4G的進程地址空間,最上面1G是內核用的,下面3G是用戶態。0xc000000之上是內核,下面是用戶態可訪問。
默認進程是從0x8048000開始加載,先是頭部(大小不固定),還把代碼和數據加載到進程的地址空間。
ELF Header 中的Entry point address 即是可執行文件加載到內存中開始執行的第一行代碼。(就像上面的程序,他的值是0x8048320)
一般靜態鏈接會將所有代碼放在一個代碼段,而動態鏈接的進程會有多個代碼段。
二、編程使用exec*庫函數加載一個可執行文件
加載可執行程序前的工作:我們需要了解可執行程序的執行環境,一般是通過shell程序來啟動一個可執行程序。
當我們執行可執行文件時,即發起了一個系統調用exec時,我們就要知道shell環境位我們准備了哪些執行的上下文環境。
這樣我們就可以大概了解一下用戶態的執行環境,然后再看一個exec的系統調用,它怎么把一個可執行文件加載在內核里面,又返回到用戶它態。
命令行參數和shell 環境,一般我們執行一個程序的shell環境,我們的實驗直接使用execve系統調用。
$ls -l /usr/bin 列出/usr/bin下的目錄信息
shell本身不限制命令行參數的個數,命令行參數的個數受限於命令本身。
例如, int main(int argc, char * argv[])
又如, int main(int argc, char * argv[], char * envp[])
shell會調用execve將命令行參數和環境參數傳遞給可執行程序的main函數
int execve(const char * filename, char * const argv[], char * const envp[])
庫函數exec*都是execve的封裝例程
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 5 int main(int argc,char* argv[]) 6 { 7 int pid; 8 /*fork another process*/ 9 pid = fork(); 10 if(pid < 0) 11 { 12 /*error occurred*/ 13 fprintf(stderr,"Fork Failed!\n"); 14 exit(-1); 15 } 16 else if(pid == 0) 17 { 18 /* child process */ 19 execlp("/bin/ls","ls",NULL); 20 } 21 else 22 { 23 /* parent process */ 24 /* parent will wait for the child to complete */ 25 wait(NULL); 26 printf("Child Complete\n"); 27 exit(0); 28 } 29 }


命令行參數和環境變量的保存和傳遞是當我們創建一個子進程時,(fork是復制父進程),然后調用exece系統調用,它把要加載的可執行程序把原來的進程環境給覆蓋掉了,覆蓋了以后它的用戶態堆棧也被清空。
這時命令行參數和環境變量會被壓棧。
shell程序 -> execve -> sys_execve 然后在初始化新進程堆棧時拷貝進去。
int execve(const char * filename , char * const argv[] , char * const envp[])
創建了一個新的用戶態堆棧的時候,實際上是把命令行參數(argv[])的內容和環境變量(envp[])的內容通過指針的方式傳遞給系統調用內核處理函數的,然后內核處理函數再建一個可執行程序新的用戶態堆棧的時候,會把這些拷貝到用戶態堆棧,初始化新的可執行程序執行的上下文環境。
先函數調用參數傳遞,再系統調用參數傳遞。
裝載時動態鏈接和運行時動態鏈接舉例:
1 #include <stdio.h> 2 3 #include "shlibexample.h" 4 5 #include <dlfcn.h> 6 7 /* 8 * Main program 9 * input : none 10 * output : none 11 * return : SUCCESS(0)/FAILURE(-1) 12 * 13 */ 14 int main() 15 { 16 printf("This is a Main program!\n"); 17 /* Use Shared Lib */ 18 printf("Calling SharedLibApi() function of libshlibexample.so!\n"); 19 SharedLibApi(); 20 /* Use Dynamical Loading Lib */ 21 void * handle = dlopen("libdllibexample.so",RTLD_NOW); 22 if(handle == NULL) 23 { 24 printf("Open Lib libdllibexample.so Error:%s\n",dlerror()); 25 return FAILURE; 26 } 27 int (*func)(void); 28 char * error; 29 func = dlsym(handle,"DynamicalLoadingLibApi"); 30 if((error = dlerror()) != NULL) 31 { 32 printf("DynamicalLoadingLibApi not found:%s\n",error); 33 return FAILURE; 34 } 35 printf("Calling DynamicalLoadingLibApi() function of libdllibexample.so!\n"); 36 func(); 37 dlclose(handle); 38 return SUCCESS; 39 }
1 #ifndef _DL_LIB_EXAMPLE_H_ 2 #define _DL_LIB_EXAMPLE_H_ 3 4 5 6 #ifdef __cplusplus 7 extern "C" { 8 #endif 9 /* 10 * Dynamical Loading Lib API Example 11 * input : none 12 * output : none 13 * return : SUCCESS(0)/FAILURE(-1) 14 * 15 */ 16 int DynamicalLoadingLibApi(); 17 18 19 #ifdef __cplusplus 20 } 21 #endif 22 #endif /* _DL_LIB_EXAMPLE_H_ */
1 #include <stdio.h> 2 #include "dllibexample.h" 3 4 #define SUCCESS 0 5 #define FAILURE (-1) 6 7 /* 8 * Dynamical Loading Lib API Example 9 * input : none 10 * output : none 11 * return : SUCCESS(0)/FAILURE(-1) 12 * 13 */ 14 int DynamicalLoadingLibApi() 15 { 16 printf("This is a Dynamical Loading libary!\n"); 17 return SUCCESS; 18 }
1 #ifndef _SH_LIB_EXAMPLE_H_ 2 #define _SH_LIB_EXAMPLE_H_ 3 4 #define SUCCESS 0 5 #define FAILURE (-1) 6 7 #ifdef __cplusplus 8 extern "C" { 9 #endif 10 /* 11 * Shared Lib API Example 12 * input : none 13 * output : none 14 * return : SUCCESS(0)/FAILURE(-1) 15 * 16 */ 17 int SharedLibApi(); 18 19 20 #ifdef __cplusplus 21 } 22 #endif 23 #endif /* _SH_LIB_EXAMPLE_H_ */
1 #include <stdio.h> 2 #include "shlibexample.h" 3 4 /* 5 * Shared Lib API Example 6 * input : none 7 * output : none 8 * return : SUCCESS(0)/FAILURE(-1) 9 * 10 */ 11 int SharedLibApi() 12 { 13 printf("This is a shared libary!\n"); 14 return SUCCESS; 15 }
編譯成libshlibexample.so
gcc -shared shlibexample.c -o libshlibexample.so -m32
編譯成libdllibexample.so
gcc -shared dllibexample.c -o libdllibexample.so -m32
分別以共享庫和動態加載共享庫的方式使用libshlibexample.so文件和libdllibexample.so文件
編譯main時,注意這里只提供shlibexample的-L(庫對應的接口頭文件所在的目錄),和-l(庫名,如libshlibexample.so去掉lib和.so的部分)
並沒有提供dllibexample的相關信息,只是指名了-dll
gcc main.c -o main -L ~/my/c/SharedLibDynamicLink -lshlibexample -ldl -m32 (其中 ~/my/c/SharedLibDynamicLink 是我的這幾個文件所在的路徑)
export LD_LIBRARY_PATH=$PWD (將當前目錄加入默認路徑,否則main找不到依賴的庫文件,當然也可以以將庫文件copy到默認路徑下)
./main
運行結果如下:

三、可執行程序的裝載
sys_execve內部會解析可執行文件格式。在 /linux-3.18.6/fs/exec.c
1604SYSCALL_DEFINE3(execve, 1605 const char __user *, filename, 1606 const char __user *const __user *, argv, 1607 const char __user *const __user *, envp) 1608{ 1609 return do_execve(getname(filename), argv, envp); 1610}
1549int do_execve(struct filename *filename, 1550 const char __user *const __user *__argv, 1551 const char __user *const __user *__envp) 1552{ 1553 struct user_arg_ptr argv = { .ptr.native = __argv }; 1554 struct user_arg_ptr envp = { .ptr.native = __envp }; 1555 return do_execve_common(filename, argv, envp); 1556}
do_execve -> do_execve_common -> exec_binprm
1549int do_execve(struct filename *filename, 1550 const char __user *const __user *__argv, 1551 const char __user *const __user *__envp) 1552{ 1553 struct user_arg_ptr argv = { .ptr.native = __argv }; 1554 struct user_arg_ptr envp = { .ptr.native = __envp }; 1555 return do_execve_common(filename, argv, envp); 1556}
1427/* 1428 * sys_execve() executes a new program. 1429 */ 1430static int do_execve_common(struct filename *filename, 1431 struct user_arg_ptr argv, 1432 struct user_arg_ptr envp) 1433{ 1434 struct linux_binprm *bprm; 1435 struct file *file; 1436 struct files_struct *displaced; 1437 int retval; 1438 1439 if (IS_ERR(filename)) 1440 return PTR_ERR(filename); 1441 1442 /* 1443 * We move the actual failure in case of RLIMIT_NPROC excess from 1444 * set*uid() to execve() because too many poorly written programs 1445 * don't check setuid() return code. Here we additionally recheck 1446 * whether NPROC limit is still exceeded. 1447 */ 1448 if ((current->flags & PF_NPROC_EXCEEDED) && 1449 atomic_read(¤t_user()->processes) > rlimit(RLIMIT_NPROC)) { 1450 retval = -EAGAIN; 1451 goto out_ret; 1452 } 1453 1454 /* We're below the limit (still or again), so we don't want to make 1455 * further execve() calls fail. */ 1456 current->flags &= ~PF_NPROC_EXCEEDED; 1457 1458 retval = unshare_files(&displaced); 1459 if (retval) 1460 goto out_ret; 1461 1462 retval = -ENOMEM; 1463 bprm = kzalloc(sizeof(*bprm), GFP_KERNEL); 1464 if (!bprm) 1465 goto out_files; 1466 1467 retval = prepare_bprm_creds(bprm); 1468 if (retval) 1469 goto out_free; 1470 1471 check_unsafe_exec(bprm); 1472 current->in_execve = 1; 1473 1474 file = do_open_exec(filename); 1475 retval = PTR_ERR(file); 1476 if (IS_ERR(file)) 1477 goto out_unmark; 1478 1479 sched_exec(); 1480 1481 bprm->file = file; 1482 bprm->filename = bprm->interp = filename->name; 1483 1484 retval = bprm_mm_init(bprm); 1485 if (retval) 1486 goto out_unmark; 1487 1488 bprm->argc = count(argv, MAX_ARG_STRINGS); 1489 if ((retval = bprm->argc) < 0) 1490 goto out; 1491 1492 bprm->envc = count(envp, MAX_ARG_STRINGS); 1493 if ((retval = bprm->envc) < 0) 1494 goto out; 1495 1496 retval = prepare_binprm(bprm); 1497 if (retval < 0) 1498 goto out; 1499 1500 retval = copy_strings_kernel(1, &bprm->filename, bprm); 1501 if (retval < 0) 1502 goto out; 1503 1504 bprm->exec = bprm->p; 1505 retval = copy_strings(bprm->envc, envp, bprm); 1506 if (retval < 0) 1507 goto out; 1508 1509 retval = copy_strings(bprm->argc, argv, bprm); 1510 if (retval < 0) 1511 goto out; 1512 1513 retval = exec_binprm(bprm); 1514 if (retval < 0) 1515 goto out; 1516 1517 /* execve succeeded */ 1518 current->fs->in_exec = 0; 1519 current->in_execve = 0; 1520 acct_update_integrals(current); 1521 task_numa_free(current); 1522 free_bprm(bprm); 1523 putname(filename); 1524 if (displaced) 1525 put_files_struct(displaced); 1526 return retval; 1527 1528out: 1529 if (bprm->mm) { 1530 acct_arg_size(bprm, 0); 1531 mmput(bprm->mm); 1532 } 1533 1534out_unmark: 1535 current->fs->in_exec = 0; 1536 current->in_execve = 0; 1537 1538out_free: 1539 free_bprm(bprm); 1540 1541out_files: 1542 if (displaced) 1543 reset_files_struct(displaced); 1544out_ret: 1545 putname(filename); 1546 return retval; 1547}
1405static int exec_binprm(struct linux_binprm *bprm) 1406{ 1407 pid_t old_pid, old_vpid; 1408 int ret; 1409 1410 /* Need to fetch pid before load_binary changes it */ 1411 old_pid = current->pid; 1412 rcu_read_lock(); 1413 old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent)); 1414 rcu_read_unlock(); 1415 1416 ret = search_binary_handler(bprm); 1417 if (ret >= 0) { 1418 audit_bprm(bprm); 1419 trace_sched_process_exec(current, old_pid, bprm); 1420 ptrace_event(PTRACE_EVENT_EXEC, old_vpid); 1421 proc_exec_connector(current); 1422 }
search_binary_handler符合尋找文件格式對應的解析模板,如下:(對於給定的文件名,根據文件頭部信息尋找對應的文件格式處理模塊)
1349/* 1350 * cycle the list of binary formats handler, until one recognizes the image 1351 */ 1352int search_binary_handler(struct linux_binprm *bprm) 1353{ 1354 bool need_retry = IS_ENABLED(CONFIG_MODULES); 1355 struct linux_binfmt *fmt; 1356 int retval; 1357 1358 /* This allows 4 levels of binfmt rewrites before failing hard. */ 1359 if (bprm->recursion_depth > 5) 1360 return -ELOOP; 1361 1362 retval = security_bprm_check(bprm); 1363 if (retval) 1364 return retval; 1365 1366 retval = -ENOENT; 1367 retry: 1368 read_lock(&binfmt_lock); 1369 list_for_each_entry(fmt, &formats, lh) { 1370 if (!try_module_get(fmt->module)) 1371 continue; 1372 read_unlock(&binfmt_lock); 1373 bprm->recursion_depth++; 1374 retval = fmt->load_binary(bprm); 1375 read_lock(&binfmt_lock); 1376 put_binfmt(fmt); 1377 bprm->recursion_depth--; 1378 if (retval < 0 && !bprm->mm) { 1379 /* we got to flush_old_exec() and failed after it */ 1380 read_unlock(&binfmt_lock); 1381 force_sigsegv(SIGSEGV, current); 1382 return retval; 1383 } 1384 if (retval != -ENOEXEC || !bprm->file) { 1385 read_unlock(&binfmt_lock); 1386 return retval; 1387 } 1388 } 1389 read_unlock(&binfmt_lock); 1390 1391 if (need_retry) { 1392 if (printable(bprm->buf[0]) && printable(bprm->buf[1]) && 1393 printable(bprm->buf[2]) && printable(bprm->buf[3])) 1394 return retval; 1395 if (request_module("binfmt-%04x", *(ushort *)(bprm->buf + 2)) < 0) 1396 return retval; 1397 need_retry = false; 1398 goto retry; 1399 } 1400 1401 return retval; 1402} 1403EXPORT_SYMBOL(search_binary_handler); 1404

對於ELF格式的可執行文件fmt->load_binary(bprm); 執行的應該是load_elf_binary其內部是和ELF文件格式解析的部分需要和ELF文件格式標准結合起來閱讀。
load_elf_binary在/linux-3.18.6/fs/binfmt_elf.c
Linux內核是如何支持多種不同的可執行文件格式的? 在 /linux-3.18.6/fs/binfmt_elf.c


elf_format(觀察者)和init_elf_binfmt(被觀察者),他們是觀察者模式。
四、實驗,使用gdb跟蹤sys_execve內核函數的處理過程
實驗步驟
先切換到 LinuxKernel目錄下
rm menu -rf //刪除menu
git clone https://github.com/mengning/menu.git //下載克隆新的menu
cd menu //切換到menu目錄下
mv test_exce.c test.c //把test_exce.c 改成 test.c ,這里是應為Makefile里面用的是test.c
vi test.c //查看一下test.c的內容
vi makefile //查看一下Makefile的內容
make rootfs //
這時候就運行了,結果如下:

我們在打開一個新的終端,在終端里右鍵,選擇打開終端(或水平分割)用2表示新的終端
2 gdb
2 file ../linux-3.18.6/vmlinux
2 target remote:1234
然后我選取了幾個斷點
2 b sys_execve (可以先停在sys_exceve然后再設置其他斷點)
2 b search_bianry_handler
2 b load_elf_binary
2 b start_thread
運行的部分過程如下:


以上實驗是靜態鏈接可執行程序。而大部分執行程序是用動態鏈接,需要用到動態鏈接庫。需要動態鏈接的可執行文件先加載連接器ld,將cpu控制權交給ld來加載依賴庫並完成動態鏈接。對於靜態鏈接的文件elf_entry是新進程的執行起點。
實際上動態鏈接庫的依賴關系會形成一個圖。其實不是由內核負責加載可執行程序依賴的動態鏈接庫的,而是由動態鏈接器。動態鏈接庫的裝載過程是一個圖的遍歷。裝載和鏈接之后ld將cpu的控制權交給可執行程序。
五、總結
我們通過跟蹤以及查看分析代碼,執行的流程是:(代碼見上面)
sys_execve -> do_execve -> do_execve_common -> exec_binprm -> search_binary_handler -> load_binary ->(對於我們這里的ELF,會跳轉到)load_elf_binary (也執行了elf_format)-> start_thread
我們調用execve的可執行程序時,當執行到exceve時,系統調用exceve陷入內核,這時會創建一個新的用戶態堆棧,實際是把命令行參數的內容和環境變量的內容通過指針的方式傳遞給系統調用內核處理函數的,然后內核處理函數在創建可執行程序新的用戶態堆棧的時候,會把這些拷貝到用戶態堆棧初始化新的可執行程序的執行上下文環境(先函數調用參數傳遞,再系統調用參數傳遞)。這時就加載了新的可執行程序。系統調用exceve返回用戶態的時候,就變成了被exceve加載的可執行程序。
