上一篇文章《增加Linux系统调用——通过增加内核模块》中,介绍了如何通过动态加载内核模块的方式来增加Linux系统调用。但是这种方法有一个缺点是,必须预先在系统调用表中增加一个函数指针,如果新的系统调用和这个函数指针的形式不同,则需要重新编译内核,很不方便。这里将介绍另一种通过动态加载内核模块来增加Linux系统调用的方法。具体解释就不写了,仅贴上代码。
在linux-2.6.30.6/arch/x86/include/asm/unistd_32.h文件中发现,223号系统调用没有被使用,所以这里将新增的系统调用号设为223。
内核模块sys_call.c的代码如下:
/* sys_call.c */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <asm/unistd.h>
#define _NR_testsyscall 223
unsigned long *sys_call_table=NULL;
asmlinkage int (*orig_mkdir)(const char *,int);
unsigned int orign_cr0 = 0;
unsigned int clear_and_return_cr0(void)
{
unsigned int cr0 = 0;
unsigned int ret;
asm("movl %%cr0, %%eax":"=a"(cr0));
ret = cr0;
cr0 &= 0xfffeffff;
asm("movl %%eax, %%cr0"::"a"(cr0));
return ret;
}
void setback_cr0(unsigned int val) //读取val的值到eax寄存器,再将eax寄存器的值放入cr0中
{
asm volatile("movl %%eax, %%cr0"::"a"(val));
}
struct _idt
{
unsigned short offset_low,segment_sel;
unsigned char reserved,flags;
unsigned short offset_high;
};
unsigned long *getscTable()
{
unsigned char idtr[6],*shell,*sort;
struct _idt *idt;
unsigned long system_call,sct;
unsigned short offset_low,offset_high;
char *p;
int i;
/* get the interrupt descriptor table */
__asm__("sidt %0" : "=m" (idtr));
/* get the address of system_call */
idt=(struct _idt*)(*(unsigned long*)&idtr[2]+8*0x80);
offset_low = idt->offset_low;
offset_high = idt->offset_high;
system_call=(offset_high<<16)|offset_low;
shell=(char *)system_call;
sort="\xff\x14\x85";
/* get the address of sys_call_table */
for(i=0;i<(100-2);i++)
if(shell[i]==sort[0]&&shell[i+1]==sort[1]&&shell[i+2]==sort[2])
break;
p=&shell[i];
p+=3;
sct=*(unsigned long*)p;
return (unsigned long*)(sct);
}
asmlinkage int hacked_mkdir(const char * pathname, int mode){
printk("PID %d called sys_mkdir !\n",current->pid);
//return orig_mkdir(pathname,mode);
return 7;
}
static int __init find_init(void){
sys_call_table = getscTable();
printk("sys_call_table:%p\n",sys_call_table);
orign_cr0 = clear_and_return_cr0();
orig_mkdir=(int(*)(const char*,int))(sys_call_table[_NR_testsyscall]);
sys_call_table[_NR_testsyscall]=(unsigned long)hacked_mkdir;
setback_cr0(orign_cr0);
return 0;
}
static void __exit find_cleanup(void){
orign_cr0 = clear_and_return_cr0();
sys_call_table[_NR_testsyscall]=(unsigned long)orig_mkdir;
setback_cr0(orign_cr0);
}
module_init(find_init);
module_exit(find_cleanup);
MODULE_LICENSE("Daul BSD/GPL");
MODULE_AUTHOR("B1305");
MODULE_DESCRIPTION("Kernel memory share module.");
其实中hacked_mkdir()函数为内核模块的功能函数。这里仅返回值7来做为测试。
make并insmod,编写测试代码test.c。
/* test.c */
#include <sys/syscall.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
long pid = 0;
pid = syscall(223,8);
printf("%ld\n",pid);
return 0;
}
执行test文件,会发现输出值7,说明实验成功。
附:强行卸载内核模块。
/* rmmod_module.c */
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/module.h>
#include<linux/list.h>
#include<linux/cpumask.h>
#include<asm/local.h>
/*
注意:以下代码不知道内核版本,但模块卸载不掉的原因大致是以下四种
模块卸载代码,卸载出错情况分为四种
asmlinkage long sys_delete_module(const char __user *name_user, unsigned int flags)
{
...
0.如果其它模块依赖该模块,则不删除
if (!list_empty(&mod->modules_which_use_me)) {
Other modules depend on us: get rid of them first.
ret = -EWOULDBLOCK;
goto out;
}
...
1.如果模块不是LIVE状态,那么就无法进行下去了,得到的结果是busy
if (mod->state != MODULE_STATE_LIVE) {
...
ret = -EBUSY;
goto out;
}
...
2.如果引用计数不是0,则等待其为0
if (!forced && module_refcount(mod) != 0) 2.
wait_for_zero_refcount(mod);
up(&module_mutex);
3.如果在这个里面阻塞了,那就无法返回了
mod->exit();
down(&module_mutex);
free_module(mod);
...
}
*/
static char *module_name = NULL;
/*
使用 module_param 给内核模块传递参数
module_param (name,type,perm);
name: 变量名
type: 类型 bool 一个布尔型( true 或者 false)值(相关的变量应当是 int 类型).
invbool 颠倒了值, 所以真值变成 false, 反之亦然.
charp 一个字符指针值. 内存为用户提供的字串分配, 指针因此设置.
int
long
short
uint
ulong
ushort
perm: 权限掩码,一般设0
具体内容参考:
http://blog.chinaunix.net/uid-10747583-id-2920908.html
*/
module_param(module_name,charp,0);
void force(void)
{
}
int __init rm_init(void)
{
struct module *mod = NULL;
int i;
list_for_each_entry(mod,THIS_MODULE->list.prev,list)
{
if(strcmp(mod->name,module_name)==0)
{
printk(KERN_ALERT"name:%s state:%d refcnt:%lu ",mod->name,mod->state,module_refcount(mod));
/*
enum module_state
{
MODULE_STATE_LIVE; // 模块存活,0
MODULE_STATE_COMING; // 正在加载模块,1
MODULE_STATE_GOING; // 正在卸载模块,2
};
模块的三种状态
为了卸载能进行下去,也就是避开情况1,将模块的状态改变为LIVE
*/
mod->state = MODULE_STATE_LIVE;
/*
防止原因3
替换mod的exit。
再次调用rmmod的时候会调用到sys_delete_module,
最后会调用 exit回调函数,新的exit当然不会阻塞,成功返回,进而可以free掉module
*/
mod->exit = force;
/*
减小改变内核模块的引用计数
防止情况2
*/
module_put(mod);
printk(KERN_ALERT"name:%s state:%d refcnt:%lu ",mod->name,mod->state,module_refcount(mod));
}
}
return 0;
}
void __exit rm_exit(void)
{
printk(KERN_ALERT"[rmmod mymod] name:%s state:%d\n",THIS_MODULE->name,THIS_MODULE->state);
}
module_init(rm_init);
module_exit(rm_exit);
MODULE_AUTHOR("B1305");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Rmmod");
可能会有资源泄露,因为要卸载的模块,里面的资源可能得不到释放。