内核模块编译报错:hci_uart.ko中func未定义的排查求助
问题背景
我正在修改一个基于Linux内核的大型仓库,简化后的文件结构如下:
├── asm │ ├── mytee.h ├── kernel │ ├── mytee.S ├── drivers │ ├── bluetooth | | ├── hci_uart.h | | ├── hci_h4.c │ ├── video | | ├── fbmem.c
mytee.h中声明的func函数在mytee.S中通过汇编实现:
ENTRY(func) // assembly code ENDPROC(func)
在hci_h4.c中调用func时出现链接错误:
ERROR: "func" [drivers/bluetooth/hci_uart.ko] undefined!
hci_uart.ko是hci_h4.c对应的内核模块,但func在fbmem.c中可正常调用,未修改前仓库编译正常,推测是链接环节问题。相关Kbuild片段:
obj-$(CONFIG_FB) += fb.o fb-y := fbmem.o fbmon.o fbcmap.o fbsysfs.o \ modedb.o fbcvt.o fb-objs := $(fb-y)
已完成的排查步骤:
- 确认
func已正确定义 hci_h4.c和fbmem.c均已包含mytee.h- 未发现两者的Makefile、Kbuild、Kconfig存在相关差异
- 两者对
func的调用方式完全一致 - 确认
fbmem.c和hci_h4.c在Makefile中晚于mytee.S被添加到drivers-y - 不存在同名文件导致符号混淆
我的疑问:
- C文件与汇编文件如何链接?未发现
mytee.h与mytee.S的引用关系 - 带Hypercall参数的函数工作机制是怎样的?
mytee.h未在任何Makefile中列出,是否会被编译?若未编译为何函数可被声明?hci_uart.ko出现在module.order但不在modules.builtin中,而fb.ko相反
我对C语言和Makefile较为陌生,可提供更多信息,请求协助排查问题。
问题根源分析与解决方案
核心错误原因
fb.ko是内置模块(出现在modules.builtin),会直接链接到内核镜像中;而hci_uart.ko是可加载模块(仅在module.order),需要单独链接。func定义在kernel/mytee.S中,属于内核核心部分:内置模块可直接引用内核核心的所有符号,但可加载模块默认只能访问内核导出的符号,这就是两者调用结果不同的原因。
疑问逐一解答
1. C文件与汇编文件的链接逻辑
- 汇编文件
mytee.S会被编译成目标文件(.o),最终链接到内核核心镜像中。 - C文件调用汇编函数时,只需通过
mytee.h声明函数原型(告知编译器函数的参数、返回值类型),编译阶段编译器生成符号引用,链接阶段再从内核镜像或导出符号表中查找定义。 mytee.h和mytee.S不需要在Makefile中建立直接引用关系,只要mytee.S被编译进内核核心,且C文件包含头文件声明即可。
2. 带Hypercall参数的函数工作机制
Hypercall是内核态向虚拟机监控器(VMM)发起的调用,这类函数的典型工作流程是:
- 汇编代码中将Hypercall的参数、调用号放入指定寄存器
- 执行特定指令(如x86的
vmcall、ARM的hvc)陷入VMM - VMM根据调用号处理对应逻辑,完成后返回内核态
- 汇编函数整理返回值,传递给调用的C函数
3. mytee.h是否需要被编译?
头文件不需要单独编译,它只是在C文件编译时通过#include被引入,提供函数声明、宏定义等信息,让编译器知道如何处理对func的调用。只要C文件包含了mytee.h,编译器就能识别函数原型,不会在编译阶段报错,链接阶段才会检查符号是否存在。
4. module.order与modules.builtin的区别
modules.builtin:记录内置模块,这类模块会被直接编译进内核镜像,启动时自动加载,可直接访问内核核心的所有符号。module.order:记录所有可编译的模块(包括内置和可加载),不在modules.builtin中的就是可加载模块,这类模块只能访问内核通过EXPORT_SYMBOL或EXPORT_SYMBOL_GPL导出的符号。
解决链接错误的两种方案
方案1:将hci_uart.ko改为内置模块
在对应的Kconfig配置中,将CONFIG_BT_HCI_UART的设置从m(编译为可加载模块)改为y(编译为内置模块),这样编译时会把它直接链接到内核镜像,即可正常访问func符号。
方案2:导出func符号
在mytee.S中为func添加符号导出声明(不同架构语法略有差异,以x86为例):
ENTRY(func) // assembly code ENDPROC(func) EXPORT_SYMBOL(func)
添加后,hci_uart.ko作为可加载模块就能通过内核导出符号表找到func的定义。
内容的提问来源于stack exchange,提问作者Temeraire

