linux debug code---记录文件操作(linux4.4)

背景

系统中一些文件丢失了,需要查明丢失的原因,对这个问题有很多方法可以debug,比如ftrace、fsnotify、在unlink、rename函数中加printk等方法,因为ftrace、fsnotify不能获取进程名,并且我们系统中printk默认是关闭的,所以采取了“内核记录文件,存储在磁盘中”的方法,以便异常复现后进行分析。

示例代码不依赖于任何平台,在节尾的一些解释中,比如access_ok,会基于arm64进行解释。

这篇文章记录下内核写文件的代码,以便以后复用。

代码

debug_record_file函数对/data/data、/data/app中的任意文件监控,并将信息存储在TMP_RECORD_FILE_NAME文件。
注意:该函数打开文件指针后,没有关闭,这是为了避免频繁打开、关闭影响性能,由于是debug代码,不关闭也不要紧。

struct file *tmp_debug_file = NULL;

#define TMP_RECORD_FILE_NAME "/data/local/tmp/filesysrecord"

#define TMP_DEBUG_BUF_LEN 256

loff_t tmp_debug_file_pos = 0;

int tmp_debug_file_record = 0;

static DEFINE_MUTEX(tmp_debug_file_lock);

void debug_record_file(char *action, const char __user *filename, int result)

{

         char buf[TMP_DEBUG_BUF_LEN];

         struct filename *tmp;

         int ret = 0;

         tmp = getname(filename);

         if (IS_ERR(tmp))

                   return;

         if(!action) {

                   putname(tmp);

                   return;

         }

         if (strstr(tmp->name, "/data/data") || strstr(tmp->name, "/data/app")) {

                   mutex_lock(&tmp_debug_file_lock);

                   if (!tmp_debug_file) {

                            tmp_debug_file = filp_open(TMP_RECORD_FILE_NAME, O_RDWR | O_CREAT | O_DSYNC | O_APPEND, 0777);

                            if ( IS_ERR(tmp_debug_file)) {

                                     printk("creat %s fail\n", TMP_RECORD_FILE_NAME);

                                     tmp_debug_file = NULL;

                                     mutex_unlock(&tmp_debug_file_lock); 

                                    putname(tmp);

                                     return;

                            }

                   }

                   memset(&buf[0], 0, TMP_DEBUG_BUF_LEN);

                   snprintf(&buf[0], TMP_DEBUG_BUF_LEN, "line%d: %s(%d) %s %s [%d]\n", tmp_debug_file_record, current->comm, current->pid, action, tmp->name, result);

                   buf[TMP_DEBUG_BUF_LEN-1] = '\0';

                   tmp_debug_file_record++;

                   //printk("my debug %s %d:vfs_write [%s]\n", __func__, __LINE__, &buf[0]);

                   ret = kernel_write(tmp_debug_file, (const char *)&buf[0], strlen(&buf[0]), &tmp_debug_file_pos);

                   if (ret < 0) {

                            printk("my debug %s %d:kernel_write error %d\n", __func__, __LINE__, ret);

                   }

                   mutex_unlock(&tmp_debug_file_lock);

         }

        

         putname(tmp);

         return;

}

比如监控删除文件操作:

SYSCALL_DEFINE1(unlink, const char __user *, pathname)

{

        int error;

        error = do_unlinkat(AT_FDCWD, pathname);

        debug_record_file("delete", pathname, error);

       return error;

}

TMP_RECORD_FILE_NAME文件输出格式: lineN:     进程名(pid)    文件操作    文件名      [操作结果]

line167: Binder:1370_4(2656) delete /data/app/FamilyGuard/oat/arm64/FamilyGuard.vdex [-2]

line168: Binder:1370_4(2656) open-creat /data/app/FamilyGuard/oat/arm64/FamilyGuard.vdex [19]

line169: Binder:1370_4(2656) delete /data/app/FamilyGuard/oat/arm64/FamilyGuard.odex.swap [-2]

line170: Binder:1370_4(2656) open-creat /data/app/FamilyGuard/oat/arm64/FamilyGuard.odex.swap [20]

line171: Binder:1370_4(2656) delete /data/app/FamilyGuard/oat/arm64/FamilyGuard.odex.swap [0]

line172: Binder:1370_4(2656) delete /data/app/FamilyGuard/oat/arm64/FamilyGuard.art [-2]

在开了selinux的系统上使用上面代码时,需要注意权限问题。我们没法提前知道调用文件系统接口的进程是谁,所以为了保证每个进程都有权限写TMP_RECORD_FILE_NAME文件,需要替换执行路径 kernel_write --> vfs_write --> rw_verify_area,比如改成rw_verify_area_debug:

int rw_verify_area_debug(int read_write, struct file *file, const loff_t *ppos, size_t count)
{
    struct inode *inode;
    loff_t pos;
    int retval = -EINVAL;

    inode = file_inode(file);
    if (unlikely((ssize_t) count < 0))
        return retval;
    pos = *ppos;
    if (unlikely(pos < 0)) {
        if (!unsigned_offsets(file))
            return retval;
        if (count >= -pos) /* both values are in 0..LLONG_MAX */
            return -EOVERFLOW;
    } else if (unlikely((loff_t) (pos + count) < 0)) {
        if (!unsigned_offsets(file))
            return retval;
    }

    if (unlikely(inode->i_flctx && mandatory_lock(inode))) {
        retval = locks_mandatory_area(inode, file, pos, pos + count - 1,
                read_write == READ ? F_RDLCK : F_WRLCK);
        if (retval < 0)
            return retval;
    }

    //注掉selinux权限检查,直接返回0

    return 0;
   // return security_file_permission(file,
              //  read_write == READ ? MAY_READ : MAY_WRITE);
}

解释

1、debug_record_file的第2个参数定义为const char __user *filename,是一个用户态地址。

2、该函数不在系统调用的子函数中调用的原因如下:

  • debug_record_file中用到了mutex锁,系统调用的子函数中一般也是有锁的,多个锁在一起需要考虑会不会引起死锁问题。
  • open等内部子函数中用的是dentry,只能通过denty->d_name.name获取到单级文件名,如果要获取全路径名,还需要通过dentry->d_parent来构造处全路径名。

3、kernel_write函数中会执行set_fs(KERNEL_DS)。

    kernel_write --> vfs_write --> access_ok(buf, count)会对地址空间进行检查,write系统调用是将用户态buf中的内容写入文件中,为了防止恶意访问内核地址空间,access_ok函数会检查buf+count是否在进程的地址空间中,即(u65)addr + (u65)size <= (u65)current->addr_limit + 1(见linux\arch\arm64\include\asm\uaccess.h文件access_ok --> __range_ok),addr_limit 即为进程的地址空间上限。

在示例代码中,buf属于内核空间,所以如果直接用vfs_write将buf内容写入文件,access_ok检查返回无效值0(buf+count超出了用户态地址空间范围),导致vfs_write失败。根据原理,只要修改current->addr_limit成内核地址空间上限即可,这是由set_fs(KERNEL_DS)实现的。
另说明一下,用户空间上限、内核空间上限,定义在linux\arch\arm64\include\asm\processor.h中:

#define KERNEL_DS        UL(-1)
#define USER_DS            ((UL(1) << MAX_USER_VA_BITS) - 1)

MAX_USER_VA_BITS定义在linux\arch\arm64\include\asm\memory.h中:

#ifdef CONFIG_ARM64_USER_VA_BITS_52
#define MAX_USER_VA_BITS    52
#else
#define MAX_USER_VA_BITS    VA_BITS
#endif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值