bootm的镜像加载地址与uImage镜像的加载地址、入口地址之间的关系

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-Bootbootm的流程:

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时镜像加载到内存的地址 + 64sizeof(struct image_header):uImage的头)。

那么就有两种情况,示例:

以下测试场景中,都是在Image镜像前加了128字节的元数据信息,所以最终生成的uImage格式为:

bash 复制代码
|    64byte   |  128byte    |           |
|-------------|-------------|-----------|
| header      |   offset    |   Image   |
|-------------|-------------|-----------|

主要看image_decomp函数中loadimage_start的值:

  • 地址相同:(load == image_start

    bash 复制代码
    mkimage -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

    那么镜像不会被移动,加载到内存的哪儿就是哪儿;

    bash 复制代码
    0x403fffc0    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

    bash 复制代码
    mkimage -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位置(相当于去掉了头);

    bash 复制代码
    0x40400000    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指定的地址的
相关推荐
不知疲倦的仄仄8 小时前
第四天:Netty 核心原理深度解析&EventLoop、Future/Promise 与 Pipeline
linux·服务器·网络
橘颂TA8 小时前
【Linux 网络编程】网络是怎么 “跑” 起来的?从协议入门到 TCP/ IP 模型的底层逻辑
linux·运维·服务器·网络
looking_for__8 小时前
【Linux】进程间通信
linux
oMcLin8 小时前
如何在 Oracle Linux 8.3 上通过配置 LVM 与 RAID 结合,提升存储系统的性能与数据冗余性
linux·数据库·oracle
cuijiecheng20188 小时前
Linux控制台下git使用图形化界面进行文件对比
linux·运维·git
春日见8 小时前
控制算法:PID算法
linux·运维·服务器·人工智能·驱动开发·算法·机器人
JiMoKuangXiangQu8 小时前
Linux eBPF 虚拟机简析
linux·ebpf 虚拟机
EndingCoder8 小时前
接口基础:定义对象形状
linux·运维·前端·javascript·typescript
遇见火星9 小时前
Linux 运维:删除大日志文件时避免磁盘 IO 飙升,echo 空文件 vs truncate 命令对比实操
linux·运维·服务器