Linux驱动模块生成和加载分析
0x00 Hello World
先奉上本文需要分析的例子,这里以Hello World程序作为例子来分析吧:
hello.c
#include
#include
int __init hello_init(void)
{
printk(KERN_INFO "Hello world!\n");
return 0;
}
void __exit hello_exit(void)
{
printk(KERN_INFO "Hello module exit done!\n");
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("macwe");
MODULE_DESCRIPTION("This is a hello world module");
module_init(hello_init);
module_exit(hello_exit);
Makefile
obj-m:=hello.o
KDIR=/lib/modules/$(shell uname -r)/build
PWD=$(shell pwd)
all:
$(MAKE) -C KDIR M=$(PWD) modules
编译
我的编译环境如下:
Linux: Debian 8.0 x86_64
Kernel: 3.16.0-4-amd64
GCC: 4.9.2 (Debian 4.9.2-10)
运行Make后会生成以下文件:
➜ hello ls -al
total 352
drwxr-xr-x 3 root root 4096 Jul 22 22:10 .
drwx------ 20 root root 4096 Jul 22 22:19 ..
-rw-r--r-- 1 root root 196 Jul 22 21:26 .hello.ko.cmd
-rw-r--r-- 1 root root 12288 Jul 22 22:10 .hello.mod.c.swp
-rw-r--r-- 1 root root 36812 Jul 22 20:00 .hello.mod.o.cmd
-rw-r--r-- 1 root root 36670 Jul 22 21:26 .hello.o.cmd
drwxr-xr-x 2 root root 4096 Jul 22 21:26 .tmp_versions
-rw-r--r-- 1 root root 118 Jul 22 20:00 Makefile
-rw-r--r-- 1 root root 0 Jul 22 20:00 Module.symvers // 记录模块导出函数的信息
-rw-r--r-- 1 root root 392 Jul 22 21:26 hello.c
-rw-r--r-- 1 root root 3176 Jul 22 21:27 hello.ko
-rw-r--r-- 1 root root 660 Jul 22 20:00 hello.mod.c
-rw-r--r-- 1 root root 63936 Jul 22 20:00 hello.mod.o
-rw-r--r-- 1 root root 51024 Jul 22 21:26 hello.o
-rw-r--r-- 1 root root 28 Jul 22 21:26 modules.order
其中hello.ko就是编译好的二进制模块文件。注意有几个文件是隐藏文件,别忽略喽。
有的发行版本默认生成的模块文件是带调试信息的,我们暂时不关心调试信息,通过strip命令去掉先:
strip --strip-debug --strip-unneeded hello.ko
加载模块看一下效果:
➜ hello insmod hello.ko
➜ hello dmesg | tail -n 1
[21747.509290] Hello world!
➜ hello rmmod hello.ko
➜ hello dmesg | tail -n 2
[21747.509290] Hello world!
[21812.503243] Hello module exit done!
.*.cmd文件
该文件由scripts/Kbuild.include脚本创建,
0x01 hello.ko文件分析
基本信息:
➜ hello file hello.ko
hello.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=7a3200ea06f414895526a920e6d91375078446b7, not stripped
➜ hello modinfo hello.ko
filename: /root/hello/hello.ko
description: This is a hello world module
author: macwe
license: GPL
depends:
vermagic: 3.16.0-4-amd64 SMP mod_unload modversions
➜ hello readelf -h hello.ko
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 1960 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 19
Section header string table index: 16
.ko文件是elf格式的可重定位二进制文件,相当于给内核用的.o文件,所有代码和数据都是从0开始的,我们主要看看文件中区段。
区段
➜ hello readelf -SW hello.ko
There are 19 section headers, starting at offset 0x7a8:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .note.gnu.build-id NOTE 0000000000000000 000040 000024 00 A 0 0 4
[ 2] .text PROGBITS 0000000000000000 000064 000000 00 AX 0 0 1
[ 3] .init.text PROGBITS 0000000000000000 000064 000011 00 AX 0 0 1
[ 4] .rela.init.text RELA 0000000000000000 000718 000030 18 I 17 3 8
[ 5] .exit.text PROGBITS 0000000000000000 000075 00000e 00 AX 0 0 1
[ 6] .rela.exit.text RELA 0000000000000000 000748 000030 18 I 17 5 8
[ 7] .rodata.str1.1 PROGBITS 0000000000000000 000083 00002b 01 AMS 0 0 1
[ 8] .modinfo PROGBITS 0000000000000000 0000ae 00007f 00 A 0 0 1
[ 9] __versions PROGBITS 0000000000000000 000140 000080 00 A 0 0 32
[10] .data PROGBITS 0000000000000000 0001c0 000000 00 WA 0 0 1
[11] .gnu.linkonce.this_module PROGBITS 0000000000000000 0001c0 000258 00 WA 0 0 32
[12] .rela.gnu.linkonce.this_module RELA 0000000000000000 000778 000030 18 I 17 11 8
[13] .bss NOBITS 0000000000000000 000418 000000 00 WA 0 0 1
[14] .comment PROGBITS 0000000000000000 000418 00003a 01 MS 0 0 1
[15] .note.GNU-stack PROGBITS 0000000000000000 000452 000000 00 0 0 1
[16] .shstrtab STRTAB 0000000000000000 000452 0000b4 00 0 0 1
[17] .symtab SYMTAB 0000000000000000 000508 0001c8 18 18 13 8
[18] .strtab STRTAB 0000000000000000 0006d0 000047 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
这个ko中由19个区段,下面看看几个比较有用的区段
.gnu.linkonce.this_module
这个区段保存了一份 struct module结构体,参见hello.mod.c
// 来自 hello.mod.c
7 __visible struct module __this_module
8 __attribute__((section(".gnu.linkonce.this_module"))) = {
9 .name = KBUILD_MODNAME,
10 .init = init_module,
11 #ifdef CONFIG_MODULE_UNLOAD
12 .exit = cleanup_module,
13 #endif
14 .arch = MODULE_ARCH_INIT,
15 };
KBUILD_MODNAME
__versions
该区段记录使用到的内核函数的crc值,用来校验版本用,参见hello.mod.c
其他区段
区段
解释
.rela*
Elf64_Rela结构体
.rodata.str1.1
保存了全局的字符串常量
.modinfo
modinfo hello.ko看到的信息是这里的
.comment
编译器信息
.shstrtab
各区段的名称字符串
.strtab
符号字符串
0x02 模块加载
以下是模块加载的伪代码:
调用syscall: init_module后
// src/kernel/module.c
sys_init_module:->{
struct load_info info;
// copy模块文件到内核空间,info->hdr指向模块头部,info->len为模块的大小
copy_module_from_user(umod, len ,&info);
return load_module:->{
struct module *mod;
// 检查签名 [CONFIG_MODULE_SIG]
module_sig_check(info);
// 检查模块映象的文件头是否合法(magic,type,arch,shoff)
elf_header_check(info);
//加载模块的各区段,创建struct module
mod = layout_and_allocate:->{
// 重新定位代码和数据的地址。
mod = setup_load_info(info);
// 检查模块的合法性,比如:vermagic,license
check_modinfo(mod, info, 0);
// 继续初始化mod
layout_sections(mod, info);
layout_symtab(mod, info);
}
// 添加mod到全局模块链表中,并标记为MODULE_STATE_UNFORMED
add_unformed_module(mod);
......
//释放掉info,现在内容已经保存在mod中了
return do_init_module:->{
// 调用自己的模块初始化函数,既hello_init();
do_one_initcall(mod->init);
// 设置模块状态为MODULE_STATE_LIVE
}
}
}
0x03 模块卸载
卸载过程相对简单一些
调用syscall: delete_module后
// src/kernel/module.c
sys_delete_module:->{
struct module *mod;
// 查找模块
mod = find_module(name);
// 检查是否还有模块依赖关系
// 检查状态是否为MODULE_STATE_LIVE
// 状态设置为MODULE_STATE_GOING
// 调用 mod->exit(), 既hello_exit()
// 通知内核我要卸载了
// 释放内存
}
0x04 参考
http://stackoverflow.com/questions/17922234/meaning-of-version-info-in-mod-c-file-in-linux-kernel