# Linux Project 1 http://www.cjwind.idv.tw/Add-system-call-to-linux/ https://medium.com/@lars10192002/%E7%B7%A8%E8%AD%AFlinux-kernel-%E7%89%88%E6%9C%AC-system-call-83c8ebbae62f https://wenyuangg.github.io/posts/linux/linux-add-system-call.html hint 1. ctrl+alt+T 可快速叫出終端機 2. shift+↓(方向鍵) 可快速翻頁(用來找特定要修改的code) ``` 1.cd /usr/src 2.ls 3.sudo wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.10.14.tar.xz --no-check-certificate 4.sudo tar xvf linux-4.10.14.tar.xz 5.sudo rm linux-4.10.14.tar.xz ``` ``` cd /usr/src/linux-4.10.14 # 在裡面創建一個名叫 hello 的資料夾 mkdir hello # 把目錄轉到 hello 資料夾 cd hello vim hello.c ``` 進入vim 後按a插入文字,編輯完按esc後輸入:wq hello.c內容 ``` /* hello.c */ #inlcude <linux/kernel.h> asmlinkage long sys_hello(void){ printk("Hello world!\n"); return 0; } ``` 於同一個下再建立一個 Makefile ``` vim Makefile ``` Makefile 的內容 ``` obj-y := hello.o ``` 接著在 linux-4.10.14 目錄下改 Makefile 告訴你的 compiler ,新的 system call 可以在 hello 目錄中找到 ``` # 把目錄轉回去 cd /usr/src/linux-4.10.14 # 打開此目錄下的 Makefile vim Makefile ``` 你可以在裡面找到一行, 如下我括起來的地方 ![](https://i.imgur.com/HwvFNcq.png) 請你在後面補上 hello 這樣 kernel 在編譯時才會到你的 hello 目錄 改成下面這樣 ``` core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ hello/ ``` 接著因為我的系統是 32 位元的 ((我想大部分的人都是)) 所以我要去修改 syscall_32.tbl 檔 ``` cd arch/x86/entry/syscalls vim syscall_32.tbl ``` 請你在最後一行添加上你的 system call 然後請把編號記住, 等一下會用 ![](https://i.imgur.com/4rHhaRe.png) 接著編輯 syscalls.h 檔 將我們 syscall 的原型添加進檔案的最後一行 (#endif之前) ``` # 把目錄轉回去 cd /usr/src/linux-4.10.14 cd include/linux vim syscalls.h ``` ![](https://i.imgur.com/lVJW4X9.png) 改完後當然是 compile 跟安裝啦! ``` $sudo apt-get install fakeroot build-essential kernel-package libncurses5 libncurses5-dev $sudo apt-get install python-pip python-dev libffi-dev libssl-dev libxml2-dev libxslt1-dev libjpeg8-dev zlib1g-dev $ make menuconfig # 如果之前設定過可以不用 $ make -j4 # 可以加 -j N 來平行編譯 $ sudo make modules_install $ sudo make install $ reboot ``` 重開機後選進階選項 ![](https://i.imgur.com/0CEILRG.png) 選最新版本且kernel正確的版本 ![](https://i.imgur.com/oW4R8Qj.png) 打開終端機後開啟一個hello.c ``` #include <unistd.h> #include <sys/syscall.h> #include <stdio.h> int main() { long ret = syscall(383); // 335 是剛剛的 system call 編號 printf("sys_hello return %ld\n", ret); return 0; } ``` 將hello.c編譯 ``` gcc hello.c ``` 因為沒有指定編譯後的檔名,因此預設檔名為a.out 執行a.out ``` ./a.out ``` 執行正常會看到: ``` sys_hello return 0 ``` 在 kernel 裡用 printk() 印出的訊息可以用指令 dmesg(需要 root 權限)看到: ``` [ 3.211745] AVX2 version of gcm_enc/dec engaged. [ 3.211748] AES CTR mode by8 optimization enabled [ 3.459866] IPv6: ADDRCONF(NETDEV_CHANGE): ens3: link becomes ready [ 81.608954] Hello World! ``` ## 測試方法 https://blog.gtwang.org/linux/linux-tmux-terminal-multiplexer-tutorial/ 測試使用tmux 首先寫一個需要輸入值的測試檔hello.c 編譯成a.out後 使用tmux建立兩個session(ctrl+b %) 一邊讓a.out執行 (./a.out) 一邊看正在執行的process ``` pa -a ``` ![](https://i.imgur.com/hSzZbCW.png) 可以看到正在執行的process id 接下來進入該process看位址分布 ``` cd /proc/6436 ls ``` 使用ls可以看到process內的資料 然後cat maps可以看到process 位址的分布 ``` cat maps ``` ![](https://i.imgur.com/OkgNRz4.png =1500x) 其中第一行可執行的就是目標text段的開頭位置 ## 作業的system call ``` #include <linux/kernel.h> #include <linux/string.h> #include <linux/uaccess.h> #include <linux/init_task.h> // you need to pass this data struct to userspace and print on terminal struct data_segment { unsigned long start_code; unsigned long end_code; }; asmlinkage int pass_kernel_data(pid_t user_pid, void* __user user_address) { struct data_segment my_data_degment; struct task_struct *task; for_each_process(task) { if(task->pid == user_pid) { //what is x1, x2, x3 my_data_segment.start_code = task->mm->start_code; //what is y1, y2, y3 my_data_segment.end_code = task->mm->end_code; // xxx is key function copy_to_user(user_address, &my_data_segment, sizeof(struct data_segment)); } } ... return 0; ``` 編譯完kernel 後再開一個測試檔test.c test.c內容 ``` #include <syscall.h> #include <sys/types.h> #include <stdio.h> #include <unistd.h> #include <time.h> struct data_segment { unsigned long start_code; unsigned long end_code; }; int main() { struct data_segment my_data_segment; int a = syscall(384, getpid(), (void*)&my_data_segment); printf("Start: %lx\nEnd: %lx\n", my_data_segment.start_code, my_data_segment.end_code); int b; scanf("%d",&b); return 0; } ``` ![](https://i.imgur.com/7SbpQZr.png) ![](https://i.imgur.com/CzxDWN4.png) 問題 1.這個function 怎麼去完成這件事情 2.有哪些system call可能有用到這個function 3.what is "asmlinkage" 當 system call handler 要呼叫相對應的 system call routine 時,便將一般用途暫存器的值 push 到 stack 裡,因此 system call routine 就要由 stack 來讀取 system call handler 傳遞的參數。這就是 asmlinkage 標籤的用意。 why 用stack 傳參數? 因為傳參數的方式需要有通用性,在32位元和64位元時的參數儲存方式不同,64位元是使用暫存器儲存參數,因此利用asmlinkage將 ![](https://i.imgur.com/obtL996.png =500x) 4.memcpy與copy_to_user差別/為啥不用memcpy 1. 因為copy_to_user會檢查access_ok是否使用者操作非法地址,例如避免輸入kernel space的位址會讓這個指令去修改其他kernel的資料。 2. 會自我修復page fault的問題 3. 硬體會有一個開關來確定是否開啟存取,copy_to_user會經過這個PAN,增加安全性 https://blog.csdn.net/lhl_blog/article/details/107030517 5.vm_area_struct與mm差別 ![](https://i.imgur.com/CzxDWN4.png) vm_area_struct可以顯示較詳細的process分配內容,像cat maps所顯示出來的資料分配就是vm_area_struct mm只能找到概略性的資源分配(如圖) ![](https://i.imgur.com/3fs2dT0.jpg) 6.syscall define的方式(3.X版kernel不需要,為何4.X 5.X版後需要加) 為了通用性,直接在4.X版或5.X版直接加入sys_也不一定能夠讓程式成功執行,系統利用SYSCALL_DEFINE4先定義了不同個參數的systemcall function定義(巨集) 7.code_start跟code_end範圍這麼大為啥沒用這麼多 因為cat maps所顯示出來的範圍是指code text page的範圍,因為一次會開一個page給code使用,所以實際顯示的範圍會比所查到的還要大 而我們的程式顯示出來的是實際code所佔有的大小