linux内核zImage详解
参考文档: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的启动过程,接下来将跳转内核。