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的启动过程,接下来将跳转内核。

原文地址:https://www.cnblogs.com/genshu123/p/11210990.html