Linux驱动模块生成和加载分析
0x00 Hello World
先奉上本文需要分析的例子,这里以Hello World程序作为例子来分析吧:
hello.c
#include <linux/init.h> #include <linux/kernel.h> 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
时间: 2024-10-13 09:23:06