KernelShark是Linux系统上的一个优化工具,最初是由红帽公司开发的,主要开发者是Steven Rostedt。
根据内核代码中的签名信息,Steven在2007年接手内核的ftrace模块,他可能是在开发ftrace的过程中,出于测试的需要,开发了建立在ftrace功能上的两个工具程序,一个是命令行形式的trace-cmd,另一个便是图形界面的KernelShark。
Steven在1998年读硕士时便参与Linux内核开发,在2006年加入RedHat,一直工作2016年。正是在RedHat工作时,Steven开发了ftrace和KernelShark。最初版本的KernelShark大约是在2010年时对外发布的。
Steven在Linux基金会官网上的照片 (来自LF官网)
我一直很喜欢KernelShark工具,常常使用这个工具,也总在讲Linux内核调试工具时介绍这个工具。
不知道是不是因为这个工具源自RedHat,在Ubuntu上,这个工具总是有些问题。比如在几年前的Ubuntu 16上,从Ubuntu仓库下载安装的的KernelShark菜单缺少重要的Record项。
近日,在较新的Ubuntu 20.04上安装KernelShark,使用Ubuntu仓库安装,结果又是Record功能不工作。
于是我只好又用几年前的老办法,自己下载代码,自己build。
和上次一样,我到KernelShark的官网下载了最新的代码。
但是构建时,发现构建方法变了,不如以前简单方便了。但装了一些依赖后,还是构建通过了。
但运行时,启动Record功能,立刻出了问题。
gebox@gebox-VirtualBox:~/kernel-shark-kernelshark-v2.1.0/bin$ ./kshark-su-record
QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-root'
free(): invalid pointer
./kshark-su-record: line 5: 13167 Aborted (core dumped) pkexec kshark-record -o ${PWD}/trace.dat
看起来是释放无效指针,glibc发起abort信号,紧急停车。
于是上调试器,追查。
有了调试器,很快定位到发起错误free的代码,即KsCaptureDialog.cpp的230行。
找到出问题的函数,其代码如下:
看来出问题的free释放的是来自kshark_tracecmd_local_plugins的字符串数组,是由tracefs_tracers返回的。
/** Get an array of available tracer plugins. */
char **kshark_tracecmd_local_plugins()
{
return tracefs_tracers(tracefs_tracing_dir());
}
tracefs_tracers是libtracefs的接口函数,tracefs是ftrace的文件系统接口,主要开发者也是Steven。
根据librtracefs的文档,必须使用tracefs_list_free函数来释放tracefs_tracers返回的字符串数组。
The tracefs_tracers() function returns array of strings with the names of supported tracer
plugins, located in the given tracing_dir directory. This could be NULL or the location of
the tracefs mount point for the trace systems of the local machine, or it may be a path to
a copy of the tracefs directory from another machine. The last entry in the array as a
NULL pointer. The array must be freed with tracefs_list_free() API.
如此看来,这里直接使用free来释放,是个大bug,如果glibc不检查到,那么就可能把堆搞乱,导致更难发现的bug。
纠正了free问题后,重新编译,再运行,这下Record功能可以工作了,可以抓到事件和产生trace.dat文件了。但是打开文件分析时,KernelShark的界面挂住了,长时间没有更新,也没有反应。
我是昨晚做上面的安装和调试工作的,改掉了一个bug后,没想到又出来一个。因为今天还要讲课,所以我想放弃新版本,改用老版本了。但是在要关闭电脑的时候,大脑中又有了继续战斗的念头。
于是,再次上调试器,切换UI线程,查找挂死原因。仔细一看,居然和白天讲课时讲的案例一模一样,UI线程在等待互斥量。
查找互斥量的拥有者线程,再找到等待这个互斥量的函数,查看源代码,发现了一个很明显的bug。
789行获取锁,然后调用tracecmd_read_at函数,判断函数返回值,如果返回值为空,794行直接返回了,忘记释放锁。唉,这也太草率了。
在794行前面增加释放锁的代码,再次编译运行,这下终于可以正常工作了,熟悉的界面再次出现。
需要说明的是,上面两个比较明显、也比较严重的Showstopper级别的bug并不是出自Steven之手,Steven在2016年时就离开RedHat,加入Vmware,在Vmware工作四年后,目前在Google工作。
在今晚准备写这个文章时,顺着Steven的Linkedin动态找到trace-cmd 3.0的发布说明,又到trace-cmd的git网页。
https://git.kernel.org/pub/scm/utils/trace-cmd/kernel-shark.git/commit/
偶然中看到一个7天前的补丁信息:
Branch Commit message Author Age
kernelshark kernelshark: Release input_mutex on not finding record Steven Rostedt (Google) 7 days
看详细描述:
居然就是上面的第二个bug。看来Steven已经发现了这个问题。
查看git.kernel.org上第一个bug对应的源代码,bug还是在的。
如此看来,git.kernel.org上的代码与kernelshark.org上的代码相比,fix掉了第二个bug,更新一些。
第一个bug也许与libtracefs的实现有关,也就是与发行版有关,或许在某些实现中,返回的字符串数组可以直接使用free来释放。但仔细看了Steven本人写的tracefs的实现,在注释里也明确说了要使用专门的函数释放,言外之意是不可以使用free。
https://git.kernel.org/pub/scm/libs/libtrace/libtracefs.git/tree/src/tracefs-events.c
如此看来,第一个bug确实是bug无疑。
参考资料:
1. Steven的Linkedin页面
https://www.linkedin.com/in/steven-rostedt-0159437a/
2. Steven的个人网站
http://goodmis.org/
(写文章很辛苦,恳请各位读者点击“在看”,也欢迎转发)
*************************************************
正心诚意,格物致知,以人文情怀审视软件,以软件技术改变人生
扫描下方二维码或者在微信中搜索“盛格塾”小程序,可以阅读更多文章和有声读物
也欢迎关注格友公众号