參考文檔:https://blog.csdn.net/haoge921026/article/details/46785995
以下內容基於s5pv210進行分析
zImage由head.o,piggy.gzip.o,misc等鏈接組成,piggy.gzip.o中包含壓縮的內核鏡像,zImage的作用實際上就是對內核進行解碼。
zImage還是位置無關碼,它的鏈接地址為0,可以在任何地址運行,因為在對其源文件進行編譯時編譯器參數設置了-fpic,通過反匯編看到編譯生成了.got和.got.plt段。.dot.plt為空,查看反匯編得知編譯器對c語言函數的調用是通過bl指令實現的,所以c的函數調用是位置無關碼;而對於c中全局變量的處理是通過相對尋址找到全局變量一一對應的.got地址(這里的相對尋址是:在每個函數段中如果使用了全局變量都會存放.got首地址相對運行pc的偏移量以及全局變量在.got中的偏移),所以無論運行地址和鏈接地址匹不匹配,代碼都能正確找到全局變量的.got地址。.got地址中存放了全局變量的鏈接地址,所以只要在zImage的初始化c語言運行環境部分增加對.got部分全局變量的重定位則代碼將正確運行,因此zImage成為了位置無關碼
現在開始分析arch/arm/boot/compressed/head.s進行代碼分析:
start: .type start,#function //用於指定標號start為函數 .rept 8 //指定.endr以前的指令循環8次 mov r0, r0 .endr b 1f .word 0x016f2818 @魔數用於表示zImage的身份 .word start @ zImage的鏈接地址 .word _edata @ zImage的鏈接結束地址 1: mov r7, r1 @ save architecture ID mov r8, r2 @ save atags pointer
#ifndef __ARM_ARCH_2__
/*用於判斷是不是angel啟動,我們是u-boot啟動進來時已經是svc模式了所以直接跳到
not_angel */
mrs r2, cpsr @ get current mode
tst r2, #3 @ not user?
bne not_angel
mov r0, #0x17 @ angel_SWIreason_EnterSVC
ARM( swi 0x123456 ) @ angel_SWI_ARM
THUMB( svc 0xab ) @ angel_SWI_THUMB
not_angel:
mrs r2, cpsr @ turn off interrupts to
orr r2, r2, #0xc0 @ prevent angel from running
msr cpsr_c, r2
#else
teqp pc, #0x0c000003 @ turn off interrupts
#endif
.text adr r0, LC0 //將LC0的運行地址加載到r0, ARM( ldmia r0, {r1, r2, r3, r4, r5, r6, r11, ip, sp}) /*將r0指定的地址中的數據依次加載到括號里的寄存器中: r1 : LC0的鏈接地址 r2 : BSS 起始鏈接地址 r3 : BSS 結束鏈接地址 r4 : 內核的鏈接地址 r5 : zImage的鏈接地址 r6 : 內核的大小 r11 :.got的起始鏈接地址, ip :.got的結束鏈接地址 sp :鏈接下的棧頂 r0 : LC0的運行運行地址*/ THUMB( ldmia r0, {r1, r2, r3, r4, r5, r6, r11, ip} ) //無效 THUMB( ldr sp, [r0, #32] ) //無效 subs r0, r0, r1 //r0成為運行地址與鏈接地址的偏移量 beq not_relocated //運行地址與連接地址相同跳轉該語句 add r5, r5, r0 //r5 : zImage的運行地址 add r11, r11, r0 //r11:.got的起始運行地址 add ip, ip, r0 //ip:.got的結束運行地址
#ifndef CONFIG_ZBOOT_ROM add r2, r2, r0 //r2 :bss的運行起始地址 add r3, r3, r0 //r3:bss的運行結束地址 add sp, sp, r0 //sp:運行的棧頂地址 /* * 將.got中全局變量的鏈接地址重定位為運行地址 */ 1: ldr r1, [r11, #0] @ relocate entries in the GOT add r1, r1, r0 @ table. This fixes up the str r1, [r11], #4 @ C references. cmp r11, ip blo 1b #else /* 未編譯 */ 1: ldr r1, [r11, #0] @ relocate entries in the GOT cmp r1, r2 @ entry < bss_start || cmphs r3, r1 @ _end < entry addlo r1, r1, r0 @ table. This fixes up the str r1, [r11], #4 @ C references. cmp r11, ip blo 1b #endif
not_relocated: mov r0, #0 1: str r0, [r2], #4 @ clear bss str r0, [r2], #4 str r0, [r2], #4 str r0, [r2], #4 cmp r2, r3 blo 1b
bl cache_on mov r1, sp //r1 : 運行的棧頂地址 add r2, sp, #0x10000 //r2: 堆結束地址64k
/*堆的結束地址大於內核的起始地址或者內核的結束地址大於zImage的運行起始地址將發生覆蓋,我們這邊會發生覆蓋所以不跳轉繼續往下執行*/ cmp r4, r2 bhs wont_overwrite add r0, r4, r6 cmp r0, r5 bls wont_overwrite /* r0:堆結束的地址 r1:堆起始的地址 r2:堆結束的地址 r3:機器ID */ mov r5, r2 @ decompress after malloc space mov r0, r5 mov r3, r7 bl decompress_kernel
這里看看 decompress_kernel中的傳入參數
unsigned long decompress_kernel(
unsigned long output_start, //r0 解壓內核輸出地址
unsigned long free_mem_ptr_p,//r1 堆起始地址
unsigned long free_mem_ptr_end_p,//r2 堆結束地址
int arch_id//r3 機器ID
)
解壓后返回到head中繼續執行
add r0, r0, #127 + 128 @ alignment + stack bic r0, r0, #127 @ align the kernel length 分析如下: r0為decompress_kernel()函數的返回值,它的返回值最終為Linux內核解壓后的長度, 這里的第一條指令完成的功能是在解壓后的Linux內核后面預留128字節的棧空間, 第二條指令使最終r0的值為128字節對齊
此時我們的內存空間分布如下:
| |
| |
| |
|----------------|----
| 128byte | |
| | |
| + | |
| | r0
| 解壓后的內核 | |
| | |
| | |
|----------------|<-------r5
| 堆64k |
|----------------|
| 棧4k |
|----------------|
| |
| 壓縮的內核 |
| 當前運行的代碼|
| |
|----------------|0x30008000 zImage的加載地址
| |
---------------------
/* * r0 = decompressed kernel length * r1-r3 = unused * r4 = kernel execution address * r5 = decompressed kernel start * r7 = architecture ID * r8 = atags pointer * r9-r12,r14 = corrupted */ add r1, r5, r0 @ end of decompressed kernel adr r2, reloc_start ldr r3, LC1 add r3, r2, r3 1: ldmia r2!, {r9 - r12, r14} @ copy relocation code stmia r1!, {r9 - r12, r14} ldmia r2!, {r9 - r12, r14} stmia r1!, {r9 - r12, r14} cmp r2, r3 blo 1b mov sp, r1 add sp, sp, #128 @ relocate the stack bl cache_clean_flush ARM( add pc, r5, r0 ) @ call relocation code THUMB( add r12, r5, r0 ) THUMB( mov pc, r12 ) @ call relocation code 解析如下: r1 = r5 + r0 = 解壓后內核存放的地址 + 內核大小 r2 = 當前reloc_start標簽所在的地址 r3 = *LC1 LC1: .word reloc_end - reloc_start 所以r3 為重定位代碼段的大小 r3 = r2 + r3 =重定位代碼段的結束地址 接下來的指令就是將重定位的代碼段搬移到解壓的Linux內核后面 並且重定義了棧最后跳轉重定義代碼
| |
|----------------|<---sp
| 128byte |
|----------------|<---r1
| |
| 重定位代碼段 |
| |
|----------------|<---pc
| 128byte | |
| | |
| + | |
| | r0
| 解壓后的內核 | |
| | |
| | |
|----------------|------->r5
| 堆64k |
|----------------|
| 棧4k |
|----------------|
| |
| 壓縮的內核 |
| 當前運行的代碼|
| |
|----------------|0x30008000 zImage的加載地址
| |
---------------------
/ * r0 = decompressed kernel length * r1-r3 = unused * r4 = kernel execution address * r5 = decompressed kernel start * r7 = architecture ID * r8 = atags pointer * r9-r12,r14 = corrupted */ .align 5 reloc_start: add r9, r5, r0 //內核的結束地址 sub r9, r9, #128 //減掉棧部分 debug_reloc_start mov r1, r4 1: .rept 4 ldmia r5!, {r0, r2, r3, r10 - r12, r14} //一次copy28個字 stmia r1!, {r0, r2, r3, r10 - r12, r14} .endr cmp r5, r9 blo 1b mov sp, r1 add sp, sp, #128 @ relocate the stack debug_reloc_end call_kernel: bl cache_clean_flush bl cache_off mov r0, #0 @ must be zero mov r1, r7 @ restore architecture number mov r2, r8 @ restore atags pointer mov pc, r4 @ call kernel
以上就是zImage的啟動過程,接下來將跳轉內核。