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

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

Linux驱动模块生成和加载分析的相关文章

linux如何发现和加载对应的USB设备驱动

linux如何发现和加载对应的USB对应的设备驱动1.整体流程加载USB设备驱动-->注册USB设备(注册的USB设备结构体包含了USB设备的vender ID和product ID)USB设备插入后,匹配到vendor ID和product ID,即调用改USB注册结构里的probe函数,开始和USB设备进行通信.2.USB设备结构体static struct usb_driver test_usb_driver={.OWNER = THIS_MODULE,.name = "test U

以菱形链接(diamond link)为例,探讨Linux下连接器和加载器对Shared libarary兼容性的处理

1. 什么是菱形链接(diamond link) 菱形链接(diamond link)(参考文献 1)能十分清楚的描述出我们要讨论的问题. 如上图所示,我们的程序将要使用某厂家的共享库libvendor1.so,同时也要使用另外一个厂家的共享库libvendor2.so. libvendor1.so和libvendor2.so都将使用某知名开源共享库libopensource.so.xxx(xxx表示版本). 但是这两个厂家提供给我们的都是自己编译维护的libopensource.so.xxx.

VS2008 动态库和静态库的生成和加载

第一:动态库和静态库的生成: 1) 新建一个生成dll工程: 文件->新建->项目->Win32->Win32控制台应用程序 输入项目名称:dllTest ,项目路径:D:\VC 确定 下一步 应用程序类型:选择DLL 完成 2) 编写代码: 2-1) 增加一个头文件:dllTest.h #define ICILIB_UTIL_API __declspec(dllexport)  //生成动态库时,同时在Debug或Release目录下生成.lib和.dll文件. class IC

camer驱动模块加载分析

在平时工作中,camera模块是经常进行调试修改的模块,所以熟悉camera的工作流程以及工作原理将会大大的提供工作效率,但对于整个android系统camera是个十分复杂的模块,下面对camera的驱动加载进行分析. 1. Camera成像简介 景物通过镜头(LENS)生成的光学图像投射到图像传感器(Sensor)表面上,然后转为模拟的电信号,经过 A/D(模数转换)转换后变为数字图像信号,再送到数字信号处理芯片(DSP)中加工处理,再通过 IO 接口传输到 CPU 中处理,通过 LCD 就

简单的Linux 驱动模块编译,加载过程

简单的Linux 驱动模块编译,加载过程 2010-03-14 14:48:24|  分类: Driver |  标签: |字号大中小 订阅 本文记录我的第一个Linux设备驱动程序的编译过程.遇到问题的解决方法. 环境:2.4.18-14的内核,Linux内核源码:2.4.18.       Linux内核源码路径:/usr/src/linux(这个源码是从kernel.org网站download的2.4.18版本)        按照<linux设备驱动开发详解>一书中的步骤实现经典例子&

linux中mmap系统调用原理分析与实现

参考文章:http://blog.csdn.net/shaoguangleo/article/details/5822110 linux中mmap系统调用原理分析与实现 1.mmap系统调用(功能)      void* mmap ( void * addr , size_t len , int prot , int flags ,int fd , off_t offset )      内存映射函数mmap, 负责把文件内容映射到进程的虚拟内存空间, 通过对这段内存的读取和修改,来实现对文件的

u-boot中链接地址和加载地址的相关知识

以zc702开发板的u-boot为例 链接地址(运行地址):链接地址是在程序编译链接阶段就确定好的地址. u-boot的链接脚本由CONFIG_SYS_LDSCRIPT宏定义来指定,如在zynq_common.h当中有如下代码: #define CONFIG_SYS_LDSCRIPT "arch/arm/cpu/armv7/zynq/u-boot.lds" 在该链接脚本中指定了u-boot中各部分的链接顺序.同时zynq_common.h中的CONFIG_SYS_TEXT_BASE则指

linux实践之ELF文件分析

linux实践之ELF文件分析 下面开始elf文件的分析. 我们首先编写一个简单的C代码. 编译链接生成可执行文件. 首先,查看scn15elf.o文件的详细信息. 以16进制形式查看scn15elf.o文件. 查看scn15elf.o中各个段和符号表的信息. 各个段的详细信息如下. 符号表的信息如下: 使用readelf命令查看各个段的详细信息: 段表信息如下: 符号表信息如下: 下面让我们开始分析文件头吧! 由于我的虚拟机是32位的,我下面就主要以32位的系统进行分析,就不比较32位机和64

Linux SCSI回调IO的分析

本文转载自:http://blog.csdn.net/xushiyan/article/details/6941640,如需参考,请访问原始链接地址. 没找到如何转载的入口,只好全文copy了. -----------------------------------分割线------------------------------------------------- LINUX 内核中 SCSI 子系统由 SCSI 上层,中间层和底层驱动模块 [1] 三部分组成,主要负责管理 SCSI 资源和