18

我在 Ubuntu 工作。我正在尝试制作两个使用彼此功能的内核模块。我的问题是我得到了正确编译的模块,但是其中一个没有解析符号。

为简单起见,我们将这些模块称为m1m2

m2 是导出功能void func_m2(void)m1正在调用此函数。两个模块都能正确编译。

全部编译完成后,我需要先加载m2模块(因为它具有导出func_m2功能),然后再加载m1模块。所以,让我们实现它:

volodymyr@sv1:~/development/kmodules/m2$ sudo insmod ./m2.ko

现在,让我们加载m1正在尝试使用的模块func_m2

volodymyr@sv1:~/development/kmodules/m1$ sudo insmod ./m1.ko
insmod: error inserting './m1.ko': -1 Unknown symbol in module

以下是我在日志中看到的内容:

volodymyr@sv1:~/development/kmodules/m1$ dmesg | tail
[ 3938.166616] Loading m2 module ...
[ 3963.078055] m1: no symbol version for func_m2
[ 3963.078059] m1: Unknown symbol func_m2

因此,似乎对符号的引用func_m2没有解决。有趣的。让我们检查它是否存在于符号表中:

volodymyr@sv1:~/development/kmodules$ cat /proc/kallsyms | grep 'func_m2'
ffffffffa00530d0 r __ksymtab_func_m2    [m2]
ffffffffa00530e8 r __kstrtab_func_m2    [m2]
ffffffffa00530e0 r __kcrctab_func_m2    [m2]
ffffffffa0053000 T func_m2      [m2]
000000004edd543f a __crc_func_m2        [m2]

如您所见,func_m2实际上存在于符号表中。那么为什么m1加载不出来呢?

我已经为我的内核和 Linux 源正确安装了 Linux 头文件。我没有对内核进行任何修改,它没有被修改,它的版本是:2.6.31-16-generic(我运行 x64)

现在,为了向您展示全貌,我将源代码和 Makefile 我用于此测试的源代码和 Makefile 放在m1这里m2

m1模块:

m1.c:

#include <linux/module.h>
#include <linux/kernel.h>

extern void func_m2(void);

int hello_start(void)
{
    printk(KERN_INFO "Loading m1 module ...\n");

    func_m2();

    return 0;
 }

 void hello_end(void)
 {
    printk(KERN_INFO "Unloading m1 ...\n");
 }

module_init(hello_start);
module_exit(hello_end);

MODULE_LICENSE("GPL");

生成文件:

obj-m := m1.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

m2模块:

m2.c:

#include <linux/module.h>
#include <linux/kernel.h>

int hello_start(void)
{
    printk(KERN_INFO "Loading m2 module ...\n");

    return 0;
}

void hello_end(void)
{
    printk(KERN_INFO "Unloading m2 ...\n");
}

void func_m2(void)
{
    printk(KERN_INFO "This a function in m2\n");
}

module_init(hello_start);
module_exit(hello_end);

MODULE_LICENSE("GPL");
EXPORT_SYMBOL(func_m2);

生成文件:

obj-m := m2.o
export-objs := m2.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

基本上我的问题是:为什么不能m1加载?

如果有人能回答会很有帮助。

4

3 回答 3

8

当您构建 m2 时,它会创建一个Module.symvers文件。

将此文件复制到您正在构建 m1 的位置。然后制作 m1,并对其进行安装。

您之前在构建 m1 时可能会收到警告,例如:

警告:“func_m2”[/tmp/m1/m1.ko] 未定义!

一旦您使用Module.symvers来自 m2 模块,这应该会消失。

来自http://www.kernel.org/doc/Documentation/kbuild/modules.txt

--- 6.2 符号和外部模块

构建外部模块时,构建系统需要访问内核中的符号以检查是否定义了所有外部符号。这是在 MODPOST 步骤中完成的。modpost 通过从内核源代码树中读取 Module.symvers 来获取符号。如果正在构建外部模块的目录中存在 Module.symvers 文件,则该文件也将被读取。在 MODPOST 步骤中,将写入一个新的 Module.symvers 文件,其中包含所有未在内核中定义的导出符号。

这也值得一读,来自同一个文件:

--- 6.3 来自另一个外部模块的符号

有时,外部模块使用来自另一个外部模块的导出符号。kbuild 需要完全了解所有符号,以避免发出有关未定义符号的警告。针对这种情况存在三种解决方案。

注意:建议使用顶级 kbuild 文件的方法,但在某些情况下可能不切实际。

使用顶级 kbuild 文件 如果你有两个模块,foo.ko 和 bar.ko,其中 foo.ko 需要来自 bar.ko 的符号,你可以使用一个通用的顶级 kbuild 文件,这样两个模块在同一个编译建造。考虑以下目录布局:

./foo/ <= 包含 foo.ko ./bar/ <= 包含 bar.ko

顶级 kbuild 文件将如下所示:

$ ./Kbuild (或 ./Makefile): obj-y := foo/ bar/

并执行

$ make -C $KDIR M=$PWD

然后将执行预期并编译两个模块,并充分了解任一模块的符号。

使用额外的 Module.symvers 文件 构建外部模块时,会生成一个 Module.symvers 文件,其中包含所有未在内核中定义的导出符号。要从 bar.ko 访问符号,请将 Module.symvers 文件从 bar.ko 的编译复制到构建 foo.ko 的目录。在模块构建期间,kbuild 将读取外部模块目录中的 Module.symvers 文件,当构建完成时,会创建一个新的 Module.symvers 文件,其中包含所有已定义符号的总和,而不是内核的一部分。

使用“make”变量 KBUILD_EXTRA_SYMBOLS 如果从另一个模块复制 Module.symvers 不切实际,您可以在构建文件中为 KBUILD_EXTRA_SYMBOLS 分配一个以空格分隔的文件列表。这些文件将在 modpost 的符号表初始化期间加载。

于 2015-11-15T11:00:35.573 回答
4

以下是我在您的代码中发现的一些问题:

(一个)。您的初始化和终止函数应声明为静态并正确标识。例如,在 m1.c -

static int __init hello_start(void)
{
     printk(KERN_INFO "Loading m1 module ...\n");

    func_m2();

    return 0;
}

static void __exit hello_end(void)
{
    printk(KERN_INFO "Unloading m1 ...\n");
}

对 m2.c 重复此操作

(b)。使用相同的 Makefile 一起构建两个模块。我敢打赌,如果您仔细查看现有 Makefile for m1.c 的输出,您会看到一条警告,表明 func_m2() 未定义。无论如何,合并后的 Makefile 应该看起来像 -

SRCS   = m1.c m2.c
OBJS   = $(SRCS:.c=.o)

obj-m += $(OBJS)

EXTRA_CFLAGS = -O2


all:
    $(MAKE) -C /lib/modules/`uname -r`/build M=$(PWD) modules

clean:
    $(MAKE) -C /lib/modules/`uname -r`/build M=$(PWD) clean
    $(RM) Module.markers modules.order

在构建两个模块后,在为“m1.ko”发出 insmod 之前在“m2.ko”上运行 insmod。通过 dmesg 检查结果。

另外,在这里我假设 m1.c 和 m2.c 都在同一个目录中。即使它们在不同的目录中,这种技术也可以工作,但会很混乱。如果它们位于不同的目录中,请执行以下操作。

我做了很少的研究,并找到了一种在单独的目录中构建模块的方法。我使用的示例比您的示例要简单得多,但也许它具有适应性。

我在名为ExportSymbol的目录中有以下文件清单...

$ ls -CFR
.:
include/  Makefile  mod1/  mod2/

./include:
m2_func.h

./mod1:
Makefile  module1.c

./mod2:
Makefile  module2.c

m2_func.h 显示为:

#ifndef M2_FUNC_H
#define M2_FUNC_H

void m2_func(void);

#endif

顶级 Makefile 显示为:

obj-y := mod1/ mod2/

all:
    $(MAKE) -C /lib/modules/`uname -r`/build M=$(PWD) modules

clean:
    $(MAKE) -C /lib/modules/`uname -r`/build M=$(PWD) clean
    $(RM) Module.markers modules.order

位于 mod1/ 中的 Makefile 和 module1.c 显示为:

SRCS   = module1.c
OBJS   = $(SRCS:.c=.o)

obj-m += $(OBJS)

EXTRA_CFLAGS += -I${PWD}/include

all:
    $(MAKE) -C /lib/modules/`uname -r`/build M=$(PWD) modules

clean:
    $(MAKE) -C /lib/modules/`uname -r`/build M=$(PWD) clean
    $(RM) Module.markers modules.order

#include <linux/module.h>
#include <linux/kernel.h>

static int __init hello_start(void)
{
 printk(KERN_INFO "Loading m1 module ...\n");

 m2_func();

 return 0;
}

static void __exit hello_end(void)
{
 printk(KERN_INFO "Unloading m1 ...\n");
}

module_init(hello_start);
module_exit(hello_end);

MODULE_LICENSE("GPL");

位于 mod2/ 中的 Makefile 和 module2.c 显示为:

SRCS   = module2.c
OBJS   = $(SRCS:.c=.o)

obj-m += $(OBJS)

EXTRA_CFLAGS += -I${PWD}/include

all:
    $(MAKE) -C /lib/modules/`uname -r`/build M=$(PWD) modules

clean:
    $(MAKE) -C /lib/modules/`uname -r`/build M=$(PWD) clean
    $(RM) Module.markers modules.order

#include "m2_func.h"
#include <linux/module.h>
#include <linux/kernel.h>

static int __init hello_start(void)
{
 printk(KERN_INFO "Loading m2 module ...\n");

 return 0;
}

static void __exit hello_end(void)
{
 printk(KERN_INFO "Unloading m2 ...\n");
}

void m2_func(void)
{
 printk(KERN_INFO "This a function in m2\n");
} 

module_init(hello_start);
module_exit(hello_end);

MODULE_LICENSE("GPL");
EXPORT_SYMBOL(m2_func);

注意:我不能使用您的 makefile,因为它会为每个 c 文件生成 *.ko。Makefile 正在完成它的工作。“ko”文件是内核对象文件;每个 .c 源文件都有一个。没有办法解决这个问题。如果您不想要多个 ko 文件,请将所有代码放在一个源文件中。

于 2015-11-16T05:39:29.167 回答
0

最简单的方法是同时为两个模块创建一个 Makefile

例子 :

 obj-m += hello.o
 obj-m += world.o
 
 all:
      make -C /lib/modules/`uname -r`/build M=$(PWD) modules
install: 
      make -C /lib/modules/`uname -r`/build M=$(PWD) modules_install
clean:
      make -C /lib/modules/`uname -r`/build M=$(PWD) clean

第一个模块 hello.c

#inluce  <linux/module.h>
#include <linux/init.h>

int hello_print(){
 printk("<0> Hello -> %s : %d",__FUNCTION__,__LINE__);
return 0;
}
EXPORT_SYMBOL(hello_print),

static int __init open(void)
{
    printk("<0> Module Hello Start!");
    return 0;
}

static void __exit close(void) {
    printk("<0> Module Hello Stop!");
}

module_init(open);
module_exit(close);
MODULE_LICENSE("GPL v2");

第二个模块 world.c

#inluce  <linux/module.h>
#include <linux/init.h>

extern int hello_print();

static int __init open(void)
{
    printk("<0> Init World Module");
    printk("<0> Call hello_print() from Hello Module");
    hello_print();
    return 0;
}

static void __exit close(void) {
    printk("<0> Module Hello Stop!");
}

module_init(open);
module_exit(close);
MODULE_LICENSE("GPL v2");

然后

$ make 
$ insmod hello.ko
$ insmod world.ko
于 2021-05-09T05:51:30.720 回答