实践二——模块的建立

Posted 20135302魏静静

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实践二——模块的建立相关的知识,希望对你有一定的参考价值。

内核模块编程

  • 内核模块的基本结构

linux内核模块的程序结构有:模块加载函数(必须),模块卸载函数(必须),模块许可证声明(必须),模块参数(可选),模块导出符号(可选),模块作者的等信息声明(可选)。

一个内核模块应该至少包含两个函数。一个“开始”(初始化)的函数被称为init_module(),当内核模块被insmod 加载时被执行,还有一个“结束”(要完成与模块加载函数相反的功能)的函数被称为cleanup_module() ,当内核模块被rmmod 卸载时被执行。实际上,从内核版本2.3.13 开始我们就可以为开始和结束函数起任意的名字了。这可以通过宏module_init()module_exit()实现,需要注意的地方是函数必须在宏的使用前定义,否则会有编译错误。

模块许可证声明描述内核模块的许可权限,格式为MODULE_LICENSE("Dual BSD/GPL")Linux可接受的 LICENSE 包括”GPL","GPL v2""GPL and additional rights""Dual BSD/GPL""Dual MPL/GPL""Proprietary"。可以不加,则系统默认。如果不声明 LICENSE ,模块被加载时,将收到内核的警告。

模块参数是“模块被加载的时候可以被传递给模块的值”,它本身对应模块内部的全部变量。 可以使用module_param(参数名,参数类型,/写权限)为模块定义一个参数。

内核模块可以导出符号(symbol,对应与函数或变量),这样其他模块可以使用本模块中的变量和函数。/proc/kallsyms文件对应这内核符号表,它记录了符号以及符号符号所在的内存地址。

模块可以使用如下宏导出符号到内核符号表:

    EXPORT_SYMBOL(符号名);

    EXPORT_SYMBOL_GPL(符合名);    //只是用于GPL许可权模块。

导出的符合将可以被其他模块使用,使用前声明以下既可以。

模块作者的等信息声明:

MODULE_AUTOR("作者信息");

MODULE_DESCRIPTION("模块描述信息");

MODULE_VERSION("版本信息");

MODULE_ALIAS("别名信息");

MODULE_DEVICE_TABLE("设备表信息");

对于USB,PCI等设备驱动,通常会创建一个MODULE_DEVICE_TABLE,表示驱动所支持的设备列表。

  • 编写内核模块的基本步骤

1、根据自己的需求编写内核模块源代码

2、将源代码进行编译,生成.ko文件

在编译内核模块时需要用到Makefile

obj-m :=syscall.o myname.o procadd.o fortune.o

PWD := $(shell pwd)

KDIR:=/lib/modules/3.0.0-17-generic/build

all:

make -C $(KDIR) M=$(PWD) modules

clean:

make -C $(KDIR) M=$(PWD) clean

obj-m:这个变量是指定要编译的模块

KDIR:这是我们正在运行的操作系统内核编译目录,也就是编译模块需要的环境

PWD:这是当前工作路径,$(shell )make的一个内置函数,用来执行shell命令

注意:要将Makefile文件与四个内核模块源代码放在同一个文件夹中。

 

3、用insmod命令加载模块

4、测试内核模块功能

5、用rmmod命令卸载模块

  • 内核模块编程——系统调用

1)设计思路

首先,必须知道系统调用表的地址才能够实现替换,可以直接用终端命令“cat /proc/kallsyms |grep sys_call_table”获得本机系统调用表地址,我得到系统调用表地址为0xc1539160。可以通过查看文件查看系统调用表,查看/usr/src/linux-headers-3.0.0-17/arch/x86/include/asm/unistd_32.h文件可以明显看到系统调用表中的223是空的。 

CR0的第16位置位则禁止超级权限,若清零了则允许超级权限。这里的超级权限当然包括往内核空间写的权限。这样,我可以在写入之前,把那一位清零,使我可以写入,然后写完后,又将那一位复原就行了。

有了这些我就可以做到将系统调用表中的223替换成自己的系统调用了。我添加的系统调用是打印当前进程PID和名称的函数。

模块加载函数static int __init call_init(void):先保存系统调用表中223位置的系统调用,然后用static int clear_cr0(void)清零cr0的第十六位,使内核空间可写,在内核空间可写的情况下用自己的系统调用asmlinkage long sys_mycall(void)替换223位置的系统调用,在完成替换后重新将cr0第十六位置位static void setback_cr0(int val)

模块卸载函数static void __exit call_exit(void):用static int clear_cr0(void)清零cr0的第十六位,使内核空间可写,将保存的系统调用anything_saved恢复,完成后重新将cr0第十六位置位static void setback_cr0(int val)

2)源代码

#include <linux/kernel.h>

#include <linux/init.h>

#include <linux/module.h>

#include <linux/unistd.h>

#include <linux/sched.h>

 

MODULE_LICENSE("GPL");

 

#define SYS_CALL_TABLE_ADDRESS 0xc1539160  ///proc/kallsyms 提取的 sys_call_table对应的地址

#define NUM 223  //系统调用号为223

int orig_cr0;  //用来存储cr0寄存器原来的值

unsigned long *sys_call_table_my = 0;

static int (*anything_saved)(void);  //定义一个函数指针,用来保存一个系统调用

 

static int clear_cr0(void)  //使cr0寄存器的第17位设置为0(即是内核空间可写)

{

unsigned int cr0 = 0;

unsigned int ret;

asm volatile ("movl %%cr0, %%eax":"=a"(cr0));  //cr0寄存器的值移动到eax的寄存器中,同时输出到cr0变量中

ret = cr0;

cr0 &= 0xfffeffff;  //cr0变量的值中的第17位清0,一会将修改后的值写入cr0寄存器

asm volatile ("movl %%eax, %%cr0": :"a"(cr0));  //cr0变量的值做为输入,输入到寄存器eax中,同时移动到寄存器cr0

return ret;

}

 

static void setback_cr0(int val)  //使cr0寄存器设置为不可写

{

asm volatile ("movl %%eax, %%cr0": : "a"(val));

}

 

asmlinkage long sys_mycall(void)  //定义自己的系统调用

{

printk("模块系统调用-当前pid:%d, 当前comm:%s\\n", current->pid, current->comm);

return current->pid;

}

 

static int __init call_init(void)

{

sys_call_table_my = (unsigned long*)(SYS_CALL_TABLE_ADDRESS);

printk("call_init.......\\n");

anything_saved = (int (*)(void))(sys_call_table_my[NUM]);  //保存系统调用表中的NUM位置上的系统调用

orig_cr0 = clear_cr0();  //使内核地址空间可写

sys_call_table_my[NUM] =(unsigned long) &sys_mycall;  //用自己的系统调用替换NUM位置上的系统调用

setback_cr0(orig_cr0);  //使内核地址空间不可写

return 0;

}

 

static void __exit call_exit(void)

{

printk("call_exit..........\\n");

orig_cr0 = clear_cr0();

sys_call_table_my[NUM] = (unsigned long)anything_saved;  //将系统调用恢复

setback_cr0(orig_cr0);

}

 

module_init(call_init);

module_exit(call_exit);

 

MODULE_AUTHOR("QianXinTong");

MODULE_VERSION("v1.0");

MODULE_DESCRIPTION("A module for replace a syscall");

3)测试结果

因为用自己的系统调用替换了系统内空的系统调用,所以可以编写一个简单的小程序来测试这个系统调用。我编写的测试程序是syscalltry,源代码如下:

#include <stdio.h>

#include <stdlib.h>

int main()

{

unsigned long x = 0;

x = syscall(223);        //测试223号系统调用

printf("Hello, %ld\\n", x);

return 0;

}

运行这个程序即可测试系统调用。

 

以上是关于实践二——模块的建立的主要内容,如果未能解决你的问题,请参考以下文章

Linux及安全实践二

实验二 内核模块编译

20135336王维臻模块实践报告

20135306黄韧模块实践报告

实践二

开启驱动开发之路