【转载】Linux SCSI Fault Injection
SystemTap
简介
SystemTap的简介,只需要7个字!狂拽酷炫屌炸天!自从读了Brendan Gregg大神的<Systems Performance>,DTrace,SystemTap这类的动态追踪工具就成了我眼中的大杀器!第一次实际使用它,我就被其缓慢的启动速度,诡异的报错信息以及强大的破坏力深深地打动了……下面就来详细说一下吧。
配置
在RHEL或者CentOS下的安装应该都比较顺利,可以直接yum install
或者从源码编译(版本会比较新)。然后要装一下kernel的debuginfo包,否则直接在地址信息上插入probe有点暴力的……
装完试一下经典的Hello World!
1 |
' |
还可以试试这个:
1 |
$ sudo stap -l 'kernel.function("scsi_*")' |
列出了各种可以获取到的kernel function,之后就可以以此来注入probe啦!
使用
由于对Linux SCSI驱动层面几乎一无所知,所以基本沿用了这个开源项目中的SystemTap代码。从这个项目中也学习到了很多SystemTap的优秀feature:
SystemTap的脚本语言也比较直观,可以用-I来加载lib,方便组织代码重用。
Probe的注入点非常灵活,所以对于错误注入的控制粒度也是最细的了,感觉甚至可以用它来帮助观察内核的瞬时call stack以增进对kernel工作原理的理解,比直接看代码更快捷。
这个项目也有些年头了,代码一直没有更新,所以拿来直接跑会碰到不少问题:
在SystemTap 1.7之后嵌入C代码时函数的参数从
THIS->arg
形式改为了STAP_ARG_arg
,另外返回值也从THIS->__retvalue
变成了STAP_RETVALUE
。碰到临时编译的C代码报错问题时可以在运行stap时加上-k
参数,这样自动生成的C代码会在/tmp
下保留,方便进一步排查问题。原来的代码适应的内核版本较老,在2.6.32以后的版本中会报有些struct的成员变量已经不存在的错误,比如
drivers/scsi/scsi.c
中的$cmd->request->sector
就找不到了,打开lxr查找scsi_cmnd以及request struct的定义,可以看到sector被改成了__sector。更严重的是timeout的错误注入,drivers/scsi/scsi_error.c
这个文件中的各个函数都被改得面目全非,所以暂时没有尝试这种错误注入。我在跑脚本的时候还碰到了找不到那些function的错误,后来发现是因为我们的kernel是把scsi模块直接编译进内核的,所以要在
kernel.function
里找。默认的编译方式好像是把这些东西编译成module的,因而原先的脚本都是在module("*").function
里找。
由于原脚本的逻辑看起来异常复杂,难以调试,所以我把其中的代码一块一块剥离出来自己拼接使用,为了更好地控制错误注入的方式,比如通过pid,inode,device来过滤需要错误注入的目标,加上失败次数,失败概率等各种常见调控参数,我又以自己拙劣的SystemTap代码能力……历经千辛万苦……从网上找了另一个fault injection的framework来用…………组装成功后真是简单好用,想怎么注入就怎么注入啊!
先来看下效果:
准备工作
简单起见,我就用读写一个文本文件来做测试了。随便建立一个文件
1 |
n |
在跑脚本时需要指定一下错误注入的目标也就是这个文件的inode号或block号,可以用下面的方法:
123456789101112131415 |
$ sudo debugfs -R "stat /home/admin/zijie/test_fault_injection" /dev/sda2debugfs 1.41.12 (17-May-2010)Inode: 2992033 Type: regular Mode: 0664 Flags: 0x0Generation: 2438482565 Version: 0x00000000User: 505 Group: 505 Size: 130File ACL: 0 Directory ACL: 0Links: 1 Blockcount: 8Fragment: Address: 0 Number: 0 Size: 0ctime: 0x53745b2d -- Thu May 15 14:14:05 2014atime: 0x5390017b -- Thu Jun 5 13:34:51 2014mtime: 0x53745b2d -- Thu May 15 14:14:05 2014Size of extra inode fields: 4BLOCKS:(0):11982918TOTAL: 1 |
最后还要获取一下device的major, minor id,我用的是
123456789 |
$ ls -l /dev | grep sdabrw-r----- 1 root disk 8, 0 May 15 02:46 sdabrw-r----- 1 root disk 8, 1 May 14 18:46 sda1brw-r----- 1 root disk 8, 2 May 15 02:46 sda2brw-r----- 1 root disk 8, 3 May 15 02:46 sda3brw-r----- 1 root disk 8, 4 May 15 02:46 sda4brw-r----- 1 root disk 8, 5 May 14 18:46 sda5brw-r----- 1 root disk 8, 6 May 14 18:46 sda6brw-r----- 1 root disk 8, 7 May 15 02:46 sda7 |
可以看到major id是8,minor id从0到7。
注入起来
错误注入前:
12345678 |
t |
跑一下脚本命令:
1 |
$" |
测试读取文件:
12 |
$ |
看一下/var/log/message
:
1234 |
s |
写入也是一样:
12 |
$ echo "test_after_fault_inject" >> test_fault_injection-bash: echo: write error: Input/output error |
把脚本停了之后,文件中原来的内容还是没有任何损坏。
另外可以在脚本里加print_backtrace()
来查看错误注入时的call stack。如果对内核代码非常了解的话,就可以几乎在任意的位置注入错误了!
工作原理
详细的SystemTap使用可以看官方教程,另外它本身就自带了许多示范脚本,已经可以满足很多日常的监控分析工作,推荐大家试试!另外其工作原理可以参考这篇文章。
在这个SCSI错误注入脚本里基本上就是在scsi_decide_disposition
这个function上加probe,然后进去后获取一系列变量信息以此来控制是否进行错误注入。在控制是否错误注入时使用了fij的库,在代码里看就是那些fij_params
和fij_should_fail()
等。截取一个代码片段来看下:
12345678910111213141516171819202122232425262728 |
p} |
类似C的语法的脚本语言!还是很直观好用的吧!
SCSI错误类型
在脚本中有两个关键的错误注入点,一个是set_sense_buf
函数
1234567891011 |
function set_sense_buf:long (cmd:long, result:long, sensekey:long, asc:long, ascq:long )%{struct scsi_cmnd * scmd = (struct scsi_cmnd *)(long)STAP_ARG_cmd;scmd->result = (int)(long)STAP_ARG_result; /* case DID_BUS_BUSY: 0x02 */scmd->sense_buffer[0] = 0x70; /* current, fixed format */scmd->sense_buffer[2] = (unsigned char)(long)STAP_ARG_sensekey; /* 0x03 */scmd->sense_buffer[7] = 0x13; /* length */scmd->sense_buffer[12] = (unsigned char)(long)STAP_ARG_asc; /* 0x11 */scmd->sense_buffer[13] = (unsigned char)(long)STAP_ARG_ascq; /* 0x04 */%} |
一头雾水的感觉啊有没有!这又是SCSI驱动编程的新领域了……去查文档。终于大致理解了上面这段代码:
第0个字节默认设置为0x70或0x71
第2个字节为Sense Key,脚本中设置为0x03,意为medium error,与系统报错一致
第7个字节为长度,不知道为何设置成0x13,看代码应该大于十进制的13才能读取后面用到的两个field
第12,13字节组成了一套复杂的状态
脚本中设置的0x11, 0x04含义为:UNRECOVERED READ ERROR - AUTO REALLOCATE FAILED,与系统报错一致
我们可以利用这些信息返回任意我们想要的SCSI错误。
另外一个是修改scsi command,脚本中有如下代码:if (($cmd->cmnd[0] == 0x28) || ($cmd->cmnd[0] == 0x2a))
参考维基百科
可以看到这两个命令是:28 READ(10)
和2A WRITE(10)
读或者写10个字节
但是后面设置了$cmd->cmnd[7] = 0
, $cmd->cmnd[8] = 0
这两个,苦苦搜索都没找到相关文档,看到内核源码中有这么一段:
1234567 |
} else if (cmd->cmnd[0] == WRITE_10) {cmnd_lba = ((u64)cmd->cmnd[2] << 24) |(cmd->cmnd[3] << 16) |(cmd->cmnd[4] << 8) |cmd->cmnd[5];cmnd_count = (cmd->cmnd[7] << 8) |cmd->cmnd[8]; |
所以这两个应该是scsi command的数量,在这里强制设为了0,可能是直接取消了后续的任何读写操作?还望大牛指点迷津啊!
总结
SystemTap果然是极其强大啊!当然对使用者要求也比较高,需要对内核本身的各种function,运作流程比较了解。使用中感觉它最大的优势是控制点非常细,几乎可以在任意的代码路径上注入想要的错误。如果我们能取得kernel panic时候的call stack,就可以准确地在原本出错的函数返回处注入错误,以此来进行bug的修复验证,而不用长时间反复跑压力测试来期望硬件问题的复现,可以极大地提高效率!
转自https://zijie0.github.io/2014/06/05/linux-scsi-fault-injection/
- 点赞
- 收藏
- 关注作者
评论(0)