16 驱动模块的符号表与符号导出

时间:2022-06-08 06:07:47

查看elf文件的信息
readelf test.ko -a

ko文件组成:

1). elf文件头
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
...

2). 记录各个段的信息
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 000000 00 AX 0 0 1
[ 2] .init.text PROGBITS 00000000 000034 00004c 00 AX 0 0 4
...

3). 各个段的具体内容


4). Symbol table
Symbol table '.symtab' contains 64 entries:
Num: Value Size Type Bind Vis Ndx Name
...
56: 00000000 0 NOTYPE GLOBAL DEFAULT UND gpio_free
57: 00000000 76 FUNC GLOBAL DEFAULT 2 init_module
58: 00000000 0 NOTYPE GLOBAL DEFAULT UND gpio_direction_output
59: 00000000 0 NOTYPE GLOBAL DEFAULT UND printk
60: 00000000 0 NOTYPE GLOBAL DEFAULT UND __gpio_set_value
61: 00000000 0 NOTYPE GLOBAL DEFAULT UND gpio_request

//注意 UND标识的意思是此驱动模块里使用了这些函数,但这些函数的函数体并不在驱动模块里的,所以它们的函数地址还不确定的,现只存为0地址. 这些UND标识函数会由内核在加载驱动模块时在内核符号表里查找到它们的函数地址,并替换驱动模块里的函数地址.

//注意,UND标识的函数在内核符号表里都是”T”, 表示是全局函数. 也就是说只有全局函数,内核才会帮我们把相应的函数地址转换好.

///////////////////////////////////////////////////////////////
驱动模块里默认情况下不管是函数还是全局变量都是作局部使用(相当于在函数/变量名前加了”static”).
如果需要作为全局使用,需要使用导出符号”EXPORT_SYMBOL(函数名/变量)”,声明函数/变量为全局使用.

如实现一个内核里的全局函数”myfunc”,在其它驱动模块里调用.

myfunc.c:

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

void myfunc(char *str)
{
printk("in myfunc: %s\n", str);
}

EXPORT_SYMBOL(myfunc);

MODULE_LICENSE("GPL");

编译加载模块后,可以内核符号表里查看到myfunc函数:

    cat /proc/kallsyms | grep myfunc
输出的内容:
bf081000 T myfunc [myfunc]

调用myfunc的test模块:
test.c:

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

extern void myfunc(char *);

static int __init test_init(void)
{
myfunc("test init");
return 0;
}

static void __exit test_exit(void)
{
myfunc("test exit");
}

module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");

编译后,加载和卸载test驱动模块时都会调用到myfunc函数.
//需注意加载驱动模块的顺序, 如果myfunc驱动模块不先加载,则test模块会加载不成功.