内核模块读取用户进程栈崩溃问题求助
问题原因分析与解决方案
你的内核模块崩溃的核心原因是内核态直接访问了用户进程的虚拟地址空间,这是Linux内核里严格禁止的操作,下面详细拆解原因和修复方法:
为什么直接访问会崩溃?
Linux系统中,内核空间和用户空间的地址空间是完全隔离的:
- 用户进程的栈地址(比如你拿到的
00007fffbd922e30)是用户虚拟地址,这个地址只在该用户进程的上下文里有效。 - 当你的内核模块运行时,处于内核地址空间,此时直接解引用用户虚拟地址,CPU会尝试用内核的页表去查找这个地址,显然找不到对应的物理页,就会触发页错误(Page Fault),也就是你看到的
BUG: unable to handle kernel paging request。
另外,用户进程的栈内存可能还存在内存保护机制(比如页权限、ASLR地址随机化),但最根本的原因还是地址空间隔离导致的直接访问无效。
正确的处理方式
内核提供了专门的函数来安全地和用户空间交换数据,读取用户空间内存应该使用copy_from_user()函数,它会:
- 检查用户地址的合法性(是否属于该进程的有效地址空间)
- 处理页表切换,安全地将用户空间的数据拷贝到内核空间
- 如果访问失败会返回错误码,不会直接导致内核崩溃
修改后的代码示例
#include <linux/kernel.h> #include <linux/module.h> #include <linux/sched.h> #include <linux/pid.h> #include <linux/uaccess.h> // 必须包含这个头文件来使用copy_from_user struct task_struct *ed_task; long int val; void *stack_p; int i = 0; static int __init init(void) { ed_task = pid_task(find_vpid(860), PIDTYPE_PID); if (!ed_task || !ed_task->mm) { printk(KERN_ERR "Failed to get task or mm struct\n"); return -EINVAL; } stack_p = (void *) ed_task->mm->start_stack; printk(KERN_INFO "stack %d: %p", i, stack_p); // 使用copy_from_user代替直接解引用 void *user_addr = stack_p - sizeof(void *); if (copy_from_user(&val, user_addr, sizeof(val)) != 0) { printk(KERN_ERR "Failed to read from user stack\n"); return -EFAULT; } printk(KERN_INFO "stack %d value: %ld", i, val); return 0; } static void __exit modex(void) { } module_init(init); module_exit(modex); MODULE_LICENSE("GPL");
额外注意事项
- 必须包含
<linux/uaccess.h>头文件才能使用copy_from_user()和copy_to_user()这类函数。 - 在访问
ed_task->mm之前,最好先检查指针是否为空,避免空指针引用。 - 如果你需要访问的是当前进程的用户空间,可以用
get_user()/put_user(),但这里你访问的是其他进程的地址空间,copy_from_user()更合适。
内容的提问来源于stack exchange,提问作者Wiktoria Lewicka
相关产品推荐
相关产品推荐

