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指定的地址的
相关推荐
C++ 老炮儿的技术栈7 小时前
GCC编译时无法向/tmp 目录写入临时汇编文件,因为设备空间不足,解决
linux·运维·开发语言·汇编·c++·git·qt
爱莉希雅&&&8 小时前
linux中MySQL数据库备份恢复的四种方法(更新中)
linux·数据库·mysql·数据库备份·mysqldumper
coppher8 小时前
Ubuntu 22.04 amd64 离线安装 Docker 完整教程
linux·docker
xyz5998 小时前
如何在 WSL 中删除指定版本的 Ubuntu 以及安装
linux·运维·ubuntu
亚空间仓鼠9 小时前
OpenEuler系统常用服务(五)
linux·运维·服务器·网络
minji...10 小时前
Linux 线程同步与互斥(二) 线程同步,条件变量,pthread_cond_init/wait/signal/broadcast
linux·运维·开发语言·jvm·数据结构·c++
虚伪的空想家10 小时前
k8s集群configmap和secrets备份脚本
linux·容器·kubernetes
the sun3410 小时前
从 QEMU 直接启动到 U-Boot 引导:嵌入式 Linux 启动流程的本质差异
linux·运维·服务器
草莓熊Lotso10 小时前
【Linux 线程进阶】进程 vs 线程资源划分 + 线程控制全详解
java·linux·运维·服务器·数据库·c++·mysql
ShineWinsu10 小时前
对于Linux:文件操作以及文件IO的解析
linux·c++·面试·笔试·io·shell·文件操作