使用SYSCALL_DEFINE0实现自定义系统调用的内核模块编译报未定义符号错误
报错根因
本次编译出现的4个未定义符号(enter_syscall_print_funcs、event_class_syscall_enter、exit_syscall_print_funcs、event_class_syscall_exit),均是内核开启CONFIG_FTRACE_SYSCALLS配置时,SYSCALL_DEFINE系列宏自动关联的系统调用追踪内部钩子。
上述钩子函数属于内核trace子系统的内部实现,没有通过EXPORT_SYMBOL导出为外部可加载模块可访问的接口,在独立模块中使用SYSCALL_DEFINE0定义系统调用时,modpost模块检查阶段会因找不到这些未导出符号,直接中断编译流程。
解决办法
优先方案:绕开SYSCALL_DEFINE宏的自动逻辑
放弃在外部模块中使用SYSCALL_DEFINE0定义系统调用的写法,通过手动修改系统调用表的方式注册自定义系统调用,从根源上避开宏展开带来的未定义符号引用:
- 移除原代码中的
SYSCALL_DEFINE0代码块,将系统调用逻辑改写为普通的内核静态函数:
static long testsyscall_handler(void) { printk(KERN_INFO "Test\n"); return 0; }
- 模块初始化阶段,通过
kallsyms_lookup_name接口获取未导出的sys_call_table内核符号地址,按x86平台要求临时关闭页表写保护,将系统调用号436对应的表项值替换为testsyscall_handler的函数地址;模块卸载时恢复该表项的原始值。 - 补全原代码的内存回收逻辑:在
cleanup_module中调用kfree(addr),避免模块卸载后出现永久内核内存泄漏。
该方案无需重新编译内核,Makefile保持原有配置即可正常完成编译。
备选方案:将系统调用编入内核镜像
如果必须使用SYSCALL_DEFINE系列宏的原生行为,可以将自定义系统调用的代码移入内核核心源码目录,修改对应的系统调用表注册逻辑后重新编译、安装内核。该方式虽能解决符号链接问题,但流程繁琐,调试效率极低,仅适合长期运行的定制化内核场景。
风险提示
- 不要尝试通过硬编码地址、运行时符号解析的方式强行链接上述trace类内部符号,这类符号属于内核非公开实现,不同内核版本的内存布局、权限控制逻辑差异极大,强行引用会直接触发内核空指针异常、权限校验失效,导致系统崩溃。
- 修改
sys_call_table的操作必须临时关闭页表写保护位,操作完成后第一时间恢复原有写保护配置,避免破坏内核的内存安全防护机制。
内容的提问来源于stack exchange,提问作者BitFriends
相关产品推荐
相关产品推荐

