1. 程式人生 > >核心必須懂(一): 用系統呼叫列印Hello, world!

核心必須懂(一): 用系統呼叫列印Hello, world!

目錄

  • 前言
  • 模組與系統呼叫
  • 用模組列印Hello, world!
  • 用模組新增自定義系統呼叫
  • top指令
  • 關閉Linux圖形介面
  • 重編核心新增系統呼叫
  • 解壓系統原始碼
  • 撰寫自定義系統呼叫
  • 編譯核心
  • 測試新核心
  • 最後

前言

要自定義系統呼叫, 常規的兩個方法是模組和重編核心, 一起來看看吧.

更新:
在64位ubuntu12.04.5上也成功執行.
解決了14.04, 16.04, 18.04上的問題.

模組與系統呼叫

用模組列印Hello, world!

首先看下系統版本和核心版本. 我用的是32位的ubuntu12.04.5LTS, 我很不喜歡用這麼舊的版本, 但是, 其它版本都出現各種問題, 之後我會給大家展示我掉過的坑, 如果你能幫助我解決, 請評論區, 提前感謝~~

uname -a
cat /proc/version
uname -r

核心版本

我是在mac端用ssh訪問Linux的, 這樣是有很多好處的, 比如直接複製貼上, 不需要改鍵盤對映等等. 先來寫一個test.c.

test.c

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

MODULE_LICENSE("Dual BSD/GPL");

static int __init hello_init(void)
{
        printk("Hello, world! Written by Sorrower\n");
        return 0;
}

static void __exit hello_exit(void)
{
        printk("Exit, world! Written by Sorrower\n");
}

module_init(hello_init);
module_exit(hello_exit);

然後寫Makefile. 注意看, 如果你用的vim, make前面如果是空格不是TAB, vim是會提示你的. 我這裡就是TAB, 所以沒有提示.

Makefile

obj-m:=test.o

CURRENT_PATH :=$(shell pwd)
VERSION_NUM :=$(shell uname -r)
LINUX_PATH :=/usr/src/linux-headers-$(VERSION_NUM)

all :
        make -C $(LINUX_PATH) M=$(CURRENT_PATH) modules
clean :
        make -C $(LINUX_PATH) M=$(CURRENT_PATH) clean

輸入make指令. 會生成一些檔案, 要用的是.ko檔案.

make

你可以用lsmod指令看下有什麼模組. 然後插入剛才生成的test.ko.

lsmod

sudo insmod test.ko

然後看下列印了訊息沒.

dmesg | grep "sorrower"

dmesg

可以再用lsmod看一下. 然後解除安裝模組. 用dmesg | grep "Sorrower"檢視.

lsmod

sudo rmmod test

rmmod

用模組新增自定義系統呼叫

注意, 題目是用系統呼叫列印Hello, world!, 之前的只是熟悉一下模組的使用, 還不是系統呼叫打印出來的.
來到/usr/include/i386-linux-gnu/asm, 檢視unistd_32.h, 注意這是32位ubutnu12.04.5中的位置, 不代表其他版本其他位數的.

unistd_32.h

看到223了嗎, 這很明顯就是拿來自定義的.

unistd_32.h

然後來到/boot, 要檢視sys_call_table的記憶體位置, 注意, 要管理員許可權.

sys_call_table

然後用vim搜尋sys_call_table. 我特意把行號標出來了, 你要是想手動找到, 祝你好運了.

sys_call_table

開始寫syscall.c. 這段程式碼不是我寫的, 來自這篇文章, 寫得很棒. 然後請原諒我不要臉地在自定義系統呼叫裡面加了自己的Hello, world!(手動滑稽)

#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("Hello, world! Written by Sorrower\n");
    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");
MODULE_DESCRIPTION("a module for replace a syscall");

Makefile檔案和之前差不多, 改下生成的.o檔名字就好.
然後要寫一個使用者態的程式來測試了.

什麼是使用者態, 來快速解釋一下. cpu有使用者態和核心態, 系統呼叫以及中斷和異常都會由使用者態變成核心態. 上一張程序轉換圖(或者叫狀態機?), 圖片來自網路, 我覺得畫得一般, 但是我不想再手動畫一張了.

程序轉換

好了, 不皮了. 來寫test.c吧. 簡單粗暴, 就一個系統223呼叫.

#include<stdio.h>
#include<stdlib.h>
int main()
{
        syscall(223);
        return 0;
}

gcc一下, 然後dmesg一下. 這下真的就結束這一部分了.

測試

top指令

中途休息一下, 來說些小技巧和指令.
mac下的top指令非常好用. 你輸入top, 然後輸入?, 就顯示全部後續操作了. 比如這裡top下輸入o, 在輸入cpu回車. 就是cpu佔有排序.

輸入?

輸入cpu

cpu消耗排序

關閉Linux圖形介面

我沒有很討厭Linux的圖形介面, 但是用了ssh之後, 你就發現確實用不到了. 我知道大家都會切換到tty的. mac是fn+ctrl+option+f3(當然了, 根據版本不同, fx有效範圍不同, 12.04是f1-f6, f7圖形介面, 測測就知道了)

tty

但是還不夠徹底, 要讓它開機直接字元介面. 關閉/開啟. 當然了, 12.04似乎不吃這個指令. 要再高版本一些.

sudo systemctl set-default multi-user.target
sudo reboot
sudo systemctl set-default graphical.target
sudo reboot

重編核心新增系統呼叫

接下來這個就很簡單了, 主要難度在找檔案位置以及cpu. 這裡切換回18.04LTS. cpu不好的, 可能2h+了, 好的cpu編個18.04LTS怎麼20min也要吧. cpu核數兩位數的麻煩關閉頁面, 不在一個頻道了(手動滑稽). 那順帶一提, 之前說的徹底關閉圖形介面在18.04LTS就生效了.

解壓系統原始碼

你可以使用指令下載原始碼, 也可以手動下載. 總之, 下完之後, 解壓檔案. 看圖片, 我就是用指令下載, 然後再解壓壓縮包, 所以有兩個同名目錄.

sudo apt-get install linux-source
sudo tar -jxvf linux-source-4.15.0.tar.bz2

解壓原始碼

撰寫自定義系統呼叫

關鍵是三個檔案sys.c, syscalls.h, syscall_32.tbl. 都在很要命的地方呢.

  • sys.c在/usr/src/linux-source-4.15.0/linux-source-4.15.0/kernel下
  • syscalls.h在/usr/src/linux-source-4.15.0/linux-source-4.15.0/arch/x86/include/asm下
  • syscall_32.tbl在/usr/src/linux-source-4.15.0/linux-source-4.15.0/arch/x86/entry/syscalls下

對著呼叫編號就是666(手動滑稽).

設定呼叫編號

開啟sys.c寫自定義函式, 注意函式名對應.

自定義系統呼叫函式

申明函式, 還是注意名稱對應.

申明函式

編譯核心

需要先補下庫.

sudo apt-get install libncurses5-dev

然後你可以設定編譯引數, 如果你知道自己在幹嘛的話.

sudo make menuconfig

編譯設定

然後就是cpu測試時間了. 編譯好了, 裝下重啟就完事了. 我就不重做了.

sudo make
sudo make modules
make modules_install
make install

測試新核心

上幾張之前實驗時候截的效果圖, 測試函式還是之前的test.c, 改下呼叫號就可以了.

在編譯

新核心測試

最後

先來幾個坑, 求人救救孩子~~
這是14.04.5中的, 說什麼Invalid module format, StackOverFlow說是核心版本不一致, 但是我Makefile中是用’uname -r’的, 怎麼會不一致呢.

1404

問題已經解決, 如果出現上述錯誤, 只需要使用:sudo apt-get install linux-source-(uname -r得到的核心號)即可.
例如:

sudo apt-get install linux-source-4.15.0

之後使用如下指令, 可能會提示補庫:

sudo make bzImage
sudo make modules
sudo make modules_install

reboot之後問題迎刃而解.

然後看一下預設的18.04, 不是我改過核心的那個. 也在google和StackOverFlow看了解決方案, 還是解決不能.

1804

解決方案除了重編核心, 就是重新安裝映象, 目前我新裝的18.04.1測試沒問題.

這次也是新開一個篇章, 和以往分享操作不同, 文章更偏向探索, 去學習更深的知識. 喜歡記得點贊, 有意見或者建議評論區見, 暗中關注我也是可以的~