树莓派5.10内核树内模块注入blkdev submit_bio函数崩溃求助
崩溃根因
- 你尝试修改的
struct block_device_operations实例默认存放于内核.rodata只读段,无写权限,直接赋值触发页写异常,对应日志中的虚拟地址访问错误。 - 5.10版本后多队列块层架构下,大部分IO路径默认直接走
blk_mq_submit_bio逻辑,不会调用块设备fops中的submit_bio回调,即使修改成功也无法拦截绝大多数IO请求。
可行的注入方案
方案1:修改request_queue的submit_bio回调(适配5.10+内核)
5.10版本struct request_queue中存在可拦截的submit_bio回调,修改该回调可覆盖绝大多数IO路径,修改前需先解除对应内存页的只读限制:
#include <linux/mm.h> #include <linux/blkdev.h> #include <linux/blk-mq.h> // 先声明原始回调 static blk_qc_t (*orig_submit_bio)(struct bio *bio); // 你的注入回调 static blk_qc_t injected_submit_bio(struct bio *bio) { pr_emerg("intercepted bio: sector=%llu, dir=%d\n", bio->bi_iter.bi_sector, bio_data_dir(bio)); return orig_submit_bio(bio); } // 注入逻辑 int inject_submit_bio(struct block_device *bdev) { struct request_queue *q = bdev_get_queue(bdev); unsigned long addr; int ret; if (!q) return -ENODEV; orig_submit_bio = q->submit_bio; if (!orig_submit_bio) return -EINVAL; // 解除submit_bio字段所在页的只读限制 addr = (unsigned long)&q->submit_bio; ret = set_memory_rw(PAGE_ALIGN_DOWN(addr), 1); if (ret) return ret; // 替换回调,替换过程可配合blk_freeze_queue/blk_unfreeze_queue保证SMP安全 WRITE_ONCE(q->submit_bio, injected_submit_bio); // 可选:改完恢复页只读权限,提升安全性 // set_memory_ro(PAGE_ALIGN_DOWN(addr), 1); return 0; } // 卸载时恢复回调 void restore_submit_bio(struct block_device *bdev) { struct request_queue *q = bdev_get_queue(bdev); unsigned long addr; if (!q || !orig_submit_bio) return; addr = (unsigned long)&q->submit_bio; set_memory_rw(PAGE_ALIGN_DOWN(addr), 1); WRITE_ONCE(q->submit_bio, orig_submit_bio); // set_memory_ro(PAGE_ALIGN_DOWN(addr), 1); }
注意:ARM架构下需确认内核开启CONFIG_SET_MEMORY_RW_RO配置,否则需手动修改页表属性。
方案2:使用Device Mapper封装(更稳定合规)
如果不需要侵入修改内核内置结构,可实现一个极简的DM target,将所有IO转发到后端块设备,在转发逻辑中完成自定义操作,该方案完全符合内核开发规范,无兼容性风险,适配所有5.0+内核版本。
注意事项
- 回调替换过程需先冻结队列,避免并发IO触发未定义行为
- 注入回调中不能随意修改bio的核心字段,除非你完全清楚块层处理逻辑
- 模块卸载前必须恢复原始回调,否则卸载后会触发Oops
内容的提问来源于stack exchange,提问作者yntnm
相关产品推荐
相关产品推荐

