前言

系统调用的基本原理

系统调用其实就是函数调用,只不过调用的是内核态的函数,但是我们知道,用户态是不能随意调用内核态的函数的,所以采用软中断的方式从用户态陷入到内核态。在内核中通过软中断0X80,系统会跳转到一个预设好的内核空间地址,它指向了系统调用处理程序(不要和系统调用服务例程混淆),这里指的是在entry.S文件中的system_call函数。就是说,所有的系统调用都会统一跳转到这个地址执行system_call函数,那么system_call函数如何派发它们到各自的服务例程呢?

我们知道每个系统调用都有一个系统调用号。同时,内核中一个有一个system_call_table数组,它是个函数指针数组,每个函数指针都指向了系统调用的服务例程。这个系统调用号是system_call_table的下标,用来指明到底要执行哪个系统调用。当int ox80的软中断执行时,系统调用号会被放进eax寄存器中,system_call函数可以读取eax寄存器获得系统调用号,将其乘以4得到偏移地址,以sys_call_table为基地址,基地址加上偏移地址就是应该执行的系统调用服务例程的地址。

系统调用的传参问题

当一个系统调用的参数个数大于5时(因为5个寄存器(eax, ebx, ecx, edx,esi)已经用完了),执行int 0x80指令时仍需将系统调用功能号保存在寄存器eax中,所不同的只是全部参数应该依次放在一块连续的内存区域里,同时在寄存器ebx中保存指向该内存区域的指针。系统调用完成之后,返回值扔将保存在寄存器eax中。由于只是需要一块连续的内存区域来保存系统调用的参数,因此完全可以像普通函数调用一样使用栈(stack)来传递系统调用所需要的参数。但是要注意一点,Linux采用的是c语言的调用模式,这就意味着所有参数必须以相反的顺序进栈,即最后一个参数先入栈,而第一个参数则最后入栈。如果采用栈来传递系统调用所需要的参数,在执行int 0x80指令时还应该将栈指针的当前值复制到寄存器ebx中。

1.添加系统调用的两种方法

方法一:编译内核法

拿到源码之后

  • 修改内核的系统调用库函数 /usr/include/asm-generic/unistd.h,在这里面可以使用在syscall_table中没有用到的223号
  • 添加系统调用号,让系统根据这个号,去找到syscall_table中的相应表项。在 /arch/x86/kernel/syscall_table_32.s文件中添加系统调用号和调用函数的对应关系
  • 接着就是my_syscall的实现了,在这里有两种方法:第一种方法是在kernel下自己新建一个目录添加自己的文件,但是要编写Makefile,而且要修改全局的Makefile。第二种比较简便的方法是,在kernel/sys.c中添加自己的服务函数,这样子不用修改Makefile.

以上准备工作做完之后,然后就要进行编译内核了,以下是我编译内核的一个过程。

1.make menuconfig (使用图形化的工具,更新.config文件)
2.make -j3 bzImage (编译,-j3指的是同时使用3个cpu来编译,bzImage指的是更新grub,以便重新引导)
3.make modules (对模块进行编译)
4.make modules_install(安装编译好的模块)
5.depmod (进行依赖关系的处理)
6.reboot (重启看到自己编译好的内核)

方法二:内核模块法

这种方法是采用系统调用拦截的一种方式,改变某一个系统调用号对应的服务程序为我们自己的编写的程序,从而相当于添加了我们自己的系统调用。具体实现,我们来看下:

2.通过内核模块实现添加系统调用

这种方法其实是系统调用拦截的实现。系统调用服务程序的地址是放在sys_call_table中通过系统调用号定位到具体的系统调用地址,那么我们通过编写内核模块来修改sys_call_table中的系统调用的地址为我们自己定义的函数的地址,就可以实现系统调用的拦截。

想法有了:那就是通过模块加载时,将系统调用表里面的那个系统调用号的那个系统调用号对应的系统调用服务例程改为我们自己实现的系统历程函数地址。但是内核已经不知道从哪个版本就不支持导出sys_call_table了。所以首先要获取sys_call_table的地址。

网上介绍了好多种方法来得到sys_call_table的地址,这里介绍最简单的一种方法

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

这样就得到了sys_call_table的地址,但同时也得到了一个重要的信息,该符号对应的内存区域是只读的。所以我们要修改它,必须对它进行清楚写保护,这里介绍两种方法:

第一种方法::我们知道控制寄存器cr0的第16位是写保护位。cr0的第16位置为了禁止超级权限,若清零了则允许超级权限往内核中写入数据,这样我们可以再写入之前,将那一位清零,使我们可以写入。然后写完后,又将那一位复原就行了。

unsigned int clear_and_return_cr0(void)
{
unsigned int cr0 = 0;
unsigned int ret;
asm("movl %%cr0, %%eax":"=a"(cr0));
ret = cr0;
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));
}

第二种方法:通过设置虚拟地址对应的也表项的读写属性来设置:

int make_rw(unsigned long address)
{
unsigned int level;
pte_t *pte = lookup_address(address, &level);//查找虚拟地址所在的页表地址
if (pte->pte & ~_PAGE_RW) //设置页表读写属性
pte->pte |= _PAGE_RW; return 0;
} int make_ro(unsigned long address)
{
unsigned int level;
pte_t *pte = lookup_address(address, &level);
pte->pte &= ~_PAGE_RW; //设置只读属性 return 0;
}

3.编写系统调用指定自己的系统调用

内核的初始化函数

在这里我使用系统空闲的223号空闲的系统调用号,你也可以换成其他系统调用的调用号,这样你在执行其他函数时,就会调用自己的写的函数的内容。

static int syscall_init_module(void)
{
printk(KERN_ALERT "sys_call_table: 0x%p\n", sys_call_table);//获取系统调用表的地址
orig_saved = (unsigned long *)(sys_call_table[223]); //保存原有的223号的系统调用表的地址
printk(KERN_ALERT "orig_saved : 0x%p\n", orig_saved ); make_rw((unsigned long)sys_call_table); //修改页的写属性
sys_call_table[223] = (unsigned long *)sys_mycall; //将223号指向自己写的调用函数
make_ro((unsigned long)sys_call_table); return 0;
}

自己的系统调用服务例程

asmlinkage long sys_mycall(void)
{
printk(KERN_ALERT "i am hack syscall!\n");
return 0;
}

移除内核模块时,将原有的系统调用进行还原

static void syscall_cleanup_module(void)
{
printk(KERN_ALERT "Module syscall unloaded.\n"); make_rw((unsigned long)sys_call_table);
sys_call_table[223] = (unsigned long *) orig_saved ;
make_ro((unsigned long)sys_call_table);
}

模块注册相关

module_init(syscall_init_module);
module_exit(syscall_cleanup_module); MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("mysyscall");

4.编写用户态的测试程序

  1 #include <linux/unistd.h>
2 #include <syscall.h>
3 #include <sys/types.h>
4 #include <stdio.h>
5
6 int main(void)
7 {
8 long pid = 0;
9 pid = syscall(223);
10 printf("%ld\n",pid);
11 return 0;
12 }

当我们使用syscall()这个函数去触发223的系统调用时,dmesg会发现我们自己写的服务函数的输出结果:

Linux添加系统调用的两种方法的更多相关文章

  1. Linux安装MySQL的两种方法

    转载:http://blog.csdn.net/superchanon/article/details/8546254/ 1.       运行平台:CentOS 6.3 x86_64,基本等同于RH ...

  2. unity3d为对象添加脚本的两种方法

    首先添加一个物体,然后新建一个C#脚本.接下去有两种方法把C#脚本与物体绑定. 1.在类声明上方添加如下代码: [AddComponentMenu("a/b")] 这句话表示在该物 ...

  3. 关于MySQL中添加数据的两种方法

    下面介绍两种执行SQL命令的方法,并作出相应地总结,第一种介绍一种常规用法,下面进行做简要地分析,首先我们需要执行打开数据库操作首先创建一个MySqlConnection对象,在其构造函数中传入一个连 ...

  4. 添加Labels的两种方法

    private void AddLabel(IFeatureLayer pLayer,string fieldname,ITextSymbol Symbol) { container.DeleteAl ...

  5. view添加毛玻璃效果两种方法

    第一种方法: UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; UIVisualEffectV ...

  6. Linux 下系统调用的三种方法

    系统调用(System Call)是操作系统为在用户态运行的进程与硬件设备(如CPU.磁盘.打印机等)进行交互提供的一组接口.当用户进程需要发生系统调用时,CPU 通过软中断切换到内核态开始执行内核系 ...

  7. Cocos Creator 为Button添加事件的两种方法

    Button添加事件 Button 目前只支持 Click 事件,即当用户点击并释放 Button 时才会触发相应的回调函数.通过脚本代码添加回调方法一这种方法添加的事件回调和使用编辑器添加的事件回调 ...

  8. Cocos Creator EditBox(编辑框/输入框)添加事件的两种方法

    EditBox添加事件方法一这种方法添加的事件回调和使用编辑器添加的事件回调是一样的,通过代码添加, 你需要首先构造一个 cc.Component.EventHandler 对象,然后设置好对应的 t ...

  9. Cocos Creator scrollview添加事件的两种方法

    scrollview添加事件 方法一这种方法添加的事件回调和使用编辑器添加的事件回调是一样的,通过代码添加, 你需要首先构造一个 cc.Component.EventHandler 对象,然后设置好对 ...

随机推荐

  1. 软件工程第三次作业-结对作业NO.1

    第一次结对作业 结对人员: 潘伟靖 170320077 张 松 170320079 方案分析 我们对所供的资料进行分析,如下: 从提供的资料可以看出,需要解决的问题以及满足的需求主要有两类目标用户,各 ...

  2. Django 视图层

    URL映射 1.分布式url映射 include()函数提供分布式url映射功能,使URL映射可以被编写在多个url.py文件中 from django.conf.urls import url fr ...

  3. JAVA线程池原理详解(1)

    线程池的优点 1.线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用. 2.可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃. 线 ...

  4. MariaDB/MySQL存储过程和函数

    本文目录:1.创建存储过程.函数 1.1 存储过程的IN.OUT和INOUT2.修改和删除存储过程.函数3.查看存储过程.函数信息 在MySQL/MariaDB中,存储过程(stored proced ...

  5. JAVA_SE基础——66.StringBuffer类 ③

    如果需要频繁修改字符串 的内容,建议使用字符串缓冲 类(StringBuffer). StringBuffer 其实就是一个存储字符 的容器. 容器的具备 的行为 常用方法 String  增加 ap ...

  6. SpringBoot的重要特性

    一.Web特性 Spring Boot 提供了spring-boot-starter-web来为Web开发予以支持,spring-boot-starter-web为我们提供了嵌入的Tomcat以及Sp ...

  7. ASP.NET MVC5 Forms登陆+权限控制(控制到Action)

    一.Forms认证流程 请先参考如下网址: http://www.cnblogs.com/fish-li/archive/2012/04/15/2450571.html 本文主要介绍使用自定义的身份认 ...

  8. centos6.5时间相关

    时间同步 service ntpdate start 开启网络时间同步

  9. MYSQL 面试查询系列问题

    表结构: `student`('id'.'name'.'code'.'age'.'sex')学生表 `teacher`('id'.'name')教师表 `course`('id'.'name'.'te ...

  10. python CSS

    CSS 一. css的四种引入方式   1.行内式  2.嵌入式  3. 链接式 将一个.css文件引入到HTML文件中 1 <link href="mystyle.css" ...