内核模块无法调用 libc,因为 libc 运行在用户空间下。
还有一些其他内核指定的 API,就像 printk() 一样,可以使模块正常工作。
据我了解,libc 是几个标准 c 函数 obj(s) 的集合。
它应该存在一个集合(或库)来包含几个内核标准函数对象。
所以我可以将我的内核模块与这些内核标准库链接起来工作,对吗?
简而言之,我的问题如下...
在用户空间:
aaa.o 链接 bbb.o 调用 myfunc()
aaa.o 链接 libc.so 以调用 printf()
在内核空间中:
aaa.ko 链接 bbb.ko 调用 myfunc() ?这是问题 1
aaa.ko 链接 xxx 调用 printk() ? xxx 叫什么,问题 2
谢谢!
内核模块只能调用内核函数(在内核的固定部分)。他们不会也不能使用任何外部库。
所以没有内核标准库(它是内核本身包含
printk
)。
概念上,内核代码是 C 的 freestanding 方言;它不使用任何 C 标准库函数(由于不明原因,Linux 内核代码未使用
-ffreestanding
方言选项 编译为 gcc
)
你得到一个 .ko 文件,它是一个内核目标文件。 GCC 不链接它。您插入它的内核会在运行时执行。
问题2
aaa.ko 链接 xxx 调用 printk() ? xxx叫什么
这仍然是动态运行时重定位,就像在用户空间中一样,只是没有映射到库中。因此动态链接器
ld.so
不处理重定位,而是由 insmod(2)
函数处理。实际上insmod(2)
只是在.ko中映射并将请求传递给系统调用,sys_init_module
.
在
/include/linux/syscall.h
中可以看到init_module
是在kernel/module.c
中定义的。我正在使用5.4.x
版本作为参考。如果你浏览代码,你会看到一系列这样的调用:init_module() -> load_module() -> simplify_symbols() -> lookup_symbol()
.
因为 .ko 是一个 ELF 对象,它有一个符号表和重定位部分,就像任何用户空间 ELF 二进制文件一样。
Simplify_symbols
遍历模块的符号表并在运行时解析可重定位符号,此时可以知道地址。
对于用户态进程,必须将库映射到进程内存中。对于内核模块,则相反。该模块被映射到所有函数都存在的内核内存中,它们只需要被解析/找到。要解决的问题是,内核在固定位置有一个符号表,在其内存的早期,不受 ASLR 的影响。
Lookup_symbol
使用符号名称搜索内核符号表,并解析其地址。
这是对过程的非常简短和简化的描述。您需要掌握的只是
insmod
对init_module
进行系统调用,它通过搜索内核符号表在运行时解析函数地址。然后将模块代码中的地址固定为真实地址(或更可能是地址的偏移量)。
问题一
aaa.ko 链接 bbb.ko 调用 myfunc()
当一个模块被加载到内存中时,它就成为内核代码的一部分。在模块中每次使用
EXPORT_SYMBOL()
宏都会将这些符号添加到内核符号表中。所以 bbb.ko
会包含 EXPORT_SYMBOL(myfunc)
行,这导致它在加载模块时被添加到内核符号表中。当模块 aaa.ko
必须解决 myfunc()
时,它将像问题 2 中描述的过程一样这样做。
人为的例子
内核核心 exports 默认情况下对大多数内核开发人员有用的许多有用的函数和变量,例如您提到的
printk
。 Printk
在 kernel/printk/printk.c
中定义,您可以看到紧跟在函数之后的是 EXPORT_SYMBOL(printk)
,因为所有符号都必须显式全局导出。你可以这样验证它:
$sudo grep -E '\bprintk\b' /proc/kallsyms
<some address> T printk
用导出函数 myfunc 加载假设的
bbb.ko
模块后,您还可以在内核符号表中找到它:
$sudo grep -E '\bmyfunc\b' /proc/kallsyms
<some address> T myfunc [bbb]