Posted in

开局一张图,debug 全靠瞪|内核问题定位与静态分析实战_AI阅读总结 — 包阅AI

包阅导读总结

1. 内核崩溃、AnolisOS 8.8、静态分析、debug、crash原因

2. 本文讲述在 AnolisOS 8.8 操作系统上,对内核崩溃问题进行静态分析 debug 的过程,最终找出原因并解决。

3.

– 问题背景

– 用户机器安装 AnolisOS 8.8 系统镜像过程中内核崩溃,无 coredump,只有屏幕打印信息。

– 调试过程

– 确定内核版本为 5.10.134-13。

– 分析汇编代码,确认问题出在 resctrl_mon_resource_init 函数中的循环结构结束处。

– 找到 dom_data_init 函数中 kcalloc 返回不合法值导致后续访问内存时 panic。

– 发现是客户机器 cpu 不支持 CQM_LLC 导致。

– 问题解决

– 出验证内核,用户验证问题解决。指出完整解决涉及 resctrl 整体 init 逻辑,是历史遗留问题。

思维导图:

文章地址:https://mp.weixin.qq.com/s/cAqTC_3gKd6nuy__-kdAyw

文章来源:mp.weixin.qq.com

作者:库恩

发布时间:2024/8/7 9:28

语言:中文

总字数:2386字

预计阅读时间:10分钟

评分:84分

标签:内核调试,静态分析,AnolisOS,内核崩溃,汇编分析


以下为原文内容

本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com

本文描述了一个在AnolisOS 8.8操作系统上遇到的内核崩溃问题的调试过程。

一般的debug的手段无非是依赖gdb、coredump,那是因为gdb类似的动态调试工具非常完善,可以帮助我们极快地完成debug。除此之外,debug还有一种方式,就是纯静态分析,在条件有限的情况下,gdb类的动态工具无法施展拳脚,只能依靠人脑模拟cpu运行以静态的方式来debug了。

有用户在OpenAnolis Kernel SIG群里发了个有点模糊的截图和一句话:

经过简单询问发现客户机器是12代intel i5-12400,在安装官网上下载的AnolisOS 8.8系统镜像过程中crash,无法进入系统,没有coredump,只有这个屏幕的打印的信息,无法看到更多的log。

好了,这就是所有的信息,现在开始debug阶段。

首先根据图中的信息判断,此时应该是内核发生了crash,并且crash的地方发生在resctrl_mon_resource_init()函数中。

确定内核版本范围:跟baseOS的同学确认官网上下载的AnolisOS 8.8镜像里面包含了4.19和5.10.134-13两个版本的内核,经客户确认,发生问题的是5.10内核。因此发生问题的内核版本是5.10.134-13。

可以看到5.10.134-13的内核代码中resctrl_mon_resource_init()是一个23行的小函数,做了一些初始化的工作。

从log中可以看到当cpu执行到resctrl_mon_resource_init+0xc0的地方时出现了panic,因此需要找到:

resctrl_mon_resource_init+0xc0

  1. 这里需要将对应的vmlinux解析成汇编,通过objdump -S -I -z vmlinux > vmlinux.txt解析成汇编的形式,解析出来最左边是地址,中间是二进制,右边是二进制对应的汇编代码。

  1. 通过解析出来的汇编可以看到resctrl_mon_resource_init函数的地址是ffffffff8148eea0,0xc0是ffffffff8148ef60,对应截图中的RIP 部分的二进制,刚好都能对应上,因此可以确认就是这里出现了问题。

  1. 到底汇编对应代码的哪里呢?这里分享个小经验:在汇编代码里面看到类似这样的结构,一个向下的大跳+一个向上的小跳,这种结构一般对应代码中的循环结构。汇编中的向上跳转比较特殊,一般只有在goto、循环这样的结构才能汇编出向上跳的情况。可以看到crash时RIP指向了一个循环结构结束的地方。

  1. 在resctrl_mon_resource_init()——

    dom_data_init()中发现了这个for循环,大概捋一下汇编代码前后的逻辑基本就能很快确定crash的时候RIP执行到了dom_data_init()第23行的位置。

dom_data_init()的先alloc了一块空间存放rmid_entry数组,并且将数组的首地址存在了全局变量rmid_ptrs中,接着for循环挨个对rmid_entry数据初始化加到rmid_free_lru链表中,由于第0个rmid_entry节点比较特殊,所以在for循环过后又把这个节点从rmid_free_lru中单独拿出来了。
  1. dom_data_init() 23行中:

    resctrl_arch_rmid_idx_encode(0,0)返回0,然后在rmid_ptrs中取第0个元素并返回。

  1. 通过对汇编分析,可以知道在crash RIP处,RBX就是rmid_ptrs。

(图中分析是一种方法,还有一种方法就是看R12寄存器,R12寄存器为0说明代码完全没有进到for循环中,最后还是可以得到RBX就是rmid_ptrs这个结论,感兴趣的读者可以自行分析一下)而报错中可以看到crash时 RBX值为0x10!

  1. 所以原因就很明显了,crash是因为dom_data_init()中kcalloc()返回了一个非空,但是却不合法的值。在后续的代码中又对这个值取地址操作,产生了panic!

所以为啥kcalloc()会返回0x10呢?我们可以看到nr_idx是从下方函数来的:

resctrl_arch_system_num_rmid_idx()

而resctrl_arch_system_num_rmid_idx()的返回值为:

x86_cache_max_rmid + 1

而x86_cache_max_rmid在初始化阶段如果检测cpu不支持CQM_LLC这个feature的话就会被置为-1 !所以在客户机器中,可能存在的情况是:客户机器的cpu不支持CQM_LLC,在这种情况下nr_id就会等于0,所以kcalloc()返回了不合法的值,然而dom_data_init()对kcalloc()返回值检查的时候只检查了是否非空,导致后续对该内存访问的过程中出现了panic!

至此,crash原因已经分析明确,后面找客户确认客户机器的cpu确实不支持 CQM_LLC。

在知道crash原因后,迅速出了一个验证内核给用户,用户验证问题解决。(此处感谢涤安提供快速修复补丁和出验证内核)。证明之前所有分析正确,此次debug全程只通过静态的代码分析完成了问题定位和原因分析。

当然,这个问题完整的解决涉及到resctrl的整体init逻辑:为什么在用户cpu不支持该feature的情况下还会走到这个init函数?这是个历史遗留问题:之前合入的某个patch修改了resctrl init流程的判断逻辑,导致了在特定的cpu型号下会出现这个bug,具体的细节就不在本文讨论了。至少我们已经明确了这个问题和知道这个问题的解决方案,后面的修复过程都好说。

通过内容安全API提供直播场景的文本检测能力,响应时间短、支持类型多,多维度判断风险行为。

点击阅读原文查看详情。