Kernel之MODVERSION
Kernel之MODVERSION
官方说明
关于modversion
,内核文档官方解释(Documentation/kbuild/modules.rst
):
6 Module Versioning
Module versioning is enabled by the CONFIG_MODVERSIONS tag, and is used
as a simple ABI consistency check. A CRC value of the full prototype
for an exported symbol is created. When a module is loaded/used, the
CRC values contained in the kernel are compared with similar values in
the module; if they are not equal, the kernel refuses to load the
module.Module.symvers contains a list of all exported symbols from a kernel
build.6.1 Symbols From the Kernel (vmlinux + modules)
During a kernel build, a file named Module.symvers will be generated. Module.symvers contains all exported symbols from the kernel and compiled modules. For each symbol, the corresponding CRC value is also stored. The syntax of the Module.symvers file is:: <CRC> <Symbol> <Module> <Export Type> <Namespace> 0xe1cc2a05 usb_stor_suspend drivers/usb/storage/usb-storage EXPORT_SYMBOL_GPL USB_STORAGE The fields are separated by tabs and values may be empty (e.g. if no namespace is defined for an exported symbol). For a kernel build without CONFIG_MODVERSIONS enabled, the CRC would read 0x00000000. Module.symvers serves two purposes: 1) It lists all exported symbols from vmlinux and all modules. 2) It lists the CRC if CONFIG_MODVERSIONS is enabled.
6.2 Symbols and External Modules
When building an external module, the build system needs access to the symbols from the kernel to check if all external symbols are defined. This is done in the MODPOST step. modpost obtains the symbols by reading Module.symvers from the kernel source tree. If a Module.symvers file is present in the directory where the external module is being built, this file will be read too. During the MODPOST step, a new Module.symvers file will be written containing all exported symbols that were not defined in the kernel.
6.3 Symbols From Another External Module
Sometimes, an external module uses exported symbols from another external module. Kbuild needs to have full knowledge of all symbols to avoid spitting out warnings about undefined symbols. Three solutions exist for this situation. NOTE: The method with a top-level kbuild file is recommended but may be impractical in certain situations. Use a top-level kbuild file If you have two modules, foo.ko and bar.ko, where foo.ko needs symbols from bar.ko, you can use a common top-level kbuild file so both modules are compiled in the same build. Consider the following directory layout:: ./foo/ <= contains foo.ko ./bar/ <= contains bar.ko The top-level kbuild file would then look like:: #./Kbuild (or ./Makefile): obj-m := foo/ bar/ And executing:: $ make -C $KDIR M=$PWD will then do the expected and compile both modules with full knowledge of symbols from either module. Use an extra Module.symvers file When an external module is built, a Module.symvers file is generated containing all exported symbols which are not defined in the kernel. To get access to symbols from bar.ko, copy the Module.symvers file from the compilation of bar.ko to the directory where foo.ko is built. During the module build, kbuild will read the Module.symvers file in the directory of the external module, and when the build is finished, a new Module.symvers file is created containing the sum of all symbols defined and not part of the kernel. Use "make" variable KBUILD_EXTRA_SYMBOLS If it is impractical to copy Module.symvers from another module, you can assign a space separated list of files to KBUILD_EXTRA_SYMBOLS in your build file. These files will be loaded by modpost during the initialization of its symbol tables.
意义及用途
主要可以进行简单的ABI一致性检查
模块版本控制(MODVERSION)用途:
- 它会捕获数据结构可见性发生的更改。如果模块可以更改不透明数据结构(例如不属于
KMI 的数据结构),则将在相应结构未来发生更改后出现异常。- 它会添加运行时检查,以避免意外加载 KMI 与内核不兼容的模块。(例如,日后不兼
容的新内核加载当前模块时。)与发生难以调试的后续运行时问题或内核崩溃相比,最
好使用它。- 就在某些卷积情况下确定 CONFIG_MODVERSIONS 可以捕获的 ABI 差异而言, abidiff
存在限制。
在Android系统上的应用
应用二进制接口(ABI)监控,使用dump_abi和diff_abi工具
参考Android内核,ABI 监控工具在 AOSP 中开发
(https://android.googlesource.com/kernel/build/+/refs/heads/master/abi/),使用 libabigail(https://sourceware.org/libabigail/) 生成和比较
相关使用
Module.symvers文件
Module.symvers
包含了kernel的所有的导出符号的CRC校验值,其是由genksym
命令生成
可以对比Module.symvers文件是否一致使用
nm
命令查看特定符号CRCnm <path to vmlinux>/vmlinux | grep __crc_<symbol name>
查看模块KO中的
modversions
信息:modprobe --dump-modversions xxx.KO
ABI合规性破坏原因及修复
最常见的ABI合规性破坏原因包括:
添加或删除了函数
更改了数据结构
添加配置选项
修复CRC不一致:
- 构建内核时,命令前添加
KBUILD_SYMTYPES=1
,这将为每个 .o 文件生成一个 .symtypes 文件 - 对比正常和不正常下对应的该.symtypes文件差异,并修复其不一致问题
注意: 最好在 diff
工具中启用换行。(例如,对于 vimdff
,使用 :set wrap
。)
相关代码分析
当CONFIG_MODVERSIONS
打开时,内核会在加载外部模块KO时, 去对比符号的CRC,
主要代码如下(kernel/module.c
):
static int check_version(const struct load_info *info,
const char *symname,
struct module *mod,
const s32 *crc)
{
Elf_Shdr *sechdrs = info->sechdrs;
unsigned int versindex = info->index.vers;
unsigned int i, num_versions;
struct modversion_info *versions;
/* Exporting module didn't supply crcs? OK, we're already tainted. */
if (!crc)
return 1;
/* No versions at all? modprobe --force does this. */
if (versindex == 0)
return try_to_force_load(mod, symname) == 0;
versions = (void *) sechdrs[versindex].sh_addr;
num_versions = sechdrs[versindex].sh_size
/ sizeof(struct modversion_info);
for (i = 0; i < num_versions; i++) {
u32 crcval;
if (strcmp(versions[i].name, symname) != 0)
continue;
if (IS_ENABLED(CONFIG_MODULE_REL_CRCS))
crcval = resolve_rel_crc(crc);
else
crcval = *crc;
if (versions[i].crc == crcval)
return 1;
pr_debug("Found checksum %X vs module %lX\n",
crcval, versions[i].crc);
goto bad_version;
}
/* Broken toolchain. Warn once, then let it go.. */
pr_warn_once("%s: no symbol version for %s\n", info->name, symname);
return 1;
bad_version:
pr_warn("%s: disagrees about version of symbol %s\n",
info->name, symname);
return 0;
}
static inline int check_modstruct_version(const struct load_info *info,
struct module *mod)
{
const s32 *crc;
/*
* Since this should be found in kernel (which can't be removed), no
* locking is necessary -- use preempt_disable() to placate lockdep.
*/
preempt_disable();
if (!find_symbol("module_layout", NULL, &crc, true, false)) {
preempt_enable();
BUG();
}
preempt_enable();
return check_version(info, "module_layout", mod, crc);
}