当前位置: 首页 > news >正文

[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

解压过程

  1. 依据arch/arm/kernel/vmlinux.lds 生成linux内核源码根目录下的vmlinux,这个vmlinux属于未压缩,带调试信息、符号表的最初的内核,大小约23MB;
  2. 将上面的vmlinux去除调试信息、注释、符号表等内容,生成arch/arm/boot/Image,这是不带多余信息的linux内核,Image的大小约3.2MB
  3. 将 arch/arm/boot/Image 用gzip -9 压缩生成arch/arm/boot/compressed/piggy.gz大小约1.5MB;
  4. 编译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;
  5. 依据arch/arm/boot/compressed/vmlinux.lds 将arch/arm/boot/compressed/目录下的文件head.o 、piggy.o 、misc.o链接生成 arch/arm/boot/compressed/vmlinux,这个vmlinux是经过压缩且含有自解压代码的内核,大小约1.5MB;
  6. 将arch/arm/boot/compressed/vmlinux去除调试信息、注释、符号表等内容,生成arch/arm/boot/zImage大小约1.5MB;这已经是一个可以使用的linux内核映像文件了;
  7. 将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

  1. 生成vmlinux时一同生成piggy_data
  2. piggy_data包含了内核的压缩数据,压缩算法由 CONFIG_KERNEL_GZIP等决定
  3. 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中检查内存是否可用,返回可用内存起始地址

  1. 检查传入的 R1,即 mov r8, r2 @ save atags pointer传入的FDT指针是否为空
  2. 32位地址空间,检查 #address-cells#size-cells是否大于2
  3. 检查 /chosen节点下是否存在 linux,usable-memory-range属性,是否有可用内存限制
  4. 检查 device_typememory的节点,是否存在 linux,usable-memoryreg属性
  5. 获取内存的起始地址和大小,检查是否在可用内存范围内
  6. 如果在可用内存范围内,则返回传入的 mem_start;否则更新可用内存的起始地址使用最小的可用内存
  7. 如果没有找到可用内存,则使用传入的 mem_start作为返回值
  8. 返回值:要使用的物理内存起始地址
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

  1. 根据所选的解压算法,包含不同的解压算法.同时只能选择一个解压算法
  2. 注意定义了STATICSTATIC_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 保存着内核的基地址和数据段结束地址

  1. 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 使用位置无关执行重定向栈顶地址,并开启缓存页表

  1. 对齐128MB
  2. 修正sp栈顶指针
  3. 通过 fdt_check_mem_start检查内存是否可用,并返回可用内存起始地址
  4. 确定最终的内核映像地址
  5. 判断页表是否会覆盖内核映像.(在内核映像地址范围下的16kb页表与1mb的DTB)
  6. 设置之后的页表超过了内核映像,则跳过cache_on不进行设置.r4的LSB位表示
    • 如果r4的LSB位为1,则跳过cache_on
    • 如果r4的LSB位为0,则调用cache_on开启缓存
  7. 否则调用 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 开启缓存

  1. 32字节对齐,传入8,调用 call_cache_fn,然后返回
  2. 这段代码的目的是开启缓存(cache)。为了开启指令缓存(I cache)和数据缓存(D cache),需要设置一些页表。页表被放置在内核执行地址向下16KB的位置,希望这个位置没有被其他东西使用。如果被使用了,可能会导致程序崩溃。
  3. 在进入这个例程时:
    • r4 寄存器包含内核执行地址
    • r7 寄存器包含架构编号
    • r8 寄存器包含 ATAGs 指针
  4. 在退出这个例程时,以下寄存器的值可能会被破坏:
    • r0, r1, r2, r3, r9, r10, r12
  5. 这个例程必须保留 r4, r7, r8 的值。
		.align	5
cache_on:	mov	r3, #8			@ cache_on functionb	call_cache_fn

call_cache_fn 获取处理器ID

  1. r12设置为 proc_types的地址
  2. 获取处理器ID
  3. 根据处理器ID跳转到不同的函数
  4. 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 重定向并获取解压后的内核大小

  1. 重定向栈顶地址
  2. 重定向数据段结束地址
  3. 调用 get_inflated_image_size获取解压后的内核大小
  4. 分配64k的内存空间用于malloc
    #arch/arm/boot/compressed/Makefile
    MALLOC_SIZE	:= 65536
    AFLAGS_head.o += -DTEXT_OFFSET=$(TEXT_OFFSET) -DMALLOC_SIZE=$(MALLOC_SIZE)
    
  5. 检查页表是否会覆盖内核映像
  6. 不会则跳转到 wont_overwrite函数
  7. 会则修正内核映像地址,拷贝内核到新的内存区域,重定向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 重定向已完成执行

  1. 循环清除 BSS 区域
  2. 判断是否跳过缓存设置,如果跳过则直接调用cache_on开启缓存
  3. 传入参数进行解压内核
    • 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 进入内核

  1. V7M不执行ARM,执行THUMBM_CLASS
  2. 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
http://www.lryc.cn/news/611166.html

相关文章:

  • Android 之 Kotlin 和 MVVM 架构的 Android 登录示例
  • 腾讯云对象存储服务COS
  • QtPromise第三方库的介绍和使用
  • 人工智能领域、图欧科技、IMYAI智能助手2025年1月更新月报
  • ubuntu24中部署k8s 1.30.x-底层用docker
  • 相机拍摄的DNG格式照片日期如何修改?你可以用这款工具修改
  • Android异常信号处理详解
  • 【网络运维】Linux:系统启动原理与配置
  • Coze开源了!意味着什么?
  • 在Linux上部署RabbitMQ、Redis、ElasticSearch
  • 无监督学习聚类方法——K-means 聚类及应用
  • NFS CENTOS系统 安装配置
  • 走进“Mesh无线自组网”:开启智能家居和智慧工厂
  • 安科瑞智慧能源管理系统在啤酒厂5MW分布式光伏防逆流控制实践
  • uv与conda环境冲突,无法使用uv环境,安装包之后出现ModuleNotFoundError: No module named ‘xxx‘等解决方法
  • unity之 贴图很暗怎么办
  • 【STM32】HAL库中的实现(四):RTC (实时时钟)
  • python的教务管理系统
  • 江协科技STM32学习笔记1
  • Spring 的依赖注入DI是什么?
  • 【计算机网络】6应用层
  • PostgreSQL——函数
  • 【语音技术】什么是VAD
  • Windows 电脑远程访问,ZeroTier 实现内网穿透完整指南(含原理讲解)
  • NLP自然语言处理 03 Transformer架构
  • 人工智能-python-Sklearn 数据加载与处理实战
  • ChatGPT以及ChatGPT强化学习步骤
  • MLIR Bufferization
  • Linux驱动学习(八)设备树
  • 《手撕设计模式》系列导学目录