[Linux]学习笔记系列 -- [arm]boot
文章目录
- 解压过程
- Makefile
- TEXT_OFFSET 内核映像的字节偏移量 0x00008000
- piggy_data
- include/asm/unified.h
- AR_CLASS M_CLASS 选择使用的架构指令
- compressed/efi-header.S
- __nop 空指令
- __initial_nops 执行2个__nop
- compressed/fdt_check_mem_start.c 从FDT中检查内存是否可用,返回可用内存起始地址
- compressed/piggy.S 设置piggy_data的内容与piggy_data_end的地址
- compressed/misc.c 杂项
- decompress_kernel 调用解压内核函数,并打印日志
- arch/arm/boot/compressed/decompress.c 不同解压的中间层
- compressed/decompress.c
- compressed/head.S
- start 入口函数
- LC0 保存着内核的基地址和数据段结束地址
- LC1 存放着内核的栈顶地址和数据段结束地址的位置无关偏移量
- Lheadroom 内核代码大小+16kb(页表)+DTB预留大小(1mb)
- 1 使用位置无关执行重定向栈顶地址,并开启缓存页表
- cache_on 开启缓存
- call_cache_fn 获取处理器ID
- reloc_code_end 记录重定向代码的结束地址
- dbgkc 打印调试内核信息
- cache_clean_flush 清除缓存并刷新
- restart 重定向并获取解压后的内核大小
- Linflated_image_size_offset 解压后的内核大小偏移量
- get_inflated_image_size 获取解压后的内核大小
- wont_overwrite 修正LC0存储的内容,BSS区域的起始和结束地址,GOT表的起始和结束地址
- not_relocated 重定向已完成执行
- __enter_kernel 进入内核

https://github.com/wdfk-prog/linux-study
解压过程
- 依据arch/arm/kernel/vmlinux.lds 生成linux内核源码根目录下的vmlinux,这个vmlinux属于未压缩,带调试信息、符号表的最初的内核,大小约23MB;
- 将上面的vmlinux去除调试信息、注释、符号表等内容,生成arch/arm/boot/Image,这是不带多余信息的linux内核,Image的大小约3.2MB
- 将 arch/arm/boot/Image 用gzip -9 压缩生成arch/arm/boot/compressed/piggy.gz大小约1.5MB;
- 编译arch/arm/boot/compressed/piggy.S 生成arch/arm/boot/compressed/piggy.o大小约1.5MB,这里实际上是将piggy.gz通过piggy.S编译进piggy.o文件中。而piggy.S文件仅有6行,只是包含了文件piggy.gz;
- 依据arch/arm/boot/compressed/vmlinux.lds 将arch/arm/boot/compressed/目录下的文件head.o 、piggy.o 、misc.o链接生成 arch/arm/boot/compressed/vmlinux,这个vmlinux是经过压缩且含有自解压代码的内核,大小约1.5MB;
- 将arch/arm/boot/compressed/vmlinux去除调试信息、注释、符号表等内容,生成arch/arm/boot/zImage大小约1.5MB;这已经是一个可以使用的linux内核映像文件了;
- 将arch/arm/boot/zImage添加64Bytes的相关信息打包为arch/arm/boot/uImage大小约1.5MB;
Makefile
TEXT_OFFSET 内核映像的字节偏移量 0x00008000
# 文本偏移量。此列表按地址进行数字排序,以便
# 提供一种避免/解决多架构内核冲突的方法。
# 注意:该值以下的 32kB 是预留给内核使用的
# 在引导期间,这个偏移量对于kexec-tools 的
textofs-y := 0x00008000
# TRAM 中内核映像的字节偏移量从 RAM 开始。
TEXT_OFFSET := $(textofs-y)
piggy_data
- 生成vmlinux时一同生成piggy_data
- piggy_data包含了内核的压缩数据,压缩算法由
CONFIG_KERNEL_GZIP
等决定 - piggy_data.o,使用piggy_data的内容生成.o文件
include/asm/unified.h
AR_CLASS M_CLASS 选择使用的架构指令
#ifdef CONFIG_CPU_V7M
#define AR_CLASS(x...)
#define M_CLASS(x...) x
#else
#define AR_CLASS(x...) x
#define M_CLASS(x...)
#endif
compressed/efi-header.S
__nop 空指令
.macro __nopAR_CLASS( mov r0, r0 )M_CLASS( nop.w ).endm
__initial_nops 执行2个__nop
.macro __initial_nops
#ifdef CONFIG_EFI_STUB.inst MZ_MAGIC | (0xe225 << 16) @ eor r5, r5, 0x4d000eor r5, r5, 0x4d000 @ undo previous insn
#else__nop__nop
#endif.endm
compressed/fdt_check_mem_start.c 从FDT中检查内存是否可用,返回可用内存起始地址
- 检查传入的
R1
,即mov r8, r2 @ save atags pointer
传入的FDT指针是否为空 - 32位地址空间,检查
#address-cells
和#size-cells
是否大于2 - 检查
/chosen
节点下是否存在linux,usable-memory-range
属性,是否有可用内存限制 - 检查
device_type
为memory
的节点,是否存在linux,usable-memory
或reg
属性 - 获取内存的起始地址和大小,检查是否在可用内存范围内
- 如果在可用内存范围内,则返回传入的
mem_start
;否则更新可用内存的起始地址使用最小的可用内存 - 如果没有找到可用内存,则使用传入的
mem_start
作为返回值 - 返回值:要使用的物理内存起始地址
uint32_t fdt_check_mem_start(uint32_t mem_start, const void *fdt)
{uint32_t addr_cells, size_cells, usable_base, base;uint32_t fdt_mem_start = 0xffffffff;const fdt32_t *usable, *reg, *endp;uint64_t size, usable_end, end;const char *type;int offset, len;if (!fdt)return mem_start;if (fdt_magic(fdt) != FDT_MAGIC)return mem_start;/* There may be multiple cells on LPAE platforms */addr_cells = get_cells(fdt, "#address-cells");size_cells = get_cells(fdt, "#size-cells");if (addr_cells > 2 || size_cells > 2)return mem_start;/** 崩溃转储内核时的可用内存* 此属性描述限制:此范围内的内存为* 仅在通过其他机制进行描述时有效*/usable = get_prop(fdt, "/chosen", "linux,usable-memory-range",(addr_cells + size_cells) * sizeof(fdt32_t));if (usable) {size = get_val(usable + addr_cells, size_cells);if (!size)return mem_start;if (addr_cells > 1 && fdt32_ld(usable)) {/* Outside 32-bit address space */return mem_start;}usable_base = fdt32_ld(usable + addr_cells - 1);usable_end = usable_base + size;}/* 遍历所有内存节点和区域 */for (offset = fdt_next_node(fdt, -1, NULL); offset >= 0;offset = fdt_next_node(fdt, offset, NULL)) {type = fdt_getprop(fdt, offset, "device_type", NULL);if (!type || strcmp(type, "memory"))continue;reg = fdt_getprop(fdt, offset, "linux,usable-memory", &len);if (!reg)reg = fdt_getprop(fdt, offset, "reg", &len);if (!reg)continue;for (endp = reg + (len / sizeof(fdt32_t)); //获取当前节点的reg属性endp - reg >= addr_cells + size_cells; //reg属性的长度大于一对参数reg += addr_cells + size_cells) { //获取下一个reg属性size = get_val(reg + addr_cells, size_cells); //获取当前节点的sizeif (!size)continue;if (addr_cells > 1 && fdt32_ld(reg)) {/* Outside 32-bit address space, skipping */continue;}base = fdt32_ld(reg + addr_cells - 1);end = base + size;if (usable) {/** Clip to usable range, which takes precedence* over mem_start*/if (base < usable_base)base = usable_base;if (end > usable_end)end = usable_end;if (end <= base)continue;} else if (mem_start >= base && mem_start < end) {/* 计算的地址有效,使用它*/return mem_start;}//更新可用内存的起始地址,使用最小的可用内存if (base < fdt_mem_start)fdt_mem_start = base;}}if (fdt_mem_start == 0xffffffff) {/* 未找到可用内存,正在回退到默认值*/return mem_start;}/**计算出的地址不可用,或者被* “linux,usable-memory-range” 属性。* 改用 DTB 中可用的最低物理内存地址,* 并确保这是 2 MiB 的倍数,以便进行 phys/virt 修补。*/return round_up(fdt_mem_start, SZ_2M);
}
compressed/piggy.S 设置piggy_data的内容与piggy_data_end的地址
//只读属性.section .piggydata, "a"//全局符号.globl input_data
input_data://指定文件 piggy_data 的内容直接包含到当前的汇编文件中.incbin "arch/arm/boot/compressed/piggy_data".globl input_data_end
input_data_end:
compressed/misc.c 杂项
decompress_kernel 调用解压内核函数,并打印日志
void decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,unsigned long free_mem_ptr_end_p,int arch_id)
{int ret;output_data = (unsigned char *)output_start;free_mem_ptr = free_mem_ptr_p;free_mem_end_ptr = free_mem_ptr_end_p;__machine_arch_type = arch_id;arch_decomp_setup(); //arch/arm/include/debug/uncompress.h为空函数不执行putstr("Uncompressing Linux..."); //putstr最终调用puts,在debug.s中实现ret = do_decompress(input_data, input_data_end - input_data,output_data, error);if (ret)error("decompressor returned an error"); //输出错误后,while(1)死循环elseputstr(" done, booting the kernel.\n");
}
arch/arm/boot/compressed/decompress.c 不同解压的中间层
- makefile和konconfig中进行了选择配置需要的解压算法
#ifdef CONFIG_KERNEL_GZIP
#include "../../../../lib/decompress_inflate.c"
#endif#ifdef CONFIG_KERNEL_LZO
#include "../../../../lib/decompress_unlzo.c"
#endif#ifdef CONFIG_KERNEL_LZMA
#include "../../../../lib/decompress_unlzma.c"
#endif#ifdef CONFIG_KERNEL_XZ
/* Prevent KASAN override of string helpers in decompressor */
#undef memmove
#define memmove memmove
#undef memcpy
#define memcpy memcpy
#include "../../../../lib/decompress_unxz.c"
#endif#ifdef CONFIG_KERNEL_LZ4
#include "../../../../lib/decompress_unlz4.c"
#endifint do_decompress(u8 *input, int len, u8 *output, void (*error)(char *x))
{return __decompress(input, len, NULL, NULL, output, 0, NULL, error);
}
compressed/decompress.c
- 根据所选的解压算法,包含不同的解压算法.同时只能选择一个解压算法
- 注意定义了
STATIC
和STATIC_RW_DATA
,并且decompress_inflate.c
是**include
**的.分析时需要注意包括这两个宏
#define STATIC static
#define STATIC_RW_DATA /* non-static please */#ifdef CONFIG_KERNEL_GZIP
#include "../../../../lib/decompress_inflate.c"
#endif#ifdef CONFIG_KERNEL_LZO
#include "../../../../lib/decompress_unlzo.c"
#endif#ifdef CONFIG_KERNEL_LZMA
#include "../../../../lib/decompress_unlzma.c"
#endif#ifdef CONFIG_KERNEL_XZ
#include "../../../../lib/decompress_unxz.c"
#endif#ifdef CONFIG_KERNEL_LZ4
#include "../../../../lib/decompress_unlz4.c"
#endifint do_decompress(u8 *input, int len, u8 *output, void (*error)(char *x))
{return __decompress(input, len, NULL, NULL, output, 0, NULL, error);
}
compressed/head.S
- 使用__nop指令填充32字节,跳过a.out头部.
- 其中
__initial_nops
跳过2 - 循环5个
M_CLASS( nop.w )
跳过一个
- 其中
- 跳转到1,重定向栈顶地址,开启缓存页表
- cache_on开启缓存,call_cache_fn获取处理器ID.(V7M直接返回)
restart
函数重定向并获取解压后的内核大小,判断页表是否会覆盖内核映像,如果会则拷贝内核到新的内存区域,重定向zimage的地址,并跳转到重定向后的restart的地址进行执行.不会则跳转到wont_overwrite
函数wont_overwrite
修正LC0存储的内容,BSS区域的起始和结束地址,GOT表的起始和结束地址,顺序执行到not_relocated
标签
start 入口函数
.section ".start", "ax".alignAR_CLASS( .arm )
start:.type start,#function/** 这 7 个 nop 以及下面的 1 个 nop 为* !THUMB2 形成 8 个 nop,使压缩的内核可启动* 在假定内核在 a.out 中的传统 ARM 系统上* 二进制格式。这些系统上的引导加载程序将* 将 32 字节跳转到图像中以跳过 a.out 标头。* 这 8 个 nop 正好填满了 32 个字节,一切仍然如此* 在这些旧系统上按预期工作。Thumb2 模式保持* 7 个 nop,因为事实证明一些引导加载程序* 正在修补内核的初始指令,即* 已开始利用此 “补丁区域”。*/__initial_nops.rept 5__nop.endr
#ifndef CONFIG_THUMB2_KERNEL__nop
#elseAR_CLASS( sub pc, pc, #3 ) @ A/R: switch to Thumb2 modeM_CLASS( nop.w ) @ M: already in Thumb2 mode.thumb @使用thumb指令集
#endifW(b) 1f @跳转到1@r1和r2中分别存放着由bootloader传递过来的architecture ID和指向标记列表的指针。mov r7, r1 @ save architecture IDmov r8, r2 @ save atags pointer
LC0 保存着内核的基地址和数据段结束地址
- got:全局偏移表,存储动态链接的函数的地址
.align 2.type LC0, #object
LC0: .word LC0 @ r1.word __bss_start @ r2.word _end @ r3.word _got_start @ r11.word _got_end @ ip.size LC0, . - LC0
LC1 存放着内核的栈顶地址和数据段结束地址的位置无关偏移量
- 在
LC1
中存放着内核的栈顶地址和数据段结束地址 - 通过存储栈顶地址-
LC1
的地址来获取位置无关的地址
.type LC1, #object
LC1: .word .L_user_stack_end - LC1 @ sp.word _edata - LC1 @ r6 image_end.size LC1, . - LC1 @确定LC1占用的字节数.L_user_stack: .space 4096
.L_user_stack_end:
Lheadroom 内核代码大小+16kb(页表)+DTB预留大小(1mb)
.Lheadroom:.word _end - restart + 16384 + 1024*1024
1 使用位置无关执行重定向栈顶地址,并开启缓存页表
- 对齐128MB
- 修正sp栈顶指针
- 通过
fdt_check_mem_start
检查内存是否可用,并返回可用内存起始地址 - 确定最终的内核映像地址
- 判断页表是否会覆盖内核映像.(在内核映像地址范围下的16kb页表与1mb的DTB)
- 设置之后的页表超过了内核映像,则跳过cache_on不进行设置.r4的LSB位表示
- 如果r4的LSB位为1,则跳过cache_on
- 如果r4的LSB位为0,则调用cache_on开启缓存
- 否则调用
cache_on
开启缓存
.text
#ifdef CONFIG_AUTO_ZRELADDR/** 查找物理内存的起点。 当我们执行时如果不开启 MMU,我们将处于物理地址空间。* 我们只需要通过对齐地址。** 这种一致性是不同的平台 - 我们选择了 128MB 来允许对齐其物理内存开始的平台* 设置为 128MB 以使用此功能,同时允许 zImage放置在其他平台。 增加对齐意味着我们将* 对物理开始时的对齐要求更严格 记忆,但放松它意味着我们打破了那些 已经将他们的 zImage 放在(例如)前 64MB 中* 的*/mov r0, pcand r0, r0, #0xf8000000 @对齐128MB
#ifdef CONFIG_USE_OF@LC1 存放着内核的栈顶地址和数据段结束地址adr r1, LC1 @r1 存储LC1的内存地址ldr sp, [r1] @ get stack location@ 这里需要加上LC1的地址偏移量,修正存放在LC1处减去的偏移量add sp, sp, r1 @ apply relocation/*根据通过的 DTB 验证计算的开始时间 */mov r1, r8 @r8 save atags pointerbl fdt_check_mem_start
1:
#endif /* CONFIG_USE_OF *//*确定最终的内核映像地址. */add r4, r0, #TEXT_OFFSET
#endif/** 仅当页表不会覆盖我们自己时,才设置页表。* 这意味着 r4 < pc || r4 - 16k 页目录 > &_end。* 鉴于 r4 > &_end 最不频繁,我们添加了一个粗略的* 额外 1MB 的空间用于可能的附加 DTB。*/mov r0, pc/*if(pc <= 确定最终的内核映像地址) {r0 = headroom;r0 += pc;}//设置之后的页表超过了内核映像,则跳过cache_on不进行设置if(确定最终的内核映像地址 <= r0) {r4 |= 1;//跳过cache_on}*/cmp r0, r4ldrcc r0, .Lheadroomaddcc r0, r0, pccmpcc r4, r0orrcc r4, r4, #1 @ 记住我们跳过了cache_on ,用R4的LSB位表示blcs cache_on @上次第一个操作数大于或等于第二个操作数,调用cache_on
cache_on 开启缓存
- 32字节对齐,传入8,调用
call_cache_fn
,然后返回 - 这段代码的目的是开启缓存(cache)。为了开启指令缓存(I cache)和数据缓存(D cache),需要设置一些页表。页表被放置在内核执行地址向下16KB的位置,希望这个位置没有被其他东西使用。如果被使用了,可能会导致程序崩溃。
- 在进入这个例程时:
- r4 寄存器包含内核执行地址
- r7 寄存器包含架构编号
- r8 寄存器包含 ATAGs 指针
- 在退出这个例程时,以下寄存器的值可能会被破坏:
- r0, r1, r2, r3, r9, r10, r12
- 这个例程必须保留 r4, r7, r8 的值。
.align 5
cache_on: mov r3, #8 @ cache_on functionb call_cache_fn
call_cache_fn 获取处理器ID
- 将
r12
设置为proc_types
的地址 - 获取处理器ID
- 根据处理器ID跳转到不同的函数
CONFIG_CPU_V7M
则在其他地方实现,这里直接返回
/** 以下是* 各种处理器。 这是一个通用的钩子,用于定位* 输入并跳转到指定偏移量处的指令* 从区块的开头开始。 请注意,这是所有位置* 独立代码。** r1 = 损坏* r2 = 损坏* r3 = 块偏移* r9 = 损坏* r12 = 损坏*/
call_cache_fn: adr r12, proc_types
#ifdef CONFIG_CPU_CP15mrc p15, 0, r9, c0, c0 @ get processor ID
#elif defined(CONFIG_CPU_V7M)/** 在 v7-M 上,处理器 ID 位于 V7M_SCB_CPUID* register,但由于缓存处理是在* v7-M(如果存在的话)我们只是提前返回这里。* 如果使用了 V7M_SCB_CPUID 则 cpu ID 函数(即* __armv7_mmu_cache_{on,off,flush}) 将被选中,该* 使用未在 v7-M 上实现的 CP15 寄存器。*/bx lr
#elseldr r9, =CONFIG_PROCESSOR_ID
#endif
reloc_code_end 记录重定向代码的结束地址
restart: //331
reloc_code_end: //1437
dbgkc 打印调试内核信息
.macro kputc,valmov r0, \valbl putc.endm/** Debug kernel copy by printing the memory addresses involved*/.macro dbgkc, begin, end, cbegin, cend
#ifdef DEBUGkputc #'C'kputc #':'kputc #'0'kputc #'x'kphex \begin, 8 /* Start of compressed kernel */kputc #'-'kputc #'0'kputc #'x'kphex \end, 8 /* End of compressed kernel */kputc #'-'kputc #'>'kputc #'0'kputc #'x'kphex \cbegin, 8 /* Start of kernel copy */kputc #'-'kputc #'0'kputc #'x'kphex \cend, 8 /* End of kernel copy */kputc #'\n'
#endif.endm
cache_clean_flush 清除缓存并刷新
/** 清理并刷新缓存以保持一致性。** 入境时,* r0 = 起始地址* r1 = 结束地址(不包括)* 退出时,* R1、R2、R3、R9、R10、R11、R12 损坏* 此例程必须保留:* R4、R6、R7、R8*/.align 5
cache_clean_flush:mov r3, #16mov r11, r1b call_cache_fn
restart 重定向并获取解压后的内核大小
- 重定向栈顶地址
- 重定向数据段结束地址
- 调用
get_inflated_image_size
获取解压后的内核大小 - 分配64k的内存空间用于malloc
#arch/arm/boot/compressed/Makefile MALLOC_SIZE := 65536 AFLAGS_head.o += -DTEXT_OFFSET=$(TEXT_OFFSET) -DMALLOC_SIZE=$(MALLOC_SIZE)
- 检查页表是否会覆盖内核映像
- 不会则跳转到
wont_overwrite
函数 - 会则修正内核映像地址,拷贝内核到新的内存区域,重定向zimage的地址,并跳转到重定向后的restart的地址进行执行
restart: adr r0, LC1 //获取LC1的地址ldr sp, [r0] //获取栈顶地址ldr r6, [r0, #4] //获取数据段结束地址add sp, sp, r0 //修正栈顶地址add r6, r6, r0 //修正数据段结束地址get_inflated_image_size r9, r10, lr
#ifndef CONFIG_ZBOOT_ROM //ROM/flash 中的压缩引导加载程序/* malloc 空间位于重新定位的堆栈上方(最大 64K) */add r10, sp, #MALLOC_SIZE
#else/** 如果 BSS/堆栈ZBOOT_ROM不可重定位,* 但是有人仍然可以从 RAM 运行此代码,* 在这种情况下,我们的参考是 _edata。*/mov r10, r6
#endifmov r5, #0 @ init dtb size 设置为 0
// 使用附加的设备树blob到zImage(实验)
#ifdef CONFIG_ARM_APPENDED_DTB
#endif
/** 检查我们是否会覆盖自己。* r4 = 最终内核地址(可能设置了 LSB)* r9 = 解压缩图像的大小* r10 = 此映像的结尾,包括 bss/stack/malloc 空间(如果非 XIP)* 我们基本上想要:* r4 - 16k 页面目录 >= r10 -> OK* r4 + 图像长度 <= wont_overwrite 的地址 -> OK* 注意:r4 中可能的 LSB 在这里是无害的。*/add r10, r10, #16384 //页表大小cmp r4, r10 //是否会覆盖自己bhs wont_overwrite //if(r4 >= r10 + 16k)跳转到wont_overwriteadd r10, r4, r9 //r10 = 解压缩图像的结束地址adr r9, wont_overwrite //将 wont_overwrite 标签的地址加载到 r9 寄存器中。cmp r10, r9bls wont_overwrite //if(解压缩图像的结束地址 <= wont_overwrite 的地址)跳转到wont_overwrite// 没有跳转,需要修复
/** 重新定位到解压缩的内核末尾之后。* r6 = _edata(image_end)* r10 = 解压内核的结束地址* 因为我们总是提前复制,所以我们需要从头开始做.如果源和目标重叠,则向后移动。*//** 移动到已添加重定位代码的下一个 256 字节边界.* 这样可以避免当偏移量较小时覆盖我们自己。*/add r10, r10, #((reloc_code_end - restart + 256) & ~255)bic r10, r10, #255 @对齐到256字节/* 获取我们想要复制的代码的开头并将其对齐。 */adr r5, restartbic r5, r5, #31 @对齐到32字节
/* Relocate the hyp vector base if necessary */
#ifdef CONFIG_ARM_VIRT_EXT //无使用
#endif//R9 = _edata(image_end) + restart的地址sub r9, r6, r5 @ size to copyadd r9, r9, #31bic r9, r9, #31 @ 确保 r9 的值是32字节对齐的。add r6, r9, r5 //r6 = restart + size to copyadd r9, r9, r10 //r9 = size to copy + 解压内核的结束地址
#ifdef DEBUG //打印内核信息sub r10, r6, r5sub r10, r9, r10/** 我们即将将内核复制到新的内存区域。* 新内存区域的边界可以在 R10 和 R9,而 R5 和 R6 包含边界* 的 MEMORY 中。致电 dbgkc 将有助于打印此内容信息。*/dbgkc r5, r6, r10, r9/* Start of compressed kernel End of compressed kernelStart of kernel copyEnd of kernel copyC:0xC0080060-0xC02151C0->0xC02E3000-0xC0478160*/
#endif// 将image_end的数据拷贝到新的内存区域(size to copy + 解压内核的结束地址)// r6 image_end
1: ldmdb r6!, {r0 - r3, r10 - r12, lr}cmp r6, r5stmdb r9!, {r0 - r3, r10 - r12, lr}bhi 1b //if(r6 > restart的地址)跳转到1继续拷贝/*保留 offset 以重新定位的代码。*/sub r6, r9, r6mov r0, r9 @ 重新定位的 zImage 的开始add r1, sp, r6 @ 重新定位的 zImage 结束bl cache_clean_flushbadr r0, restart //将 restart 标签的地址加载到 r0 寄存器中add r0, r0, r6 //重定位后的代码的起始地址mov pc, r0 //跳转到重定位后的代码的起始地址 restart处
Linflated_image_size_offset 解压后的内核大小偏移量
- 减去 4 的目的是为了获取解压后内核镜像大小的地址,因为这个大小通常存储在 input_data_end 之前的4个字节中。减去当前地址
.
是为了得到一个相对偏移量,这样在运行时可以正确地访问解压后内核镜像大小的位置。
.long (input_data_end - 4) - .
get_inflated_image_size 获取解压后的内核大小
/** The kernel build system appends the size of the* decompressed kernel at the end of the compressed data* in little-endian form.* 执行之后res = 解压后的内核大小*/.macro get_inflated_image_size, res:req, tmp1:req, tmp2:req//获取解压后的内核大小偏移量的地址-当前地址adr \res, .Linflated_image_size_offsetldr \tmp1, [\res] //获取解压后的内核大小偏移量到\tmp1add \tmp1, \tmp1, \res @ 解压的图像大小地址 = 偏移量 + 当前地址//通过一系列的 ldrb 和 orr 指令以小端格式读取解压后镜像大小的四个字节。ldrb \res, [\tmp1] @ get_unaligned_le32ldrb \tmp2, [\tmp1, #1]orr \res, \res, \tmp2, lsl #8ldrb \tmp2, [\tmp1, #2]ldrb \tmp1, [\tmp1, #3]orr \res, \res, \tmp2, lsl #16orr \res, \res, \tmp1, lsl #24.endm
- 验证如下:
Image未压缩的,去除调试信息的内核大小为2992000(0x2DA780)字节
-rwxrwxr-x 1 embedsky embedsky 2992000 Mar 2 14:49 Image*
piggy_data的最后四字节转换成小端也为2992000(0x2DA780)字节
hexdump -C arch/arm/boot/compressed/piggy_data | tail -n 2
00190730 10 ca f1 73 80 a7 2d 00
wont_overwrite 修正LC0存储的内容,BSS区域的起始和结束地址,GOT表的起始和结束地址
wont_overwrite:adr r0, LC0ldmia r0, {r1, r2, r3, r11, r12}sub r0, r0, r1 @ 偏移量 = 存储的LC0的地址 - 当前LC0地址/** 如果 delta 为零,我们正在运行链接地址。* r0 = delta* r2 = BSS 起始地址* r3 = BSS 结束地址* r4 = 内核执行地址(可能设置了 LSB)* r5 = 附加的 dtb 大小(如果不存在则为 0)* r7 = 架构 ID* r8 = atags 指针* r11 = GOT 起始地址* r12 = GOT 结束地址* sp = 堆栈指针*/orrs r1, r0, r5 //delta 不为0 或者 附加的 dtb 大小不为0,则不会跳转到 not_relocatedbeq not_relocatedadd r11, r11, r0add r12, r12, r0#ifndef CONFIG_ZBOOT_ROM/** 如果我们完全运行 PIC === CONFIG_ZBOOT_ROM = n,* 我们需要修正指向 BSS 区域的指针。* 请注意,堆栈指针已经被修正。*/add r2, r2, r0add r3, r3, r0/** 重新定位GOT表中的所有条目。将bss条目提升到_edata + dtb大小*/
1: ldr r1, [r11, #0] @ 重新定位 GOT 中的条目add r1, r1, r0 @ 这修复了 C 引用cmp r1, r2 @ if entry >= bss_start &&cmphs r3, r1 @ bss_end > entryaddhi r1, r1, r5 @ entry += dtb sizestr r1, [r11], #4 @ next entrycmp r11, r12 @ if not end of GOTblo 1b @ 跳到1处继续循环/* 也提升我们的 BSS 指针*/add r2, r2, r5 @调整bss指针到dtb 大小后add r3, r3, r5 @调整bss结束指针到dtb 大小后
#endif
not_relocated 重定向已完成执行
- 循环清除 BSS 区域
- 判断是否跳过缓存设置,如果跳过则直接调用cache_on开启缓存
- 传入参数进行解压内核
- r0 = 内核执行地址
- r1 = 堆栈上方的 malloc 空间
- r2 = 表示动态内存分配空间的最大地址
- r3 = 架构 ID
not_relocated: mov r0, #0// 循环清除 BSS 区域
1: str r0, [r2], #4 @ clear bssstr r0, [r2], #4str r0, [r2], #4str r0, [r2], #4cmp r2, r3blo 1b/** 我们之前跳过了缓存设置吗?* 这由 r4 中的 LSB 表示。* 如果是这样,现在就做。*///这`1 使用位置无关执行重定向栈顶地址,并开启缓存页表`中进行判断//设置之后的页表超过了内核映像,则跳过cache_on不进行设置,否则直接进行cache_ontst r4, #1 //将 r4 寄存器的值与立即数1进行按位与操作,并根据结果更新条件标志bic r4, r4, #1 //清除最低有效位blne cache_on //如果 tst 指令的结果表明 r4 的最低有效位为1(即之前跳过了缓存设置)/** 现在应该已经充分设置了 C 运行环境。* 设置一些指针,然后开始解压。* r4 = 内核执行地址* r7 = 架构 ID* r8 = atags 指针*/mov r0, r4mov r1, sp @ 堆栈上方的 malloc 空间add r2, sp, #MALLOC_SIZE @ 64k maxmov r3, r7/** r0 = 内核执行地址* r1 = 堆栈上方的 malloc 空间* r2 = 表示动态内存分配空间的最大地址* r3 = 架构 ID*/bl decompress_kernel/*进入这个宏之后r1 = 解压后的镜像大小*/get_inflated_image_size r1, r2, r3mov r0, r4 @ 解压镜像的开始地址add r1, r1, r0 @ 解压镜像的结束地址bl cache_clean_flushbl cache_offmov r0, r4 @ start of inflated imageadd r1, r1, r0 @ end of inflated imagebl cache_clean_flushbl cache_off#ifdef CONFIG_ARM_VIRT_EXT //ARM 虚拟化#elseb __enter_kernel //跳转进入内核
#endif
__enter_kernel 进入内核
V7M
不执行ARM
,执行THUMB
和M_CLASS
r4
为内核的入口地址
mov r0, #0 @ must be 0mov r1, r7 @ 恢复架构编号mov r2, r8 @ 恢复 Atags 指针ARM( mov pc, r4 ) @ call kernelM_CLASS( add r4, r4, #1 ) @ 在 M 类的 Thumb 模式下进入THUMB( bx r4 ) @ 对于 A/R 类,入口点始终为 ARM