上调试器:生擒黑屏背后的元凶

一篇文章中,我们基于以物为师的思想比较黑屏和正常启动两种情况,核对黑屏故障时的报错信息,确定三条信息中有一条是误导,有两条是真实合理的。经过确认,两种情况使用的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的位置没有错。既然如此,那么第二种可能就成为新的焦点了。这也意味着,这个黑屏问题的根因指向了软件世界的第一大故障根源:内存越界。

3fbcddb30c2ea8734f7f89b52d1855e1.jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值