bootm的镜像加载地址与uImage镜像的加载地址、入口地址之间的关系
分析的U-Boot源码版本为2021.07:
bash
wget ftp://ftp.denx.de/pub/u-boot/u-boot-2021.07.tar.bz2
make ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- clean distclean
make ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- qemu_arm64_defconfig
make ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- menuconfig
make ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- -j64
qemu测试,通过修改uImage的加载地址来进行验证:
bash
qemu-system-aarch64 \
-machine virt,gic_version=3,virtualization=on \
-cpu cortex-a710 \
-m size=4G \
-smp cpus=4 \
-nographic \
-bios u-boot.bin \
-device loader,file=uImage,force-raw=on,addr=0x40400000 \
-dtb virt.gicv3.dtb
bootm 0x40400000 - 0x40000000
使用mkimage生成uImage:
bash
Usage: mkimage -l image
-l ==> list image header information
mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
-A ==> set architecture to 'arch'
-O ==> set operating system to 'os'
-T ==> set image type to 'type'
-C ==> set compression type 'comp'
-a ==> set load address to 'addr' (hex)
-e ==> set entry point to 'ep' (hex)
-n ==> set image name to 'name'
-d ==> use image data from 'datafile'
-x ==> set XIP (execute in place)
示例:
bash
mkimage -A arm64 -O linux -T kernel -C none -a 40400000 -e 40400080 -n linux -d Image uImage
其中关于-a参数指定的加载地址与-e参数指定的入口地址的关系,网上的文章搜出来都是在乱说,根本没经过自己验证。
先来分析下U-Boot中bootm的流程:
cmd/bootm.c:
c
int do_bootm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |
BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
BOOTM_STATE_LOADOS |
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
BOOTM_STATE_RAMDISK |
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_MIPS)
BOOTM_STATE_OS_CMDLINE |
#endif
BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
BOOTM_STATE_OS_GO, &images, 1);
common/bootm.c:
c
int do_bootm_states(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[], int states, bootm_headers_t *images,
int boot_progress)
{
if (!ret && (states & BOOTM_STATE_FINDOS))
ret = bootm_find_os(cmdtp, flag, argc, argv); // 查找镜像
...
if (!ret && (states & BOOTM_STATE_LOADOS)) {
iflag = bootm_disable_interrupts();
ret = bootm_load_os(images, 0); // 加载镜像
...
boot_fn = bootm_os_get_boot_func(images->os.os); // 根据镜像类型,选择对应的启动函数
...
ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images); // 跳转到内核
common/bootm.c:
c
static int bootm_find_os(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
/* get kernel image header, start address and length */
os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,
&images, &images.os.image_start, &images.os.image_len); // image_start: 拿到镜像加载到内存的起始地址(跳过了header)
...
case IMAGE_FORMAT_LEGACY:
images.os.type = image_get_type(os_hdr);
images.os.comp = image_get_comp(os_hdr);
images.os.os = image_get_os(os_hdr);
images.os.end = image_get_image_end(os_hdr);
images.os.load = image_get_load(os_hdr); // 拿到镜像的加载地址(mkimage时-a参数指定的地址)
images.os.arch = image_get_arch(os_hdr);
break;
...
} else if (images.legacy_hdr_valid) {
images.ep = image_get_ep(&images.legacy_hdr_os_copy); //拿到入口地址(mkimage时-e参数指定的地址)
common/bootm.c:
c
static const void *boot_get_kernel(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[], bootm_headers_t *images,
ulong *os_data, ulong *os_len)
{
img_addr = genimg_get_kernel_addr_fit(argc < 1 ? NULL : argv[0],
&fit_uname_config,
&fit_uname_kernel); // 拿到镜像的加载到内存的地址(包含header)
...
case IMAGE_FORMAT_LEGACY:
printf("## Booting kernel from Legacy Image at %08lx ...\n",
img_addr);
hdr = image_get_kernel(img_addr, images->verify);
...
case IH_TYPE_KERNEL_NOLOAD:
*os_data = image_get_data(hdr); // 拿到镜像加载到内存的起始地址(跳过了header)
*os_len = image_get_data_size(hdr);
break;
...
/* save pointer to image header */
images->legacy_hdr_os = hdr;
images->legacy_hdr_valid = 1;
common/image.c:
c
ulong genimg_get_kernel_addr_fit(char * const img_addr,
const char **fit_uname_config,
const char **fit_uname_kernel)
{
...
} else {
kernel_addr = simple_strtoul(img_addr, NULL, 16); // 执行bootm时传入的kernel地址
debug("* kernel: cmdline image address = 0x%08lx\n",
kernel_addr);
}
return kernel_addr;
common/bootm.c:
c
static int bootm_load_os(bootm_headers_t *images, int boot_progress)
{
err = image_decomp(os.comp, load, os.image_start, os.type,
load_buf, image_buf, image_len,
CONFIG_SYS_BOOTM_LEN, &load_end);
common/image.c:
c
int image_decomp(int comp, ulong load, ulong image_start, int type,
void *load_buf, void *image_buf, ulong image_len,
uint unc_len, ulong *load_end)
{
debug("%s 0x%lx 0x%lx 0x%lx 0x%x\n", __func__, load, image_start, image_len, unc_len);
switch (comp) {
case IH_COMP_NONE:
// load:mkimage时-a参数指定的加载地址
// image_start:bootm传入的镜像加载到的内存地址 + 64(sizeof(struct image_header):uImage的头)
if (load == image_start)
break;
printf("%s %d\n", __func__, __LINE__);
if (image_len <= unc_len)
memmove_wd(load_buf, image_buf, image_len, CHUNKSZ);
else
ret = -ENOSPC;
break;
从image_decomp函数中可得知,U-Boot对比的是mkimage时-a参数指定的加载地址与bootm时镜像加载到内存的地址 + 64(sizeof(struct image_header):uImage的头)。
那么就有两种情况,示例:
以下测试场景中,都是在Image镜像前加了128字节的元数据信息,所以最终生成的uImage格式为:
bash
| 64byte | 128byte | |
|-------------|-------------|-----------|
| header | offset | Image |
|-------------|-------------|-----------|
主要看image_decomp函数中load和image_start的值:
-
地址相同:(
load==image_start)bashmkimage -A arm64 -O linux -T kernel -C none -a 40400000 -e 40400080 -n linux -d Image uImage bootm 0x403fffc0 - - load = 0x40400000 image_start = 0x403fffc0 + 0x40 = 0x40400000 ep = 0x40400080那么镜像不会被移动,加载到内存的哪儿就是哪儿;
bash0x403fffc0 0x40400000 0x40400080 |-------------|-------------|-----------| | header | offset | Image | |-------------|-------------|-----------|U-Boot log:bash## Booting kernel from Legacy Image at 403fffc0 ... ... image_decomp 0x40400000 0x40400000 0x15a688 0x4000000 // 地址相同,不会移动 kernel loaded at 0x40400000, end = 0x4055a688 ## initrd_high = 0xffffffff, copy_to_ram = 1 ramdisk load start = 0x00000000, ramdisk load end = 0x00000000 using: FDT Loading Device Tree to 00000000ffff6000, end 00000000fffffd1f ... OK ## Transferring control to Linux (at address 40400080)... // 跳转入口 Starting kernel ... -
地址不同:(
load!=image_start)bashmkimage -A arm64 -O linux -T kernel -C none -a 40400000 -e 40400080 -n linux -d Image uImage bootm 0x40400000 - - load = 0x40400000 image_start = 0x40400000 + 0x40 = 0x40400040 ep = 0x40400080那么镜像会被移动,会从
image_start位置移动到load位置(相当于去掉了头);bash0x40400000 0x40400040 0x404000c0 |-------------|-------------|-----------| | header | offset | Image | |-------------|-------------|-----------| || || \/ 0x40400000 0x40400080 |-------------|-------------| | offset | Image | |-------------|-------------|U-Boot log:bash## Booting kernel from Legacy Image at 40400000 ... ... image_decomp 0x40400000 0x40400040 0x15a688 0x4000000 // 地址不同,会被移动 image_decomp 462 kernel loaded at 0x40400000, end = 0x4055a688 images.os.start = 0x40400000, images.os.end = 0x4055a6c8 images.os.load = 0x40400000, load_end = 0x4055a688 ## initrd_high = 0xffffffff, copy_to_ram = 1 ramdisk load start = 0x00000000, ramdisk load end = 0x00000000 using: FDT Loading Device Tree to 00000000ffff6000, end 00000000fffffd1f ... OK ## Transferring control to Linux (at address 40400080)... // 跳转入口 Starting kernel ...
综上可得出的结论:
mkimage中-a和-e参数指定的地址之间可以说是没有半毛钱关系-e参数的作用,在某些场景,比如真正要执行的镜像前面需要加一些元数据时,这个时候镜像的执行地址是要跳过元数据的,那么就通过-e参数来进行偏移(即上图的offset值);大部分情况基本上-a指定的地址都是等于-e指定的地址的