工具代码中在遍历访问d_lru链表时安全起见本来应该是要加内核dcache_lru_lock锁保护的,但是由于内核未将该锁导出给模块使用,所以代码实现的时候无法加上dcache_lru_lock锁保护,因此存在因刚好访问了被删除的dentry而引起系统panic重启的风险,线上机器跑这个工具还是需要视情况谨慎评估。
# stap -L 'kernel.function("dput")'
kernel.function("dput@fs/dcache.c:641") $dentry:struct dentry*
SystemTap工具guru模式代码实现如下:
#cat dump_dentry_path.stp
#!/usr/bin/stap
%{
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <asm/segment.h>
#include <asm/uaccess.h>
#include <linux/buffer_head.h>
%}
global n_limit=0
%{
static char buf[PAGE_SIZE];
static char path[PATH_MAX];
%}
function get_devname:string(dev:string)
%{
char *devname = (char *)STAP_ARG_dev;
//snprintf(STAP_RETVALUE, MAXSTRINGLEN, ""%s"",devname);
snprintf(STAP_RETVALUE, MAXSTRINGLEN, ""%s"",kread(&devname));
%}
//function get_dentry_path:long(dentry:long,path:string)
function get_dentry_path:long(dentry:long)
%{
struct file *filp = NULL;
mm_segment_t oldfs;
int ret=-1;
unsigned int offset=0,len=0;
struct super_block *sb=NULL;
char *temp=NULL;
unsigned long long dcache_referenced_nr=0;
//char *path = NULL;
struct dentry *dentry = NULL;
dentry = (struct dentry *)STAP_ARG_dentry;
// path = (char *)STAP_ARG_path;
if(!dentry /*|| !path*/)
{
goto EXIT;
}
sb=dentry->d_sb;
//printk("d_sb->s_id=%srn",sb->s_id);
oldfs = get_fs();
set_fs(get_ds());
// filp = filp_open(path, O_WRONLY|O_CREAT, 0644);
filp = filp_open("/run/dump_dentry.txt", O_WRONLY|O_CREAT, 0644);
ret=PTR_ERR(filp);
if (!IS_ERR(filp)) {
// printk("sucess to create file /run/log.txtrn");
if(!list_empty(&sb->s_dentry_lru)) {
list_for_each_entry(dentry, &sb->s_dentry_lru, d_lru) {
if(dentry->d_sb != sb)
continue;
dcache_referenced_nr ;
memset(path, 0, PATH_MAX);
temp = dentry_path_raw(dentry, path,PATH_MAX);
if (!IS_ERR(temp)) {
len = strlen(temp) sizeof("rn");
if((offset len) >= PAGE_SIZE)
{
ret=filp->f_op->write(filp, buf, offset, &filp->f_pos);
offset = 0;
memset(buf,0,PAGE_SIZE);
}
offset = snprintf(buf offset, PAGE_SIZE-offset, "%srn", temp);
}
}
if(strlen(buf))
ret=filp->f_op->write(filp, buf, offset, &filp->f_pos);
printk("total unused dentry(%s)=%lldrn",sb->s_id,dcache_referenced_nr);
}
filp_close(filp,NULL);
}
set_fs(oldfs);
EXIT:
STAP_RETVALUE=ret;
%}
probe kernel.function("dput") {
DevName=get_devname(@1) //@1为stap命令行传递的参数1
if(DevName==$dentry->d_sb->s_id$ && !n_limit && $dentry)
{
n_limit = 1
//retval=get_dentry_path($dentry,@2)
retval=get_dentry_path($dentry)
printf("retval:%drn",retval)
exit()
}
}
测试步骤:
1. mount /dev/vdb1 /mnt/
2. for(( i=0; i <5000; i ));do echo "hello" >/mnt/file$i.txt;done
3. rm /mnt/file*.txt -rf
4.stap -g -v dump_dentry_path.stp vdb1
5. systemtap代码将unused dentry对应的文件路径保存到 /run/dump_dentry.txt:
cat /run/dump_dentry.txt
因为SystemTap运行时会关闭中断,而当调用file_open打开ext3/ext4文件系统的文件时内核接口函数
__find_get_block会检查是否关闭中断,如果关闭中断就BUG_ON函数触发panic,所以这里将生成的文件保存到tmpfs文件系统文件/run/dump_dentry.txt中。