让内核模块链接到的标准内核库在哪里?

问题描述 投票:0回答:3

内核模块无法调用 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

谢谢!

c linux linux-kernel kernel kernel-module
3个回答
8
投票

内核模块只能调用内核函数(在内核的固定部分)。他们不会也不能使用任何外部库。

所以没有内核标准库(它是内核本身包含

printk
)。

概念上,内核代码是 C 的 freestanding 方言;它不使用任何 C 标准库函数(由于不明原因,Linux 内核代码未使用

-ffreestanding
方言选项 编译为
gcc


2
投票

你得到一个 .ko 文件,它是一个内核目标文件。 GCC 不链接它。您插入它的内核会在运行时执行。


0
投票

问题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]  
© www.soinside.com 2019 - 2024. All rights reserved.