作者:Sandy 原創作品轉載請註明出處
《Linux核心分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000 ”
實驗環境:c+Linux64位 (32位系統可能結果會不同)
依照學術誠信條款,我保證此回答為本人原創,所有回答中引用的外部材料已經做了出處標記。 一,可執行檔的建立與格式 1,Linux可執行檔的建立:預先處理、編譯、彙編、連結
shiyanlou:~/ $ cd Code [9:27:05]shiyanlou:Code/ $ vi hello.c [9:27:14]shiyanlou:Code/ $ gcc -E -o hello.cpp hello.c -m32 [9:34:55]shiyanlou:Code/ $ vi hello.cpp [9:35:04]shiyanlou:Code/ $ gcc -x cpp-output -S -o hello.s hello.cpp -m32 [9:35:21]shiyanlou:Code/ $ vi hello.s [9:35:28]shiyanlou:Code/ $ gcc -x assembler -c hello.s -o hello.o -m32 [9:35:58]shiyanlou:Code/ $ vi hello.o [9:38:44]shiyanlou:Code/ $ gcc -o hello hello.o -m32 [9:39:37]shiyanlou:Code/ $ vi hello [9:39:44]shiyanlou:Code/ $ gcc -o hello.static hello.o -m32 -static [9:40:21]shiyanlou:Code/ $ ls -l [9:41:13]-rwxrwxr-x 1 shiyanlou shiyanlou 7292 3\u6708 23 09:39 hello-rw-rw-r-- 1 shiyanlou shiyanlou 64 3\u6708 23 09:30 hello.c-rw-rw-r-- 1 shiyanlou shiyanlou 17302 3\u6708 23 09:35 hello.cpp-rw-rw-r-- 1 shiyanlou shiyanlou 1020 3\u6708 23 09:38 hello.o-rw-rw-r-- 1 shiyanlou shiyanlou 470 3\u6708 23 09:35 hello.s-rwxrwxr-x 1 shiyanlou shiyanlou 733254 3\u6708 23 09:41 hello.static
以上流程可以簡單的用下圖表示:
圖片來自:深入理解電腦系統-第一章 電腦系統漫遊
更明確的過程是:
2,目標檔案的格式
目標檔案中的內容至少有編譯後的機器指令代碼、資料。沒錯,除了這些內容以
外,目標檔案中還包括了連結時所須要的一些資訊,比如符號表、調試資訊、字串
等。
符號修飾標準、變數內層布局、函數調用方式等這些跟可執行代碼二進位相容性相關的內容稱為ABI(Application Binary Interface)。我們常見的目標檔案(ABI)格式:
一般目標檔案將這些資訊按不同的屬性,以“節”(Section)的形式儲存,有時候也叫“段”(Segment)。
下面分別介紹這幾種檔案格式。
2.1 A.Out 目標檔案的格式
a.out是早期unix系統使用的可執行檔格式,由AT&T設計,由其格式和頭部結構可以看出,a.out格式非常緊湊,只包含程式啟動並執行必須資訊(代碼、資料),每個節的順序是固定的,這種結構這種結構缺乏擴充性,如不能包含“現代”可執行檔中常見的調試資訊 。
A.out現在基本上已被ELF格式取代。
2.2 COFF 目標檔案的格式
2.3 PE 目標檔案的格式
2.4 ELF 目標檔案的格式
ELF的管理資訊主要由三部分組成:ELF頭、程式頭(程式段)表、節區表;一個段的地址空間通常包括一個或多個節區。
如資料區段包括資料節區、BSS節區等
查看ELF檔案的頭部:
shiyanlou:Code/ $ readelf -h hello
ELF格式詳細分析:http://www.xfocus.net/articles/200105/174.html 二,可執行程式的執行環境
在瞭解了可執行檔的格式之後,就可以考慮可執行檔載入的問題。然而可執行檔載入之前,需要先瞭解其執行環境。
命令列參數和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的封裝常式
envp指的是環境變數,一般由shell自動添加
#include <stdio.h>#include <stdlib.h>#include <unistd.h>int main(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 */ execlp("/bin/ls","ls",NULL); } else { /* parent process */ /* parent will wait for the child to complete*/ wait(NULL); printf("Child Complete!"); exit(0); }}
傳遞方向是:shell程式->execve->sys_execve
然後在初始化新程式堆棧時拷貝到堆棧中(見下圖),即先是函數調用參數傳遞,再系統調用參數傳遞。
注意:命令列參數和環境串都放在使用者態堆棧中
三,裝載時動態連結和運行時動態連結應用舉例
上述的參數傳遞只是簡單的靜態連結,而現在的程式一般都是動態連結的,此時需要依賴動態連結程式庫;動態連結分為裝載時動態連結和運行時動態連結,下面分析這兩種不同的方式:
1,準備.so檔案(即window下的.dll檔案)
shlibexample.h (1.3 KB) - Interface of Shared Lib Example
shlibexample.c (1.2 KB) - Implement of Shared Lib Example
源碼:
/********************************************************************//* Copyright (C) SSE@USTCSZ, 2012 *//* *//* FILE NAME : shlibexample.c *//* PRINCIPAL AUTHOR : Mengning *//* SUBSYSTEM NAME : *//* MODULE NAME : *//* LANGUAGE : C *//* TARGET ENVIRONMENT : ANY *//* DATE OF FIRST RELEASE : 2012/5/3 *//* DESCRIPTION : Implement of Shared Lib Example *//********************************************************************//* * Revision log: * * Created by Mengning,2012/5/3 * */#include <stdio.h>#include "shlibexample.h"/* * Shared Lib API Example * input : none * output : none * return : SUCCESS(0)/FAILURE(-1) * */int SharedLibApi(){ printf("This is a shared libary!\n"); return SUCCESS;}
/********************************************************************//* Copyright (C) SSE@USTCSZ, 2012 *//* *//* FILE NAME : shlibexample.h *//* PRINCIPAL AUTHOR : Mengning *//* SUBSYSTEM NAME : *//* MODULE NAME : *//* LANGUAGE : C *//* TARGET ENVIRONMENT : ANY *//* DATE OF FIRST RELEASE : 2012/5/3 *//* DESCRIPTION : Interface of Shared Lib Example *//********************************************************************//* * Revision log: * * Created by Mengning,2012/5/3 * */#ifndef _SH_LIB_EXAMPLE_H_#define _SH_LIB_EXAMPLE_H_#define SUCCESS 0#define FAILURE (-1)#ifdef __cplusplusextern "C" {#endif/* * Shared Lib API Example * input : none * output : none * return : SUCCESS(0)/FAILURE(-1) * */int SharedLibApi();#ifdef __cplusplus}#endif#endif /* _SH_LIB_EXAMPLE_H_ */
編譯成libshlibexample.so檔案
$ gcc -shared dllibexample.c -o libdllibexample.so -m32
dllibexample.h (1.3 KB) - Interface of Dynamical Loading Lib Example
dllibexample.c (1.3 KB) - Implement of Dynamical Loading Lib Example
/********************************************************************//* Copyright (C) SSE@USTCSZ, 2012 *//* *//* FILE NAME : dllibexample.c *//* PRINCIPAL AUTHOR : Mengning *//* SUBSYSTEM NAME : *//* MODULE NAME : *//* LANGUAGE : C *//* TARGET ENVIRONMENT : ANY *//* DATE OF FIRST RELEASE : 2012/5/3 *//* DESCRIPTION : Implement of Dynamical Loading *//* Lib Example *//********************************************************************//* * Revision log: * * Created by Mengning,2012/5/3 * */#include <stdio.h>#include "dllibexample.h"#define SUCCESS 0#define FAILURE (-1)/* * Dynamical Loading Lib API Example * input : none * output : none * return : SUCCESS(0)/FAILURE(-1) * */int DynamicalLoadingLibApi(){ printf("This is a Dynamical Loading libary!\n"); return SUCCESS;}
/********************************************************************//* Copyright (C) SSE@USTCSZ, 2012 *//* *//* FILE NAME : dllibexample.h *//* PRINCIPAL AUTHOR : Mengning *//* SUBSYSTEM NAME : *//* MODULE NAME : *//* LANGUAGE : C *//* TARGET ENVIRONMENT : ANY *//* DATE OF FIRST RELEASE : 2012/5/3 *//* DESCRIPTION : Interface of Dynamical Loading *//* Lib Example *//********************************************************************//* * Revision log: * * Created by Mengning,2012/5/3 * */#ifndef _DL_LIB_EXAMPLE_H_#define _DL_LIB_EXAMPLE_H_#ifdef __cplusplusextern "C" {#endif/* * Dynamical Loading Lib API Example * input : none * output : none * return : SUCCESS(0)/FAILURE(-1) * */int DynamicalLoadingLibApi();#ifdef __cplusplus}#endif#endif /* _DL_LIB_EXAMPLE_H_ */
編譯成libdllibexample.so檔案
gcc -shared dllibexample.c -o libdllibexample.so -m32
分別以共用庫和動態載入共用庫的方式使用libshlibexample.so檔案和libdllibexample.so檔案
main.c (1.9 KB) - Main program
/********************************************************************//* Copyright (C) SSE@USTCSZ, 2012 *//* *//* FILE NAME : main.c *//* PRINCIPAL AUTHOR : Mengning *//* SUBSYSTEM NAME : *//* MODULE NAME : *//* LANGUAGE : C *//* TARGET ENVIRONMENT : ANY *//* DATE OF FIRST RELEASE : 2012/5/3 *//* DESCRIPTION : Main program *//********************************************************************//* * Revision log: * * Created by Mengning,2012/5/3 * */#include <stdio.h>#include "shlibexample.h" #include <dlfcn.h>/* * 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); if(handle == NULL) { printf("Open Lib libdllibexample.so Error:%s\n",dlerror()); return FAILURE; } int (*func)(void); char * error; func = dlsym(handle,"DynamicalLoadingLibApi"); if((error = dlerror()) != NULL) { printf("DynamicalLoadingLibApi not found:%s\n",error); return FAILURE; } printf("Calling DynamicalLoadingLibApi() function of libdllibexample.so!\n"); func(); dlclose(handle); return SUCCESS;}
編譯main,注意這裡只提供shlibexample的-L(庫對應的介面標頭檔所在目錄)和-l(庫名,如libshlibexample.so去掉lib和.so的部分),並沒有提供dllibexample的相關資訊,只是指明了-ldl
$ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32$ export LD_LIBRARY_PATH=$PWD #將目前的目錄加入預設路徑,否則main找不到依賴的庫檔案,當然也可以將庫檔案copy到預設路徑下。$ ./mainThis is a Main program!Calling SharedLibApi() function of libshlibexample.so!This is a shared libary!Calling DynamicalLoadingLibApi() function of libdllibexample.so!This is a Dynamical Loading libary!
兩種動態庫實現檔案比較起來就沒有差別。
差異關鍵在調用端:main函數 四,可執行程式的裝載 shell相關
命令列參數和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的封裝常式 sys_execv相關
sys_execve內部會解析可執行檔格式
- do_execve -> do_execve_common -> exec_binprm
search_binary_handler符合尋找檔案格式對應的解析模組,如下:
list_for_each_entry(fmt, &formats, lh) { if (!try_module_get(fmt->module)) continue; read_unlock(&binfmt_lock); bprm->recursion_depth++; retval = fmt->load_binary(bprm); read_lock(&binfmt_lock);
對於ELF格式的可執行檔fmt->load_binary(bprm);執行的應該是load_elf_binary其內部是和ELF檔案格式解析的部分需要和ELF檔案格式標準結合起來閱讀
Linux核心是如何支援多種不同的可執行檔格式的。
82static struct linux_binfmt elf_format = {83 .module = THIS_MODULE,84 .load_binary = load_elf_binary,85 .load_shlib = load_elf_library,86 .core_dump = elf_core_dump,87 .min_coredump = ELF_EXEC_PAGESIZE,88};
2198static int __init init_elf_binfmt(void)2199{2200 register_binfmt(&elf_format);2201 return 0;2202}
參考文獻
http://blog.csdn.net/morphad/article/details/8967000
http://blog.csdn.net/titer1/article/details/45127543
http://blog.csdn.net/ma89481508/article/details/8996436