延續前一則日記的介紹,我們繼續以 s3c2410 的平臺為例。U-Boot 的 startup code(hardware bring-up code)for s3c2410 位於 cpu/arm920t/start.S,此部份的研究重心如下:
- Monitor relocation.
- Stack setup.
- BSS clearing.
- Jump to high-level language.
基本概念分述如後。不過,在開始前,必須具備幾個基本背景知識:
- 您必須先學會 ARM assembly 後再來閱讀此部份。
- 必須知道什麼叫「symbol(符號)」以及「memory address」。
- 能區分 symbol 與 variable 的差異。
- 一定要能看得懂 symbol table,以 U-Boot 為例,在編譯好 U-Boot 後便會產生檔名為System.map 的 symbol table。
1. Monitor relocation.
一開始 U-Boot 是在 SMDK2410 的 nor flash 執行,所以 U-Boot 心須把自己由 nor flash 搬到 RAM,才能執行接下來的工作,這個動作就稱為 relocation。相關的程式片斷如下:
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
程式裡有相當詳細的註解。首先,U-Boot 先把 symbol _start 載到 register r0,再把 symbol_TEXT_BASE 載到 register r1,然後比較 r0 是否等於 r1。如果 r0 等於 r1,表示 U-Boot 目前是在 RAM 裡頭,所以不必做 relocation。
symbol 代表一個記憶體位址(memory address),所以要找出 _start symbol 的 memory address,方式是由 board 的 linker script 來看,所以把 board/smdk2410/u-boot.lds 叫出來瞧瞧:
... ENTRY(_start) SECTIONS { . = 0x00000000; . = ALIGN(4); .text : { cpu/arm920t/start.o (.text) *(.text) } ... }
看懂 linker script 是您的功課,不過這個部份其實很直覺。程式的進入點(ENTRY command)定義為 _start symbol,而程式一開始的 value to current address(看到沒,就是 SECTIONS 後的那個小點點!)為 0x00000000,所以 _start symbol 代表的是 memory address 0x0。請不要用 _start 等於 0x0 的方式來解說,因為 _start 壓根兒就是一個 symbol 並不是 variable。
Linker script 的 "." 稱為 location counter,這是一個指定運算子,也就是「assign value to symbol」的意思。因此,_start symbol 為 address 0x0,且 value to _start symbol 為 0x00000000。
所以,總結來看 "adr r0, _start" 把 symbol _start 的 memory address 放到 r0,adr 是 ARM 的 pseudo-instruction;"ldr r1, _TEXT_BASE" 把 symbol _TEXT_BASE 的值放到 r1,ldr 是 ARM 的 memory addressing 指令,這裡用到的 addressing mode 是 direct addressing。
回頭找一下 symbol _TEXT_BASE,_TEXT_BASE 可就不在 linker script 裡頭了。把 start.S 程式移到開頭,就可以找到 _TEXT_BASE:
_TEXT_BASE:
.word TEXT_BASE
這是一個 symbol 的定義與 GNU assembly 的 variable 宣告語法,所以 symbol _TEXT_BASE 所代表的 memory address 之處,放了一個值(value),其值為 TEXT_BASE。TEXT_BASE 便是一個變數,此變數是 linking 階段(GNU ld)所定義的,以 U-Boot for SMDK2410 為例,便是 0x33F80000(board/smdk2410/config.mk):
TEXT_BASE = 0x33F80000
接下來的程式是:
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
這裡做的是「計算程式長度」的動作。把 _armboot_start 放到 r2、_bss_start 放到 r3,然後 r2 = r3 - r2 算出要 relocation 的長度,此時 r2 放的便是 U-Boot 後面「要 relocate 到 RAM 的程長度」。下一行做 r2 = r0 + r2,很清楚,再把程式尾段的 memory address 算出來,所以 r2 最後放的是「source end address」。
這個時候就要把 symbol table 請出來看了。
先在 start.S 裡找到 _armboot_start:
.globl _armboot_start
_armboot_start:
.word _start
再對照 System.map:
33f80000 t $a 33f80000 T _start 33f80020 t $d ...
不過,_bss_start 可就不是對照 System.map 就能解決的了。概念上,_bss_start 是 linker script 所定義的 symbol,可用來表示程式「實體程式碼」的 end address;概念上來說雖然簡單,不過仍強烈建議了解 ELF 的格式與 .bss section 的整體觀念,畢道這是基本功,不可不練。
可在 U-Boot 程式碼裡頭找到這行宣告:
extern ulong _bss_start; /* code + data end == BSS start */
剩下來的部份就容易了,只要以上這些觀念都能確實掌握,startup code 並沒有什麼困難的地方。