ILD

initramfs解压出现corrupt分析
作者:Yuan Jianpeng 邮箱:yuanjp89@163.com
发布时间:2022-6-8 站点:Inside Linux Development

initramfs开启xz压缩。在启动过程中出现下列日志:

[    2.510114] Initramfs unpacking failed: XZ-compressed data is corrupt

可以启动到rootfs,但是rootfs中的/web/wifi.htm损坏。md5 checksum发现,md5有了变化。怀疑是memory region冲突导致的。


uboot memory region

先研究了下uboot的内存使用区域,打开include/configs/ipq5018.h。


#define CONFIG_SYS_SDRAM_BASE                   0x40000000

物理内存的地址。在ipq5018平台上是0x40000000。


#define CONFIG_SYS_SDRAM_SIZE                   0x10000000

物理内存的大小,这里是256M。


#define CONFIG_SYS_TEXT_BASE                    0x4A920000

uboot链接地址,如果uboot是binary格式,也就是说uboot的第一条指令就是uboot的第一个字节,因此,这个地址也是uboot在ram

中存放的地址。


#define CONFIG_SYS_LOAD_ADDR                    (CONFIG_SYS_SDRAM_BASE + (64 << 20))

系统加载的地址,也就是tftpboot,bootm的默认地址,物理内存基地址加上64M。也就是0x44000000


#define CONFIG_SYS_INIT_SP_ADDR                 (CONFIG_SYS_TEXT_BASE -\

            CONFIG_SYS_MALLOC_LEN - CONFIG_ENV_SIZE -GENERATED_BD_INFO_SIZE)

uboot运行栈的地址。可以看到这里的地址在TEXT_BASE之下了,默认应该在物理内存最顶端往下的。


IPQ5018# bdinfo

arch_number = 0x08040000

boot_params = 0x40000100

DRAM bank   = 0x00000000

-> start    = 0x40000000

-> size     = 0x10000000

eth0name    = eth0

ethaddr     = a:d1:59:71:15:2

eth1name    = eth1

eth1addr    = a:d1:59:71:15:1

current eth = eth1

ip_addr     = 192.168.10.1

baudrate    = 115200 bps

TLB addr    = 0x4A9D0000

relocaddr   = 0x4A920000

reloc off   = 0x00000000

irq_sp      = 0x4A882A90

sp start    = 0x4A882A80


bdinfo查看,sp是0x4A882A80。


reserved memory region

dts中定义了一些保留内存:这些内存是保留个某些模块特殊使用的。


        reserved-memory {

                #address-cells = <2>;

                #size-cells = <2>;

                ranges;


                nss@40000000 {

                        no-map;

                        reg = <0x0 0x40000000 0x0 0x0800000>;

                };


                uboot@4a800000 {

                        no-map;

                        reg = <0x0 0x4a800000 0x0 0x00200000>;

                };


                sbl@4aa00000 {

                        no-map;

                        reg = <0x0 0x4aa00000 0x0 0x00100000>;

                };


可以看到,初识的8M是个nss使用的。高地址内存中,最小的0x4a800000是个uboot保留的使用的,大小2M。


initramfs加载地址


IPQ5018# tftpboot 48000000 ipq50xx_32.itb

Port1 Down Speed :10M Half duplex

Port3 Up Speed :1000M Full duplex

Using eth1 device

TFTP from server 192.168.10.100; our IP address is 192.168.10.1

Filename 'ipq50xx_32.itb'.

Load address: 0x48000000

Loading: *

Got TFTP_OACK: TFTP remote port: changes from 69 to 56089

#################################################################

         #################################################################

         #################################################################

         #################################################################

         #################################################################

         #################################################################

         #################################################################

         #################################################################

         #####################

         8.3 MiB/s

done

Bytes transferred = 7929828 (78ffe4 hex)

IPQ5018# bootm

## Loading kernel from FIT Image at 48000000 ...

   Using 'config-1' configuration

   Trying 'kernel' kernel subimage

     Description:  Linux kernel 5.15.38

     Type:         Kernel Image

     Compression:  uncompressed

     Data Start:   0x480000c8

     Data Size:    2889808 Bytes = 2.8 MiB

     Architecture: ARM

     OS:           Linux

     Load Address: 0x43208000

     Entry Point:  0x43208000

     Hash algo:    sha1

     Hash value:   0401e27bc01853c8c05ab27f0394940653444cff

   Verifying Hash Integrity ... sha1+ OK

## Loading ramdisk from FIT Image at 48000000 ...

   Using 'config-1' configuration

   Trying 'initramfs' ramdisk subimage

     Description:  Compressed Initramfs

     Type:         RAMDisk Image

     Compression:  uncompressed

     Data Start:   0x482cc834

     Data Size:    4993596 Bytes = 4.8 MiB

     Architecture: ARM

     OS:           Linux

     Load Address: 0x44000000

     Entry Point:  0x44000000

     Hash algo:    sha1

     Hash value:   bdb903aee1c5347c31d13d9bc03c89eb765a29f2

   Verifying Hash Integrity ... sha1+ OK

   Loading ramdisk from 0x482cc834 to 0x44000000

## Loading fdt from FIT Image at 48000000 ...

   Using 'config-1' configuration

   Trying 'fdt' fdt subimage

     Description:  Flattened Device Tree blob

     Type:         Flat Device Tree

     Compression:  uncompressed

     Data Start:   0x482c1a08

     Data Size:    44396 Bytes = 43.4 KiB

     Architecture: ARM

     Hash algo:    sha1

     Hash value:   922d5291ba07c72a132d3af738b56b39f62dd094

   Verifying Hash Integrity ... sha1+ OK

   Booting using the fdt blob at 0x482c1a08

   Loading Kernel Image ... OK

   Loading Ramdisk to 4a3be000, end 4a88123c ... OK

   Loading Device Tree to 4a3b0000, end 4a3bdd6b ... OK

Could not find PCI in device tree

Could not find PCI in device tree

Using machid 0x8040000 from environment


可以看到 结尾地址 4a88123c接近栈顶0x4A882A80。差距6212。



查看uboot代码 common/image


int boot_ramdisk_high(struct lmb *lmb, ulong rd_data, ulong rd_len,

                  ulong *initrd_start, ulong *initrd_end)

{

        char    *s;

        ulong   initrd_high;

        int     initrd_copy_to_ram = 1;


        if ((s = getenv("initrd_high")) != NULL) {

                /* a value of "no" or a similar string will act like 0,

                 * turning the "load high" feature off. This is intentional.

                 */

                initrd_high = simple_strtoul(s, NULL, 16);

                if (initrd_high == ~0)

                        initrd_copy_to_ram = 0;

        } else {

                /* not set, no restrictions to load high */

                initrd_high = ~0;

        }


        if (rd_data) {

                if (!initrd_copy_to_ram) {      /* zero-copy ramdisk support */

                        debug("   in-place initrd\n");

                        *initrd_start = rd_data;

                        *initrd_end = rd_data + rd_len;

                        lmb_reserve(lmb, rd_data, rd_len);

                } else {

                        if (initrd_high)

                                *initrd_start = (ulong)lmb_alloc_base(lmb,

                                                rd_len, 0x1000, initrd_high);

                        else

                                *initrd_start = (ulong)lmb_alloc(lmb, rd_len,

                                                                 0x1000);


                        if (*initrd_start == 0) {

                                puts("ramdisk - allocation error\n");

                                goto error;

                        }

                        bootstage_mark(BOOTSTAGE_ID_COPY_RAMDISK);


                        *initrd_end = *initrd_start + rd_len;

                        printf("   Loading Ramdisk to %08lx, end %08lx ... ",

                                        *initrd_start, *initrd_end);


如果



uboot lmb

参考文档2,移植了内核的lmb到uboot。作为启动镜像的内存管理工具。


lmb包括两个类型,

#define MAX_LMB_REGIONS 8


struct lmb_property {

        phys_addr_t base;

        phys_size_t size;

};


struct lmb_region {

        unsigned long cnt;

        phys_size_t size;

        struct lmb_property region[MAX_LMB_REGIONS+1];

};


struct lmb {

        struct lmb_region memory;

        struct lmb_region reserved;

};


memory是总内存区域,reserved是保留内存区域。分配的时候从总内存区域分配,但是不和保留内存区域重叠。


common/bootm.c

设置lmb

static void boot_start_lmb(bootm_headers_t *images)

{

        ulong           mem_start;

        phys_size_t     mem_size;


        lmb_init(&images->lmb);


        mem_start = getenv_bootm_low();

        mem_size = getenv_bootm_size();


        lmb_add(&images->lmb, (phys_addr_t)mem_start, mem_size);


        arch_lmb_reserve(&images->lmb);

        board_lmb_reserve(&images->lmb);

}

bootm_low和bootm_size默认没有定义,会添加整个dram内存区域到lmb。


arch/arm/lib/bootm.c

void arch_lmb_reserve(struct lmb *lmb)

{

        ulong sp;


        /*

         * Booting a (Linux) kernel image

         *

         * Allocate space for command line and board info - the

         * address should be as high as possible within the reach of

         * the kernel (see CONFIG_SYS_BOOTMAPSZ settings), but in unused

         * memory, which means far enough below the current stack

         * pointer.

         */

        sp = get_sp();

        debug("## Current stack ends at 0x%08lx ", sp);


        /* adjust sp by 4K to be safe */

        sp -= 4096;

        lmb_reserve(lmb, sp,

                    gd->bd->bi_dram[0].start + gd->bd->bi_dram[0].size - sp);

}

添加保留区域。可以看到添加sp-4K到dram结尾为保留区域。


分配

__lmb_alloc_base()

从后往前遍历memory region。一个region内部从高往低分配。并确保不和reserved重叠。


zImage内核自解压过程

根据文档1。

内核会解压到TEXT_OFFSET。如果当前的压缩内核区域和解压后的区域重叠,则会将当前的压缩的内核往后移动。

IPQ5018的TEXT_OFFSET定义为:0x01208000。因为将fit镜像加载到0X44000000,和内核的解压地址有40多M的距离,

不会发生重叠。


总结

initramfs默认会移动到lmb的高区。高区是顶端是 sp-4K的位置。且对齐到4K。当代码的运行栈大于4K时,可能会写坏。initramfs。

可以手动设置initrd_high环境变量,使其离sp更远一些。比如sp为0x4A882A80,设置initrd_high为0x4a870000


设置之后initramfs的加载位置降低了。不会被sp写坏:

Loading Ramdisk to 4a3ad000, end 4a86f8bc ... OK


启动后/web/wifi.htm正常。且无Initramfs unpacking failed: XZ-compressed data is corrupt打印。


关于initramfs load address

将initramfs的load address设置为44000000后,出现了Error: ramdisk overwritten打印。原因是fit镜像加载在44000000。出现了重叠。

导致common/image-fit.c 抱怨。实际上,initramfs不需要load address和entry address。initramfs会被拷贝到high。将其设置为0即可。


IPQ5018# run u

Port1 Down Speed :10M Half duplex

Port3 Up Speed :1000M Full duplex

Using eth1 device

TFTP from server 192.168.10.100; our IP address is 192.168.10.1

Filename 'ipq50xx_32.itb'.

Load address: 0x44000000

Loading: *

Got TFTP_OACK: TFTP remote port: changes from 69 to 56069

#################################################################

         #################################################################

         #################################################################

         #################################################################

         #################################################################

         #################################################################

         #################################################################

         #################################################################

         #####################

         8.5 MiB/s

done

Bytes transferred = 7929828 (78ffe4 hex)

IPQ5018# bootm

## Loading kernel from FIT Image at 44000000 ...

   Using 'config-1' configuration

   Trying 'kernel' kernel subimage

     Description:  Linux kernel 5.15.38

     Type:         Kernel Image

     Compression:  uncompressed

     Data Start:   0x440000c8

     Data Size:    2889808 Bytes = 2.8 MiB

     Architecture: ARM

     OS:           Linux

     Load Address: 0x43208000

     Entry Point:  0x43208000

     Hash algo:    sha1

     Hash value:   0401e27bc01853c8c05ab27f0394940653444cff

   Verifying Hash Integrity ... sha1+ OK

## Loading ramdisk from FIT Image at 44000000 ...

   Using 'config-1' configuration

   Trying 'initramfs' ramdisk subimage

     Description:  Compressed Initramfs

     Type:         RAMDisk Image

     Compression:  uncompressed

     Data Start:   0x442cc834

     Data Size:    4993596 Bytes = 4.8 MiB

     Architecture: ARM

     OS:           Linux

     Load Address: 0x44000000

     Entry Point:  0x44000000

     Hash algo:    sha1

     Hash value:   bdb903aee1c5347c31d13d9bc03c89eb765a29f2

   Verifying Hash Integrity ... sha1+ OK

Error: ramdisk overwritten

Ramdisk image is corrupt or invalid



参考:

【1】

linusw. How the ARM32 Linux kernel decompresses. 2020/8/13.

https://people.kernel.org/linusw/how-the-arm32-linux-kernel-decompresses


【2】

Introduce lmb from linux kernel for memory mgmt of boot images

https://lists.denx.de/pipermail/u-boot/2008-February/030053.html


Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.