利用内核模块法加载带参数的系统调用
前言
考虑到每次重新编译内核时间过长,调试自己写的系统调用太慢了,因此学习了一下内核模块法添加系统调用的方法,并分享。以下是参考的比较好的文章:
不带参数的实现1: https://blog.youkuaiyun.com/ThugKd/article/details/71001853
不带参数的实现2: https://blog.youkuaiyun.com/ShirokoYae/article/details/119036040
传参的实现: https://zhuanlan.zhihu.com/p/446785414
不带参数的实现
1、查看系统sys_call_table的地址
sudo cat /proc/kallsyms | grep sys_call_table
第一个就是sys_call_table的地址,不过每次重启内核,这个地址会变。
2、找一个可用的系统调用号
cd /usr/src/linux-6.1.1/arch/x86/entry/syscalls/
vim syscall_64.tbl
发现335号是可用的(此处不宜过大,测试了400多号的,貌似不行)
3、编写内核模块syscall.c
代码如下,其中系统调用sys_mycall()的作用是printk一行文字。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/unistd.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#define my_syscall_num 335 //第二步查到的数值
#define sys_call_table_adress 0xffffffff83a00320//(第一步查出的地址)
unsigned int clear_and_return_cr0(void);
void setback_cr0(unsigned int val);
static int sys_mycall(void);
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 volatile("movq %%cr0, %%rax":"=a"(cr0)); //64位机器使用 movq rax
ret = cr0;
printk("cr0 = %d\n",ret);
cr0 &= 0xfffeffff;
asm volatile("movq %%rax, %%cr0"::"a"(cr0));
return ret;
}
void setback_cr0(unsigned int val) //读取val的值到rax寄存器,再将rax寄存器的值放入cr0中
{
asm volatile("movq %%rax, %%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;
}
static int sys_mycall(void)
{
printk("My syscall is successful \n");
return 0;
}
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");
4、编写Makefile
obj-m := syscall.o
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 Modules.* modules.* Module.* *.mod
5、编译
make
6、加载内核模块
sudo insmod syscall.ko
lsmod | grep syscall
7、验证系统调用是否加载成功
test.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/syscall.h>
#include <linux/kernel.h>
int main()
{
int i = syscall(335);
printf("%d \n",i);
return 0;
}
gcc test.c
./a.out
dmesg
打印出“My syscall is successful”, 说明成功通过加载内核模块的方式创建系统调用。
带参数的实现
syscall是x64的系统调用(在用户程序中使用,需要 #include<unistd.h>)
其参数通过标签为pt_regs的结构体进行传递,x86_64 下的 sysdep.h 文件给出如下说明:
/* The Linux/x86-64 kernel expects the system call parameters in
registers according to the following table:
syscall number rax
arg 1 rdi
arg 2 rsi
arg 3 rdx
arg 4 r10
arg 5 r8
arg 6 r9
......
*/
将系统调用改为计算两个数相加
// 参数传递顺序 rdi,rsi,rdx,r10,r8,r9
static int sys_mycall(struct pt_regs * regs)
{
int k=regs->di+regs->si;
printk("i and j is %d %d\n",regs->di,regs->si);
return k;
}
加载内核模块,验证可以正确传参。