Linux之使用内核模块增加一个系统调用
2016-03-01 10:22
951 查看
使用内核模块的方式添加系统调用
1,为什么?
编译内核的方式费时间,一般的PC机都要两三个小时,而且不方便调试,一旦出现问题前面的工作都前功尽弃,所以我使用内核模块的方式添加系统调用。
2,怎么做?
在内核模块中实现系统调用函数,修改映射在内存中的系统调用表,把一个空闲的系统调用表项指向自己写的模块中的函数。具体步骤如下:
a. 找系统调用表在内存中的位置;
以上为定义系统调用表在内存中的位置,通过命令grep | sys_call_tables /boot/System.map.xxx查找该地址。
b. 找一个空闲的系统调用号;
以上为空闲系统调用号,在源码目录的/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.
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;}
模块加载完毕后,使用dmesg命令只能看到模块加载函数内的打印语句,想要看到自己编写的系统调用内的打印语句,还要进行调用测试,编写如下test.c文件,使用gcc编译运行,再使用dmesg进行查看,就可以看到系统调用函数起作用了。
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>
相关文章推荐
- Linux C中库函数与系统调用的区别详细解析
- 三种方法实现Linux系统调用
- Linux 可加载内核模块剖析
- 【C/C++】Linux下使用system()函数一定要谨慎
- linux系统调用出错时的处理函数
- 系统调用(system_call)
- linux文件操作笔记(1)——系统调用、库函数
- Linux设备驱动程序,个人学习,第一个模块hello world
- 编译linux内核模块错误
- linux系统调用流程浅析
- android常用的系统调用
- Linux中自定义系统调用
- 系统调用
- 个人学习笔记---linux系统调用怎么陷入内核空间
- 个人学习笔记---软中断(下半部)和软件中断(系统调用)的区别
- 库函数调用与系统调用的区别
- Exec函数对应的系统调用处理过程
- 中断-学习总结
- 系统调用--学习总结
- 在linux 4.x内核中增加系统调用