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

来源:漫长当下 嵌入式技术 19 次阅读
摘要:bootm的镜像加载地址与uImage镜像的加载地址、入口地址之间的关系 分析的U-Boot源码版本为2021.07: 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 CROS

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

分析的U-Boot源码版本为2021.07

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的加载地址来进行验证:

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

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)

示例:

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:

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:

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:

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;
    ...
    } elseif (images.legacy_hdr_valid) {
  images.ep = image_get_ep(&images.legacy_hdr_os_copy); //拿到入口地址(mkimage时-e参数指定的地址)

common/bootm.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:

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:

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:

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格式为:

|    64byte   |  128byte    |           |
|-------------|-------------|-----------|
| header      |   offset    |   Image   |
|-------------|-------------|-----------|

主要看image_decomp函数中loadimage_start的值:

  • 地址相同:(load == image_start
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

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

0x403fffc0    0x40400000    0x40400080
     |-------------|-------------|-----------|
     | header      |   offset    |   Image   |
     |-------------|-------------|-----------|

U-Boot log:

## 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
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位置(相当于去掉了头);

0x40400000    0x40400040    0x404000c0
     |-------------|-------------|-----------|
     | header      |   offset    |   Image   |
     |-------------|-------------|-----------|
                      ||
                      ||
                      \/
0x40400000    0x40400080
     |-------------|-------------|
     | offset      |   Image     |
     |-------------|-------------|

U-Boot log:

## 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指定的地址的
评论区

登录后即可参与讨论

立即登录