看板 C_and_CPP 關於我們 聯絡資訊
( https://goo.gl/bZc9q7 ) 與智者同行, 你會不同凡響; 與高人為伍, 你能登上巔峰。 這次來談談 relocation, 對於 relocation 的概念疑惑很久了, 一直想找個方法來驗 證, 知道概念很簡單, 要怎麼實作一個小程式來驗證就困難很多, 大概是 1:50 的難度。 在看過以下 2 篇有關 u-boot relocation 的文章之後, 再複習一下「程式設計師的自我 修養 chapter 7」, 我終於有了實作的方向。 uboot的relocation原理详细分析 ( https://goo.gl/a8RjuK ) [uboot] (番外篇)uboot relocation介绍 ( https://goo.gl/ZCzqCv ) 建議先閱讀以上 2 篇文章, 因為以上 2 篇文章寫的內容我不會在重複介紹。 一開始思考要在 arm 還是 x86 寫這個測試程式, 畢竟是從 u-boot 借鏡而來 (u-boot 也有 x86 版本), 想了一陣子之後, 打算從 x86 來實作這個程式, 在 arm 上有些環境實 在不好設置, 用 qemu 模擬不太有真實感, 用真實的開發板, 開發環境又很麻煩, 也不一 定每個人都有一樣的開發板, 沒 ice/jtag 更麻煩, 所以最後還是選了 x86, 對每個人來 說都是容易接觸的環境。而且 x86 和 arm 的 relocation 實作有點不同, 還好選了 x86, 我才能理解這些不同之處, 算是意外的收穫。 本來打算使用 bare-metal 方式, 但 legcy bios 512 byte 限制真的太麻煩, 最後這個 程式超過 512 很多, uefi 我還沒能完全掌握, 改用 dos .com 來實作, 不怕, 之前都做 過了, 不辛苦, 這是「累積」的力量, 程式本身大概花了 3 天。 期間出動了 qemu/bochs 這 2 個模擬神器, bochs 內建除錯器幫了很大的忙, 讓我找出 程式的錯誤, 學會她是很有幫助的。其實我很不想靠這些工具, 希望靠自己的「冥想」可 以想出問題點, 但我實在抵擋不了「時間」的誘惑, 用這些除錯工具, 我能在短時間就找 到問題, 用「冥想」的話, 可能要超過一天才能想出問題吧! 疑! 不是說用模擬器會有不保險的問題嗎? 怎麼還是用了, 在 x86 上, 可以很容易把模 擬器的環境在真實 PC 上測試, 沒問題的。 reloc.cpp 學習「uboot的relocation原理详细分析 ( https://goo.gl/a8RjuK )」這篇 文章, L40 ~ 46 就是一模一樣的程式碼。 不過和 arm 的 machine code 不同, L41 test_val 不需要做 relocation 的處理。這讓 我知道原來不同的 cpu, 是有不同的 relocation 情形, 而我也知道為什麼作者要加上 function pointer 的測試。最後我自己還加上 bss section 的測試, 該篇文章沒提到這 個。 一開始的觀念很模糊, 不太好掌握, 什麼位址無關的程式碼, share object 之類的, 一 堆和 relocate 相關的 elf section, 把我搞的一團亂, 我把這些東西都攪和在一起了, 後來選定目標, 就類似 u-boot, 把自己 relocalte 到新位址, 這麼做, 就比 share object 單純一些, 只要看 rel.dyn section 就可以。如果是要處理類似 share object 的東西, 就要看更多的 section。 要作到 relocation 需要: cpu 支援 編譯器支援 自己還需要寫額外的程式來處理 由以上條件看來, relocation 相當不容易處理, 除了靠自己, 還得靠別人幫忙。 x86/arm 這麼流行的 cpu 當然符合第一、二個條件。 「程式設計師的自我修養」7.3.3 在說明這個, 不清楚這觀念的朋友可以參考這章節, 也 會提到 GOT 這東西, 不過我的實做參考 u-boot 作法, 不使用 GOT, 只使用 rel.dyn section。 ld 加上 -pie, 就可以製作出一個可以 relocation 的執行檔。 ld -pie -m elf_i386 -static -Treloc.ld -nostdlib -o reloc.elf cpp_init.reloc.o reloc.o dos_io.o -pie 是 ld 選項, 用來造出可 relocaltion 的執行檔, 當然並不是只加上這個選項, 你 的執行檔就有能力 relocation (有這麼簡單就好了), 而是額外造出 .rel.dyn section, 讓程式碼在 relocation 時, 可以針對這些項目做修改, 進而達到 relocation 的能力。 Relocation section '.rel.dyn' at offset 0xa28 contains 28 entries: Offset Info Type Sym.Value Sym. Name 在移動自己的時候, 會需要這個資訊來修正對應的值。 -fPIC 則是在 share object 時會看到, 那在移動自己的時候, 需要使用 -fPIC 來編譯 嗎? 你寫的程式載入到 0x100 可以執行, 載入到 0x500 也可以執行, 這就是 PIC, 和位 址無關的程式碼。 如果你對於載入到 0x100, 0x500 覺得沒有差異的話, 表示 link 的概念還不熟。 測試使用了 -fno-pic + -pie 的組合, 發現還是會輸出 .rel.dyn section, 但是會多出 很多項目, 猜測不需要 -fPIC 還是可以做到移動自己, 只是需要改變的項目會比較多, 這部份待以後有時間在研究。 本來我以為需要加入 -fPIC 的選項來輸出 PIC 的組合語言, 不過卻發現預設好像就是輸 出 PIC 的組合語言, 不同的 gcc 版本, 預設的 option 不同。 -fPIC 會輸出 list 3 L1 這種程式, 令人看不懂的是 ebx 值是多少, 因為若不知道 ebx 的值, 就無法看懂這個組合語言, list 3 L2 之後, ebx 會是 2c3 (2c3 就是 list 3 L2 這行程式碼本身的位址), 相當於 arm 的 PC, 很神奇吧! 「程式設計師的自我修養 」有說明這段 code, 就不重複了。 在 x86-64 之後, 可以直接使用 rip, 不需要在用這麼迂迴的作法, ref:64下PIC的新 寻址方式:RIP相对寻址 ( https://goo.gl/f1AS4R ), 有點類似 arm 的 pc。 list 3. -fPIC 1 2bd: 66 e8 bb 06 00 00 calll 97e <__x86.get_pc_thunk.bx> 2 2c3: 66 81 c3 1d 0a 00 00 add $0xa1d,%ebx 編譯器已經做好他該做的事情, 剩下的該程式本身自己來了, 需要做的有: 複製程式本身到新位址 修改需要 relocation 的變數/函式 如何跳到新位址 大家猜猜看, 那個最難? 沒有相關經驗的程式員, 光第一點就會難倒人。因為這需要修改 linker script, 一般程 式員幾乎是不會去修改這個的, 但這只是最簡單的第一關而已。 這次的範例程式大膽的使用了 c++, 甚至使用了 c++17 標準 (因為我用了 g++8), 但其 實和 c 不會差太多, 因為 c++17 的很多特性我也不會, 有些地方麻煩了一點, 但已經難 不倒我了。 reloc.cpp 1 __asm__(".code16gcc\n"); 2 #include "io.h" 3 #include "obj.h" 4 5 typedef signed char s8; 6 typedef signed short s16; 7 typedef signed int s32; 8 9 typedef unsigned char u8; 10 typedef unsigned short u16; 11 typedef unsigned int u32; 12 13 extern "C" void jmp_to_reloc_addr(); 14 extern "C" u32 get_pc(); 15 16 #define R_386_RELATIVE 0x00000008 17 18 #define BOCHS_MB __asm__ __volatile__("xchg %bx, %bx"); 19 20 extern int _start_ctors; 21 extern int _end_ctors; 22 23 void s16_print_int(int i, int radix); 24 void print_str(const char *s); 25 26 void s32_memcpy(u8 *dest, const u8 *src, u32 n) 27 { 28 for (u32 i=0; i < n ; ++i) 29 *dest++ = *src++; 30 } 31 32 void test_func(void) 33 { 34 print_str("test func\r\n"); 35 u32 v = get_pc(); 36 s16_print_int(v, 16); 37 print_str("\r\n"); 38 } 39 40 static auto test_func_val = test_func; 41 static int test_val = 10; 42 int data1; 43 44 void (*data2)(void); 45 46 int rel_dyn_test() 47 { 48 BOCHS_MB 49 print_str("data_1: "); 50 s16_print_int(data1, 16); 51 print_str("\r\n"); 52 //data1 = 5; 53 data2 = test_func; 54 int i; 55 i = test_val; 56 print_str("test_func_val: "); 57 s16_print_int((int)test_func_val, 16); 58 print_str("\r\n"); 59 (*test_func_val)(); 60 //printf("test = 0x%x\n", test_func); 61 //printf("test_func = 0x%x\n", test_func_val); 62 test_func(); 63 return i + data1; 64 } 65 66 extern int __image_copy_start; 67 extern int __image_copy_end; 68 extern int __rel_dyn_start; 69 extern int __rel_dyn_end; 70 extern u32 __bss_start__; 71 extern u32 __bss_end__; 72 73 void init_reloc_bss(u32 reloc_offset) 74 { 75 u32 reloc_bss_b = (int)&__bss_start__ + reloc_offset; 76 u32 reloc_bss_e = (int)&__bss_end__ + reloc_offset; 77 print_str("reloc_bss_b: "); 78 s16_print_int(reloc_bss_b, 16); 79 print_str("\r\n"); 80 print_str("reloc_bss_e: "); 81 s16_print_int(reloc_bss_e, 16); 82 print_str("\r\n"); 83 for (u32 b = reloc_bss_b ; b < reloc_bss_e ; b++) 84 { 85 *(u8*)b = 1; 86 } 87 } 88 89 void reloc(u32 reloc_addr) 90 { 91 int from = (int)&__image_copy_start; 92 int to = (int)&__image_copy_end; 93 int image_size = to - from; 94 u32 reloc_off = reloc_addr - from; 95 96 print_str("reloc_addr: "); 97 s16_print_int(reloc_addr, 16); 98 print_str("\r\n"); 99 100 print_str("reloc_off: "); 101 s16_print_int(reloc_off, 16); 102 print_str("\r\n"); 103 104 105 int rel_dyn_from = (int)&__rel_dyn_start; 106 int rel_dyn_to = (int)&__rel_dyn_end; 107 108 s16_print_int(from, 16); 109 print_str("\r\n"); 110 s16_print_int(to, 16); 111 print_str("\r\n"); 112 s16_print_int(rel_dyn_from, 16); 113 print_str("\r\n"); 114 s16_print_int(rel_dyn_to, 16); 115 print_str("\r\n"); 116 117 s32_memcpy((u8*)reloc_addr, (u8*)from, image_size); 118 init_reloc_bss(reloc_off); 119 s16_print_int(image_size, 16); 120 print_str("\r\n"); 121 122 // modify rel.dyn section 123 for (int i = rel_dyn_from ; i < rel_dyn_to ; i+=8) 124 { 125 u32 v1 = *(u32*)i; 126 u32 v2 = *(u32*)(i+4); 127 #if 0 128 print_str("v1: "); 129 s16_print_int(v1, 16); 130 print_str("\r\n"); 131 132 print_str("v2: "); 133 s16_print_int(v2, 16); 134 print_str("\r\n"); 135 #endif 136 if (v2 == R_386_RELATIVE) 137 { 138 u32 mem_data = *(u32*)(v1 + reloc_off); // 0xa2c 139 #if 0 140 print_str("mem_data: "); 141 s16_print_int(mem_data, 16); 142 print_str("\r\n"); 143 #endif 144 145 *(u32*)(v1+reloc_off) = mem_data + reloc_off; // locate offset 146 mem_data = *(u32*)(v1 + reloc_off); 147 #if 0 148 print_str("after reloc mem_data: "); 149 s16_print_int(mem_data, 16); 150 print_str("\r\n"); 151 #endif 152 } 153 print_str("\r\n"); 154 } 155 156 //s16_print_int(rel_dyn_to, 16); 157 //print_str("\r\n"); 158 //s16_print_int(v, 16); 159 //print_str("\r\n"); 160 161 } 162 163 extern "C" int cpp_main(void) 164 { 165 print_str("cpp_main\r\n"); 166 //s16_print_int(obj_count, 10); 167 rel_dyn_test(); 168 u32 v = get_pc(); 169 print_str("before reloc pc: "); 170 s16_print_int(v, 16); 171 print_str("\r\n"); 172 173 reloc(0x1100); 174 jmp_to_reloc_addr(); 175 176 print_str("after reloc to %cs:0x1100\r\n"); 177 178 v = get_pc(); 179 print_str("after reloc pc: "); 180 s16_print_int(v, 16); 181 print_str("\r\n"); 182 183 rel_dyn_test(); 184 185 return 0; 186 } [移動自己] 這需要靠 linker script 幫忙, reloc.ld L7, L24 將執行檔本身的 text, rodata, data section 的開始/結束位址記錄下來, 將他們複製到新的位址 0x1100 即可, 選擇移 動到 cs:0x1100 也不是胡亂選的, 這是思考之後的決定, 最主要是除錯時比較方便。 reloc.cpp L117 的 s32_memcpy 就是在做這件事情。 reloc.ld 1 /* for cb.c */ 2 ENTRY(_start); 3 SECTIONS 4 { 5 6 . = 0x100; 7 __image_copy_start = .; 8 .text : 9 { 10 *(.text) 11 *(.gnu.linkonce.t*) 12 } 13 .rodata : 14 { 15 *(.rodata*) 16 *(.gnu.linkonce.r*) 17 } 18 19 .data : 20 { 21 *(.data.*) 22 *(.gnu.linkonce.d*) 23 } 24 __image_copy_end = .; 25 26 . = ALIGN(4); 27 28 __rel_dyn_start = .; 29 .rel.dyn : { 30 *(.rel.dyn*) 31 } 32 __rel_dyn_end = .; 33 34 /* for g++ 4.7 */ 35 .init_array : 36 { 37 __start_global_ctor__ = .; 38 } 39 __end_global_ctor__ = .; 40 .ctors : 41 { 42 start_ctors = .; _start_ctors = .; __start_ctors = .; 43 *(.ctor*) 44 end_ctors = .; _end_ctors = .; __end_ctors = .; 45 /* . = ALIGN(0x1000); */ 46 } 47 /* 48 .dtors : 49 { 50 start_dtors = .; _start_dtors = .; __start_dtors = .; 51 *(.dtor*) 52 end_dtors = .; _end_dtors = .; __end_dtors = .; 53 . = ALIGN(0x1000); 54 } 55 */ 56 57 58 .bss : 59 { 60 sbss = .; __bss_start__ = .; 61 *(.bss) 62 ebss = .; __bss_end__ = .; 63 *(COMMON) 64 *(.gnu.linkonce.b*) 65 } 66 67 /DISCARD/ : 68 { 69 *(.comment) 70 *(.eh_frame) /* discard this, unless you are implementing runtime support for C++ exceptions. */ 71 } 72 } 簡單吧! [修正 test_func_val] list 2 L10, 11 是 test_val, L21, 22 是 test_func_val, 和 arm 的版本不同, x86 沒有 lable 這種東西, 直接就定位到 static 變數的位址, 所以處理 relocation 的方 式也不同。 需要修改的只有 test_func_val, 因為 test_func_val 的值是 23d (list 2 L29,30), 是 test_func() 的位址, relocate 之後, test_func() 會變成 123d (我把整個程式從 cs:0x100 移動到 cs:0x1100), 所以 test_func_val (a48, list 2 L29) 的值要從 23d 改成 0x123d, 但是 test_func_val 也從 a48 移動到 1a48 了, 所以真正要改的位址是 1a48, 其內容要改成 0x123d, 很複雜吧! 知道理論和寫出程式難度還真的不同, 理論的 話, 剛剛提到的細節都不用管, 只要知道 test_func_val 要做 relocation 的修改即可 。 再來就是我怎麼會知道要改這個呢? 總不能每次都人工反組譯看吧, 所以編譯器很義氣的 幫我們產生 rel.dyn section, 這樣就知道要改的就是這個地方。 list 2 L54 .rel.dyn section 不就告訴我們 a48 這個地方要調整嗎? 就是我上述說明 的那樣修改。 修改方式:https://goo.gl/Gy86jZ ( https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-54839.html#chapter6-26 ) 就是下表 Table 12-15, R_386_RELATIVE 的改法, 取 32bit 長度, 加上一個 base address。 32-bit x86: Relocation Types The relocations that are listed in the following table are defined for 32– bit x86. Table2-15 32-bit x86: ELF Relocation Types ( https://goo.gl/K1GXXw ) ( https://goo.gl/jTrRQg ) ( https://goo.gl/jTrRQg ) ( https://goo.gl/yEbHWp ) ( https://goo.gl/jTrRQg ) ( https://goo.gl/yEbHWp ) ( https://goo.gl/yEbHWp ) ( https://goo.gl/GSdxiZ ) ( https://goo.gl/U4j4aK ) ( https://goo.gl/ULDpGZ ) ( https://goo.gl/ULDpGZ ) ( https://goo.gl/ki6Mzk ) ( https://goo.gl/ULDpGZ ) ( https://goo.gl/ki6Mzk ) ( https://goo.gl/ki6Mzk ) ( https://goo.gl/JM8D7R ) ( https://goo.gl/PgvaYF ) ( https://goo.gl/ursjYF ) ( https://goo.gl/ursjYF ) ( https://goo.gl/NAFERH ) ( https://goo.gl/ursjYF ) ( https://goo.gl/NAFERH ) ( https://goo.gl/NAFERH ) ( https://goo.gl/vucM1Z ) Name Value Field Calculation R_386_NONE 0 None None R_386_32 1 word32 S + A R_386_PC32 2 word32 S + A - P R_386_GOT32 3 word32 G + A R_386_PLT32 4 word32 L + A - P R_386_COPY 5 None Refer to the explanation following this table. R_386_GLOB_DAT 6 word32 S R_386_JMP_SLOT 7 word32 S R_386_RELATIVE 8 word32 B + A R_386_GOTOFF 9 word32 S + A - GOT R_386_GOTPC 10 word32 GOT + A - P R_386_32PLT 11 word32 L + A R_386_16 20 word16 S + A R_386_PC16 21 word16 S + A - P R_386_8 22 word8 S + A R_386_PC8 23 word8 S + A - P R_386_SIZE32 38 word32 Z + A [bss section 的修改] bss section 一樣被移動到 0x1100 的地方, 所以也需要修改其內容, L72, L80 2c3+a05+54 = d1c 剛好是 data1 的位址 readelf -a reloc.elf 45: 00000d1c 4 OBJECT GLOBAL DEFAULT 14 data1 52: 00000d1c 0 NOTYPE GLOBAL DEFAULT 14 __bss_start__ 57: 00000d24 0 NOTYPE GLOBAL DEFAULT 14 __bss_end__ data1 位於 bss section, 而 bss 落在 00000d1c - 00000d24, 8 byte 就是 data1, data2 佔的空間。 移動之後的 bss 會落在 1d1c ~ 1d24, 只要把這裡清成 0 即可。 [跳到 relocation 之後的位址] 這是個大問題, 也是理論派不會注意的地方, 因為這也和平台有關, arm 是這樣做, x86 又是另外的作法。我用了類似 __x86.get_pc_thunk.b 來移動到新的位址, 這是我自己想 出來的作法, 不確定有沒有什麼比較正規的作法。 呼叫 jmp_to_reloc_addr() 之後會 jmp 到新的位址, 怎麼辦到的? jmp_to_reloc_addr() 結束之後, 會執行下一個位址也就是 reloc.cpp L176 print_str(), 我得讓他跳到新的 print_str() 而不是原來的那個 print_str(), 只要我 可以得知這個位址, 加上 0x1100 - 0x100 就可以跳到新位址的 print_str(), jmp_to_reloc_addr() 就是在做這件事情。 list 2 的反組譯和 reloc.cpp 有點不同, 因為 reloc.cpp 我後來有再次修改, 反組譯 的結果就不更新了。 list 2. objdump -m i8086 -CD reloc.elf 91 0000023d <test_func()>: 92 23d: 66 55 push %ebp 93 23f: 66 89 e5 mov %esp,%ebp 94 242: 66 53 push %ebx 95 244: 66 83 ec 14 sub $0x14,%esp 96 248: 66 e8 47 07 00 00 calll 995 <__x86.get_pc_thunk.bx> 97 24e: 66 81 c3 f2 08 00 00 add $0x8f2,%ebx 98 255: 66 83 ec 0c sub $0xc,%esp 99 259: 67 66 8d 83 5c fe ff lea -0x1a4(%ebx),%eax 90 260: ff 91 261: 66 50 push %eax 92 263: 66 e8 e5 04 00 00 calll 74e <print_str(char const*)> 93 269: 66 83 c4 10 add $0x10,%esp 94 26d: 66 e8 d2 fe ff ff calll 145 <get_pc> 95 273: 67 66 89 45 f4 mov %eax,-0xc(%ebp) 96 278: 67 66 8b 45 f4 mov -0xc(%ebp),%eax 97 27d: 66 83 ec 08 sub $0x8,%esp 98 281: 66 6a 10 pushl $0x10 99 284: 66 50 push %eax 90 286: 66 e8 96 06 00 00 calll 922 <s16_print_int(int, int)> 1 000002b2 <rel_dyn_test()>: 71 2bd: 66 e8 2d 08 00 00 calll af0 <__x86.get_pc_thunk.bx> 72 2c3: 66 81 c3 05 0a 00 00 add $0xa05,%ebx 73 2ca: 87 db xchg %bx,%bx 74 2cc: 66 83 ec 0c sub $0xc,%esp 75 2d0: 67 66 8d 83 3e fe ff lea -0x1c2(%ebx),%eax 76 2d7: ff 77 2d8: 66 50 push %eax 78 2da: 66 e8 c9 05 00 00 calll 8a9 <print_str(char const*)> 79 2e0: 66 83 c4 10 add $0x10,%esp 80 2e4: 67 66 8b 83 54 00 00 mov 0x54(%ebx),%eax // data1 2 2b2: 66 55 push %ebp 3 2b4: 66 89 e5 mov %esp,%ebp 4 2b7: 66 53 push %ebx 5 2b9: 66 83 ec 14 sub $0x14,%esp 6 2bd: 66 e8 ab 06 00 00 calll 96e <__x86.get_pc_thunk.bx> 7 2c3: 66 81 c3 45 08 00 00 add $0x845,%ebx 8 2ca: 87 db xchg %bx,%bx 9 10 test_val 11 2cc: 67 66 8b 83 64 ff ff mov -0x9c(%ebx),%eax 12 2d3: ff 13 2d4: 67 66 89 45 f4 mov %eax,-0xc(%ebp) 14 2d9: 66 83 ec 0c sub $0xc,%esp 15 2dd: 67 66 8d 83 7c fe ff lea -0x184(%ebx),%eax 16 2e4: ff 17 2e5: 66 50 push %eax 18 2e7: 66 e8 3a 04 00 00 calll 727 <print_str(char const*)> 19 2ed: 66 83 c4 10 add $0x10,%esp 20 21 test_func_val 22 2f1: 67 66 8b 83 40 ff ff mov -0xc0(%ebx),%eax 23 2f8: ff 24 2f9: 66 83 ec 08 sub $0x8,%esp 25 2fd: 66 6a 10 pushl $0x10 26 300: 66 50 push %eax 27 302: 66 e8 f3 05 00 00 calll 8fb <s16_print_int(int, int)> 28 29 00000a48 <test_func_val>: 30 a48: 3d 02 00 cmp $0x2,%ax 31 ... 32 33 34 00000a6c <test_val>: 35 a6c: 0a 00 or (%bx,%si),%al 36 ... 37 38 Disassembly of section .dynamic: 39 40 00000a70 <_DYNAMIC>: 41 a70: 04 00 add $0x0,%al 42 a72: 00 00 add %al,(%bx,%si) 43 a74: 20 0a and %cl,(%bp,%si) 44 a76: 00 00 add %al,(%bx,%si) 45 a78: f5 cmc 46 a79: fe (bad) 47 48 descent@debian64:dos_cpp$ readelf -r reloc.elf 49 50 Relocation section '.rel.dyn' at offset 0xb14 contains 3 entries: 51 Offset Info Type Sym.Value Sym. Name 52 0000014e 00000008 R_386_RELATIVE 53 00000154 00000008 R_386_RELATIVE 54 00000a48 00000008 R_386_RELATIVE [relocate stack] 需要 relocate stack 嗎? u-boot 有 relocate stack 嗎? 我不知道, 但就算要做也不 困難。好吧! 有點難, 和我想的不太一樣, 花了一天搞定, 嘴炮果然輕鬆多了。 本來使用 c++ function 來寫這功能, 一直有錯, 後來用了 bochs 除錯, 看了很多組合 語言之後, 決定改用組合語言來寫這段 (因為 c++ 怎麼寫都寫不出來), 終於搞定。 list 3. qemu 執行結果 1 Booting from Hard Disk... 2 Boot failed: could not read the boot disk 3 4 Booting from Floppy... 5 Starting MS-DOS... 6 7 A:\>reloc 8 cpp_main 9 data_1: 0 10 test_func_val: 26A 11 test func 12 2A0 13 test func 14 2A0 15 before reloc pc: 968 16 reloc_addr: 1100 17 reloc_off: 1000 18 100 19 DB4 20 E70 21 EB8 22 reloc_bss_b: 1EB8 23 reloc_bss_e: 1EC0 24 CB4 25 --- 26 yy from: 27 yy to: 258A26AC 28 after reloc to %cs:0x1100 29 after reloc pc: 15F2 30 data_1: 1010101 31 test_func_val: 126A 32 test func 33 12A0 34 test func 35 12A0 36 37 A:\>QEMU: Terminated ( https://goo.gl/8c3Gn5 ) ( https://goo.gl/d3NB3k ) 這個 relocation 的 dos 程式是用 g++ 8.2/c++17 開發的, 雖然用模擬器跑過了, 但不 在真實機器上執行過一次, 就是不放心。 不過測試過程比我想的還難一點, 一開始找的 usb 隨身碟都無法正常開機, 突然想到我 的 usb floppy, 好吧! 也可以, 剩下的 2 片 3.5 磁碟片, 只剩下一片是好的, 這是我 最後一片可以用的 3.5 磁片了。 ( https://goo.gl/vDc2AS )正常開啟 dos 時, 看到了久違的 A:\>, 懷念的感覺突然湧 現, 當敲下鍵盤執行 reloc 時, 心中很緊張, 怕會執行錯誤, 好在結果是正確的, 程式 正常的執行成功, 也印出預期的結果, 當然也正常的結束。 這個程式會在 cs:0x100 上執行, 然後將自己複製/移動到 cs:0x1100 並把 cs:0x100 的 原本程式碼清成0, 再重新執行同一個函式。 bss/stack 也都做了 relocate 的動作, 最後在回到 dos。 dos 6.22 也能跟上 c++17 呢! test func 在 relocate 之前印出 2A0, 在 relocate 之後印出 12A0, 同一個函式執行 2 次, 印出不同的 pc, 確認位址真的被移動到 0x1100 開始。事實上, 我做了更多的確 認, 確認程式本身, bss, stack 真的都被移動到 offset 0x1000 (0x1100 - 0x100) 上, 全靠 bochs 的內建除錯器才完成。 這個技術有什麼功用呢? 說真的, 我還真不知道可以幹麻, 雖然 share object 有類似的 東西, 但細節又有點不同, 也許可以當作處理 share object 的前哨站。看看能不能寫一 個載入 share object 的程式, 也許下次可以挑戰看看。 u-boot 這麼做有他自己的理由, 但我實在想不出一般「正常」的程式用這招可以幹麻, 就 have fun 吧! 在完成這個程式之後, 我理解了部份 Dynamic section, 但為了不讓本篇更複雜, Dynamic section 補在最後面, 照理來說應該透過 Dynamic section 把 rel.dyn section 找出來, 而不是用 __rel_dyn_start, __rel_dyn_end 這個方式, 但這樣比較簡 單, 也簡化整個程式的撰寫。 list 5 L12, 13, 14 就是用來找出 rel.dyn section, 大小是 24 byte, 每一個 entry 是 8 byte, 所以總共有 3 個 entry, rel.dyn section 位址在 0xe58, 就是 __rel_dyn_start 這個地方。 看來 relocation 還有很多相關的東西可以寫, 就留到來日吧! list 5 1 descent@debian64:dos_cpp$ readelf -d reloc.elf 2 3 Dynamic section at offset 0xdb4 contains 15 entries: 4 Tag Type Name/Value 5 0x00000004 (HASH) 0xd64 6 0x6ffffef5 (GNU_HASH) 0xd74 7 0x00000005 (STRTAB) 0xd60 8 0x00000006 (SYMTAB) 0xd50 9 0x0000000a (STRSZ) 1 (bytes) 10 0x0000000b (SYMENT) 16 (bytes) 11 0x00000015 (DEBUG) 0x0 12 0x00000011 (REL) 0xe58 13 0x00000012 (RELSZ) 24 (bytes) 14 0x00000013 (RELENT) 8 (bytes) 15 0x00000016 (TEXTREL) 0x0 16 0x0000001e (FLAGS) TEXTREL 17 0x6ffffffb (FLAGS_1) Flags: PIE 18 0x6ffffffa (RELCOUNT) 3 19 0x00000000 (NULL) 0x0 寫這個程式時, 經常出動 readelf, objdump 等相關程式, 學會使用他們也是很重要的。 // 本文使用 Blog2BBS 自動將Blog文章轉成縮址的BBS純文字 http://goo.gl/TZ4E17 // blog 版本: https://descent-incoming.blogspot.com/2018/12/code-relocation.html -- 紙上得來終覺淺,絕知此事要躬行。 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 49.216.177.225 ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1547132346.A.600.html ※ 編輯: descent (49.216.177.225), 01/10/2019 22:59:58
sunneo: 越來越hardcore了 真不錯 01/10 23:09
sarafciel: 推 每次看d大的文章都會覺得自己要學的東西還很多 01/11 17:47
bigbite: 推 01/11 18:39
BlazarArc: 推 01/12 20:21
mikukonn: 推 01/13 12:37
PkmX: -fno-pic + -pie 會有text relocation 造成SO的.text無法被 01/14 02:22
PkmX: 共用 且某些架構(例如RISCV)或memory model下非pic的code 01/14 02:25
PkmX: linker也是沒辦法插dynamic relocation讓它變成pic的 01/14 02:26
PkmX: 另外x86_64 ABI有規定把.dynamic的位置塞在GOT的頭 01/14 02:30
PkmX: 可以從_GLOBAL_OFFSET_TABLE_拿或是直接用_DYNAMIC這個符號 01/14 02:42
PkmX: 簡易版從.dynamic找.rel[a].dyn做reloc可以參考我之前寫的 01/14 02:44
PkmX: u-boot/tools/prelink-riscv.inc 不過這是static time時操作 01/14 02:44
descent: 感謝分享 01/15 13:13