linux 为什么编译为模块,Linux驱动模块生成和加载分析

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

你可能感兴趣的