u-boot連結分析
http://www.rritw.com/a/bianchengyuyan/C__/20130312/279763.html
一個典型的嵌入式系統中,bootloader代碼放在NOR Flash或NAND Flash裡面,系統加電或複位後,首先運行這段代碼。通常把bootloader代碼放在NOR Flash裡面,NAND Flash由於硬體原因不能隨機訪問,需要特殊的硬體支援機制。
bootloader代碼除了初始化以外就是搬運程式,即地址重定位(relocate)。我們為什麼需要relocate?主要是經濟方面和速度方面的原因。經濟方面,NOR Flash和NAND Flash每兆價格相差懸殊,bootloader代碼一般在幾十到幾百K大小,而應用程式通常都很大,幾M到幾十M的大小,所以用價格低廉的NAND Flash儲存。速度方面,程式在NOR Flash裡執行的速度遠遠小於在SDRAM中執行的速度,為了追求更高的速度,也需要relocate,讓程式在SDRAM裡面執行。
relocate涉及到載入域(VMA)和運行域(LMA)兩個概念。載入域是程式碼在ROM、FLASH中的排列次序及地址安排,運行域是程式運行時代碼在SRAM、SDRAM中地址安排。儲存代碼時按照載入域存放在FLASH中,運行時再從FLASH中取出代碼到RAM運行域運行,一段代碼的載入域和儲存域可以不同。(可以參考杜春雷的《arm體繫結構與編程》一書的有關章節)。
以smdk2410為例,密切相關的就兩個檔案夾/board/smdk2410和/cpu/arm920t,裡面核心檔案就u-boot.lds 、config.mk 、start.S。
/cpu/arm920t/u-boot.lds
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000; // 從0地址起始
. = ALIGN(4);
.text :
{
cpu/arm920t/start.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;
.bss (NOLOAD) : { *(.bss) . = ALIGN(4); }
_end = .;
}
串連指令檔lds中沒有設定LMA,只是設定了VMA。VMA的設定是通過頂層目錄下的config.mk檔案中的LDFLAGS實現的,TEXT_BASE在/board/smdk2410/config.mk中定義為0x33F80000(SDRAM地址)。
LDFLAGS += -Bstatic -T $(obj)u-boot.lds $(PLATFORM_LDFLAGS)
ifneq ($(TEXT_BASE),)
LDFLAGS += -Ttext $(TEXT_BASE)
endif
查看u-boot.map檔案,代碼的串連地址是從0x33F80000開始的。
167 .text 0x33f80000 0x232c8
168 cpu/arm920t/start.o(.text)
169 .text 0x33f80000 0x4a0 cpu/arm920t/start.o
170 0x33f80048 _bss_start
171 0x33f8004c _bss_end
172 0x33f80044 _armboot_start
173 0x33f80000 _start
174 board/samsung/fs2410/lowlevel_init.o(.text)
175 .text 0x33f804a0 0x64 board/samsung/fs2410/lowlevel_init.o
176 0x33f804a4 lowlevel_init
177 board/samsung/fs2410/nand_read.o(.text)
178 .text 0x33f80504 0xe8 board/samsung/fs2410/nand_read.o
179 0x33f80504 wait_idle
180 0x33f80518 nand_read_ll
bootloader代碼上電之後之所以能夠正確執行,有個很重要的原因,就是最初執行的bootloader代碼是地址無關的,即這個映象檔案可以被放在記憶體中的任何一個地址上運行。
對於地址無關的代碼, 定址是基於pc值的, 在pc值上+/-一個位移值得到運行地址,如跳轉指令B。當執行完代碼搬運,就需要跳到和地址相關的地方去執行,即RAM中。一般是跳轉到一個標號,這時地址相關代碼就開始運行了,如:ldr pc,_start_armboot。
因為在bin映象產生的時候,就已經把_start_armboot這個符號和實際地址綁定在一起,當執行ldr pc,_start_armboot 語句時,程式就從在ROM中執行跳入到RAM中了,前提是進行了代碼搬移。如果沒有代碼搬運就執行ldr pc,_start_armboot,因為RAM中沒有正確的可執行代碼,程式就馬上飛掉了,所有在搬運之前不能定址絕對位址有關代碼,必須執行代碼地址無關.
下面的代碼是從NOR Flash向SDRAM搬運的代碼:
relocate:
adr r0, _start
ldr r1, _TEXT_BASE
cmp r0, r1
beq stack_setup
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2
add r2, r0, r2
copy_loop:
ldmia r0!, {r3-r10}
stmia r1!, {r3-r10}
cmp r0, r2
ble copy_loop
注意其中的 adr r0, _start,這是一條偽指令,一般被編譯器替換為sub r0, pc,#offset ,不要理解為讀取符合表中_start符號的地址(0x33F80000)。上電開始執行時,pc從0開始,所以現在r0值為0+offset,不等於_TEXT_BASE(0x33F80000)。接下來要用到連結時確定的符號地址_armboot_start(0x33F80044)了,把_start:0x0 (NOR Flash)裡的.text、.data的代碼往SDRAM裡_TEXT_BASE確定的地址: 0x33f80000搬運。s3c2410的SDRAM基地址是0x3000_0000,由於uboot支援的這個board SDRAM64M(0x3000_0000-0x3400_0000),所以把u-boot.bin搬運到記憶體的高端地址.然後跳到記憶體中執行,提高速度。