Linux編譯模塊及通過模塊修改系統調用【轉】

轉自:https://www.jianshu.com/p/64def4ed0849

理解內核模塊原理及正確編寫源代碼

原理:內核模塊可以作爲獨立程序來編譯的函數和數據類型的集合。之所以提供模塊機制,是因爲Linux本身是一個單內核。單內核由於所有內容都集成在一起,效率很高,但可擴展性和可維護性相對較差,模塊機制可以彌補這一缺陷。

Linux模塊可以通過靜態或動態的方法加載到內核空間,靜態加載是指在內核啓動過程中加載;動態加載是指在內核運行的過程中隨時加載。

一個模塊被加載到內核中時,就成爲內核代碼的一部分。模塊加載入系統時,系統修改內核中的符號表,將新加載的模塊提供的資源和符號添加到內核符號表中,以便模塊間通信。

編寫模塊代碼

  • 模塊構造函數:
  • 執行insmod或modprobe指令加載內核模塊時會調用的初始化函數。函數原型必須是module_init(),內是函數指針。
  • 模塊析構函數(釋放模塊對象所佔用的內存空間前所必須執行的一個函數):
  • 執行rmmod指令卸載模塊時調用的函數。函數原型是module_exit();
  • 模塊模塊許可聲明:
  • 函數原型是MODULE_LICENSE(),告訴內核程序使用的許可證,不然在加載時它會提示該模塊污染內核。一般會寫GPL。

向內核輸出簡單信息的代碼:

test.c

#include<linux/init.h>
#include<linux/module.h>

MODULE_LICENSE("Dual BSD/GPL");

static char *name="tomorrow";
static void __init name_init(void)
{
        printk("Hello World~\n");
        printk("Hello %s\n",name);
}

static void __exit name_exit(void)
{
        printk(KERN_INFO"Name module exit\n");
}

module_init(name_init);
module_exit(name_exit);

module_param(name,charp,S_IRUGO);

Makefile文件

obj-m:=test.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-4.4.0-21-generic
all:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

make運行成功後會在原目錄下生成以下文件:

 
 

將模塊加載進內核:

sudo insmod test.ko

查看模塊向內核輸入的信息:

dmesg

 
 

卸載模塊:

sudo rmmod test

 
 

實現輸出當前進程信息的功能

module2.c

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/sched.h>

static struct task_struct *pcurrent;

int print_current_task_info(void);
static int __init print_init(void)
{
        printk(KERN_INFO "print current task info\n");
        print_current_task_info();
        return 0;
}

static void __exit print_exit(void)
{
        printk(KERN_INFO "Finished\n");
}

int print_current_task_info(void)
{
        pcurrent=get_current();
        printk(KERN_INFO "Task state: %ld\n",current->state);
        printk(KERN_INFO "pid: %d\n",current->pid);
        printk(KERN_INFO "tgid: %d\n",current->tgid);
        printk(KERN_INFO "prio: %d\n",current->prio);
        return 0;
}
module_init(print_init);
module_exit(print_exit);

Makefile

obj-m:=module2.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-4.4.0-21-generic
all:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

運行後在內核中將輸出以下信息:

 
 

卸載後效果如下:

 
 

module3.c

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/sched.h>

static struct task_struct *pcurrent;

static int __init print_init(void)
{
        printk(KERN_INFO "print current task info\n");
        printk("pid\ttgid\tprio\tstate\n");
        for_each_process(pcurrent){
                printk("%d\t",pcurrent->pid);
                printk("%d\t",pcurrent->tgid);
                printk("%d\t",pcurrent->prio);
                printk("%ld\n",pcurrent->state);
        }
        return 0;
}
static void __exit print_exit(void)
{
        printk(KERN_INFO "Finished\n");
}

module_init(print_init);
module_exit(print_exit);

Makefile

obj-m:=module3.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-4.4.0-21-generic
all:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

加載後效果如下:

 
 

卸載後效果如下:

 
 

修改系統調用的模塊

在/usr/include/i386-linux-gnu/asm/unistd_32.h文件中查看系統調用序號

 
 

發現222號和223號系統調用是空的,因此選取223作爲新的系統調用號。

在/boot/System.map-3.16.0-30-generic查看系統調用表的內存地址:

 
 

爲0xc1697140

編寫模塊文件:

module5.c

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/unistd.h>
#include <linux/sched.h>



MODULE_LICENSE("Dual BSD/GPL");

#define SYS_CALL_TABLE_ADDRESS 0xc1697140  //sys_call_table對應的地址
#define NUM 223  //系統調用號爲223
int orig_cr0;  //用來存儲cr0寄存器原來的值
unsigned long *sys_call_table_my=0;

static int(*anything_saved)(void);  //定義一個函數指針,用來保存一個系統調用

static int clear_cr0(void) //使cr0寄存器的第17位設置爲0(內核空間可寫)
{
    unsigned int cr0=0;
    unsigned int ret;
    asm volatile("movl %%cr0,%%eax":"=a"(cr0));//將cr0寄存器的值移動到eax寄存器中,同時輸出到cr0變量中
    ret=cr0;
    cr0&=0xfffeffff;//將cr0變量值中的第17位清0,將修改後的值寫入cr0寄存器
    asm volatile("movl %%eax,%%cr0"::"a"(cr0));//將cr0變量的值作爲輸入,輸入到寄存器eax中,同時移動到寄存器cr0中
    return ret;
}

static void setback_cr0(int val) //使cr0寄存器設置爲內核不可寫
{
    asm volatile("movl %%eax,%%cr0"::"a"(val));
}

asmlinkage long sys_mycall(void) //定義自己的系統調用
{   
    printk("模塊系統調用-當前pid:%d,當前comm:%s\n",current->pid,current->comm);
    return current->pid;    
}
static int __init call_init(void)
{
    sys_call_table_my=(unsigned long*)(SYS_CALL_TABLE_ADDRESS);
    printk("call_init......\n");
    anything_saved=(int(*)(void))(sys_call_table_my[NUM]);//保存系統調用表中的NUM位置上的系統調用
    orig_cr0=clear_cr0();//使內核地址空間可寫
    sys_call_table_my[NUM]=(unsigned long) &sys_mycall;//用自己的系統調用替換NUM位置上的系統調用
    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_init(call_init);
module_exit(call_exit);

MODULE_AUTHOR("25");
MODULE_VERSION("BETA 1.0")
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章