使用内核模块的方式添加系统调用


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>





Logo

更多推荐