核心必須懂(一): 用系統呼叫列印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.
#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有使用者態和核心態, 系統呼叫以及中斷和異常都會由使用者態變成核心態. 上一張程序轉換圖(或者叫狀態機?), 圖片來自網路, 我覺得畫得一般, 但是我不想再手動畫一張了.
好了, 不皮了. 來寫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佔有排序.
關閉Linux圖形介面
我沒有很討厭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測試沒問題.
這次也是新開一個篇章, 和以往分享操作不同, 文章更偏向探索, 去學習更深的知識. 喜歡記得點贊, 有意見或者建議評論區見, 暗中關注我也是可以的~