内核调试:一次多线程调试与KASAN检测实例
内核调试:一次多线程调试与KASAN检测实例
- 1. 环境说明
- 2. 问题描述
- 3. 问题排查与定位
- 3.1 线程并发问题(减少线程数)
- 3.2 轻量地跟踪对象的分配与释放
- 3.3 检查空指针与潜在修改者
- 3.4 KASAN检查
- 4. 总结
博主最近遇到一个非常顽固的多线程BUG,复现起来具有很大的随机性,本文介绍博主一步步定位问题并解决BUG的思路和方案,希望对大家有启发(注:本思路同样适用于用户态调试)
1. 环境说明
- OS内核:本文内核跑在QEMU环境上,详细配置见我之前的博客:用VSCode + QEMU跑起来能够可视化Debug的NOVA文件系统
- 测试程序:
Filebench,一个文件系统测试工具。参考这篇博客了解其编译并放入QEMU的方法:编译静态文件系统测试工具【Filebench】并在QEMU中运行
2. 问题描述
在虚拟机上用50个线程运行如下Filebench脚本:
...
define fileset name=bigfileset,path=$dir,size=$filesize,entries=$nfiles,dirwidth=$meandirwidth,prealloc=80define process name=filereader,instances=1
{thread name=filereaderthread,memsize=10m,instances=$nthreads{flowop createfile name=createfile1,filesetname=bigfileset,fd=1flowop writewholefile name=wrtfile1,srcfd=1,fd=1,iosize=$iosizeflowop closefile name=closefile1,fd=1flowop openfile name=openfile1,filesetname=bigfileset,fd=1flowop appendfilerand name=appendfilerand1,iosize=$meanappendsize,fd=1flowop closefile name=closefile2,fd=1flowop openfile name=openfile2,filesetname=bigfileset,fd=1flowop readwholefile name=readfile1,fd=1,iosize=$iosizeflowop closefile name=closefile3,fd=1flowop deletefile name=deletefile1,filesetname=bigfilesetflowop statfile name=statfile1,filesetname=bigfileset}
}
简单来说,他就类似每个线程执行一堆文件操作序列,对应的:
createfile: 创建文件writewholefile: 填充文件closefile:关闭文件openfile:打开文件appendfilerand:随机追加写文件readwholefile:读整个文件deletefile:删除文件statfile:输出文件基本属性
在运行过程中,发现被测试的文件系统删除了本不应该存在的File(具体而言,被测试的文件系统为每个目录维护一个hash表用于快速目录项索引,但是在文件删除过程中,发现在hash表里找不到对应文件),至此,事情变得扑朔迷离起来。可能的原因有很多,包括:
- 文件删除(
unlink)部分实现存在BUG - 文件创建(
create)部分实现存在BUG - 多线程BUG
- 内存踩踏BUG
最后,还可能是VFS本身的BUG,这是我最不敢想像的,如果是VFS本身的BUG,那需要做的工作就太多了……
3. 问题排查与定位
3.1 线程并发问题(减少线程数)
我在单线程下运行Filebench,发现了不少单线程下存在的内存越界问题以及强转问题。其中:
-
强转问题通常带来数值的overflow进而导致不正确的内存访问,例如,计算32位
blk对应的相对偏移地址,即:unsigned int blk = 0xffff0000; unsigned long addr;addr = blk << 12;上述结果
b的值为0xf0000000,而非0xffff0000000,正确的写法应该是:addr = (unsigned long)blk << 12; -
其次,无符号数大小比较问题。切忌不能直接两数相减然后与0比较,如下:
unsigned int a = 0; unsigned int b = 1;assert(a - b > 0);上述减法出来的结果是
UINT32_MAX,即:0xffffffff。
这些问题解决后,单线程下的大多数BUG都消失了。接着提升线程数至2线程,同样没有问题发生;当线程数回调到50时,同样的BUG再次出现,还伴随着其他种种问题,例如:空指针访问、写入已经被删除的文件、读取已经被删除的文件等。这证明问题仍在高并发场景下仍然存在,我们继续检查前面提到的点:
- 文件删除(
unlink)部分实现存在BUG - 文件创建(
create)部分实现存在BUG 多线程BUG- 内存踩踏BUG
除此之外,由于观察到了空指针访问,我们还希望检查相应的指针修改情况,看看是哪行代码有可能修改该指针,即:
- 空指针访问BUG
3.2 轻量地跟踪对象的分配与释放
为什么文件系统会访问已经被删除的File呢? 验证问题的思路是跟踪整个Filebench运行过程中文件的创建和删除情况,看看这个文件究竟有没有被创建。
在高并发场景下,通用调试手段printk()(即内核的打印)函数已经不能够及时输出,表现为:printk: X messages dropped。
针对难以通过print调试的问题,可以考虑自行构建跟踪文件的代码,然后在出错的地方自行输出跟踪的信息。例如,在博主调试过程中,我在栈上分配了固定大小的数组,每次创建和删除文件时便向其中追加写入当前文件的inode号,在unlink调用且在父目录中找不到该文件时强行停止内核(BUG_ON(1);,详见3.3节)并输出该文件号的所有创建的和删除记录。这里要注意追踪器的轻量高效性,不能使其过度影响程序的并发,否则可能BUG无法发作。
为此博主构造了一个per cpu文件号跟踪器,相关代码如下:
#define CPU 32
#define MAX_FILE 4000u32 remove_lists_pos[CPU];
u32 remove_lists[CPU][MAX_FILE];
spinlock_t remove_locks[CPU];u32 create_lists_pos[CPU];
u32 create_lists[CPU][MAX_FILE];
spinlock_t create_locks[CPU];// 创建文件部分伪代码
int create(dir) {...// per cpu create跟踪器int cpuid, i, is_find = 0;int start_cpuid = smp_processor_id();for (i = 0; i < 32; i++) {cpuid = (start_cpuid + i) % CPU ;spin_lock(&create_locks[cpuid]);if (create_lists_pos[cpuid] < MAX_FILE ) {create_lists[cpuid][create_lists_pos[cpuid]++] = inode;spin_unlock(&create_locks[cpuid]);break;}spin_unlock(&create_locks[cpuid]);}...
}// 删除文件部分伪代码
int unlink(dir, inode) {...// per cpu unlink跟踪器int cpuid, i, is_find = 0;int start_cpuid = smp_processor_id();for (i = 0; i < 32; i++) {cpuid = (start_cpuid + i) % CPU ;spin_lock(&remove_locks[cpuid]);if (remove_lists_pos[cpuid] < MAX_FILE ) {remove_lists[cpuid][remove_lists_pos[cpuid]++] = inode;spin_unlock(&remove_locks[cpuid]);break;}spin_unlock(&remove_locks[cpuid]);}...// 目录里面没有找到inodeif (inode not found in dir) {int i, j;// 输出跟踪记录for (i = 0; i < 32; i++) {for (j = 0; j < remove_lists_pos[i]; j++) {if (create_lists[i][j] == inode) {hk_info("%s: create_lists[%d][%d] %lu\n", __func__, i, j, create_lists[i][j]);}if (remove_lists[i][j] == inode) {hk_info("%s: remove_lists[%d][%d] %lu\n", __func__, i, j, remove_lists[i][j]);}}}// 停止内核BUG_ON(1);}
}
折腾一番后,博主发现几个更有意思的问题:有些文件确实根本就没有create就被unlink了,这是根本不能发生的事情(除非VFS有BUG)。再者反复核对文件的删除和创建逻辑,也没有发现问题,看来事出另有因,我们继续往下检查:
文件删除(unlink)部分实现存在BUG文件创建(create)部分实现存在BUG多线程BUG- 内存踩踏BUG
- 空指针访问BUG
3.3 检查空指针与潜在修改者
在内核中,开发者通常使用BUG_ON(condition)来当作断言assert(condition)。例如:
void *pointer = get_pointer();
BUG_ON(pointer == NULL);
这样就能使系统在上述pointer为空时停下来,验证确实是这个变量为空。
此外,博主还经常使用BUG_ON()来验证某个函数一定不会被调用,某个可能修改pointer的的变量是否真的被调用等(即用于确定潜在修改者),这点比较鸡肋,不过对于视觉疲劳懒得翻printk记录的人来说,这是省事的好方法。举个例子:
void *pointer = NULL;void threadB(){// 确认线程B永远不会调用,一旦调用系统就会报错BUG_ON(1);// 如果线程B会被调用,删除BUG_ON(1),浏览代码// 确认线程B会对pointer做出什么事pointer = 0x1;
}
总而言之,博主在代码中各式各样的指针处都写上了BUG_ON(1),但遗憾的是,我并没有发现存在修改者会使该指针变空。此时,只剩下一条路可走:内存溢出或内存踩踏使得乱象丛生。
文件删除(unlink)部分实现存在BUG文件创建(create)部分实现存在BUG多线程BUG- 内存踩踏BUG
空指针访问BUG
3.4 KASAN检查
KASAN是一个强大的内存泄漏、越界访问问题的检测工具,参考资料很多:
- KASAN实现原理
- KASAN配置
- KASAN实践
进一步的,我们cd到内核源码目录下,直接用脚本开启下述config即可:
[deadpool@localhost linux-5.1]$ ls
arch COPYING Documentation include Kbuild lib Makefile modules.order README security tools vmlinux
block CREDITS drivers init Kconfig LICENSES mm Module.symvers samples sound usr vmlinux-gdb.py
certs crypto fs ipc kernel MAINTAINERS modules.builtin net scripts System.map virt vmlinux.o
[deadpool@localhost linux-5.1]$ ./scripts/config -e CONFIG_SLUB_DEBUG
[deadpool@localhost linux-5.1]$ ./scripts/config -e CONFIG_SLUB_DEBUG_ON
[deadpool@localhost linux-5.1]$ ./scripts/config -e CONFIG_KASAN
[deadpool@localhost linux-5.1]$ ./scripts/config -e CONFIG_KASAN_INLINE
然后make -j32编译即可。接着,果然,检查出如下类似错误:
==================================================================
BUG: KASAN: use-after-free in function+0xxx/0xxx [xx_module]
Write of size 8 at addr `addr1` by task filebench/2760
...
The buggy address belongs to the object at `addr`
...
==================================================================
上面报错信息中,关注:
- 报错类型:
use-after-free,访问了free后的内存 function:具体哪个函数访问的addr1: 具体访问addr1处变量产生的错误addr:addr1属于addr处的对象(malloc出来的对象)
接着,检查function函数,至此,终于找到了这个藏得非常深的BUG,由于不方便开放源码,这里简单来说就是在并发地插入hash表时,没有正确地上锁,导致出现各种各样的问题。
4. 总结
至此,终于结束了这为期两天的DEBUG之旅,现在回想起来,要是从一开始就使用KASAN,也许一下就能够解决遇到的问题,以后一定要多多使用KASAN来帮忙检查内存相关的错误。
题外话,听说rust好像可以在编译阶段就避免很多这样类似的问题,看来真的有必要向Linux Kernel中引入rust。
OK,就这样,起飞🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫🛫
相关文章:
内核调试:一次多线程调试与KASAN检测实例
内核调试:一次多线程调试与KASAN检测实例1. 环境说明2. 问题描述3. 问题排查与定位3.1 线程并发问题(减少线程数)3.2 轻量地跟踪对象的分配与释放3.3 检查空指针与潜在修改者3.4 KASAN检查4. 总结博主最近遇到一个非常顽固的多线程BUG&#x…...
Java - 数据结构,队列
一、什么是队列 普通队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(FirstIn First Out) 入队列:进行插入操作的一端称为队尾(Tail/Rear) 出队列…...
ccc-pytorch-感知机算法(3)
文章目录单一输出感知机多输出感知机MLP反向传播单一输出感知机 内容解释: w001w^1_{00}w001:输入标号1连接标号0(第一层)x00x_0^0x00:第0层的标号为0的值O11O_1^1O11:第一层的标号为0的输出值t:真实…...
LeetCode 225.用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。实现 MyStack 类:void push(int x) 将元素 x 压入栈顶。int pop() 移除并返回栈顶元素。int top() …...
【面试】spring控制反转IOC
目录一.说明二.ioc的概念和作用三.优点四.实现机制五.IOC和DI的区别六.设计原则一.说明 1.ioc的概念2.ioc的作用3.ioc的优点4.ioc的实现机制 二.ioc的概念和作用 1.全称Inversion of Control2.控制:创建对象的控制权3.反转:以前对象是程序员主动去new…...
Spring 事务管理详解及使用
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
LeetCode 232.用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):实现 MyQueue 类:void push(int x) 将元素 x 推到队列的末尾int pop() 从队列的开头移除并返回元素int peek() 返回队列开头的元…...
go面向对象思想封装继承多态
go貌似都没有听说过继承,当然这个继承不像c中通过class类的方式去继承,还是通过struct的方式,所以go严格来说不是面向对象编程的语言,c和java才是,不过还是可以基于自身的一些的特性实现面向对象的功能,面向…...
【网络原理9】HTTP响应篇
在前两篇文章当中,已经分别介绍了HTTP是什么,以及常见的请求头当中的属性。【网络原理7】认识HTTP_革凡成圣211的博客-CSDN博客HTTP抓包,Fiddler的使用https://blog.csdn.net/weixin_56738054/article/details/129148515?spm1001.2014.3001.…...
SpringCloud之Seata(二)
4.Seata如何应用于项目? 安装seata及修改配置 4.1 官网下载Seata安装包 4.2 修改seata/config.txt 4.2.1 修改存储方式 store.db.dbTypemysql store.db.driverClassNamecom.mysql.jdbc.Driver store.db.urljdbc:mysql://你的IP:3306/seata?useUnicodetrue sto…...
【Redis-入门阶段】基本数据结构
Redis支持多种数据结构,包括字符串、列表、哈希、集合和有序集合。这些数据结构在Redis中被称为键值对,其中键是一个字符串,值可以是一个字符串、列表、哈希、集合或有序集合。接下来,我们将详细介绍这些数据结构的使用方法。字符…...
BACnet协议详解————MS/TP物理层,数据链路层和网络层
文章目录写在前面1 物理层2 数据链路层MSTP的流程如下noteMS/TP帧格式3 网络层写在前面 这周加更一篇,来弥补一下之前落下的进度。简单的说两句,之前讲应用层的时候,只是跟官方的手册来同步一下,但是从个人理解来说,自…...
Tomcat
Tomcat 1 简介 1.1 什么是Web服务器 Web服务器是一个应用程序(软件),对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作,让Web开发更加便捷。主要功能是"提供网上信息浏览服务"。 Web服务器是安…...
创客匠人直播:构建公域到私域的用户增长模型
进入知识付费直播带货时代,很多拥有知识技能经验的老师和培训机构吃到了流量红利。通过知识付费直播,老师们可以轻松实现引流、变现,还可以突破时间、地域的限制,为全国各地的学员带来优质的教学服务,因此越来越受到教…...
机试指南
文章目录零、绪论和IDE安装int取值范围常犯的编程小错误一、枚举和模拟 (暴力求解)(一) 枚举1.Reverse函数 求 反序数2.程序出错的原因1.编译错误 (compile):基本语法错误2.链接错误 (link):函数名写错了3.运行错误 (run):结果与预期不符&…...
Android CTA认证设定首选网络类型
需求 硬件只支持4G,过CTA认证时打网络电话,会出现3G网络的选择,会导致过不了,需要禁用3G网络选择功能。 Android 8.1.0 分析 可adb命令查看当前的网络类型 getprop | grep “network” 打印如下: [gsm.network.type]: [LTE,LTE] [ro.telephony.default_network]: [9] …...
Android 动态切换应用图标方案
经常听到大家讨论类似的需求,怀疑大厂是不是用了此方案,据我个人了解,多数头部 app 其实都是发版来更新节假日的 icon。当然本方案也是一种可选的方案,以前我也调研过,存在问题和作者所述差不多,此外原文链…...
SMART PLC斜坡函数功能块(梯形图代码)
斜坡函数Ramp的具体应用可以参看下面的文章链接: PID优化系列之给定值斜坡函数(PLC代码+Simulink仿真测试)_RXXW_Dor的博客-CSDN博客很多变频器里的工艺PID,都有"PID给定值变化时间"这个参数,这里的给定值变化时间我们可以利用斜坡函数实现,当然也可以利用PT1…...
不那么认真的linux复习
这是个不那么认真的linux总结,可能有一些错误 1、linuxkernel(内核)shell(外壳)fs(文件系统)pro/uti/tol(应用程序) 2、ls(列出文件) -a…...
Redis系列文章总纲
跟着老万学Redis 前言 从事开发工作这么久,很多核心技术其实都还只是局限在满足日常开发工作中的基础使用,并没有完整的总结研究。今年的目标之一是完成几个技术栈的系列博客,系统的总结一下知识体系,目前计划是从Redis开始。 Re…...
深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
