开局一张图,debug全靠瞪|内核问题定位与静态分析实战

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 本文描述了一个在AnolisOS 8.8操作系统上遇到的内核崩溃问题的调试过程。

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


一、起因

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

image.png

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


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


二、debug


收集信息

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


确定内核范围

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


确定函数范围

image.png

可以看到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解析成汇编的形式,解析出来最左边是地址,中间是二进制,右边是二进制对应的汇编代码。

image.png


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

image.png

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

image.png

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

image.png


代码逻辑分析

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个元素并返回。

image.png

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


image.png

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

image.png


  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!

image.png

image.png


打完收工!

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

image.png

三、结果

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


四、写在后面

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






来源  |  阿里云开发者公众号
作者  |  库恩


相关文章
|
7月前
|
监控 NoSQL Java
十八张图带你入门实时监控系统HertzBeat
我们经常讲:研发人员有两只眼睛,一只是监控平台,另一只是日志平台。在对性能和高可用讲究的场景里,监控平台的重要性再怎么强调也不过分。 这篇文章,我们聊聊开源实时监控告警系统 HertzBeat 赫兹跳动。
十八张图带你入门实时监控系统HertzBeat
|
监控 NoSQL Linux
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解(一)
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解
795 0
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解(一)
麒麟系统开发笔记(十一):在国产麒麟系统上使用gdb定位崩溃异常方法流程进阶定位代码行数及专项测试Demo
上一篇,通过研究,可以定位到函数,本篇进一步优化,没有行数,程序较为复杂的时候,就无法定位,所以进一步定位。   本篇做了qBreakpad的研究,但是没有成功,过程也还是填出来,后来突然注意到gdb出现行数的方法,并通过了几轮测试以及实战,确实可以定位到行数,所以为了大家方便,把国企麒麟上的Qt崩溃方法分享出来。   本篇文章比较长,就不分篇了,同时还做了专项测试。
麒麟系统开发笔记(十一):在国产麒麟系统上使用gdb定位崩溃异常方法流程进阶定位代码行数及专项测试Demo
|
NoSQL Shell C语言
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解(二)
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解
455 0
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解(二)
|
存储 NoSQL IDE
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解(三)
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解
366 0
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解(三)
|
人工智能 JSON 前端开发
一眼就懂的TVM7大功能
简明扼要的介绍TVM的7大功能
|
Arthas NoSQL Java
线上服务器CPU100%的真相排查【Bug利器Arthas】
这起CPU100%的事故,由某个客户演示的bug暴露出来,气氛比较尴尬....
767 0
线上服务器CPU100%的真相排查【Bug利器Arthas】
|
存储 自然语言处理 Java
Elixir 连续运行时代码覆盖率采集方案
## 1. 浅谈代码覆盖率 作为 SET 和 SWE, 我们经常需要编写单元测试或集成测试用例来验证系统/应用的正确性, 但同时我们也常会质疑我们的测试是否充分了. 这时测试覆盖率是可以辅助用来衡量我们测试充分程度的一种手段, 增强发布成功率与信心, 同时给了我们更多可思考的视角. 值的注意的是代码覆盖率高不能说明代码质量高, 但是反过来看, 代码覆盖率低, 代码质量不会高到哪里去. 大部分的编
358 0
Elixir 连续运行时代码覆盖率采集方案
|
机器学习/深度学习 Python
信用评分系统运行原理下篇(3)
信用评分系统运行原理下篇(3)
131 0
信用评分系统运行原理下篇(3)
|
算法
信用评分系统运行原理下篇(1)
信用评分系统运行原理下篇(1)
198 0
信用评分系统运行原理下篇(1)