Linux之使用内核模块增加一个系统调用
使用内核模块的方式添加系统调用1,为什么?编译内核的方式费时间,一般的PC机都要两三个小时,而且不方便调试,一旦出现问题前面的工作都前功尽弃,所以我使用内核模块的方式添加系统调用。2,怎么做?在内核模块中实现系统调用函数,修改映射在内存中的系统调用表,把一个空闲的系统调用表项指向自己写的模块中的函数。具体步骤如下:a. 找系统调用表在内存中的位置;
使用内核模块的方式添加系统调用
1,为什么?
编译内核的方式费时间,一般的PC机都要两三个小时,而且不方便调试,一旦出现问题前面的工作都前功尽弃,所以我使用内核模块的方式添加系统调用。
2,怎么做?
在内核模块中实现系统调用函数,修改映射在内存中的系统调用表,把一个空闲的系统调用表项指向自己写的模块中的函数。具体步骤如下:
a. 找系统调用表在内存中的位置;
<span style="font-family:Times New Roman;font-size:18px;">#define sys_call_table_adress 0x81801580 </span>
以上为定义系统调用表在内存中的位置,通过命令grep | sys_call_tables /boot/System.map.xxx查找该地址。
b. 找一个空闲的系统调用号;
<span style="font-family:Times New Roman;font-size:18px;">#define susu 223</span>
以上为空闲系统调用号,在源码目录的/arch/x86/include/generated/asm/下有一个unistd_32_ia32.h文件,里面列出了所有的系统调用号,其中221与224直接有两个空闲,所以我选择223作为我的系统调用号。
c. 修改寄存器写保护位(适用于32位机);
unsigned int clear_and_return_cr0(void)
{
unsigned int cr0 = 0;
unsigned int ret;
asm volatile ("movl %%cr0, %%eax": "=a"(cr0));
ret = cr0;
cr0 &= 0xfffeffff;
asm volatile ("movl %%eax, %%cr0":: "a"(cr0));
return ret;
}
void setback_cr0(unsigned int val)
{
asm volatile ("movl %%eax, %%cr0"
:
: "a"(val)
);
}
以上为修改寄存器写保护位代码。sys_call_table的属性是R,也就是sys_call_table是只读的,我们没有往内核sys_call_table[mycall]这个地址写入信息的权限,控制寄存器CR0的第十六位是写保护位即:若将CR0的第16位置位了则禁止超级权限,若清零了则允许超级权限。这里的超级权限包括往内核空间写的权限。这样,可以在写入之前,把那一位清零,使其可以被可以写入。然后写完后,又将那一位复原就行了。
d. 实现系统调用并测试。以下是一个简单的实现打印当前进程pid及进程名称的内核模块syscall.c.
<span style="font-family:Times New Roman;font-size:18px;">#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/unistd.h>
#include <linux/time.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#define susu 223 //空闲系统调用号
#define sys_call_table_adress 0x81801580 //系统调用表地址
unsigned int clear_and_return_cr0(void);
void setback_cr0(unsigned int val);
int orig_cr0;
unsigned long *sys_call_table = 0;
static int (*anything_saved)(void);
MODULE_AUTHOR("SUSU");
MODULE_LICENSE("GPL");
</span>
<span style="font-family:Times New Roman;font-size:18px;">//控制寄存器CR0的第十六位清零
unsigned int clear_and_return_cr0(void)
{
unsigned int cr0 = 0;
unsigned int ret;
asm volatile ("movq %%cr0, %%eax": "=a"(cr0));
ret = cr0;
/*clear the 20th bit of CR0,*/
cr0 &= 0xfffeffff;
asm volatile ("movq %%eax, %%cr0":: "a"(cr0));
return ret;
}
</span>
<span style="font-family:Times New Roman;font-size:18px;">//控制寄存器CR0的第十六位复原
void setback_cr0(unsigned int val)
{
asm volatile ("movq %%eax, %%cr0"
:
: "a"(val)
);
}
/*
asmlinkage long sys_mycall(void)
{
printk("I am mycall.current->pid = %d, current->comm = %s\n", current->pid, current->comm);
return current->pid;
}*/
</span>
<span style="font-family:Times New Roman;font-size:18px;">系统调用函数
asmlinkage long sys_mycall(int a)
{
printk("%d, I am mycall.current->pid = %d, current->comm = %s\n",a, current->pid, current->comm);
return current->pid;
}
</span>
<span style="font-family:Times New Roman;font-size:18px;"><pre name="code" class="cpp">//模块加载函数</span>
int __init init_addsyscall(void){ printk("hello, kernel!\n"); sys_call_table = (unsigned long *)sys_call_table_adress; anything_saved = (int(*)(void))(sys_call_table[susu]); orig_cr0 = clear_and_return_cr0(); sys_call_table[susu] = (unsigned long)&sys_mycall; setback_cr0(orig_cr0); return 0;}
<span style="font-family:Times New Roman;font-size:18px;">//模块卸载函数
</span>
<span style="font-family:Times New Roman;font-size:18px;">void __exit exit_addsyscall(void)
{
orig_cr0 = clear_and_return_cr0();
sys_call_table[susu] = (unsigned long)anything_saved;
setback_cr0(orig_cr0);
printk("call exit....\n");
}
module_init(init_addsyscall);
module_exit(exit_addsyscall);
</span>
模块代码编写完成后即可以进行测试,还需要编写Makefile文件,编写规则及加载模块等过程见http://blog.csdn.net/qq_26811393/article/details/50766901
模块加载完毕后,使用dmesg命令只能看到模块加载函数内的打印语句,想要看到自己编写的系统调用内的打印语句,还要进行调用测试,编写如下test.c文件,使用gcc编译运行,再使用dmesg进行查看,就可以看到系统调用函数起作用了。
<span style="font-family:Times New Roman;font-size:18px;">#include<stdio.h>
int main()
{
syscall(223);
}</span>
更多推荐
所有评论(0)