您的位置:首页 > 运维架构 > Linux

Linux驱动模块生成和加载分析

2015-07-23 15:59 435 查看


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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: