當執行一個elf二進制程序的時候,系統到底做了什麼?

爲什麼要有elf格式的文件?

1)可執行程序要解決的問題

  • 一句話回答,要解決怎麼讓被調用者把自己加載到內存,並執行自己代碼段的問題。
    如果沒有源碼中的僞指令指示,彙編器把第一條指令碼的地址設置成0,之後的代碼和數據以此爲準進行計算。
  • BIOS引導操作系統時,首先加載引導盤的前512字節(MBR)到和MBR約定的內存地址0x7c00,然後pc指向0x7c00開始執行代碼。彙編器在編譯MBR的程序時,按照約定把第一條指令碼的地址設置成0x7c00,之後的代碼和數據地址的計算,都是指令碼在文件中的實際偏移加上0x7c00,以此得到。這種思路可以解決運行可執行程序的運行問題,但是有個缺點,調用者必須事先知道被調用程序期望被加載到的內存地址。如果所有程序都用這種解決方法,那麼需要額外提供一張各個程序運行地址的表格,每調用一個程序,就去這個表格裏面查找其期望加載到的內存地址和其它信息。無疑,這個方法可以解決問題,但是需要多維護一張描述程序的元數據表格。
  • 爲了不維護這張表,有一種方法是把元數據寫到被調用程序的頭部,調用者和被調用者約定讀取這個頭部信息的規則,告訴調用者通過怎麼樣的方式可以找到我期望被加載的內存地址。elf格式的二進制文件就是這樣做的。

2)沒有elf格式文件的世界

3)小結

  • elf二進制程序比裸的二進制程序或者固件,多出了程序被加載、執行時需要的元數據。這些數據都放在文件的頭部,讀取這些程序,調用者和被調用者可以約定各種各樣的規則,elf程序的規則就是其中一種。

編譯源碼生成elf格式文件,到底對文件做了什麼?

1)一個裸的二進制程序長什麼樣?

  • 源代碼mbr.asm,這段程序沒有什麼具體功能,就是在屏幕中打印一段"Hello, OS World"
		org 	07c00h					; 告訴編譯器程序加載到7c00處
        jmp     07c0h:DispStrOff

code:
times   10      db      0
        ;       never reach here

DispStrOff      equ     $       -       $$
DispStr:
		mov 	edx, code				; 取code標號的地址給edx,測試彙編器計算地址
										; 如果沒有第一條org指令,nasm計算得到的code標號地址=0+jmp指令長度,
										; 如果有第一條org指令,nasm計算得到的code標號地址=07c00h+jmp指令長度
        mov     ax, BootMessage
        mov     bp, ax                  ; ES:BP = 串地址
        mov     cx, 16                  ; CX = 串長度
        mov     ax, 01301h              ; AH = 13,  AL = 01h
        mov     bx, 000ch               ; 頁號爲0(BH = 0) 黑底紅字(BL = 0Ch,高亮)
        mov     dl, 0
        int     10h                     ; 10h 號中斷
        jmp $

BootMessage:            db      "Hello, OS world!"
  • 利用匯編器將其彙編成二進制程序
    nasm -o mbr.bin mbr.asm

  • 反彙編查看其內容ndisasm -o 0x7c00 mbr.bin
    在這裏插入圖片描述

  • xxd查看文件實際內容 xxd -u -a -g 1 -c 16 mbr.bin
    在這裏插入圖片描述

  • 對比反彙編代碼和文件的數據,兩個是一樣的。裸的二進制程序包含的僅僅是二進制格式的代碼指令,這個程序能跑起來嗎?可以的,參見重新認識intel段機制尋址的實驗。

2)一個ELF格式的對象文件長什麼樣?執行nasm -f的時候我們在做什麼?

  • 對上面的源碼稍加改動
[SECTION .s16]
[BITS 16]
        global _start
_start:
        jmp 07c0h:OffDispStr

OffDispStr      equ     $ - $$
DispStr:
        mov ax, cs
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov sp, 0100h

        mov ax, BootMessage
        mov bp, ax
        mov cx, 16
        mov ax, 01301h
        mov bx, 000ch
        mov dl, 0
        int 10h
        jmp $

BootMessage:            db "Hello, OS world!"
times   510-($-$$)      db      0
dw      0xaa55
......
  • 使用nasm -f elf mbr.asm -o mbr.o將源碼編譯成elf格式的可重定向文件,反彙編查看其代碼段內容objdump -D mbr.o
    在這裏插入圖片描述
    和實際的彙編代碼有點兒不一樣,沒關係,這是由於[section .16]告訴編譯器把彙編代碼編譯成16bit 寄存器模式的二進制碼,而objdump -D 是按照32bit 寄存器模式來反彙編二進制碼,所以不大一樣。我不知道怎麼讓objdump 按照16bit反彙編二進制程序,但nasm可以設置,所以下面的方法,可以正確反彙編出elf文件格式中的代碼段

    • 通過readelf -S mbr.o找到elf文件中.s16段在mbr.o中的位置和長度,off=0x130=304,size=0x200=512
      在這裏插入圖片描述
    • 將.s16段的數據拷貝到文件dd if=mbr.o ibs=1 skip=304 of=mbr.s16 seek=0 count=512,然後反彙編ndisasm -b 16 mbr.s16 | head -n 20,得到反匯編出來的代碼
      在這裏插入圖片描述
    • xdd查看文件實際內容,xxd -u -a -g 1 -c 16 mbr.o,紅色方框內的數據是前一步反彙編的代碼,也是objdump反彙編代碼所用的數據。可以看到,ELF文件除了包含.s16這段代碼彙編出的數據,還有其它的數據。這些其它的數據,就是描述這段程序的元數據。查看ELF的規範,可以進一步讀懂這些元數據表達的意思。
      在這裏插入圖片描述

3)ELF規範

對照elf文件格式的規範手冊,分析這個數據

  • 頭部信息總體圖
    在這裏插入圖片描述
    elf文件元數據包括4個部分:
    • ELF header
      • 爲達到程序可以被執行的目的,可以設計各種不同的元數據格式規範,elf只是其中一種格式,爲了區分其它格式的文件,elf header的第一個字段時magic,固定不變。
      • elf文件有三個用途,一做可執行程序直接加載到內存運行、二做可重定向程序和別的重定向程序一起組成可執行程序、三做共享庫程序,用來鏈接成重定向程序或者動態鏈接到內存中的其它進程中。elf header中設計了e_type字段用於區分這些不同用途的程序。
      • elf設計目標是可以在不同架構下運行。elf header中設計了e_machine字段用於指明這個二進制程序在哪個架構下運行。
      • elf文件元數據除了header還有其它部分。elf header還作爲路標,提供找到其它元數據的地址。
    • Program Header Table
      • elf程序最終目的是被加載到內存,告訴被調用者怎樣把自己加載到內存,加載到什麼地址,拷貝多長的數據,這些是elf存在的意義,program header table就提供這個信息。
      • 這類信息對系統加載一個可執行程序中有用,對系統鏈接一個可重定向文件沒有,因此這部分內容可能爲空,當header中的e_type是ET_EXEC時,文件是個可執行的二進制程序,這段信息存在。當header中的e_type時ET_REL時,文件是個用於重定向的對象文件,這段信息不需要,可以爲空。
      • Sections
      • ELF全稱Executable and Linkable Format,除了爲被調用者提供加載執行程序的信息,還有一個特點是提供可鏈接的信息,Section的設計就是爲鏈接器提供這些信息。
      • 手寫的彙編的源代碼可以有自己定義的section,分別用來存放代碼或者數據。但高級語言編譯後的彙編程序,代碼和數據混雜在一起,需要統一整理,可以將可執行的代碼放在一個section,未初始化的數據放在一個section,常量放在一個section,最後生成可重定位的對象文件。鏈接程序在處理這些文件時,就可以把section當做基本操作單位,將不同對象文件的同類型的section放在一起,組成segment,並對segment進行地址綁定,告訴被調用這這段segment期望加載到的內存地址。
    • Section Header Table
      • section可以有不同的作用,用於放代碼的section,用於放數據的section,一個section有多長,它在elf文件的什麼位置,這些都需要元數據去描述,Section Header Table就是這個作用。

4)可重定向文件元數據分析實例

下面是mbr.o的elf頭部元數據分析,對照elf header 和section header,可以理解其含義

  • elf header
    在這裏插入圖片描述

  • section header
    在這裏插入圖片描述

  • elf 頭部元數據
    在這裏插入圖片描述

  • 日常應用中不可能通過查看二進制數據分析元數據,elf提供了readelf工具解析這個頭部。可以對照驗證上面的分析。

    • 讀取elf headerreadelf -h mbr.o
      在這裏插入圖片描述
    • 讀取program header tablereadelf -l mbr.o,由於是用於重定向的對象文件,這部分數據爲空
      在這裏插入圖片描述
    • 讀取section header tablereadelf -S mbr.o
      在這裏插入圖片描述

5)一個ELF格式的二進制程序長什麼樣?執行ld的時候我們在做什麼?

  • 執行ld -o mbr mbr.o -Tmbr.ld生成可執行的elf文件,mbr.ld內容。
    這段代碼的意思只有一個:把所有輸入的對象文件(例子中只有一個mbr.o)中的.s16 section集合起來,統一放到.boot section中,將.boot section的期望內存加載地址設置成0。並設置elf文件的入口點爲_start標號處的指令。
OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(_start)
 
SECTIONS
{
    . = 0;
    .boot : {*(.s16)}
    . = ASSERT(. <= 512, "Boot too big!");
    /* For Load Memory Address test
     * . = 0x10;
     * .boot : {*(.s16)}
     **/
} 
  • elf文件比對象文件多出了什麼?有什麼不同?
    elf因爲要提供程序加載信息,所以肯定多出了programe header table,elf文件是對1個及以上對象文件section的重新安排並設置加載地址,所以輸出文件中的section都由加載地址(LMA),例子中LMA=0。
    在這裏插入圖片描述
  • 改變LMA 意味着什麼?
    LMA是elf期望調用者加載自己到內存的地址,如果調用者不按這個期望值加載會有什麼後果?換句話說,改變LMA,elf程序會有什麼變化?
    • 對ld鏈接腳本做如下改動,同時在源代碼中添加一句獲取符號的mov指令
1)mbr.ld
OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(_start)
 
SECTIONS
{
    . = 0x10;
    .boot : {*(.s16)}
}

2)mbr.asm
[SECTION .s16]
[BITS 16]
        global _start
_start:
        jmp 07c0h:OffDispStr

OffDispStr      equ     $ - $$
DispStr:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0100h

    mov eax, DispStr	; for test
    mov ax, BootMessage
  • 重新編譯生成mbr,查看其section內容readelf -S mbr,addr 地址變了,.boot section在文件中的偏移也變了,seciont大小沒變
    在這裏插入圖片描述
  • 拷貝.boot section的數據dd if=mbr ibs=1 skip=4112 of=mbr.s16 seek=0 count=512並反彙編。如果程序第一條指令地址按照0來算,DispStr標號的地址應該是jmp指令的下一條指令地址0x5,但mov eax, DispStr語句被彙編成了mov eax,0x15,可見,ld會以LMA爲依據,重新計算源代碼中的標號值。如果源代碼中有位置相關的語句,那麼調用者就必須按照elf給定的LMA加載這個程序,否則程序會執行錯誤,反之,如果源代碼中所有語句都位置無關,那麼調用者就可以忽略elf中的LMA地址,隨便加載程序到某段內存執行。
    在這裏插入圖片描述
    實驗源碼見 my github gdb調試elf程序

執行一個elf程序,系統做了什麼?

/* TODO */

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章