編譯鏈接的過程
- 預(yù)處理:負責(zé)把include的文件包含進來及宏替換等工作。
gcc -E -o hello.cpp hello.c -m32
- 將cpp文件轉(zhuǎn)換成匯編文件(編譯)
gcc -x cpp-output -S -o hello.s hello.cpp -m32
- 將匯編文件轉(zhuǎn)換成目標文件(匯編)
gcc -x assembler -c hello.s -o hello.o -m32
- 將目標文件轉(zhuǎn)換成可執(zhí)行文件(鏈接)(使用共享庫)
gcc -o hello hello.o -m32
- 將目標文件轉(zhuǎn)化成可執(zhí)行文件(鏈接)(使用靜態(tài)庫)
gcc -o hello.static hello.o -m32 -static
ELF文件格式
一個ELF頭在文件的開始,保存了路線圖,描述了該文件的組織情況程序頭表包含了描述文件sections的信息。每個section在這個表中有一個入口;每個入口給出該section的名字,大小等等信息。
ELF文件默認從內(nèi)存0x8048000位置開始加載。
上圖中程序的實際入口為內(nèi)存0x8048320,在這之間是文件頭的一些信息。
剛加載過可執(zhí)行文件的進程開始執(zhí)行的第一行代碼的位置就是:0x8048320。
使用exec*庫函數(shù)加載一個可執(zhí)行文件
main.c代碼分析
#include <stdio.h>
#include "shlibexample.h" //共享庫頭文件
#include <dlfcn.h> //動態(tài)加載頭文件
/*
* Main program
* input : none
* output : none
* return : SUCCESS(0)/FAILURE(-1)
*
*/
int main()
{
printf("This is a Main program!\n");
/* Use Shared Lib */
printf("Calling SharedLibApi() function of libshlibexample.so!\n");
SharedLibApi(); //加載裝載時鏈接的共享庫
/* Use Dynamical Loading Lib */
void * handle = dlopen("libdllibexample.so",RTLD_NOW); //打開運行時動態(tài)裝載共享庫
if(handle == NULL)
{
printf("Open Lib libdllibexample.so Error:%s\n",dlerror());
return FAILURE;
}
int (*func)(void); //聲明一個函數(shù)指針
char * error;
func = dlsym(handle,"DynamicalLoadingLibApi");//定義指針指向加載運行時動態(tài)裝載的共享庫的函數(shù)
if((error = dlerror()) != NULL)
{
printf("DynamicalLoadingLibApi not found:%s\n",error);
return FAILURE;
}
printf("Calling DynamicalLoadingLibApi() function of libdllibexample.so!\n");
func(); //加載運行時動態(tài)裝載的共享庫
dlclose(handle);//關(guān)閉運行時動態(tài)裝載的共享庫
return SUCCESS;
}
使用gdb跟蹤分析一個execve系統(tǒng)調(diào)用內(nèi)核處理函數(shù)sys_execve
跟蹤的C語言代碼
int Exec(int argc, char *argv[])
{
int pid;
/* fork another process */
pid = fork();
if (pid < 0)
{
/* error occurred */
fprintf(stderr,"Fork Failed!");
exit(-1);
}
else if (pid == 0)
{
/* child process */
printf("This is Child Process!\n");
execlp("/hello","hello",NULL); //跟蹤的sys_execve
}
else
{
/* parent process */
printf("This is Parent Process!\n");
/* parent will wait for the child to complete*/
wait(NULL);
printf("Child Complete!\n");
}
}
跟蹤結(jié)果及分析
設(shè)置斷點
新的程序的執(zhí)行起點
由以上的gdb分析得出新的程序的執(zhí)行起點是new_ip即
這個語句,它將eip指針指向了新程序hello的執(zhí)行起點,該程序開始執(zhí)行。
同樣有以上的gdb分析得出,新程序執(zhí)行前系統(tǒng)創(chuàng)建了新的內(nèi)核堆棧,并將原有的環(huán)境參數(shù)復(fù)制給了各個寄存器等,這保證了返回的新的可執(zhí)行程序在合適的環(huán)境中順利執(zhí)行。如堆棧分析下圖所示。
對于靜態(tài)鏈接的可執(zhí)行程序和動態(tài)鏈接的可執(zhí)行程序execve系統(tǒng)調(diào)用返回時的不同。
靜態(tài)鏈接和動態(tài)鏈接的可執(zhí)行程序的處理代碼如下所示(代碼來自binfmt_elf.c)
if (elf_interpreter) { //如果需要依賴其他動態(tài)庫,即動態(tài)鏈接的可執(zhí)行程序。
unsigned long interp_map_addr = 0;
elf_entry = load_elf_interp(&loc->interp_elf_ex,
interpreter,
&interp_map_addr,
load_bias);//加載動態(tài)鏈接器的起點
if (!IS_ERR((void *)elf_entry)) {
/*
* load_elf_interp() returns relocation
* adjustment
*/
interp_load_addr = elf_entry;
elf_entry += loc->interp_elf_ex.e_entry;
}
if (BAD_ADDR(elf_entry)) {
retval = IS_ERR((void *)elf_entry) ?
(int)elf_entry : -EINVAL;
goto out_free_dentry;
}
reloc_func_desc = interp_load_addr;
allow_write_access(interpreter);
fput(interpreter);
kfree(elf_interpreter);
} else //靜態(tài)鏈接的可執(zhí)行程序
{
elf_entry = loc->elf_ex.e_entry;//如果是靜態(tài)鏈接庫則把loc->elf_ex.e_entry的地址作為程序入口
if (BAD_ADDR(elf_entry)) {
retval = -EINVAL;
goto out_free_dentry;
}
}
對于動態(tài)鏈接和靜態(tài)鏈接的可執(zhí)行程序處理流程圖如下圖所示。
所以動態(tài)鏈接和靜態(tài)鏈接返回時的不同是:
靜態(tài)鏈接的可執(zhí)行程序會直接進入可執(zhí)行程序的入口執(zhí)行可執(zhí)行程序,而動態(tài)鏈接器會加載連接器ld連接可執(zhí)行文件進行加載符號表等動態(tài)鏈接工作完成后,才進入可執(zhí)行程序的入口,執(zhí)行可執(zhí)行程序。
總結(jié)
Linux內(nèi)核先從ELF文件頭中找到可執(zhí)行程序的入口,然后判斷它是靜態(tài)加載的可執(zhí)行程序還是動態(tài)加載的可執(zhí)行程序,若是靜態(tài)加載的可執(zhí)行文件,直接進入可執(zhí)行程序入口執(zhí)行可執(zhí)行程序,如果是動態(tài)加載的可執(zhí)行程序則先加載連接器ld連接可執(zhí)行文件進行加載符號表等動態(tài)鏈接工作完成后,才進入可執(zhí)行程序的入口,執(zhí)行可執(zhí)行程序。
Sawoom原創(chuàng)作品轉(zhuǎn)載請注明出處
《Linux內(nèi)核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000