Linux環境下:程式的連結, 裝載和庫[ELF檔案詳解]

Aitozi發表於2023-02-04

編譯過程拆解

image.png

  • 預處理處理生成.i檔案, .i檔案還是原始碼檔案
    • 將所有的宏定義#define展開。
    • 處理#if, #else, #endif等條件編譯指令
    • 處理#include, 原地插入檔案
    • cpp HelloWorld.c > HelloWorld.i可以這樣來進行預編譯,cppC preprocessor就是專門做預處理的。或者 透過gcc -E HelloWorld.c -o HelloWorld.i也可以。
  • 經過gcc編譯生成.s檔案,這個是一個組合語言的原始碼檔案 可以這樣來將.i檔案進行編譯gcc -S HelloWorld.i -o HelloWorld.s
  • 彙編過程生成.o目標檔案 as HelloWorld.s -o HelloWorld.o 這個檔案已經不是文字檔案了,而是一個ELF檔案

ubuntu@cpp:~/Linux-compiler-linker-loader/Demo_Linker_and_Loader/PartI/Demo_3$ file Hello.o
Hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

  • 經過靜態連結或動態連結過程生成可執行檔案。連結的目的是為了讓獨立編譯的各個模組的原始碼能夠找到不在自己模組中的符號連結,連結主要就是relocate的過程
yum install glic-static
# 預設動態連結
gcc -o hello HelloWorld.c 
# 生成靜態連結庫
gcc -o hello_st -static HelloWorld.c
# 輸出中間過程
gcc -o hello_st -static -verbose HelloWorld.c

Linux目標檔案的格式(ELF)

檔案型別

elf表示 executable and Linkable Format,可執行可連結的檔案。主要有三種檔案,透過file命令可以檢視/分辨

  • 目標檔案 未進行過連結的檔案, file顯示的就是relocatable

ubuntu@cpp:~/Linux-compiler-linker-loader/Demo_Linker_and_Loader/PartI/Demo_3$ file Hello.o
Hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

  • 可執行檔案 最終生成的動態連結或靜態連結庫

靜態態連結庫
ubuntu@cpp:~/Linux-compiler-linker-loader/Demo_Linker_and_Loader/PartI/Demo_3$ file hello_st
hello_st: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=e9a2a4bbcef4617eaeae29febf2bb39797016f23, for GNU/Linux 3.2.0, not stripped
動態連結庫檔案
ubuntu@cpp:~/Linux-compiler-linker-loader/Demo_Linker_and_Loader/PartI/Demo_3$ file hello
hello: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f1432ef635a38e09c8e1d1a82257236701cdc2d4, for GNU/Linux 3.2.0, not stripped

  • core dump檔案 (ubuntu中core dump檔案位置 /var/lib/apport/coredump/ 需要設定ulimit -c unlimited)

-46b3-80d7-186db99440fa.3872.184249: ELF 64-bit LSB core file, x86-64, version 1 (SYSV), SVR4-style, from './core', real uid: 1000, effective uid: 1000, real gid: 1000, effective gid: 1000, execfn: './core', platform: 'x86_64'

檢視動態連結庫所依賴的庫檔案

ubuntu@cpp:~/Linux-compiler-linker-loader/Demo_Linker_and_Loader/PartI/Demo_3$ ldd hello_st
	not a dynamic executable
ubuntu@cpp:~/Linux-compiler-linker-loader/Demo_Linker_and_Loader/PartI/Demo_3$ ldd hello
	linux-vdso.so.1 (0x00007ffd1a784000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffb9ca0b000)
	/lib64/ld-linux-x86-64.so.2 (0x00007ffb9cc40000)

Objdump分析ELF各個段儲存內容

elf檔案的不同段的資料含義:

.bss 通常儲存未初始化的全域性變數和區域性靜態變數
.comment 存放gcc中的版本資訊
.data段儲存初始化的全域性變數和區域性靜態變數,
.rodata段儲存只讀資料,一般是隻讀變數和字串, 常量
.text程式碼段
更詳細的說明可以檢視 man elf 或者 /usr/include/elf.h

以以下這個樣例來分析下elf檔案的內容

#include <stdio.h>

int g_init_var1 = 1;
int g_uninit_var2;

void foo(int i)
{
    printf("%d",i);
}

int main(void)
{
    static int var3 = 2;
    static int var4;

    int x = 3;
    foo(x);
    return 0;
}

編譯生成.o檔案,透過objdump來分析

objdump -h c_code_obj.o 檢視各個段的資訊
image.png

objdump -d -s c_code_obj.o 檢視反彙編的內容
-d, --disassemble Display assembler contents of executable sections
-s, --full-contents Display the full contents of all sections requested
objdump_d_s.jpg
-d 引數將其中的程式碼段.text進行了反彙編
其中.data段的資料01000000 02000000 和程式碼中的1 2 是對應的,並且這是一個小端表示法,位元組序和使用的平臺相關

大端小端

大端模式,是指資料的高位元組儲存在記憶體的低地址中,而資料的低位元組儲存在記憶體的高地址中,這樣的儲存模式有點兒類似於把資料當作字串順序處理:地址由小向大增加,資料從高位往低位放;這和我們的閱讀習慣一致。
小端模式,是指資料的高位元組儲存在記憶體的高地址中,而資料的低位元組儲存在記憶體的低地址中,這種儲存模式將地址的高低和資料位權有效地結合起來,高地址部分權值高,低地址部分權值低。

ReadELF分析ELF頭資訊

readelf -h c_code_obj.o
-h --file-header Display the ELF file header
image.png
上面的魔法數字在elf.h中可以找到相應的含義, 這個命令也可以看到這個目標檔案的大小端的表示方式
readelf -S c_code_obj.o透過這個和上面 objdump -h檢視的內容類似,但是要多一些輔助性的段資訊
readelf -p .strtab c_code_obj.o 檢視某些段中的string資訊
-p --string-dump=<number|name>
image.png

readelf -s c_code_obj.o檢視符號表定義
image.png
Ndx表示符號所在的段,如果符號定義在本目標檔案中,那麼指示該符號所在段在段表中的下標。UND表示該符號未定義。所以上圖中可以看到依賴第三方原始碼中的printf函式是UND的狀態。找不到的就會在連結階段進行重定位

ELF檔案總體結構

根據左圖的資訊大致可以構建出有圖的介面
image.png

  1. Start of section headers: 360 對應右圖中的Section Table的起始位置 0x168 = 11616+6*16+8 = 360
  2. readelf -S c_code_obj.o可以看到總的有13個段,每個段header的大小是64 所以Section Table的大小就是 0x40 * 13

...

連結

https://www.bilibili.com/video/BV1hv411s7ew

相關文章