在上一篇文章中,我们基于以物为师的思想比较黑屏和正常启动两种情况,核对黑屏故障时的报错信息,确定三条信息中有一条是误导,有两条是真实合理的。经过确认,两种情况使用的NITRAMFS镜像是同一个。
哪里来的李鬼?
既然用的是一个镜像,是哪里导致的不同呢?要探索这个问题,需要梳理一下INITRAMFS的加载和使用过程。
INITRAMFS镜像是在u-boot阶段加载到内存中的,它的加载地址通过设备树的chosen参数传给Linux内核。Linux内核在完成内核空间初始化后,开始着手用户空间的建设,也就是加载根文件系统和运行上面的软件。
在启动失败的情况,在initramfs命令行中观察内核消息,搜索initram,可以找到如下两条信息:
(initramfs) dmesg | grep initram
[ 18.251448] Trying to unpack rootfs image as initramfs...
[ 18.378570] rootfs image is not initramfs (ZSTD-compressed data is corrupt); looks like an initrd
第一条信息来自init/initramfs.c中的populate_rootfs函数。
static int __init populate_rootfs(void)
{
/* Load the built in initramfs */
char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);
if (err)
panic("%s", err); /* Failed to decompress INTERNAL initramfs */
if (!initrd_start || IS_ENABLED(CONFIG_INITRAMFS_FORCE))
goto done;
if (IS_ENABLED(CONFIG_BLK_DEV_RAM))
printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n");
else
printk(KERN_INFO "Unpacking initramfs...\n");
err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start);
if (err) {
#ifdef CONFIG_BLK_DEV_RAM
populate_initrd_image(err);
#else
printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err);
#endif
}
done:
/*
* If the initrd region is overlapped with crashkernel reserved region,
* free only memory that is not part of crashkernel region.
*/
if (!do_retain_initrd && initrd_start && !kexec_free_initrd())
free_initrd_mem(initrd_start, initrd_end);
initrd_start = 0;
initrd_end = 0;
flush_delayed_fput();
return 0;
}
值得特别注意的是,上面函数中有两次调用unpack_to_rootfs()。上面的第二条信息来自第二次调用失败后的populate_initrd_image()调用,也就是第19行。
populate_initrd_image()函数中会再次调用unpack_to_rootfs(),打印信息,然后再尝试使用initrd.image。
static void __init populate_initrd_image(char *err)
{
ssize_t written;
struct file *file;
loff_t pos = 0;
unpack_to_rootfs(__initramfs_start, __initramfs_size);
printk(KERN_INFO "rootfs image is not initramfs (%s); looks like an initrd\n",
err);
file = filp_open("/initrd.image", O_WRONLY | O_CREAT, 0700);
if (IS_ERR(file))
return;
第二条信息中的括号部分来自内核lib目录下的decompress_unzstd.c。
static int INIT handle_zstd_error(size_t ret, void (*error)(char *x))
{
const int err = ZSTD_getErrorCode(ret);
if (!ZSTD_isError(ret))
return 0;
switch (err) {
case ZSTD_error_memory_allocation:
error("ZSTD decompressor ran out of memory");
break;
case ZSTD_error_prefix_unknown:
error("Input is not in the ZSTD format (wrong magic bytes)");
break;
case ZSTD_error_dstSize_tooSmall:
case ZSTD_error_corruption_detected:
case ZSTD_error_checksum_wrong:
error("ZSTD-compressed data is corrupt");
break;
default:
error("ZSTD-compressed data is probably corrupt");
break;
}
return -1;
}
根据多年的调试经验,第二条信息括号里的ZSTD-compressed data is corrupt性质严重,值得重视。
数据损坏为哪般?
深入思考,内核报告“数据损坏”可能有两个原因,一种情况是数据的位置搞错了,比如内核用的initramfs起始地址和u-boot实际放的不一样。另一种情况是位置没错,但是数据被破坏了。
对于第一种情况,我特别核对了/proc下的设备树属性:
geduer@ulan:/proc/device-tree/chosen$ hexdump linux,initrd-start
0000000 0000 0000 11eb 0030
0000008
geduer@ulan:/proc/device-tree/chosen$ hexdump linux,initrd-end
0000000 0000 0000 9ceb 9ee9
0000008
上面是启动成功时的情况,其实地址为0xeb113000。启动失败时,起始地址高一个页,0xeb114000。
root@ulan:/proc/device-tree/chosen# hexdump linux,initrd-start
0000000 0000 0000 11eb 0040
0000008
root@ulan:/proc/device-tree/chosen# hexdump linux,initrd-end
0000000 0000 0000 9ceb 9ef9
0000008
两种情况下的数据大小都是8bb99e,也就是9157022。这在串口输出信息中也有印证:
n: [load nvme 0 ${initrd_addr} initrd.img-5.10.110-rockchip-rk3588]
reading initrd.img-5.10.110-rockchip-rk3588
9157022 bytes read in 9 ms (970.3 MiB/s)
will run command [load nvme 0 ${fdt_addr_r} /dtb/rockchip/yourland.dtb]
reading /dtb/rockchip/yourland.dtb
165739 bytes read in 1 ms (158.1 MiB/s)
will run boot command
boot cmd: [booti ${loadaddr} ${initrd_addr}:8bb99e ${fdt_addr_r}]
比较内存
经过一番核对,initramfs的位置没有错。既然如此,那么第二种可能就成为新的焦点了。这也意味着,这个黑屏问题的根因指向了软件世界的第一大故障根源:内存越界。

902

被折叠的 条评论
为什么被折叠?



