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) inode = file_inode(file); if (unlikely(inode->i_flctx && mandatory_lock(inode))) { //注掉selinux权限检查,直接返回0 return 0; |
解释
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 |
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步