利用内核模块法加载带参数的系统调用

文章介绍了如何通过内核模块在Linux系统中添加自定义的系统调用,包括查看sys_call_table地址、选取未使用的系统调用号、编写内核模块代码、编译加载模块以及验证系统调用功能。对于带参数的系统调用,文章详细解释了参数如何通过寄存器传递,并给出了修改后的系统调用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

考虑到每次重新编译内核时间过长,调试自己写的系统调用太慢了,因此学习了一下内核模块法添加系统调用的方法,并分享。以下是参考的比较好的文章:

不带参数的实现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;
}

加载内核模块,验证可以正确传参。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值