最近买了个手机乐视1pro,没有内核源码,想插入一些自己编译的模块来扩展内核,于是找到了硬件相近的一加2的内核源码,把编译得到的模块拷到手机上,发现无法加载。经过摸索,发现原因是内核开启了CONFIG_MODVERSIONS,但是没开启CONFIG_MODULE_FORCE_LOAD. 内核symbol有crc校验,不同源码树编译出来的内核,symbol的crc可能不一样。
要想编译出crc一致的模块,需要用到编译内核时生成的Module.symvers这个文件,厂商没有开放内核源码,更不可能提供这个文件给你了。不过,既然内核会对模块引用到的symbol验证crc,说明内核本身就保存有一份crc,我们只要提取出来就行了。
我们只要编写如下模块,就可完成提取工作:
symcrc.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/slab.h>
static char symcrc[256];
static ssize_t symcrc_write(struct class *cls, struct class_attribute *attr, const char *_buf, size_t _count)
{
const struct kernel_symbol *ksymbol;
char *symname;
struct module *owner;
const unsigned long *crc = NULL;
bool gplok = true;
bool warn = true;
symname = kstrndup(_buf,strlen(_buf)-1,GFP_KERNEL);
ksymbol = find_symbol (symname, &owner, &crc, gplok, warn);
if (ksymbol != NULL && crc != NULL)
snprintf (symcrc, ARRAY_SIZE(symcrc), "0x%lx\t%s\tvmlinux\tEXPORT_SYMBOL\n", *crc, ksymbol->name);
else{
symcrc[0] = '\0';
}
kfree(symname);
return _count;
}
static ssize_t symcrc_read(struct class *cls, struct class_attribute *attr, char *_buf)
{
return sprintf(_buf, "%s", symcrc);
}
static struct class *symcrc_class = NULL;
static CLASS_ATTR(writesym, 0220, NULL, symcrc_write);
static CLASS_ATTR(getcrc, 0440, symcrc_read, NULL);
int __init find_symbol_init (void)
{
int ret;
symcrc_class = class_create(THIS_MODULE, "symcrc");
if (IS_ERR(symcrc_class))
{
printk("Create class symcrc_class failed.\n");
return -ENOMEM;
}
ret = class_create_file(symcrc_class, &class_attr_getcrc);
ret = class_create_file(symcrc_class, &class_attr_writesym);
return 0;
}
void __exit
find_symbol_exit (void)
{
class_remove_file(symcrc_class, &class_attr_getcrc);
class_remove_file(symcrc_class, &class_attr_writesym);
class_destroy(symcrc_class);
symcrc_class = NULL;
}
module_init (find_symbol_init);
module_exit (find_symbol_exit);
MODULE_AUTHOR("Mr Pang");
MODULE_LICENSE ("GPL");
编译这个模块,可以看到自动生成的symcrc.mod.c
#include <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>
MODULE_INFO(vermagic, VERMAGIC_STRING);
struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
.arch = MODULE_ARCH_INIT,
};
static const struct modversion_info ____versions[]
__used
__attribute__((section("__versions"))) = {
{ 0x48f7bf25, __VMLINUX_SYMBOL_STR(module_layout) },
{ 0xeb6cd1a5, __VMLINUX_SYMBOL_STR(class_destroy) },
{ 0xe8efe986, __VMLINUX_SYMBOL_STR(class_remove_file) },
{ 0x276c87ab, __VMLINUX_SYMBOL_STR(class_create_file) },
{ 0x27e1a049, __VMLINUX_SYMBOL_STR(printk) },
{ 0x8637fc77, __VMLINUX_SYMBOL_STR(__class_create) },
{ 0x91715312, __VMLINUX_SYMBOL_STR(sprintf) },
{ 0x37a0cba, __VMLINUX_SYMBOL_STR(kfree) },
{ 0x28318305, __VMLINUX_SYMBOL_STR(snprintf) },
{ 0x8b58b0d, __VMLINUX_SYMBOL_STR(find_symbol) },
{ 0xaf6ae696, __VMLINUX_SYMBOL_STR(kstrndup) },
{ 0x98cf60b3, __VMLINUX_SYMBOL_STR(strlen) },
};
static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=";
里面的static const struct modversion_info ____versions[]列举出了本模块引用到的symbol以及对应的crc。因为这个模块使用的是一加内核源码树编译的,所以部分crc与1pro内核的不一致,以至于无法加载。
为了解决这个问题,我们另外写一个模块,尽可能少的直接引用内核的symbol,
firststep.c
#include <linux/module.h>
#include <linux/init.h>
const struct kernel_symbol *(*find_symbol1)(const char *name, struct module **owner, const unsigned long **crc, bool gplok, bool warn) = (const struct kernel_symbol *(*)(const char *name, struct module **owner, const unsigned long **crc, bool gplok, bool warn))0xffffffc00027cab4;
int (*printk1)(const char *fmt, ...) = (int (*)(const char *fmt, ...))0xffffffc000be3e94;
static const char *sym_array[] = {
"module_layout",
"class_destroy",
"class_remove_file",
"class_create_file",
"printk",
"__class_create",
"sprintf",
"kfree",
"up",
"snprintf",
"down",
"find_symbol",
"kstrndup",
"strlen",
};
int __init
find_symbol_init (void)
{
const char *name;
int index;
const struct kernel_symbol *ksymbol;
struct module *owner;
const unsigned long *crc;
bool gplok = true;
bool warn = true;
for (index = 0; index < ARRAY_SIZE (sym_array); index++)
{
name = sym_array[index];
ksymbol = find_symbol1 (name, &owner, &crc, gplok, warn);
if (ksymbol != NULL)
printk1 (" %s", ksymbol->name);
else
printk1 ("<0>Failed to find symbol %s\n", name);
if (crc != NULL)
printk1 ("*crc : 0x%lx\n", *crc);
else
printk1 ("\n");
}
return 0;
}
void __exit
find_symbol_exit (void)
{
}
module_init (find_symbol_init);
module_exit (find_symbol_exit);
MODULE_LICENSE ("GPL");
MODULE_AUTHOR("Mr Pang");
这个模块引用到的两个symbol是printk和find_symbol,我们通过其绝对地址来调用,就可以避免crc的问题了。绝对地址可以查看/proc/kallsyms获得,如果/proc/kallsyms地址全是0,可以echo 0 > /proc/sys/kernel/kptr_restrict
在我的手机上,find_symbol地址是0xffffffc00027cab4,printk地址是0xffffffc000be3e94.
static const char *sym_array[] 保存的是前一个模块symcrc引用到的symbol,可以查看symcrc.mod.c获得。
编译这个模块,查看自动生成的firststep.mod.c
#include <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>
MODULE_INFO(vermagic, VERMAGIC_STRING);
struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
.arch = MODULE_ARCH_INIT,
};
static const struct modversion_info ____versions[]
__used
__attribute__((section("__versions"))) = {
{ 0x48f7bf25, __VMLINUX_SYMBOL_STR(module_layout) },
};
static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=";
发现这个模块依然引用了一个symbol–“module_layout”.这个symbol在所有模块上都可以查到,所以并不要紧,只需要提取手机上的任意一个模块,通过命令modprobe --show-modversions xxx.ko就可以找到module_layout的crc。人工修改firststep.mod.c文件,然后人工编译firststep.mod.c并人工链接firststep.ko。具体命令可以在编译模块时指定V=1查看。就两行长长的命令,复制粘贴而已。
此时生成的firststep.ko就是可以加载的了。加载之后通过dmesg查看打印出的crc,同样人工修改symcrc.mod.c里面的crc,再人工编译出symcrc.ko.
插入symcrc.ko之后,可以通过以下命令提取内核的Module.symvers:
for i in `cat /proc/kallsyms|awk '{print $3}'`;do
echo $i > /sys/class/symcrc/writesym;
cat /sys/class/symcrc/getcrc >> /sdcard/Module.symvers;
done
拿到Module.symvers之后编译模块就方便多了。
首先要make clean
然后make nconfig选择需要编译的模块
然后通过make modules KBUILD_EXTRA_SYMBOLS=/path/to/Module.symvers
编译模块,生成的模块就是带有正确crc的模块,可以直接载入。