在Linux内核中增加一个系统调用,并编写对应的linux应用程序。利用该系统调用能够遍历系统当前所有进程的任务描述符,并按进程父子关系将这些描述符所对应的进程id显示出来。
Ubuntu-20.04 64位虚拟机
1,采用hello.c实现新的系统调用的函数 ,按照内核模块编写的规范来编写程序(包含有两部分:1,修改系统调用表;2,编写自己的系统调用服务程序)
2,main.c 用来进行调用所编写的中断例程
因为之前了解过汇编的中断向量表,有过一定的实践,所以直接采用修改linux系统调用表的方法。
1,如何在系统调用表中添加新的表项?
先找到系统调用表的入口地址,由于现在的linux系统会对给表做一个安全的保护,不让用户轻易的修改系统调用表,所以需要去掉系统的保护之后,才能将我们自己编写的程序地址加载到系统调用表中,接下来才能被调用。
a,查找系统调用表地址 sudo cat /proc/kallsyms | grep sys_call_table 演示如下:
cedric@ubuntu:~/Desktop/C$ sudo cat /proc/kallsyms | grep sys_call_table
ffffffffb7400280 R x32_sys_call_table
ffffffffb74013a0 R sys_call_table
ffffffffb74023e0 R ia32_sys_call_table
其中中间的地址ffffffffb74013a0是我们后面会采用到的。注意:这个地址并非一成不变的,特别的,每次开机都需要执行该命令获得新的系统调用表的地址。
b,需要修改cr0控制寄存器的第16位以便于用来写系统调用表,将该位清0,禁用系统对调用表的写保护。在此采用汇编语言来实现。注意:在使用内嵌汇编时,在linux下需要采用AT&T的汇编语法,并且在对寄存器操作时应该禁用中断响应,防止出错。禁用写保护的汇编代码如下:
asm volatile
(
"cli;" //禁止中断
"push %rax;"
"movq %cr0,%rax;"
"and $0xfffffffffffeffff,%rax;" //第16位置0,禁用写保护
"movq %rax,%cr0;"
"popq %rax;"
//"sti;"
);
c,修改系统调用表项,将其替换为自己编写的程序。首先将得到的系统调用表地址保存到变量SYS_CALL_TABLE_ADDRESS中(每次开机都要进行修改),然后将系统调用表223号表项内容保存起来,禁用系统的写保护,将我们编写的函数sys_mycall地址替换223号表项,恢复系统的写保护,整个替换工作就此结束。代码如下:
sys_call_table_my=(unsigned long*)(SYS_CALL_TABLE_ADDRESS); //初始化全局变量
saveAddress=sys_call_table_my[NUM]; //保存223号表项
clear_cr0();
sys_call_table_my[NUM]=(unsigned long) &sys_mycall; //设置223号表项为我们自己的函数
restore_cr0();
2,实现具体想要的功能,将所有进程按照父子关系进行显示。
采用list_for_each函数,从init_task的地址开始递归访问即可,代码如下:
static void getTasks(struct task_struct* root)
{
struct task_struct* p;
struct list_head* list;
list_for_each(list,&root->children)
{
p = list_entry(list,struct task_struct,sibling);
printk("parent: id and name: %d %s myself: id and name: %d %s\n",p->parent->pid,p->parent->comm,p->pid,p->comm);
getTasks(p);
}
}
这里需要注意一下内核模块的编写规范,包括模块凭证,入口和退出函数,以及printk函数的使用。
#include
#include
#include //定义task_struct
#include //定义list_head
#include //定义init_task
MODULE_LICENSE("Dual BSD/GPL");
#define SYS_CALL_TABLE_ADDRESS 0xffffffff87a013a0 //本电脑sys_call_table对应的地址
#define NUM 223
static unsigned long saveAddress;//保存223号表项的入口地址
unsigned long* sys_call_table_my=0;//用来作为系统调用表的首地址
static int clear_cr0(void)//AT&T 64位汇编
{
asm volatile
(
"cli;" //禁止中断
"push %rax;"
"movq %cr0,%rax;"
"and $0xfffffffffffeffff,%rax;" //第16位置0,禁用写保护
"movq %rax,%cr0;"
"popq %rax;"
//"sti;"
);
printk(KERN_INFO " Hello World enter\n");
return 0;
}
static void restore_cr0(void)
{
asm volatile
(
//"cli;"
"pushq %rax;"
"movq %cr0,%rax;"
"or $0x0000000000010000,%rax;"
"movq %rax,%cr0;"
"popq %rax;"
"sti;" //恢复中断
);
printk(KERN_INFO " Cruel World exit\n");
}
static void getTasks(struct task_struct* root)
{
struct task_struct* p;
struct list_head* list;
list_for_each(list,&root->children)
{
p = list_entry(list,struct task_struct,sibling);
printk("parent: id and name: %d %s myself: id and name: %d %s\n",p->parent->pid,p->parent->comm,p->pid,p->comm);
getTasks(p);
}
}
static int sys_mycall(void)//测试自己的系统调用
{
printk(KERN_INFO"The pids will be show...: \n");
getTasks(&init_task);
return current->pid;
}
static int call_init(void)
{
sys_call_table_my=(unsigned long*)(SYS_CALL_TABLE_ADDRESS); //初始化全局变量
printk(KERN_INFO"call_init......\n");
saveAddress=sys_call_table_my[NUM]; //保存223号表项
clear_cr0();
sys_call_table_my[NUM]=(unsigned long) &sys_mycall; //设置223号表项为我们自己的函数
restore_cr0();
return 0;
}
static void call_exit(void)
{
printk(KERN_INFO"call_exit......\n");
clear_cr0();
sys_call_table_my[NUM]=saveAddress; //恢复223号表项原有内容
restore_cr0();
}
module_init(call_init);
module_exit(call_exit);
MODULE_AUTHOR("cedric");
这是一个可用的Makefile文件,文件名就是Makefile,不需要后缀名。它对hello.c程序进行编译。
ifneq ($(KERNELRELEASE),) # call from kernel build system
obj-m := hello.o
else
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
make -C $(KERNELDIR) M=$(PWD) modules
endif
它仅用来调用新的223号中断程序,测试最终结果。
#include
#include
#include
int main()
{
int pid=syscall(223); //在此调用223号中断例程。
printf("The current pid is: %d\n",pid);
return 0;
}
1,采用 sudo cat /proc/kallsyms | grep sys_call_table 命令获取当前电脑的系统调用表地址。
2,修改hello.c 中的对应语句,如下:
#define SYS_CALL_TABLE_ADDRESS 0xffffffff87a013a0
3,进入到root模式下make: sudo su + make
4,加载内核模块 hello.ko: insmod hello.ko
5,编译main.c: gcc –o main main.c
6,通过main调用223号服务例程: ./main
7,在系统日志中查看输出结果: cat /var/log/syslog
8,卸载内核模块: rmmod hello