转自:
学习linux操作系统课,有一章讲“系统调用”,其中有个内容是在linux内核中添加一个自己的系统调用。
我调试了两次终于将其调通了,第一次不知道是什么原因,编译生成的内核总是加载不上。第二次成功了。有点小成就感. 我现在将其过程记录下来。分享给大家。方式一:编译内核的方式。我本机的内核是linux-2.6.38.1添加的内核是linux-2.6.39.1第一步:在arch/x86/include/asm/unistd_32.h文件中添加系统调用号。350 #define __NR_open_by_handle_at 342351 #define __NR_clock_adjtime 343352 #define __NR_syncfs 344353 #define __NR_mysyscall 345 /*添加的部分*/354 355 #ifdef __KERNEL__356 357 #define NR_syscalls 346 /*将系统调用总数重新更新*/ |
第二步:在系统调用表中添加相应的表项。位置:arch/x86/kernel/syscall_table_32.s
344 .long sys_open_by_handle_at345 .long sys_clock_adjtime346 .long sys_syncfs347 .long sys_mysyscall /*345*/ /*添加部分*/ |
第三步:实现系统调用的服务历程。理论上,这个函数在的位置没有固定,最好加在arch/x86/kernel/目录下的文件里面。我这次是加在arch/x86/kernel/sys_i386_32.c文件中。写了一个很简单的函数。
27 asmlinkage long sys_mysyscall(long data) 28 { 29 return data; 30 }
第四步:重新编译内核。这可能要话很长的时间哦,最好是找个上课的时间,
或去吃饭的时间,写了小脚本。(以下都是root权限的操作阿)首先,执行make menuconfig。使用默认配置,就是出现图形界面后,直接选择exit退出即可。然后,执行如下一个小的脚本。makemake modulesmake modules_installmkinitramfs -o /boot/initrd-2.6.39.1.img 2.6.39.1make install
编译完后,重启新的内核,发现鼠标用不了,估计是编译内核的时候没配置好。
第五步,在用户态测试是否成功。(注意内核安装好后,重启后选择新的内核)我使用了C语言和汇编两种方式测试。#include <linux/unistd.h>#include <syscall.h>#include <sys/types.h>#include <stdio.h>int main(void){ long id1 = 0; id1 = syscall(345,190); printf("%ld\n",id1); return 0;}
# hello.s# display a string "Hello, world." .section .rodatamsg: .ascii "Hello, world.\n" .section .text.globl _start_start: movl $345,%eax movl $15 ,%ebx int $0x80 movl $4, %eax # system call movl $1, %ebx # file descriptor movl $msg, %ecx # string address movl $14, %edx # string length int $0x80 movl $1, %eax movl $0, %ebx int $0x80
注意汇编的时候要用gdb调试。用gdb调试汇编的方式见:
到这儿就结束了。这只是一个小的实验。这可以说算是迈出了一大步了。以后可以在内核中加一些比较实用的系统调用了。比如说:系统调用日志收集系统。用来监控系统调用的系统调用。可以用来防止系统攻击方式二:插入模块的方式。方式二:通过插入模块的形式插入系统调用,免去了编译内核的这个比较费时的操作。
#include#include #include #include #include #include #define my_syscall_num 223 //如下的这个值要到你机子上查。cat /proc/kallsyms | grep sys_call_table #define sys_call_table_adress 0xc1511160 unsigned int clear_and_return_cr0(void); void setback_cr0(unsigned int val); asmlinkage long 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("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)); } 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(void){ printk("current->pid = %d , current->comm = %s\n", current->pid, current->comm); 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");