原文:http://blog.163.com/xychenbaihu@yeah/blog/static/1322296552010111010191727/
其实步骤非常简单:
1.编写一个系统调用;(也称内核函数或系统调用的服务例程,即系统调用的实现)
2.在系统调用表末尾加入一个新表项;
3.在< asm/unistd.h >中添加一个新的系统调用号;
4.重新编译内核;
上述工作完成后,就可以在用户程序中使用自己所编写的系统调用了。接下来,我们将逐步分析如何上述步骤。
1.编写系统调用
我们将要实现一个获得当前进程pid的的系统调用。对于一个进程,我们可以直接通过current->pid来获得。为了使得这个系统调用同样适用于一个线程,我们使用current->tgid。这么做的原因是同一个线程组内的所有线程TGID均相同;而一个进程的PID和TGID是相同的。
1 asmlinkage long sys_mygetpid(void)
2 {
3 return current->tgid;
4 }
与普通函数不同的是,这个系统调用的函数名前有asmlinkage修饰符。这个修饰符使得GCC编译器从堆栈中取该函数的参数而不是寄存器中。另外,系统调用函数的命名规则都是sys_XXX的形式。
接下来,我们要做的是将这个函数放于何处。一种方法是,将上述函数放于/kernel/下的某个文件中;另一种方式是,将这个函数单独存放在/kernel/下的一个新建的.c文件中。不管何种方法,所做的目的都是为了在重新编译内核时将上述我们所编写的系统调用函数编译进内核。
2.在系统调用表中添加新的表项
linux中为每一个系统调用都分配一个系统调用号。也就是说,每一个系统调用号都唯一的对应着一个系统调用。内核中使用系统调用表来记录所有已经注册过的系统调用,这个系统调用表存储在sys_call_table中。在yoursource/arch/x86/kernel /syscall_table_32.S中可以看到系统系统调用表。
我们所要做的就是在该表的末尾添加我们刚编写的系统调用:.long sys_getpid。我们并不需要显式的指定系统调用号,从0开始算起,我们所编写的函数的系统调用号为341。
01 ENTRY(sys_call_table)
02 .long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */
03 .long sys_exit
04 .long ptregs_fork
05 .long sys_read
06 .long sys_write
07 .long sys_open /* 5 */
08 …… ……
09 .long sys_perf_event_open
10 .long sys_recvmmsg
11 .long sys_fanotify_init
12 .long sys_fanotify_mark
13 .long sys_prlimit64 /* 340 */
14 .long sys_mygetpid
3.在< asm/unistd.h >中添加一个新的系统调用号
上一步,在系统调用表中添加新的表项是为了将系统调用号和系统调用关联起来。现在,我们需要在unistd.h文件中添加具体的系统调用号,这样使得系统可以根据这个系统调用号在系统调用表中查找具体的系统调用。
当用户进程调用一个系统调用时,其实是通过一个中断号为128的软中断来通知内核的。此时,内核会由用户态切换到内核态转而去执行这个软中断对应的中断处理程序。而这个中断处理程序恰好就是系统调用处理程序。也就是说,任何系统调用都会引发CPU去执行这个系统调用处理程序。因此,必须通过系统调用号来识别不同的系统调用。系统调用号通常会存储在eax寄存器中。
现在我们就在yoursource/arch/x86 /include/asm/unistd_32.h文件中添加新的系统调用号。在上一步我们所添加的sys_mygetpid系统调用对应的编号为 341,因此我们在该文件的末尾添加下面的语句:#define __NR_mygetpid 341。注意这里的宏命名规则,以__NR_开头。
1 #define __NR_restart_syscall 0
2 #define __NR_exit 1
3 #define __NR_fork 2
4 #define __NR_read 3
5 #define __NR_write 4
6 …………
7 #define __NR_fanotify_mark 339
8 #define __NR_prlimit64 340
9 #define __NR_mygetpid 341
4.编译内核
如果上述三个步骤都完成后,那么接下来重新编译内核即可。具体可参见这里。
5.编写用户态的程序
1 #include < linux/unistd.h >
2
3 _syscall0(int,mygetpid)
4
5 int main()
6 {
7 printf("The current process's pid is %d\n",mygetpid());
8 return 0;
9 }
上述用户程序可能与我们平日里所写的稍有不同。主要区别是增加了_syscall0(int,mygetpid)这个宏。因为我们现在直接在程序中调用系统调用,而我们平时则是通过调用C库中的API来间接调用系统调用。
在unistd.h文件中有_syscallN()宏的定义,这里的N可取0~6。N代表的是需要传递给系统调用的参数个数。由于mygetpid系统调用需传递的参数个数为0,因此选取_syscall0。另外,这组宏的内部参数分布有如下特点:第一个参数是系统调用返回值的类型,第二个参数是系统调用函数的名称,接下来的参数按照系统调用参数的次序依次是参数类型和参数名称。对于每个宏来说,都有2+2*N个参数。
OK,上述方法即可以将我们自己编写的系统调用函数加入到内核。try!
在内核中替换替换系统调用:
基本思路,在/proc/kallsyms文件中存放这sys_call_table的地址,即系统调用表的首地址。我们调用的每一个系统调用在内核中都对应一个系统调用号,这个系统调用号,就表明了这个系统调用在sys_call_table中的下标。有了下标,就可以从sys_call_table所指的空间中取出系统调用的入口地址。
那么,如果我们将sys_call_table中系统调用号所对应的单元的值(这个系统调用号所对应的系统调用函数的入口地址)替换成自己的系统调用函数的地址。那么当再调用系统调用时,实际就是调用自己的系统调用。
问题在于,sys_call_table所对应的内存空间是只可读,不可写,所以第一步就是将该空间设置为可写。
查手册后发现,可读与否取决于cr0寄存器的第17位,为0表示可写,为1表示不可写。所以应该将其设置为0。
最终代码如下::
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/unistd.h>
#include <linux/sched.h>
#define SYS_CALL_TABLE_ADDRESS 0xc0509940 //通过cat /proc/kallsyms | grep sys_call_table
#define NUM 223 //将要替换的系统调用号
int orig_cr0;
unsigned long *sys_call_table_my = 0;
static int (*anything_saved)(void);
static int clear_cr0(void) //清除 cr0寄存器的第17位。(使sys_call_table所指的区域可写)
{
unsigned int cr0 = 0;
unsigned int ret;
asm volatile ("movl %%cr0, %%eax":"=a"(cr0));
ret = cr0; //cr0 寄存器原来的值存储在ret中。
cr0 &= 0xfffeffff;
asm volatile ("movl %%eax, %%cr0": :"a"(cr0));
return ret;
}
static void setback_cr0(int val) //将cr0寄存器原来的值恢复。(使sys_call_table所指的区域不可写)
{
asm volatile ("movl %%eax, %%cr0": : "a"(val));
}
asmlinkage long sys_mycall(void) //定义自己的系统调用
{
printk("I am mycall, current->pid:%d, and current->comm:%s\n", current->pid, current->co
mm);
return current->pid;
}
static int __init call_init(void) //用自己的系统调用的入口地址,替换sys_call_table中原有系统调用的入口的地址。
{
sys_call_table_my = (unsigned long*)(SYS_CALL_TABLE_ADDRESS);
printk("call_init.......\n");
anything_saved = (int (*)(void))(sys_call_table_my[NUM]);
orig_cr0 = clear_cr0();
sys_call_table_my[NUM] =(unsigned long) &sys_mycall;
setback_cr0(orig_cr0);
return 0;
}
static void __exit call_exit(void)
{
printk("call_exit..........\n");
orig_cr0 = clear_cr0();
sys_call_table_my[NUM] = (unsigned long)anything_saved;
setback_cr0(orig_cr0);
}
MODULE_LICENSE("GPL");
module_init(call_init);
module_exit(call_exit);
测试:
#include <stdio.h>
#include <stdlib.h>
int main()
{
unsigned long x = 0;
x = syscall(223);
printf("hello, %ld\n", x);
return 0;
}
Makefile 文件:
obj-m :=syscall.o
PWD := $(shell pwd)
KDIR ?=/lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean