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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 本文描述了一个在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,具体的细节就不在本文讨论了。至少我们已经明确了这个问题和知道这个问题的解决方案,后面的修复过程都好说。






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


相关文章
|
6月前
|
监控 NoSQL Java
十八张图带你入门实时监控系统HertzBeat
我们经常讲:研发人员有两只眼睛,一只是监控平台,另一只是日志平台。在对性能和高可用讲究的场景里,监控平台的重要性再怎么强调也不过分。 这篇文章,我们聊聊开源实时监控告警系统 HertzBeat 赫兹跳动。
十八张图带你入门实时监控系统HertzBeat
|
Dubbo Java 应用服务中间件
项目中引进这玩意,排查日志又快又准
随着微服务盛行,很多公司都把系统按照业务边界拆成了很多微服务,在排错查日志的时候,因为业务链路贯穿着很多微服务节点,导致定位某个请求的日志以及上下游业务的日志会变得有些困难。
|
1月前
|
Java 程序员 应用服务中间件
「测试线排查的一些经验-中篇」&& 调试日志实战
「测试线排查的一些经验-中篇」&& 调试日志实战
22 1
「测试线排查的一些经验-中篇」&& 调试日志实战
|
5月前
|
存储 缓存 NoSQL
不扒瞎,这个程序让我从150s优化到了5s
在优化一个业务开发组的生产问题时,发现销售管理系统查询数据延迟高达2-3分钟。问题根源在于,程序在for循环中频繁读取Redis大KEY数据,导致性能下降。解决方案是采用本地缓存HutoolCache,将耗时降至毫秒级别。此外,还对RedisTemplate配置进行了研究,Jackson2JsonRedisSerializer在序列化时包括了所有字段,即使字段值为null,增加了数据体积。通过对ObjectMapper的调整,仅序列化非空字段,可以显著提升redis读取性能。本文同时还提醒我们在使用Redis时要注意大对象缓存,强调了正确使用和配置缓存以及避免大对象存储的重要性。
65 5
|
6月前
|
程序员 Python
揭秘单步调试:掌握这一技能让你代码无懈可击
揭秘单步调试:掌握这一技能让你代码无懈可击
56 0
|
IDE 测试技术 编译器
如何管理代码仓库,可以用什么系统开发,编程工具有哪些,程序异常怎么定位分析?
随着工具的日新月异,善于利用这些工具将有利于我们开发效率的提高,本文将简要介绍标题相关知识。
|
监控 NoSQL Linux
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解(一)
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解
744 0
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解(一)
麒麟系统开发笔记(十一):在国产麒麟系统上使用gdb定位崩溃异常方法流程进阶定位代码行数及专项测试Demo
上一篇,通过研究,可以定位到函数,本篇进一步优化,没有行数,程序较为复杂的时候,就无法定位,所以进一步定位。   本篇做了qBreakpad的研究,但是没有成功,过程也还是填出来,后来突然注意到gdb出现行数的方法,并通过了几轮测试以及实战,确实可以定位到行数,所以为了大家方便,把国企麒麟上的Qt崩溃方法分享出来。   本篇文章比较长,就不分篇了,同时还做了专项测试。
麒麟系统开发笔记(十一):在国产麒麟系统上使用gdb定位崩溃异常方法流程进阶定位代码行数及专项测试Demo
|
NoSQL Ubuntu
麟系统开发笔记(十):在国产麒麟系统上使用gdb定位崩溃异常方法流程以及测试Demo
本篇就适合代码崩溃的方法,可以定位到代码崩溃原因,测试Demo。
麟系统开发笔记(十):在国产麒麟系统上使用gdb定位崩溃异常方法流程以及测试Demo
|
存储 NoSQL IDE
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解(三)
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解
357 0
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解(三)