天天看點

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

目錄

  • 前言
  • 子產品與系統調用
  • 用子產品列印Hello, world!
  • 用子產品添加自定義系統調用
  • top指令
  • 關閉Linux圖形界面
  • 重編核心添加系統調用
  • 解壓系統源代碼
  • 撰寫自定義系統調用
  • 編譯核心
  • 測試新核心
  • 最後

要自定義系統調用, 正常的兩個方法是子產品和重編核心, 一起來看看吧.

更新:

在64位ubuntu12.04.5上也成功運作.

解決了14.04, 16.04, 18.04上的問題.

首先看下系統版本和核心版本. 我用的是32位的ubuntu12.04.5LTS, 我很不喜歡用這麼舊的版本, 但是, 其它版本都出現各種問題, 之後我會給大家展示我掉過的坑, 如果你能幫助我解決, 請評論區, 提前感謝~~
uname -a           
cat /proc/version           
uname -r           
我是在mac端用ssh通路Linux的, 這樣是有很多好處的, 比如直接複制粘貼, 不需要改鍵盤映射等等. 先來寫一個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, 是以沒有提示.
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檔案.
你可以用lsmod指令看下有什麼子產品. 然後插入剛才生成的test.ko.
sudo insmod test.ko           
然後看下列印了消息沒.
dmesg | grep "sorrower"           
可以再用lsmod看一下. 然後解除安裝子產品. 用dmesg | grep "Sorrower"檢視.
sudo rmmod test           

注意, 題目是用系統調用列印Hello, world!, 之前的隻是熟悉一下子產品的使用, 還不是系統調用列印出來的.

來到/usr/include/i386-linux-gnu/asm, 檢視unistd_32.h, 注意這是32位ubutnu12.04.5中的位置, 不代表其他版本其他位數的.

看到223了嗎, 這很明顯就是拿來自定義的.
然後來到/boot, 要檢視sys_call_table的記憶體位置, 注意, 要管理者權限.
然後用vim搜尋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有使用者态和核心态, 系統調用以及中斷和異常都會由使用者态變成核心态. 上一張程序轉換圖(或者叫狀态機?), 圖檔來自網絡, 我覺得畫得一般, 但是我不想再手動畫一張了.

核心必須懂(一): 用系統調用列印Hello, world!
好了, 不皮了. 來寫test.c吧. 簡單粗暴, 就一個系統223調用.
#include<stdio.h>
#include<stdlib.h>
int main()
{
        syscall(223);
        return 0;
}           
gcc一下, 然後dmesg一下. 這下真的就結束這一部分了.

中途休息一下, 來說些小技巧和指令.

mac下的top指令非常好用. 你輸入top, 然後輸入?, 就顯示全部後續操作了. 比如這裡top下輸入o, 在輸入cpu回車. 就是cpu占有排序.

我沒有很讨厭Linux的圖形界面, 但是用了ssh之後, 你就發現确實用不到了. 我知道大家都會切換到tty的. mac是fn+ctrl+option+f3(當然了, 根據版本不同, fx有效範圍不同, 12.04是f1-f6, f7圖形界面, 測測就知道了)
但是還不夠徹底, 要讓它開機直接字元界面. 關閉/開啟. 當然了, 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'的, 怎麼會不一緻呢.

問題已經解決, 如果出現上述錯誤, 隻需要使用: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看了解決方案, 還是解決不能.

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

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