Linux內(nèi)核分析第七周作業(yè)

編譯鏈接的過程

  • 預(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容