2019年6月14日 星期五

uefi os loader (5.1) - 載入 relocatable kernel (0) - 找出 entry point

三更燈火五更雞
之前開發的 simple os 並不是 relocatable kernel, 所以她必須被載入到特定位址才能執行。

而在《[code] 自己移動自己 - relocation》之中, 我獲得了 relocation 的技能, 現在結合 uefi os loader, 來打造一個 relocatable kernel。

也許你會問 relocatable kernel 和 os loader 會有關係嗎?

的確, relocation 的程式碼都在 kernel 上, 我所設計的方式並不是由 os loader 來做 relocation 的修改, kernel 一開始就會對自己做 relocation 的動作 (這段程式得保證是 relocation, 因為沒有人會來修正這段程式的 relocation), 不過由於之前 os loader 載入 kernel 的方式和這個不太一樣, 所以還是和 os loader 有點關係。

先提這次遇到的幾個問題:
在 debian/gcc 8.3 平台的環境編譯的 k64.elf, Dynamic section 的 RELASZ 是非 0 值, 這是正常的, 這個數字就是代表要修改幾個 relocation 數值; 但是在 Ubuntu 16.04.1 LTS, gcc (Ubuntu 8.1.0-5ubuntu1~16.04) 8.1.0/GNU ld (GNU Binutils for Ubuntu) 2.26.1 環境下編譯的 k64.elf, RELASZ 是 0, 所以程式不會做任何的 relocation 動作, 但是會多了一個 RELACOUNT, 紀錄 R_X86_64_RELATIVE 個數, 也可以拿來計算要做幾次 relocation 的修改。

Program Headers 在某個 linker script 會產生 2 個 LOAD 標籤的 segment, 有時候只有一個, 我是想要有 2 個, 將 text, rodata section 集合成一個 segment, 另一個是 data section 集合的 segment, 不過目前還不知道怎麼寫出這種 linker script。

先把上述 2 個問題記錄下來, 也許以後有機會可以解除疑惑。

[載入 elf64 kernel]

首先要載入 elf64 kernel, 直接載入一個 elf64 kernel, 比較麻煩的是要算出那個位址才是 kernel 的進入點。和載入固定位址的 kernel 不同, 我打算算出這個 kernel entry, 直接跳到該位址。

這要從 elf program header 來觀察, 需要 segment 的資訊。list 0 L14, L16 是這個 elf 的可執行的相關內容, 要載入一個 elf 執行檔, 就是把這部份載入到對應的記憶體位址, 在跳到 entry 就可以執行這個 elf, 寫一個 elf loader 比想像中還容易吧!

list 0 readelf -l k64.elf
 1 
 2 Elf file type is EXEC (Executable file)
 3 Entry point 0x5010
 4 There are 6 program headers, starting at offset 64
 5 
 6 Program Headers:
 7   Type           Offset             VirtAddr           PhysAddr
 8                  FileSiz            MemSiz              Flags  Align
 9   PHDR           0x0000000000000040 0x0000000000004040 0x0000000000004040
10                  0x0000000000000150 0x0000000000000150  R      0x8
11   INTERP         0x0000000000006020 0x000000000000a020 0x000000000000a020
12                  0x000000000000000f 0x000000000000000f  R      0x1
13       [Requesting program interpreter: /lib/ld64.so.1]
14   LOAD           0x0000000000000000 0x0000000000004000 0x0000000000004000
15                  0x000000000000299d 0x000000000000299d  R E    0x1000
16   LOAD           0x0000000000003000 0x0000000000007000 0x0000000000007000
17                  0x000000000000442c 0x0000000000004448  RW     0x1000
18   DYNAMIC        0x0000000000003000 0x0000000000007000 0x0000000000007000
19                  0x0000000000000110 0x0000000000000110  RW     0x8
20   GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
21                  0x0000000000000000 0x0000000000000000  RWE    0x10
22 
23  Section to Segment mapping:
24   Segment Sections...
25    00     
26    01     .interp 
27    02     .hash .text 
28    03     .dynamic .rela .dynsym .dynstr .interp .gnu.hash .rodata.k_relocate_elf32.str1.1 .eh_frame .rodata.test_func.str1.1 .rodata.rel_dyn_test.str1.1 .rodata.k_main.str1.1 .data .data.rel.local.test_func_val .data.test_val .bss 
29    04     .dynamic 
30    05     

以下的欄位就是載入/執行 elf 的關鍵。

table 1. segment 屬性
offset virtAddr filesiz
0 4000 299d
3000 7000 442c

這個 elf 的 entry point 是 5010。

只要參照 list 1 的 pseudo code, 就可以寫出載入/執行 elf 的程式, 也就是 elf loader。

list 1 pseudo code
memcpy (0x4000, 0, 0x299d)
memcpy (0x7000, 0x3000, 0x442c)
goto 0x5010

這樣就跳到這個 elf 執行檔並且開始執行。

fig 1 對應到 list 0 的 LOAD segment, 目標要算出 entry point X 是多少?
fig 1 左邊的圖 0, 3000 是 elf file 的 offset, 右邊的圖是在記憶體的絕對位址。
fig 1 左邊的圖 0x1000 是我假設的載入到記憶體的位址。

假設 elf 被載入到 0x1000 的位址,

先用
entry 5010
offset: 0
virtAddr: 4000
來計算:

X - 0 = 5010 - 4000
X = 1010

第二組
entry 5010
offset: 3000
virtAddr: 7000

3000 - X = 7000 - 5010
X = 1010

不管用那一組 segment 值做計算, 都會得到 1010, 所以不用擔心要用那一組 segment 的值來計算。

X 就是在 elf 開始的 offset, 加上 elf 的載入位址 0x1000 就是要跳去執行的 entry point, 也就是 1010 + 1000 = 2010。

fig 1 elf load segment layout

所以在載入這個 elf 到 0x1000 位址後, 跳到 2010 的位址執行就可以了。
如果這個 elf 是載入到 0x6000 位址後, 則跳到 7010 的位址執行。

而 elf 是透過 uefi boot service api 載入的。

這樣就不用一定要把 segment 複製到 0x3000, 0x7000 在跳去 0x5010, 當然, 前提是這個 elf 執行檔, 可以在任意位址執行才行。

下篇讓我們看看怎麼讓 elf 執行檔有這個能力。

沒有留言:

張貼留言

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

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