1. 程式人生 > >linux 可執行檔案建立 學習筆記

linux 可執行檔案建立 學習筆記

劉柳 + 原創作品轉載請註明出處 + 《Linux核心分析》MOOC課程+http://mooc.study.163.com/course/USTC-1000029000[email protected].com

一. 可執行檔案的建立

預處理、編譯和連結 實踐

shiyanlou:~/ $ cd Code                                                [9:27:05]
shiyanlou:Code/ $ vi hello.c                                          [9:27:14]
shiyanlou:
Code/ $ gcc -E -o hello.cpp hello.c -m32 #替換巨集 shiyanlou:Code/ $ vi hello.cpp #可以看到預處理的中間檔案 shiyanlou:Code/ $ gcc -x cpp-output -S -o hello.s hello.cpp -m32 [9:35:21] shiyanlou:Code/ $ vi hello.s #asm code shiyanlou:
Code/ $ gcc -x assembler -c hello.s -o hello.o -m32 [9:35:58] shiyanlou:Code/ $ vi hello.o #object code ,binary file shiyanlou:Code/ $ gcc -o hello hello.o -m32 shiyanlou:Code/ $ vi hello #executive file (link dynamic library..)
shiyanlou:Code/ $ gcc -o hello.static hello.o -m32 -static [9:40:21] #executive file (link static library..) shiyanlou:Code/ $ ls -l -rwxrwxr-x 1 shiyanlou shiyanlou 7292 3\u6708 23 09:39 hello #with dyanmic -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 #with static lib ,more space, aoubt 100 times than dyanmic version

目標檔案及連結
abi 二進位制相容,已經適應某一計算機架構

ELF檔案格式

可以參考ELF檔案格式 :(中文翻譯版),以下是檔案格式的概要圖(by morphad)

elf共分為三種:

一個可重定位(relocatable)檔案儲存著程式碼和適當的資料,用來和其他的object檔案一起來建立一個可執行檔案或者是一個共享檔案。
一個可執行(executable)檔案儲存著一個用來執行的程式;該檔案指出了exec(BA_OS)如何來建立程式程序映象。
 一個共享object檔案儲存著程式碼和合適的資料,用來被下面的兩個連結器連結。第一個是連線編輯器[請參看ld(SD_CMD)],可以和其他的可重定位和共享object檔案來建立其他的object。第二個是動態連結器,聯合一個可執行檔案和其他的共享object檔案來建立一個程序映象。

檢視ELF檔案的頭部

shiyanlou:Code/ $ readelf -h hello

靜態elf 的程序地址空間

感謝:程式設計師修養書中圖,page 167

當前最基礎的是要認識:

  • 程式碼段其實地址: 0x804 8000
  • 程序地址空間分佈,按照地址遞減來看,是 stack->heap->data->code

檢視該ELF檔案依賴的共享庫

shiyanlou:sharelib/ $ ldd main                                       [21:25:56]
    linux-gate.so.1 =>  (0xf774e000) # 這個是vdso - virtual DSO:dynamically shared object,並不存在這個共享庫檔案,它是核心的一部分,為了解決libc與新版本核心的系統呼叫不同步的問題,linux-gate.so.1裡封裝的系統呼叫與核心支援的系統呼叫完全匹配,因為它就是核心的一部分嘛。而libc裡封裝的系統呼叫與核心並不完全一致,因為它們各自都在版本更新。
    libshlibexample.so => /home/shiyanlou/LinuxKernel/sharelib/libshlibexample.so (0xf7749000)
    libdl.so.2 => /lib32/libdl.so.2 (0xf7734000)
    libc.so.6 => /lib32/libc.so.6 (0xf7588000)
    /lib/ld-linux.so.2 (0xf774f000)
shiyanlou:sharelib/ $ ldd /lib32/libc.so.6                         [21:37:00]
    /lib/ld-linux.so.2 (0xf779e000)
    linux-gate.so.1 =>  (0xf779d000)

檢視依賴的so檔案

shiyanlou:sharelib/ $ readelf -d main                              [21:28:04]
Dynamic section at offset 0xf04 contains 26 entries:
 0x00000001 (NEEDED)                     共享庫:[libshlibexample.so]
 0x00000001 (NEEDED)                     共享庫:[libdl.so.2]
 0x00000001 (NEEDED)                     共享庫:[libc.so.6]
 0x0000000c (INIT)                       0x80484f0
 0x0000000d (FINI)                       0x8048804
 0x00000019 (INIT_ARRAY)                 0x8049ef8

二. 可執行程式的執行環境

  • 命令列引數和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的封裝例程
//keyword /bin/ls execlp functions

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);
    }
}
  • 命令列引數和環境串都放在使用者態堆疊中

    • what:你會看到引數塊/環境塊的指標
    • who did it: shell->execve->sys_execve在程式初始化的時候把以上環境搭建好的
  • 裝載時動態連結和執行時動態連結應用舉例
    動態連結分為可執行程式裝載時動態連結和執行時動態連結,如下程式碼演示了這兩種動態連結。

    • 準備.so檔案
      shlibexample.h (1.3 KB) - Interface of Shared Lib Example
      shlibexample.c (1.2 KB) - Implement of Shared Lib Example

    • 編譯成libshlibexample.so檔案

$ gcc -shared shlibexample.c -o libshlibexample.so -m32

dllibexample.h (1.3 KB) - Interface of Dynamical Loading Lib Example
dllibexample.c (1.3 KB) - Implement of Dynamical Loading Lib Example

  • 編譯成libdllibexample.so檔案
$ gcc -shared dllibexample.c -o libdllibexample.so -m32 #編譯方式和上面一樣

分別以共享庫和動態載入共享庫的方式使用libshlibexample.so檔案和libdllibexample.so檔案
- main.c (1.9 KB) - Main program
編譯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到預設路徑下。
$ ./main
This 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!

三. 可執行程式的裝載

shell相關

  • 命令列引數和shell環境,一般我們執行一個程式的Shell環境,我們的實驗直接使用execve系統呼叫。
    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的封裝例程
小結 引數個數; execv* ->main

sys_execv相關

sys_execve內部會解析可執行檔案格式
- do_execve -> do_execve_common -> exec_binprm

  • search_binary_handler符合尋找檔案格式對應的解析模組,如下:
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);
  • 對於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}
  • elf_format 和 init_elf_binfmt,這裡是不是就是觀察者模式中的觀察者?
小結 設計模式 觀察者模式 多型

可執行檔案開始執行的起點

可執行檔案開始執行的起點在哪裡?如何才能讓execve系統呼叫返回到使用者態時執行新程式?

  • 莊生夢蝶 —— 醒來迷惑是莊周夢見了蝴蝶還是蝴蝶夢見了莊周?

  • 莊周(呼叫execve的可執行程式)入睡(呼叫execve陷入核心),醒來(系統呼叫execve返回使用者態)發現自己是蝴蝶(被execve載入的可執行程式)

   ( ̄▽ ̄)" 原來都做夢了,睡眠是個好東東;
   睡眠前把sp/ip換掉就進入新的程式,這是我認為的
  • 修改int 0x80壓入核心堆疊的EIP

  • load_elf_binary -> start_thread

小結 codes flows: load_elf_binary --> start_thread ;//修改 ip ,sp

四. 動態連結的過程

動態連結的過程核心做了什麼?可執行檔案依賴的動態連結庫(共享庫)是由誰負責載入以及如何遞迴載入的?

基礎

首先要理解elf格式,

會發現: a.so –>b.so…(動態連結庫 呼叫另外一個動態連結庫)

實踐:ldd test
動態連結形成的樹

程式碼

解釋的過程,就用到動態解析器,
格式上就要關注 elf 格式中 .interp 和 .dynamic
程式碼就看load_elf_binary

load_elf_binary(...)
{
...
kernel_read();//其實就是檔案解析
...
//對映到程序空間 0x804 8000地址
elf_map();//
...
if(elf_interpreter) //依賴動態庫的話
{...
//裝載ld的起點  #獲得動態聯結器的程式起點
elf_entry=load_elf_interp(...);
...
}
else //靜態連結
{...
elf_entry = loc->elf_ex.e_entry;
...
}
...
//static exe: elf_entry: 0x804 8000
//exe with dyanmic lib: elf_entry: ld.so addr
start_thread(regs,elf_entry,bprm->p);

}

實際上,裝載過程是 一個 bfs ,廣度遍歷,遍歷的物件是
“依賴樹”

主要過程是動態連結器( in libc)完成,使用者態完成。
簡言之,本次學習最基礎就是要知道和靜態連結的區別,具體執行過程可以以後專題再深入。

五. do_execve 程式碼情景分析

流程是:do_execve -> do_execve_common -> exec_binprm

do_execve_common

fs/exec.c 檔案裡
可執行程式的裝載視訊(第二個)詳細解釋
do_execve //命令列引數填充結構體
do_execve_common
{
...
//開啟可執行檔案
do_open_exec  
//填充頭部
bprm->file = file;
bprem->filename = bprm->interp = filename->name;

//填充環境變數 命令列引數

//關鍵
execv_binrm(bprm);
...
}

exec_binprm

static int exec_binprm(struct linux_binprm *bprm)
{
...
//尋找對應可執行檔案格式 的 處理函式
search_binary_handle(bprm);
...

}

search_binary_handle

int search_binary_handle(struct linux_binprm *bprm)
{
...
//在連結串列裡尋找可解析的方案
list_for_each_entry(fmt, &format, lh);//line 1369

...
}

list_for_each_entry

list_for_each_entry(fmt, &formats, lh){
...
//載入可執行檔案的處理函式
//實際是load_elf_binary for elf
retval = fmt->load_binary(bprm);
...
}

load_elf_binary

//此前可以關注 elf_format結構體內的賦值
//這個結構體被放到連結串列裡面,可以看作是觀察者模式/多型的應用
//初始化也有對應的api

load_elf_binary(...)
{
...
kernel_read();//其實就是檔案解析
...
//對映到程序空間 0x804 8000地址
elf_map();//
...
if(elf_interpreter) //依賴動態庫的話
{...
//裝載ld的起點
elf_entry=load_elf_interp(...);
...
}
else //靜態連結
{...
elf_entry = loc->elf_ex.e_entry;
...
}
...
//static exe: elf_entry: 0x804 8000
//exe with dyanmic lib: elf_entry: ld.so addr
start_thread(regs,elf_entry,bprm->p);
...


}

整體流程圖

謝謝ma89481508的精心製作,訪問原帖請點選圖片

圖中有輕重的說明了execve–> do——execve –> search_binary_handle –> load_binary流程

最後奉上一張堆疊變化圖

謝謝morphad的精心製作,訪問原帖請點選圖片

圖中對於引數塊和環境塊如何被傳到新程序是很好的說明

六. 實踐 gdb跟蹤sys_execve

除錯前的準備

#command work flow
rm menu -f
git clone
cd menu
ls
mv test_exec.c test.c 
vim test.c #you will see a new function:Exec
#execlp will be called
#Makefile update
add hello and init into the rootfs

大小s來相會,開啟除錯模式

主要的斷點

#breakpoint : info b
b sys_execve #fs/exec.c
b load_elf_binary #fs/binfmt_elf.c
b start_thread

跟蹤過程

load_elf_binary 第一個被擊中,ignore it
進入系統,輸入exec,sys_execve被擊中

debug work flow:
sys_execve -> load_elf_binary ->start_thread

當跟蹤到start_thread時,檢視new_ip執行位置,new_ip是返回使用者空間執行的函式地址,鍵入以下命令:

gdb中:     po new_ip #檢視內容
主機命令列: read_elf -h hello

對比程式入口地址和 new_ip 指向地址一致!
new_ip對於靜態連結程式來說,就是真實的地址。

七. 參考 本週要求

  • Linux核心如何裝載和啟動一個可執行程式
    理解編譯連結的過程和ELF可執行檔案格式,詳細內容參考本週第一節;

  • 程式設計使用exec*庫函式載入一個可執行檔案,動態連結分為可執行程式裝載時動態連結和執行時動態連結,程式設計練習動態連結庫的這兩種使用方式,詳細內容參考本週第二節;

  • 使用gdb跟蹤分析一個execve系統呼叫核心處理函式sys_execve ,驗證您對Linux系統載入可執行程式所需處理過程的理解,詳細內容參考本週第三節;

  • 特別關注新的可執行程式是從哪裡開始執行的?
    為什麼execve系統呼叫返回後新的可執行程式能順利執行?
    對於靜態連結的可執行程式和動態連結的可執行程式execve系統呼叫返回時會有什麼不同?

    (重點)
  • 根據本週所學知識分析

    exec*函式對應的系統呼叫處理過程

    ,撰寫一篇署名部落格,並在部落格文章中註明“真實姓名(與最後申請證書的姓名務必一致) + 原創作品轉載請註明出處 + 《Linux核心分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000 ”,部落格內容的具體要求如下:

    1. 題目自擬,內容圍繞對Linux核心如何裝載和啟動一個可執行程式;
    2. 可以結合實驗截圖、ELF可執行檔案格式、使用者態的相關程式碼等;
    3. 部落格內容中需要仔細分析新可執行程式的執行起點及對應的堆疊狀態等。
    4. 總結部分需要闡明自己對“Linux核心裝載和啟動一個可執行程式”的理解


小結:截圖,程式碼,堆疊,起點,總結
有趣的-L -l 連結共享庫

其他

thanks haroopad ;chrome based opensource editor
later :共享庫程式碼
for compare convenient: (week7_args.png “” “width:30px;float:right”)恢復大小

later :fork vs execve fs/exec.c 檔案 符號表
time: 番茄計時:1.5小時 完成 section 1 2, 4小時 完成剩餘section
tips :the haroopad is so cool ,and opensource
tips :use photo ,plase add prefix https://code.csdn.net/titer1/pat_aha/blob/master/Markdown/

參考

相關推薦

linux 執行檔案建立 學習筆記

劉柳 + 原創作品轉載請註明出處 + 《Linux核心分析》MOOC課程+http://mooc.study.163.com/course/USTC-1000029000[email protected].com 一. 可執行檔案的建立 預

linux執行檔案建立過程 淺析

特色: - 加入動態演示,去驗證exec過程 - 加入自己理解的資料結構圖示(自創/網路引用) linux世界很大,我想去看看 一. 可執行檔案的建立 基礎 預處理、編譯和連結 實踐 shiyanlou:~/ $ cd Code

Python 中使用 pyrex 生成 Linux 執行檔案

 這個問題是相當有意義的,如果有了比較好的方法,Python 完全可以用來開發商業軟體,而不用擔心原始碼洩露。     前兩天我在網上看了看,有很多的人在問這個問題。大部分的人都在用 py2exe,這是個對 Python 程式打包的東東,實際上只是在 Python 程式中找

linux執行檔案新增到PATH環境變數的方法Ubuntu

linux命令列下面執行某個命令的時候,首先保證該命令是否存在,若存在,但輸入命令的時候若仍提示:command not found 這個時候就的檢視PATH環境變數的設定了,當前命令是否存在於PATH環境變數中 #檢視PATH: echo $PATH 舉

Linux 執行檔案結構與程序結構

一、Linux可執行檔案結構 在 Linux 下,程式是一個普通的可執行檔案,以下列出一個二進位制可執行檔案的基本情況: 可以看出,此可執行檔案在儲存時(沒有調入到內容)分為程式碼區(text)、資料區(data)和未初始化資料區(bss)3 個部分。各段基本內

Qt學習筆記之——生成exe執行檔案並打包生成安裝軟體

之前用MFC生成過安裝檔案,今天想嘗試採用Qt生成的exe檔案打包並生成安裝軟體。 開始我認為比較簡單,但是嘗試過程中遇到了很多問題。下面一一列出來 首先:我認為,要完成一個軟體,應儘可能的使用Release版本檔案,當然了,可以現在Debug版本下除錯通過再進行測試 打包

Linux使用者、使用者組、檔案許可權學習筆記

最近打算更仔細學習一下linux作業系統。先是惡補了一下使用者、使用者組、檔案許可權這三樣比較重要的知識。學習這幾樣東西,得先掌握linux的許可權系統相關知識。linux的許可權系統主要是由使用者、使用者組和許可權組成。使用者就是一個個的登入並使用linux的使用者。linux內部用UID表示。使用者組就是

Linux逆向---執行檔案程式碼靜態注入小實驗

分析完節頭之後,我最大的收穫就是,這麼多的01,並不是所有的都是用來執行我寫的那段輸出helloworld的程式的,而且程式碼段中有很大一段空閒空間,這就給我們一個向可執行檔案中注入自己程式碼,然後通過修改程式邏輯達到讓它去執行我們自己寫的的部分的程式碼的邏輯的機會。 這裡我們的原始碼是

軟體素材---linux C語言:linux下獲取執行檔案的絕對路徑--getcwd函式

      //標頭檔案:#include <unistd.h>     //定義函式:char * getcwd(char * buf, size_t size);    

從Python指令碼建立執行檔案

Python是我最喜歡的程式語言之一。話雖這麼說,如果你曾經不得不部署一個用Python編寫的應用程式,那麼你就知道它有多痛苦。 幸運的是,有一些非常棒的開源工具可用於將Python程式打包成獨立的二進位制可執行檔案,其中包含執行應用程式所需的一切(即Python直譯器

Linux C的執行檔案結構以及程序結構

(公共部分):程式碼區,BSS區,資料區. 1.程式碼區:存放可執行的指令.順便規劃局部變數的相關資訊(??).   獨有性:一份指令在記憶體(不管虛擬記憶體還是實際)中只要有一份就可以的   只讀性:彙編指令包含 操作碼+運算元;一般操作碼是不可變的,但是運算元可

Linux下檢視執行檔案、動態庫的ELF頭等資訊

      用法: readelf <option(s)> elf-file(s)       作用: 和Windows下的PE檔案類似,ELF檔案是linux系統下可執行檔案、動態庫檔案、靜態庫檔案的標準格式。有時候我們需要檢視ELF檔案的頭資訊,或者動態庫檔

Mono+Jexus讓C#執行Linux(centos7_x64),學習筆記

本篇目錄 前言 感謝: http://www.jexus.org/ http://www.cnblogs.com/shanyou/archive/2012/01/07/2315982.html http://www.cnblogs.com/hanyinglong/p/5456757.html http:

linux 查詢執行檔案

轉自:https://www.cnblogs.com/binyue/p/4707948.htmlLinux下的可執行檔案Linux下如何查詢可執行檔案,作為一個Linux小菜剛剛有了這個問題,在windows中,可以通過後綴名判斷是否是可執行檔案,比如.exe,.bat等是可

Linux:原始碼到執行檔案(CRF++ python安裝)

這一過程又稱為編譯軟體原始碼。編譯是將原始碼(程式語言描述)翻譯成計算機處理器能識別的語言的過程。一 語言發展phase1:機器語言(數值程式碼,二進位制指令。)phase2:組合語言(有一些人理解的符號)phase3:高階程式語言(我們現在使用的語言)二 編譯程式語言(1)

Linux執行執行檔案提示No such file or directory的解決方法

最近在使用Linux作業系統執行一個可執行檔案,結果出現了No such file or directory的提示,表示很疑惑。 ./tshrf bash: ./tshref: No such file or directory 檢視檔案資訊,可以看到

Linux下安裝pyinstaller用於將py檔案打包生成一個執行檔案

(2)cd pyinstaller-2.1 執行 python setup.py install 4. 拷貝py檔案 將需打包的py檔案如test.py 拷貝到當前目錄 5. 生成可執行檔案 cd到pyinstaller目錄, 執行  python pyinstaller.py test.py可能遇到的問題1

linux下把python檔案打包成執行檔案步驟

1.安裝PyInstaller    pip install pyinstaller    pyinstaller --version2.使用PyInstaller打包python檔案  在和myscript.py同目錄下執行命令:pyinstaller mycript.py

linux下將Python指令碼打包為執行檔案

一. 下載pyinstaller 連結 二. 解壓 無需安裝,解壓即可使用 三. 輸入命令,進行打包 命令格式: pyinstaller_path/pyinstaller.py -F s

Linux執行檔案格式詳解

Linux下面,目標檔案、共享物件檔案、可執行檔案都是使用ELF檔案格式來儲存的。程式經過編譯之後會輸出目標檔案,然後經過連結可以產生可執行檔案或者共享物件檔案。Linux下面使用的ELF檔案和Windows作業系統使用的PE檔案都是從Unix系統的COFF檔案格式演化來的