首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一次 Linux 根文件系统挂载异常的 Debug

一次 Linux 根文件系统挂载异常的 Debug

作者头像
HackforFun
发布2020-01-13 15:12:50
2.9K0
发布2020-01-13 15:12:50
举报
文章被收录于专栏:HackforFunHackforFunHackforFun

前段时间接到一个兄弟 Team 反馈,发现在他们设计的一批板子跑一个他们开发的工程(为了后面描述方便,我们简称 SLT 工程)的时候,很容易出现文件系统挂载失败或者文件系统被损坏的情况,说排查了很久没有头绪,希望能协助支援下。找到负责追踪该问题的工程师了解到大概情况如下:

  1. 启动几次后发现 rootfs 里面部分文件永久损坏,需要重新烧写 rootfs 才能恢复。
  2. 概率发现挂载 rootfs 后无法进入命令行, 系统一直卡着,敲键盘串口控制台可以朝下滑动,但是就是进不了 shell,在 fiq debug 模式下 ps 看到 shell 进程已经启动,但是用 sysrq t 命令查询发现 shell 启动后很快就退出了,所以我们 ps 看到的 shell 进程应该是在循环重启。怀疑 shell 执行文件被损坏了。
  3. 挂载 rootfs 的时候概率发现部分 so 库报错。应该也是 so 文件被损坏,就像下面这样:

另外,负责 debug 的同事说现象很发散,开一些模块、关一些模块、甚至某些程序延时增减一下,现象可能就消失了。

因为这块板子刚回来的时候发现过 eMMC 异常导致系统无法启动,所以我对这块板子的稳定性不太有信心,让负责 Debug 的同事把这套软件移植到 EVB 板子上做对比测试,结果在 EVB 板子上也测到了类似的现象。对硬件的怀疑随之排除。怀疑是软件上有冲内存的行为:文件被读到内存后,其他模块又写了这块内存,导致正常的文件数据被覆盖了。

基于软件冲内存的想法,我们打开了内核里面各种 memory 检测接口,包括 Slub Debug,KASAN, 没检测到异常。

继续做各种对比实验,包括把把 SLT 工程移植到我们的主线 develop 分支上,结果在 develop 分支上运行正常,然后让负责 Debug 的同事去排查 develop 分支和他们自己分支的差异。

于此同时,我找了一块 EVB 板子,编译了 SLT 内核烧进去,测了很久没有复现到文件系统异常。然后找 SLT 同事 check,确认了 内核之外的各个其他模块(包括 DDR 初始化程序版本, ATF 版本,u-boot 版本),最后发现只有我手上这块板子上用的 ATF 版本不会出问题。

这里解释一下:这颗板子的主控芯片是一颗 Arm64 Cortex A35,启动流程如下:

DDR Init、U-Boot、Kernel 这些模块大家看名字就知道是什么意思,这里就不多解释。

SPL 是一段 DDR 初始化完成后在 DDR 中运行的代码,它负责把 ATF 和 U-Boot 从存储设备(eMMC/NAND)中加载到 DDR 中,然后跳到 ATF 开始执行。

ATF 全称 Arm Trust Firmware,是 Armv8 A 芯片运行必须的一个模块,它负责 CPU 多核启动,以及把 CPU 从安全的 EL3 切换到 EL2,再到 EL1. 这些都是 Armv8 A 的基础概念,这里不做详细解释。

排查此版本 ATF 和其他版本的差异,发现不会出问题的 ATF 把 DDR 前 2M 空间都 reserve了,其他版本的 ATF 只会 reserved DDR 64 KB ~~2M 这段空间。Reserved 空间保留给 ATF 自己用,Linux Kernel 看不到这片空间,内核里面通过正常的软件接口(比如 kmalloc)分配不到这段空间的内存,也就访问不到这片空间。

所以现在的现象是:只要Linux Kernel 看到 DDR 前 64 KB的空间后,文件系统挂载就异常了。

排查 DDR 前 64 KB 空间被分配到什么地方了:在 __alloc_pages_nodemask 函数中加打印拦截,发现正是文件系统那边在访问这片空间。和前面的推测比较接近了,下面要找的是谁在文件系统访问后又冲了这片地址。

把这 64 KB 空间从 Linux Kernel Reserved 掉(这个只要在 dts 里面加一个 reserved-memory 节点就可以做到),这样内核里面正常的软件(包括文件系统)也就申请不到这片内存空间了,然后在 U-Boot 阶段把这段空间全部写成0xff,进入内核 console后,通过 io 命令读取这片空间,发现从 0 地址开始的几十个字节已经被改写了。果真有异常的势力在背后改写这片内存!

上 DS-5 Debug 工具,我希望通过 DS-5 的 watch point 功能能监测到是哪个模块在访问这片地址,结果很令人失望,DS-5 没监测到,但是这段地址确实被修改了!咨询 Arm 的工程师,得到的回复是 DS-5 只能监测 CPU 对内存的修改,如果是其他 master 去改写这段内存, DS-5 也无能为力。

这里提到的其他 master 提醒了我,比如 DMA,这些模块是绕过 CPU,直接通过物理地址访问内存的,只要给它一个地址,根本就不需要通过内存管理途径申请(malloc)内存,它就可以直接读写。

开始排查带有 DMA 功能的 master, 用排除法一个个的去关闭相关的模块,结果发现把 crypto 引擎关闭后,这片地址就安静了,没有人去改写了。终于找到了真凶!

查阅 crypto 的 datasheet,发现该引擎中确实有一个 LLI DMA,软件按照一定的结构,设定源地址、目的地址后,就可以做对应的数据搬运。

排查 crypto 的代码,发现里面有一个 虚拟地址到物理地址转换的函数,奇葩的是这个函数在转换失败的时候直接返回了 0 地址,而且没有做任何打印预警。就是这个 0 地址直接被配置给了 DMA,然后它就把这段空间给冲了!

这也解释了为什么前面的现象看起来很发散:因为只有在 rootfs 中的文件被加载到这段空间之后,crypto 接着开始运行,才会凑巧覆盖 rootfs 中的文件。crypto 程序如果运行的比较早就不会覆盖 rootfs 中的文件,如果运行的更晚,可能覆盖的文件没有那么关键,文件系统挂载也不会异常。所以负责 SLT 的同事发现开关某些驱动或者某些程序延时加减一下,现象就变了,因为这些都会影响 crypto 程序启动的时间点。因为是 DMA 直接篡改了这段内存,所以打开内核中的Slub debug、KASAN 这些内存检测机制也检测不到,甚至连 DS-5 这种大杀器都无能为力。

回想起来,这次问题能被定位到有很大的运气成分在里面:无意间找到了一块运行正常的板子,然后以这个为突破口发现了是 DDR 前 64 KB 的空间被异常篡改了,然后才有了后面的顺藤摸瓜。

但是从这次 Debug 的流程我也想到了一些 Debug 类似问题的切入点或者说是经验:

  • 尽量根据现象去设计尽可能多的实验,去排除或者证明自己推测,因为实验做的多,也有可能发现新的突破口,这次就是因为多找了一块板子,发现了前 64 KB 内存的差异。
  • 对于这种冲内存的Bug, 如果能尽快确认是哪段内存被冲了,然后针对这块内存上各种监控测试手段,对后面的Debug 将会很有帮助。本次Debug 后面的所有实验都是基于这块已知的内存去做各种监控,排查的。
  • 对比法、排除法在针对这种无法正面 Debug 的问题时候很有用,文件系统是很复杂的,从正面 Debug 很难。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 HackforFun 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档