Linux编译模块及通过模块修改系统调用【转】
转自:https://www.jianshu.com/p/64def4ed0849
理解内核模块原理及正确编写源代码
原理:内核模块可以作为独立程序来编译的函数和数据类型的集合。之所以提供模块机制,是因为Linux本身是一个单内核。单内核由于所有内容都集成在一起,效率很高,但可扩展性和可维护性相对较差,模块机制可以弥补这一缺陷。
Linux模块可以通过静态或动态的方法加载到内核空间,静态加载是指在内核启动过程中加载;动态加载是指在内核运行的过程中随时加载。
一个模块被加载到内核中时,就成为内核代码的一部分。模块加载入系统时,系统修改内核中的符号表,将新加载的模块提供的资源和符号添加到内核符号表中,以便模块间通信。
编写模块代码
- 模块构造函数:
- 执行insmod或modprobe指令加载内核模块时会调用的初始化函数。函数原型必须是module_init(),内是函数指针。
- 模块析构函数(释放模块对象所占用的内存空间前所必须执行的一个函数):
- 执行rmmod指令卸载模块时调用的函数。函数原型是module_exit();
- 模块模块许可声明:
- 函数原型是MODULE_LICENSE(),告诉内核程序使用的许可证,不然在加载时它会提示该模块污染内核。一般会写GPL。
向内核输出简单信息的代码:
test.c
#include<linux/init.h>
#include<linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static char *name="tomorrow";
static void __init name_init(void)
{
printk("Hello World~\n");
printk("Hello %s\n",name);
}
static void __exit name_exit(void)
{
printk(KERN_INFO"Name module exit\n");
}
module_init(name_init);
module_exit(name_exit);
module_param(name,charp,S_IRUGO);
Makefile文件
obj-m:=test.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-4.4.0-21-generic
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
make运行成功后会在原目录下生成以下文件:
将模块加载进内核:
sudo insmod test.ko
查看模块向内核输入的信息:
dmesg
卸载模块:
sudo rmmod test
实现输出当前进程信息的功能
module2.c
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/sched.h>
static struct task_struct *pcurrent;
int print_current_task_info(void);
static int __init print_init(void)
{
printk(KERN_INFO "print current task info\n");
print_current_task_info();
return 0;
}
static void __exit print_exit(void)
{
printk(KERN_INFO "Finished\n");
}
int print_current_task_info(void)
{
pcurrent=get_current();
printk(KERN_INFO "Task state: %ld\n",current->state);
printk(KERN_INFO "pid: %d\n",current->pid);
printk(KERN_INFO "tgid: %d\n",current->tgid);
printk(KERN_INFO "prio: %d\n",current->prio);
return 0;
}
module_init(print_init);
module_exit(print_exit);
Makefile
obj-m:=module2.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-4.4.0-21-generic
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
运行后在内核中将输出以下信息:
卸载后效果如下:
module3.c
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/sched.h>
static struct task_struct *pcurrent;
static int __init print_init(void)
{
printk(KERN_INFO "print current task info\n");
printk("pid\ttgid\tprio\tstate\n");
for_each_process(pcurrent){
printk("%d\t",pcurrent->pid);
printk("%d\t",pcurrent->tgid);
printk("%d\t",pcurrent->prio);
printk("%ld\n",pcurrent->state);
}
return 0;
}
static void __exit print_exit(void)
{
printk(KERN_INFO "Finished\n");
}
module_init(print_init);
module_exit(print_exit);
Makefile
obj-m:=module3.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-4.4.0-21-generic
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
加载后效果如下:
卸载后效果如下:
修改系统调用的模块
在/usr/include/i386-linux-gnu/asm/unistd_32.h文件中查看系统调用序号
发现222号和223号系统调用是空的,因此选取223作为新的系统调用号。
在/boot/System.map-3.16.0-30-generic查看系统调用表的内存地址:
为0xc1697140
编写模块文件:
module5.c
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/unistd.h>
#include <linux/sched.h>
MODULE_LICENSE("Dual BSD/GPL");
#define SYS_CALL_TABLE_ADDRESS 0xc1697140 //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("25");
MODULE_VERSION("BETA 1.0")