2019年6月29日 星期六

uefi os loader (5.3) - 載入 relocatable kernel (1) - 原理篇

我重新組織原本的文章, 將其拆成幾部份, 一口氣把所有東西塞到原本文章可能太複雜, 這篇只說明和 relocation 相關的部份, relocation 要做的事情, 以及哪些程式碼需要做 relocation, 以下正文開始。改寫之後, 篇幅短了不少, 希望不會太燒腦。

此作法我不確定是不是正統作法, 這是從 u-boot 上看來的方法, 查看 linux vmlinux, 似乎沒看到 dynamic section, 不知道 linux kernel relocation 是怎麼辦到的?

首先仿造之前的作法, 加上 k.c L19 ~ L44 的程式碼來測試 relocation。
只要功夫深, 鐵杵磨成針

k.c
19 void test_func(void)
20 {
21   com1_puts("test func\n");
22 }
23 
24 static void (*test_func_val)() = test_func;
25 static int test_val = 10;
26 
27 void rel_dyn_test()
28 {
29     test_val = 20;
30     com1_puts("\ntest: ");
31     PRINT_NUM(test_func, 16)
32     PRINT_NUM(test_func_val, 16)
33     test_func();
34     (*test_func_val)();
44 }
46 
47 void k_main()
48 {
50   const char *s = "i am 64bit kernel";
56   com1_puts("\n");
57   com1_puts(s);
58   com1_puts("\n");
59   PRINT_NUM(test_func, 16);
60   rel_dyn_test();
87   while(1);
88 }

編譯過後應該會看到 .rela.dyn section, 不過一開始連 .rela.dyn section 資訊都沒有出現, 查了很久, 原來是 k.ld L56 的問題, 由於 DISCARD .rela.dyn section, 所以 .rela.dyn section 被刪除了。

k.ld
 1 ENTRY(_start)
 2 
 3 SECTIONS
 4 {
 5   . = 0x3000;
 6   image_base = .;
 7   .hash : { *(.hash) }
 8   .text :
 9   {
10     *(.text)
11   }
12   . = ALIGN(4096);
13   .reloc : {
14    *(.reloc)
15   }
16   .= ALIGN(32);
17   .rodata :
18   {
19     *(.rodata)
20   }
21   .= ALIGN(32);
22 
23   .data :
24   {
25     *(.data)
26   }
27 
28 /*  .= 0xeffa; */
29   .= ALIGN(32);
30   __bss_start__ =.;
31   .bss :
32   {
33     *(.bss)
34   }
35   __bss_end__ = .;
36 
37 /*
38   .sig : AT(0x7DFE)
39   {
40     SHORT(0xaa55);
41   }
42   .asig : AT(0x7e50)
43   {
44     SHORT(0xefab);
45   }
46   .bsig : AT(0x7f50)
47   {
48     SHORT(0xefab);
49   }
50 */
51     /DISCARD/ :
52     {
53         *(.note*);
54         *(.iplt*);
55         *(.igot*);
56         *(.rel*);
57         *(.comment);
58 /* add any unwanted sections spewed out by your version of gcc and flags here */
59     }
60 
61 }

把 k.ld L56 刪除, 重新 link 之後, 就可以看到 list 1 的內容。

list 1. readelf -r k64.elf rela.dyn (git sha1: dddbf77fa0a595b288174700e99ac1fba709edc6)
1
2 Relocation section '.rela.dyn' at offset 0x2da8 contains 1 entry:
3   Offset          Info           Type           Sym. Value    Sym. Name + Addend
4 00000000b420  000000000008 R_X86_64_RELATIVE                    6336

只有一個地方需要處理 relocation (k.c L24 的 test_func_val)。list 2 L4412, test_func_val 在 link 完成之後的值是 6336 (little endian), L1558 test_func 就在這個位址。

再來是觀察以下 symbol 的值:
image_base
entry point
test_func
test_func_val

原本 elf 紀錄的資訊:
image_base: 0
entry point: 0x5010
test_func: 0x6336
test_func_val: 0xb420, 內容是: 0x6336

當將 k64.elf 載入到 0x6882018 時 (參閱 fig 1):
image_base: 0x687e018
entry point: 0x6883028
test_func: 0x688434e
test_func_val: ??, 內容是: 0x6336

而test_func_val 並不知道我們現在不在預期的位址上, 得在開始執行 (*test_func_val)() 前, 修正它, 這就是 relocation, 改成 0x688434e, 而 list 1 的資訊就是告知我們要把 0xb420 (test_fun_val 的位址) 其內容值 0x6336 改成 0x688434e。

fig 1 load kernel elf to 0x6882018 address

原本 elf 內容的
test_func - image_base = 0x6336 - 0 = 0x6336
=>
test_func = 6336 + image_base;

test_func_val - image_base = 0xb420 - 0 = 0xb420

當將 k64.elf 載入到 0x6882018 時 (參閱 fig 1), 這時候的 image_base 是 0x688434e。

test_func - image_base = 0x688434e - 0x687e018 = 6336
test_func = 6336 + image_base;
test_func = 6336 + 0x687e018;

test_func_val - image_base = test_func_val - 0x687e018 = 0xb420
test_func_val = 0xb420 + image_base
test_func_val = 0xb420 + 0x687e018 =

有了新的 test_func_val, test_func 位址, 就可以做 relocation 了。

所以重點在怎麼取得執行時期的 image_base, 我們會很需要 image_base 來處理 relocation, 下篇再來說明。

list 2 objdump -D k64.elf
    1     
    2 k64.elf:     file format elf64-x86-64
    3     
    4     
    5 Disassembly of section .hash:
    6     
    7 0000000000005000 <image_base+0x5000>:
    8     5000:  01 00                   add    %eax,(%rax)
    9     5002:  00 00                   add    %al,(%rax)
   10     5004:  01 00                   add    %eax,(%rax)
 1556 Disassembly of section .text.test_func:
 1557
 1558 0000000000006336 <test_func>:
 1559     6186:  48 8d 35 cb 0b 00 00    lea    0xbcb(%rip),%rsi        # 6d58 <itoa+0x3e6>
...
 4410 Disassembly of section .data.rel.local.test_func_val:
 4411
 4412 0000000000007ed0 <test_func_val>:
 4413     b420:  36 63 00                xchg   %ah,0x0(%rcx)
...
 4418 Disassembly of section .data.test_val:

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。