使用内核模块添加系统调用

使用内核模块添加系统调用

1. 为什么要使用内核模块的方式添加系统调用?

  • 编译内核的方式费时间,一般的PC机都要两三个小时。
  • 不方便调试,一旦出现问题前面的工作都前功尽弃。

2. 首先要获取系统调用表sys_call_table的地址(虚拟地址)

因为sys_call_table在内核中没有导出,可以使用如下命令查看。

cat /boot/System.map-`uname -r`|grep sys_call_table

注意点:当我把模块在一个机子上运行成功后,如果移植到另外一个机子上马上就会出现错误,为什么呢?因为每个机子上sys_call_table的地址可能不一样。


3. 需要查看预留的系统调用号。

可以到arch/x86/include/asm/unistd.h文件中查看预留的系统调用号。
可以看出223就是一个预留的系统调用号。


4. 创建c文件。实例:syscall.c

 #include 
    #include 
    #include 
    #include 
    #include 
    #include 

    #define my_syscall_num 223
    //如下的这个值要到你机子上查。cat /boot/System.map-`uname -r`|grep sys_call_table
    #define sys_call_table_adress 0xc0582130//(此处是上面命令查出的地址)
    unsigned int clear_and_return_cr0(void);
    void setback_cr0(unsigned int val);
    asmlinkage long sys_mycall(long num);
    int orig_cr0;
    unsigned long *sys_call_table = 0;
    static int (*anything_saved)(void);
    unsigned int clear_and_return_cr0(void)
    {
     unsigned int cr0 = 0;
     unsigned int ret;
     asm("movl %%cr0, %%eax":"=a"(cr0));
     ret = cr0;
     printk("cr0 = %d\n",ret);
     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));
    }

    static 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[my_syscall_num]);//保存原始系统调用的地址
     orig_cr0 = clear_and_return_cr0();//设置cr0可更改
     sys_call_table[my_syscall_num] = (unsigned long)&sys_mycall;//更改原始的系统调用服务地址
     setback_cr0(orig_cr0);//设置为原始的只读cr0
     return 0;
    }

    asmlinkage long sys_mycall(long num)
    {
            printk("This is my_syscall!\n");
         if(num%2==0)
                   printk("my id is 0%ld\n",num%10000);
         else
                   printk("my id is %ld\n",num%100000);
            return current->pid;
    }

    static void __exit exit_addsyscall(void)
    {
     //设置cr0中对sys_call_table的更改权限。
     orig_cr0 = clear_and_return_cr0();//设置cr0可更改

     //恢复原有的中断向量表中的函数指针的值。
     sys_call_table[my_syscall_num] = (unsigned long)anything_saved;

     //恢复原有的cr0的值
     setback_cr0(orig_cr0);

     printk("call exit \n");
    }

    module_init(init_addsyscall);
    module_exit(exit_addsyscall);
    MODULE_LICENSE("GPL");

5. 创建Makefile文件(与syscall.c文件创建在同一文件夹下)

obj-m := syscall.o //(与自己创建的c文件名对应)
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
         $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
         $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

clean:
         rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

6. 执行make命令


7. 执行sudo insmod syscall.ko 命令


8. 将模块插入成功后,剩下的就是在用户态下测试是否成功


9. 编写c程序(test.c)

#include
int main()
{
         syscall(223,13130000);//第二个参数填你的学号
         return 0;
}

10. 编译c程序 gcc –o test test.c


11. dmesg

你可能感兴趣的:(Linux,C/C++,c语言,内核,调试)