【转载】Linux SCSI Fault Injection

举报
Ragnar 发表于 2020/08/25 14:31:04 2020/08/25
【摘要】 SystemTap简介SystemTap的简介,只需要7个字!狂拽酷炫屌炸天!自从读了Brendan Gregg大神的<Systems Performance>,DTrace,SystemTap这类的动态追踪工具就成了我眼中的大杀器!第一次实际使用它,我就被其缓慢的启动速度,诡异的报错信息以及强大的破坏力深深地打动了……下面就来详细说一下吧。配置在RHEL或者CentOS下的安装应该都比较顺...

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:

  1. 可以直接嵌入C代码,然后就有很多神奇的用法比如霸爷的这篇以及这篇博文。

  2. SystemTap的脚本语言也比较直观,可以用-I来加载lib,方便组织代码重用。

  3. Probe的注入点非常灵活,所以对于错误注入的控制粒度也是最细的了,感觉甚至可以用它来帮助观察内核的瞬时call stack以增进对kernel工作原理的理解,比直接看代码更快捷。

这个项目也有些年头了,代码一直没有更新,所以拿来直接跑会碰到不少问题:

  1. 在SystemTap 1.7之后嵌入C代码时函数的参数从THIS->arg形式改为了STAP_ARG_arg,另外返回值也从THIS->__retvalue变成了STAP_RETVALUE。碰到临时编译的C代码报错问题时可以在运行stap时加上-k参数,这样自动生成的C代码会在/tmp下保留,方便进一步排查问题。

  2. 原来的代码适应的内核版本较老,在2.6.32以后的版本中会报有些struct的成员变量已经不存在的错误,比如drivers/scsi/scsi.c中的$cmd->request->sector就找不到了,打开lxr查找scsi_cmnd以及request struct的定义,可以看到sector被改成了__sector。更严重的是timeout的错误注入,drivers/scsi/scsi_error.c这个文件中的各个函数都被改得面目全非,所以暂时没有尝试这种错误注入。

  3. 我在跑脚本的时候还碰到了找不到那些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_paramsfij_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/



【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。