Arm64下Linux内核Image头的格式
Linux对于Arm64架构,其编译出来的内核默认是不支持压缩的;而对于Arm32版本来说,默认支持内核解压的操作(代码位于arch/arm/boot/compressed目录下,可是arm64目录下没有对应的代码)。如果实在想压缩内核,也可以在bootloader里面解压好后放到内存中指定的位置。struct arm64_image_header {__le32 code0;__l...
Linux对于Arm64架构,其编译出来的内核默认是不支持压缩的;而对于Arm32版本来说,默认支持内核解压的操作(代码位于arch/arm/boot/compressed目录下,可是arm64目录下没有对应的代码)。如果实在想压缩内核,也可以在bootloader里面解压好后放到内存中指定的位置。
每个编译好的内核image文件都有一个头,用来说明这个image所包含的一些信息,头里面所有数据字段都是小端(little-endian)字节序。至于这个头里面的内容,可以通过下面的代码了解到(代码位于arch/arm64/kernel/head.S):
__HEAD
_head:
/*
* DO NOT MODIFY. Image header expected by Linux boot-loaders.
*/
#ifdef CONFIG_EFI
/*
* This add instruction has no meaningful effect except that
* its opcode forms the magic "MZ" signature required by UEFI.
*/
add x13, x18, #0x16
b stext
#else
b stext // branch to kernel start, magic
.long 0 // reserved
#endif
le64sym _kernel_offset_le // Image load offset from start of RAM, little-endian
le64sym _kernel_size_le // Effective size of kernel image, little-endian
le64sym _kernel_flags_le // Informative flags, little-endian
.quad 0 // reserved
.quad 0 // reserved
.quad 0 // reserved
.ascii ARM64_IMAGE_MAGIC // Magic number
#ifdef CONFIG_EFI
.long pe_header - _head // Offset to the PE header.
pe_header:
__EFI_PE_HEADER
#else
.long 0 // reserved
#endif
文件头的前两个字段长度都是4字节,里面的数据其实是两条指令。bootloader在加载完内核后,都会直接会跳转到image的头部,将控制权交给内核。无论是否打开了编译选项CONFIG_EFI,内核其实最终都会跳转到stext指明的段开始的代码处。
文件头开始的第三个字段长度是8字节,是一个偏移量TEXT_OFFSET,指明了对于内核实际加载的位置相对于指定被加载到的位置的偏移。无论是物理地址还是虚拟地址都会有这个偏移量。这个偏移量在中定义:
ifeq ($(CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET), y)
TEXT_OFFSET := $(shell awk "BEGIN {srand(); printf \"0x%06x\n\", \
int(2 * 1024 * 1024 / (2 ^ $(CONFIG_ARM64_PAGE_SHIFT)) * \
rand()) * (2 ^ $(CONFIG_ARM64_PAGE_SHIFT))}")
else
TEXT_OFFSET := 0x00080000
endif
一般情况下TEXT_OFFSET的值为0x00080000。但是,如果加上了编译选项CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET,则内核会随机生成一个偏移量。但这个随机偏移量有一些限制,通过以下代码可以看出(代码位于arch/arm64/kernel/head.S):
#if (TEXT_OFFSET & 0xfff) != 0
#error TEXT_OFFSET must be at least 4KB aligned
#elif (PAGE_OFFSET & 0x1fffff) != 0
#error PAGE_OFFSET must be at least 2MB aligned
#elif TEXT_OFFSET > 0x1fffff
#error TEXT_OFFSET must be less than 2MB
#endif
可以看出,TEXT_OFFSET必须是4K对齐的,而且不能大于2M。所以,这个TEXT_OFFSET并不是为了安全的目的(区别于内核地址空间布局随机,KASLR),因为其范围实在太小了。加上这个TEXT_OFFSET的目的主要还是用来查错,因为很多bootloader可能都不会读image头的这个字段,将image加载到内存的指定位置,而是想当然的就把其放到默认偏移0x00080000上去。如果使用这个随机偏移后,内核加载执行后就会出错,从而暴露出问题。
文件头开始的第四个字段长度是8字节,内容是内核的大小。
文件头开始的第五个字段长度是8字节,是一个flag字段,指明了image的一些信息,虽然一共保留了64位,但目前只使用了最后4位。其中Bit 0指出内核image本身用的是什么字节序,0表示小端字节序,1表示大端字节序。值得注意的是,无论image采用什么字节序,image的头一定是小端字节序的。Bit 1~2表示内核所使用的内存页大小,1是4K,2是16K,3是64K,0表示未指定。Bit 3指明bootloader应该加载image到物理内存的什么位置,0表示应该尽量加载在物理内存的最低端,1表示可以加载到物理内存的任何位置,但都要保证是2M对齐的。
紧接着的三个字段长度都是8字节,并且全都是保留字段,全部设置成0。
文件头开始的第九个字段长度4字节,是一个魔数,其定义如下(代码位于arch/arm64/include/asm/image.h):
#define ARM64_IMAGE_MAGIC "ARM\x64"
文件头的最后一个4字节也是一个偏移,用来指出EFI对应的PE头相对image头的偏移。如果不支持EFI的话,那这个字段就设置成0。从前面的代码中可以看出,如果配置成支持EFI的话,PE的头是直接跟在image头后面的,而image头一共长64字节,所以这个偏移会固定设置成0x40。
下面我们来看看一个实际的例子,笔者编译了一个树莓派5.4内核(代码仓库https://github.com/raspberrypi/linux.git,分支rpi-5.4.y),其头二进制形式如下:
可以看出,其TEXT_OFFSET是0x80000;image的大小是0xFE0000;flag是0x0A,最低位是0,表示image本身是小端字节序,中间两位是01,表示内存页大小是4K,第4位是1,表示bootloader可以把内核镜像加载在物理内存的任何位置。
更多推荐
所有评论(0)