最近和同事一起处理了一个 fuse 的大bug;
首先看堆栈:
Core was generated by `/sf/cluster/bin/pmxcfs'.
Program terminated with signal SIGABRT, Aborted.
#0 0x00007f2debdcc475 in raise () from /lib/x86_64-linux-gnu/libc.so.6
(gdb) bt
#0 0x00007f2debdcc475 in raise () from /lib/x86_64-linux-gnu/libc.so.6
#1 0x00007f2debdcf6f0 in abort () from /lib/x86_64-linux-gnu/libc.so.6
#2 0x00007f2debdc5621 in __assert_fail () from /lib/x86_64-linux-gnu/libc.so.6
#3 0x00007f2ded4035d2 in fuse_do_release (f=f@entry=0x16148b0, ino=ino@entry=330, path=0x7f2ddc00e040 "/acl.cfg", fi=fi@entry=0x7f2dd73f4b50) at fuse.c:3092
#4 0x00007f2ded403656 in fuse_lib_release (req=0x7f2ddc00c230, ino=330, fi=0x7f2dd73f4b50) at fuse.c:3876
#5 0x00007f2ded4094eb in do_release (req=
#6 0x00007f2ded409f16 in fuse_ll_process_buf (data=0x1614ba0, buf=0x7f2dd73f4d00, ch=
#7 0x00007f2ded406c5b in fuse_do_work (data=0x7f2dcc0009e0) at fuse_loop_mt.c:117
#8 0x00007f2deca6eb50 in start_thread () from /lib/x86_64-linux-gnu/libpthread.so.0
#9 0x00007f2debe74a7d in clone () from /lib/x86_64-linux-gnu/libc.so.6
#10 0x0000000000000000 in ?? ()
(gdb)
分析:
1、这个是libfuse库源生的bug,在网上查看了下没有发现有这个bug的相关信息;
2、core分析:
(gdb) p *(f->id_table.array[3626])
$15 = {name_next = 0x0, id_next = 0x0, nodeid = 330, generation = 0, refctr = 1, parent = 0x7f2dee713090, name = 0x7f2dee54ab20 "acl.cfg", nlookup = 57,
open_count = 0, stat_updated = {tv_sec = 0, tv_nsec = 0}, mtime = {tv_sec = 0, tv_nsec = 0}, size = 0, locks = 0x0, is_hidden = 0, cache_valid = 0,
treelock = 1, inline_name = "acl.cfg", '\000'
对应代码:
pthread_mutex_lock(&f->lock);
node = get_node(f, ino);
assert(node->open_count > 0);// node->open_count = 0
--node->open_count;
if (node->is_hidden && !node->open_count) {//core中可以看到 is_hidden = 0, 所以在上一次调用该函数的时候,acl.cfg就已经被完全释放了;
unlink_hidden = 1;
node->is_hidden = 0;//当node->open_count == 0时,才会设置 node->is_hidden = 0,所以释放已经把最后一个连接去掉了;
}
pthread_mutex_unlock(&f->lock);
====
总结:处理这种开源软件bug时,首先是简单分析下core,然后是再到相关网站上看下,基本一些bug之前都有人遇到过;
本来想好好写排查过程的,还有一些有效的排查方法的,但发现 CSDN 好烂,都找不到预览功能在哪里了;
重现方法:
1. 修改代码,多个线程内增加调用system;
2. 修改代码,在fuse_kern_chan_receive里加强assert,如果read正常返回但是缓冲区没改过,即断言
这样可以加大重现概率,基本一天至少可以出4、5个
问题原因:
多线程内fork,父子进程先是共用内存页 准备copy-on-write, A线程等在read /dev/fuse上,A线程的read陷入系统调用 fuse.ko准备将数据写入内存,此时B线程写同一个内存页的内存触发cow,父进程最终使用新页表新内存,但read系统调用将数据写入了旧内存
对应用层的表现是,read返回值正确,但是缓冲区数据没有填充,应用层把缓冲区上旧命令当作新命令执行了两次
原始不改代码的情况下通常表现为两种core,一个是fuse_do_release里open计数断言,另一个是fuse_lib_releasedir里释放内存,其他情况虽然可能命令执行错了,但通常不会表现出core,而是上层访问可能异常;
修改办法:
1、命令结构体申请内存时,用4k对齐函数来申请(偏移量4k对齐,申请内存大小也要4k对齐);
2、把可能的在多线程中调用fork()的系统调用去掉;