linux. SCSI. 错误处理,【转载】Linux SCSI Fault Injection

SystemTap

简介

SystemTap的简介,只需要7个字!狂拽酷炫屌炸天!自从读了Brendan Gregg大神的,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:

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

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] <cmnd[3] <cmnd[4] <cmnd[5];cmnd_count = (cmd->cmnd[7] <cmnd[8];

所以这两个应该是scsi command的数量,在这里强制设为了0,可能是直接取消了后续的任何读写操作?还望大牛指点迷津啊!

总结

SystemTap果然是极其强大啊!当然对使用者要求也比较高,需要对内核本身的各种function,运作流程比较了解。使用中感觉它最大的优势是控制点非常细,几乎可以在任意的代码路径上注入想要的错误。如果我们能取得kernel panic时候的call stack,就可以准确地在原本出错的函数返回处注入错误,以此来进行bug的修复验证,而不用长时间反复跑压力测试来期望硬件问题的复现,可以极大地提高效率!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值