Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Linux 動態庫加載時重定位機制深度解析:變數與函數地址修正實戰驗證

編輯:Linux 教學

前言

動態庫在載入過程中必須將編譯時期的相對位址修正為進程虛擬位址空間中的絕對位址,這一過程稱為重定位。Linux 採用兩種主要策略:載入時重定位與位置無關程式碼 (PIC)。本文以 libincrease.so 為例,結合 readelf、objdump、gdb 實測,詳細追蹤 .rel.dyn 中 R_386_32 修正變數位址、R_386_PC32 計算函數相對偏移的完整過程。

一、測試用動態庫原始碼


/* increase.c */
#include 
#include 
#include 

int count = 7;

void increase() {
    count = count + 1;
}

void doincrease(int n) {
    count += n;
    increase();
}

二、編譯指令


gcc -m32 -g -c increase.c -o increase.o
gcc -m32 -shared -o libincrease.so increase.o
# -m32 強制 32-bit,便於人工驗證位址運算

三、ELF 結構與 Program Header


readelf -l libincrease.so
# 兩個 LOAD segment:
# 0: R E (代碼段)  VirtAddr 0x00000000
# 1: RW  (數據段)  VirtAddr 0x00001f0c  ← .data/.bss 在此

四、重定位表


readelf -r libincrease.so
Relocation section '.rel.dyn':
Offset     Info      Type            Sym.Value  Sym.Name
00002008   00000008  R_386_RELATIVE
00000440   00000701  R_386_32        0000200c   count   ← 變數重定位
00000448   00000701  R_386_32        0000200c   count
00000482   00000701  R_386_32        0000200c   count
0000048a   00000701  R_386_32        0000200c   count
0000048f   00000602  R_386_PC32      0000046c   increase ← 函數重定位
...

五、反組譯觀察重定位前指令


objdump -d -M intel libincrease.so

increase:
  43f: a1 00 00 00 00     mov eax, ds:0x0        ← 佔位 0x00000440
  447: a3 00 00 00 00     mov ds:0x0, eax        ← 佔位 0x00000448

doincrease:
  481: a1 00 00 00 00     mov eax, ds:0x0        ← 0x00000482
  489: a3 00 00 00 00     mov ds:0x0, eax        ← 0x0000048a
  48e: e8 fc ff ff ff     call 0x48f             ← 相對偏移 0xfffffffc (0x0000048f)

六、實測驗證:載入位址與重定位結果

編寫測試程式使用 dl_iterate_phdr 取得載入基址:


/* test.c */
#define _GNU_SOURCE
#include 
#include 
extern void doincrease(int);
int main() {
    dl_iterate_phdr([](struct dl_phdr_info* i, size_t, void*) {
        printf("%s @ %p\n", i->dlpi_name, (void*)i->dlpi_addr);
        return 0;
    }, NULL);
    doincrease(6);
    return 0;
}
gcc -m32 test.c -L. -lincrease -o test
LD_LIBRARY_PATH=. ./test

輸出範例:


libincrease.so @ 0xf7fd6000

1. 變數 count 重定位驗證

  • 符號在 .so 內偏移:Sym.Value = 0x200c
  • 理論絕對位址:0xf7fd6000 + 0x200c = 0xf7fd800c
  • 重定位表第一項 Offset=0x440,類型 R_386_32 → 將絕對位址直接寫入 Base+0x440。
  • GDB 驗證:

(gdb) p &count
$1 = (int *) 0xf7fd800c
(gdb) x/4xb 0xf7fd6440
0xf7fd6440: 0x0c 0x80 0xfd 0xf7   ← 小端序 0xf7fd800c 完全吻合

2. 函數 increase 重定位驗證

  • 符號在 .so 內偏移:Sym.Value = 0x46c
  • 絕對位址:0xf7fd6000 + 0x46c = 0xf7fd646c
  • 重定位項 Offset=0x48f,類型 R_386_PC32:

公式:Reloc_Result = Sym_Addr + Addend - Reloc_Addr
Addend 為原指令中相對偏移 0xfffffffc(-4)。
Reloc_Addr = Base + Offset = 0xf7fd648f.
Result = 0xf7fd646c + 0xfffffffc - 0xf7fd648f = 0xffffffd9.

  • GDB 驗證:

(gdb) p increase
$2 = {void ()} 0xf7fd646c 
(gdb) disas/r doincrease
...
0xf7fd648e: e8 d9 ff ff ff    call 0xf7fd646c 
# 0xf7fd648f 位置內容為 0xffffffd9,call 指令以此為相對偏移
# 0xf7fd6493 (下一條指令) + 0xffffffd9 = 0xf7fd646c 精確命中

七、總結對比

重定位類型適用對象修正方式指令影響
R_386_32全域/靜態變數直接寫入絕對虛擬位址mov eax, ds:addr 的 addr 欄位
R_386_PC32函數呼叫計算相對偏移 (Sym - Reloc_Addr + Addend)call rel32 的 rel32 欄位
R_386_RELATIVE.data/.bss 基址Base + Addend無直接指令,供動態連結器內部使用

八、PIC 差異提示

若編譯加 -fPIC,變數存取改為 GOT 間接寻址,函數呼叫改為 PLT 延遲綁定,重定位類型變為 R_386_GLOB_DAT、R_386_JUMP_SLOT 等,載入時不修改代碼段,實現真正的代碼共享。本文示範的 -shared 無 -fPIC 屬於載入時重定位模式,代碼段需寫入修正,無法跨進程共享代碼頁。

Copyright © Windows教程網 All Rights Reserved