关于自解压部分的逻辑和代码详解参看
http://blog.csdn.net/luckyapple1028/article/details/44726131
写得非常好,但是貌似还有一点点小错误。本文部分文字引用自这篇文章。
还有几点可能需要后续解释的先记录一下
ldr r4, =zreladdr
/*
* Set up a page table only if it won't overwrite ourself.
* That means r4 < pc || r4 - 16k page directory > &_end.
* Given that r4 > &_end is most unfrequent, we add a rough
* additional 1MB of room for a possible appended DTB.
*/
mov r0, pc
cmp r0, r4
ldrcc r0, LC0+32
addcc r0, r0, pc
cmpcc r4, r0
orrcc r4, r4, #1 @ remember we skipped cache_on
blcs cache_on
.align 2
.type LC0, #object
LC0: .word LC0 @ r1
.word __bss_start @ r2
.word _end @ r3
.word _edata @ r6
.word input_data_end - 4 @ r10 (inflated size location)
.word _got_start @ r11
.word _got_end @ ip
.word .L_user_stack_end @ sp
.word _end - restart + 16384 + 1024*1024
.size LC0, . - LC0
这里r4等于zreladdr是内核自解压的地址。
注释的描述是 :
只有在不自覆盖的情况下才会创建一个页表,也就是意味着r4<pc或者 r4-16k页目录的大小>&_end.(&_end是当前内核程序的末尾地址).通常r4>&_end是不常出现的。我们粗略添加了1MB的空间给可能附加在内核后面的DTB设备树块。
后续代码翻译成
r0=cur_pc_in_mem=pc
if(cur_pc_in_mem<zreladdr)
{
//LC0+32里面的值为=_end-restart+16K页目录+1MB的DTB空间
total_cur_image_size=_end-restart+16*1024+1024*1024;
cur_image_end_pos_in_mem=total_cur_image_size+cur_pc_in_mem;
if(zreladdr<cur_image_end_pos_in_mem)
{
//做一个标记,后续根据这个标记决定是否需要调用cache_on。总之就是cache_on是都需要
//调用的。只是先后顺序不同。这里记住了我们没有打开cache_on。其他的情况都打开了cache_on
zreladdr=zreladdr | 1;
}
else
{
//需要16KB页表 打开data_cache是需要mmu的
cache_on
}
}
else
{
//需要16KB页表 打开data_cache是需要mmu的
cache_on
}
也就是前面连接文章中的
(1) PC >= r4:直接进行缓存初始化
(2) PC < r4 && _end + 16384+ 1024*1024 > r4:不进行缓存初始化
(3) PC < r4 && _end + 16384+ 1024*1024 <= r4:执行缓存初始化
restart:
adr r0, LC0
ldmia r0, {r1, r2, r3, r6, r10, r11, r12}
ldr sp, [r0, #28]
接下来是从LC0加载对应的值到相应的寄存器中
(1) r0:LC0标签处的运行地址
(2) r1:LC0标签处的链接地址
(3) r2:__bss_start处的链接地址
(4) r3:_ednd处的链接地址(即程序结束位置)
(5) r6:_edata处的链接地址(即数据段结束位置)
(6) r10:压缩后内核数据大小位置 (解压后的内核数据大小位置)
(7) r11:GOT表的启示链接地址
(8) r12:GOT表的结束链接地址
(9) sp:栈空间结束地址
这里直接引用 但是其中r10描述应该错误了,应该是解压后的内核数据大小的位置。随后通过r0和r1的比较能够知道当前的运行位置和链接地址是不是一致的。
/*
* We might be running at a different address. We need
* to fix up various pointers.
*/
sub r0, r0, r1 @ calculate the delta offset
add r6, r6, r0 @ _edata
add r10, r10, r0 @ inflated kernel size location
用当前的内存中LC0地址-链接时LC0的地址得到差值。需要修复一些数据
数据段结束位置r6和压缩后内核数据大小位置需要加上这个差值。
这时候访问r6和r10才能得到准确的值,这时候的r6 r10是运行时在内存中对应的值,而不是链接时的值。
接下来一段是根据R10将解压后的内核大小放到r9当中。这个就要看lds脚本了
先看r10原来的值是.word input_data_end - 4@ r10 (inflated size location)
而input_data_end 的定义在 compressed/piggy.gzip.S中
.section .piggydata,#alloc
.globl input_data
input_data:
.incbin "arch/arm/boot/compressed/piggy.gzip"
.globl input_data_end
input_data_end:
可以发现input_data和input_data_end里面就是压缩的内核的数据,由于压缩内核时将解压后的大小4字节放在了piggy.gzip的末尾。也就是arm/boot/Image的大小
所以(r10=input_data_end - 4)。
为何要用一字节一字节方式访问r10地址数据拼凑得到r9呢?
因为压缩后的的内核末尾并不一定是内存对齐的。所以r10不一定是内存对齐的可访问地址,比如访问4字节,那么地址一定要是4的倍数,否则会访问出错。很多arm是不支持非对齐的数据访问,所以要拼凑出来r9。
add sp, sp, r0
add r10, sp, #0x10000
mov r5, #0 @ init dtb size to 0
接下来是修复堆栈指针。
r10=sp+64kb的malloc空间。也就是malloc空间在stack的上面,顺便看一下sp在哪里。
.word .L_user_stack_end@ sp
在head.s的最末尾
.L_user_stack: .space4096
.L_user_stack_end:
定义了4096字节的stack大小
接下来就是检查解压后内存是否会被覆盖的问题了
/*
* Check to see if we will overwrite ourselves.
* r4 = final kernel address (possibly with LSB set)
* r9 = size of decompressed image
* r10 = end of this image, including bss/stack/malloc space if non XIP
* We basically want:
* r4 - 16k page directory >= r10 -> OK
* r4 + image length <= address of wont_overwrite -> OK
* Note: the possible LSB in r4 is harmless here.
*/
add r10, r10, #16384
cmp r4, r10
bhs wont_overwrite
add r10, r4, r9
adr r9, wont_overwrite
cmp r10, r9
bls wont_overwrite
r4是内核解压缩后最终的地址,也就是最后的entry_point
r9是内核解压后的大小
r10是当前压缩内核的末尾的地址。包括了bss stack malloc空间的
也就是要比较解压后的内核是不是和当前的image内存重复了。
期望有两种。
第一种是r4的地址大于r10+16K页目录。也就是最终内核起始地址比当前image以及末尾需要的附属空间要大,解压后的整个内核在当前image的后面。
第二种是r4的地址+r9也就是解压后内核的大小,小于wont_overwrite。也就是解压后的整个内核在当前image的前面(至少是wont_overwrite的前面,之前的代码被覆盖了没事)。
其他的情况都是需要搬移代码的。如果不需要搬移,那么就直接运行到wont_overwrite。否则执行下面的搬移代码
/*
* Relocate ourselves past the end of the decompressed kernel.
* r6 = _edata
* r10 = end of the decompressed kernel
* Because we always copy ahead, we need to do it from the end and go
* backward in case the source and destination overlap.
*/
/*
* Bump to the next 256-byte boundary with the size of
* the relocation code added. This avoids overwriting
* ourself when the offset is small.
*/
add r10, r10, #((reloc_code_end - restart + 256) & ~255)
bic r10, r10, #255
/* Get start of code we want to copy and align it down. */
adr r5, restart
bic r5, r5, #31
原本r10是当解压后内核的末尾的地址。因为在前面比较是不是overwrite的时候。add r10,r4,r9 也就是 r10=r4(解压后内核的起始地址)+r9(解压后内核的大小)
r6是_edata也就是数据段的末尾。
又对r10地址进行了扩展。大小是text段的大小。并且以256对齐。这个扩展是避免搬移的新代码和自己和自己冲突。
此时r10就是搬移的目的地的起始地址了。
再得到restart的地址放到r5。对r5进行对齐。这个就是拷贝的起始地址了。
sub r9, r6, r5 @ size to copy
add r9, r9, #31 @ rounded up to a multiple
bic r9, r9, #31 @ ... of 32 bytes
add r6, r9, r5
add r9, r9, r10
1: ldmdb r6!, {r0 - r3, r10 - r12, lr}
cmp r6, r5
stmdb r9!, {r0 - r3, r10 - r12, lr}
bhi 1b
/* Preserve offset to relocated code. */
sub r6, r9, r6
/* cache_clean_flush may use the stack, so relocate it */
add sp, sp, r6
首先计算需要拷贝的大小。r9=r6(数据段的末尾)-r5(restart的地址,也即是拷贝的起始地址);
r6(结束拷贝的地址)=r9(需要拷贝的大小)+r5(restart的地址,也即是拷贝的起始地址)
r9(拷贝的目的地的结束地址)=r9(需要拷贝的大小)+r10(拷贝的目的地的起始地址);
从结束地址向起始地址进行拷贝。比较r6(结束拷贝的地址)和r5(restart的地址,也即是拷贝的起始地址) 直到最终拷贝完毕。
拷贝的时候用的寄存器都是特定的r0-r3 r10r12,lr。目的就是保护其他寄存器不被破坏,r4 r7 r8等等。
执行sub r6,r9,r6的时候。r6已经等于r5了。r9已经等于r10了,所以等同于
r6(相对偏移)=r10(拷贝的目的地的起始地址)-r5(restart的地址,也即是拷贝的起始地址)
add sp,sp,r6
将sp加上相对位移。移动到指到新的sp位置上。因为 cache_clean_flush可能会用到stack。
0x410fb767