## 版本及環境 VM 版本:ubuntu-18.04.6-desktop-amd64.iso Kernel 版本:5.15.137 VM 環境:VMware Workstation 16 Pro ## 環境設置 ### VM 下載 https://releases.ubuntu.com/bionic/ 找不到 Debian 的舊版本載點,所以還是換回 Ubuntu,選了 ubuntu-18.04.6-desktop-amd64.iso 下載,不確定是否能用 server 版實作。 ### 查看目前已安裝的 Kernel 版本 ![image](https://hackmd.io/_uploads/ryFzmEvWyx.png) ### 下載 Kernel Source Code 選取了跟組員相同的版本下載。 ```shell! $ wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.137.tar.xz ``` ```shell! $ tar Jxvf linux-5.15.137.tar.xz ``` ### 下載所需 Package ```shell $ sudo apt update $ sudo apt install build-essential libncurses-dev libssl-dev libelf-dev bison flex -y ``` ### 清除 Package 暫存檔案及舊 Package ```shell! $ sudo apt clean && sudo apt autoremove -y ``` #### apt 下載來源 ```shell $ vim /etc/apt/sources.list // 參考選項: // http://us.archive.ubuntu.com/ubuntu/ // http://ubuntu.cs.nycu.edu.tw/ // http://free.nchc.org.tw/ubuntu/ // 選擇不同站點影響下載速度,不過也可能因為站點不是最新檔案無法下載 ``` ![image](https://hackmd.io/_uploads/ByFRTRubkl.png) ## 環境測試 > 參考文章:https://phoenixnap.com/kb/build-linux-kernel ### 清理 Kernel 設定 ```shell! $ sudo make mrproper ``` ### 設定 Kernel ```shell! $ cd linux-5.15.137 // 複製原本的設定 $ cp -v /boot/config-$(uname -r) .config // GUI 介面 $ make menuconfig // 文字介面 $ make localmodconfig ``` #### 關閉憑證檢驗 ```shell! $ scripts/config --disable SYSTEM_TRUSTED_KEYS $ scripts/config --disable SYSTEM_REVOCATION_KEYS $ scripts/config --set-str CONFIG_SYSTEM_TRUSTED_KEYS "" $ scripts/config --set-str CONFIG_SYSTEM_REVOCATION_KEYS "" ``` ### 編譯 Kernel ```shell! $ make -j$(分配的核心數量) $ sudo make modules_install $ sudo make install ``` ### 重新啟動 & Grub ```shell! $ sudo update-grub $ sudo reboot ``` 重開機過程中按住 shift 可以進入 Grub 頁面 進不了 Grub 可以參考以下文章調整設定 ( `/etc/default/grub` ):https://docs.vmware.com/tw/VMware-NSX-T-Data-Center/3.2/installation/GUID-4630C9D5-71FB-4991-AC1D-9FDBA0B86120.html 進入 Grub 後選擇 `Advanced options for Ubuntu` -> `Ubuntu, with Linux 5.15.137` ( 視選用的 Kernel 版本而定 )。 透過 `uname -r` 可以查看 Kernel 版本是否成功被替換。 ![image](https://hackmd.io/_uploads/H1eOVlUo-1g.png) ![image](https://hackmd.io/_uploads/S1VUEIsZke.png) ![image](https://hackmd.io/_uploads/Hy34SIjZkg.png) ### 過程中的問題 #### sed: can't read modules.order: No such file or directory ![image](https://hackmd.io/_uploads/ryTsFlKZyx.png) - 關閉憑證檢驗sudo #### BTF: .tmp_vmlinux.btf: pahole (pahole) is not available ![image](https://hackmd.io/_uploads/SyCfBWFWke.png) 方法一 ```shell! $ sudo apt install dwarves ``` 方法二 ```shell! sudo vim .config 將 CONFIG_DEBUG_INFO_BTF 改為 n ``` #### kernel/built-in.a: member kernel/trace/trace_kdb.o in archive is not an object ![image](https://hackmd.io/_uploads/rkCPIJcWkg.png) #### 編譯過程當機 不要使用虛擬機中所有的核心去編譯 #### VM 當機後無法直接重啟 ![image](https://hackmd.io/_uploads/S1xgzBiWJl.png) 關掉 Accelerate 3D graphics ## 新增 System Call ### 建立資料夾 ```shell! $ cd linux-5.15.137 $ mkdir mycall $ cd mycall ``` ### 撰寫 System Call #### 編輯程式 ```shell! $ vim hello.c ``` #### 程式內容 ```c= #include <linux/kernel.h> #include <linux/syscalls.h> SYSCALL_DEFINE0(hello){ printk("Hello world!\n"); return 0; } ``` ### 建立 Makefile > 與 hello.c 同一目錄 #### 編輯 ```shell! $ vim Makefile ``` #### 內容 ```c= obj-y := hello.o ``` ### 修改 Linux 原始的 Makefile ```shell! $ cd .. $ vim Makefile ``` #### 將新增的資料夾加入編譯清單 ```c= core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ mycall/ ``` ![image](https://hackmd.io/_uploads/HyxkcFiZkx.png) ### 將 System Call 加入 Syscall Table ```shell! $ vim arch/x86/entry/syscalls/syscall_64.tbl ``` ![image](https://hackmd.io/_uploads/ry4OjKsZyx.png) ### 將 System Call 加入 syscall 的 header file ```shell! $ vim include/linux/syscalls.h ``` ```c= asmlinkage long sys_hello(void); ``` ![image](https://hackmd.io/_uploads/BJFblcj-1e.png) ### 重新編譯 上述檔案完成修改後重新編譯即可 ```shell! $ make -j2 $ sudo make modules_install $ sudo make install ``` ### 測試 System Call ```c= #include <unistd.h> #include <stdio.h> int main(){ long int sys = syscall(449); printf("Return value of sys_hello: %ld\n", sys); return 0; } ``` ```shell! $ gcc -o hello hello.c $ ./hello $ sudo dmesg ``` ### 測試結果 ![image](https://hackmd.io/_uploads/Skel3b6W1g.png) ![image](https://hackmd.io/_uploads/SJjlbMaWJe.png) ## Linux 基本架構 ## New Syscall: my_get_physical_addresses ### pgd_offset ```c= // include/linux/pgtable.h#L132 #define pgd_offset(mm, address) pgd_offset_pgd((mm)->pgd, (address)) // include/linux/pgtable.h#L123 static inline pgd_t *pgd_offset_pgd(pgd_t *pgd, unsigned long address) { return (pgd + pgd_index(address)); }; // include/linux/pgtable.h#L85 #define pgd_index(a) (((a) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1)) ``` Reference: https://elixir.bootlin.com/linux/v5.15.167/source/include/linux/pgtable.h#L132 https://elixir.bootlin.com/linux/v5.15.167/source/include/linux/pgtable.h#L123 https://elixir.bootlin.com/linux/v5.15.167/source/include/linux/pgtable.h#L85 ### pud_offset ### Content ```c= #include <linux/kernel.h> #include <linux/syscalls.h> SYSCALL_DEFINE1(my_get_physical_addresses, void *vaddr) { pgd_t *pgd_base = current->mm->pgd; pgd_t *pgd_entry; printk("pgd_base = 0x%lx\n",p4d_val(*p4d)) pgd_entry = pgd_offset(current->mm, vaddr); return 0; } ``` ## Question 1: copy-on-write ### Source code ```c= #include <stdio.h> //void * my_get_physical_addresses(void *); int global_a=123; //global variable void hello(void) { printf("======================================================================================================\n"); } int main() { int loc_a; void *parent_use, *child_use; printf("===========================Before Fork==================================\n"); parent_use=my_get_physical_addresses(&global_a); printf("pid=%d: global variable global_a:\n", getpid()); prinft("Offest of logical address:[%p] Physical address:[%p]\n", &global_a,parent_use); printf("========================================================================\n"); if(fork()) { /*parent code*/ printf("vvvvvvvvvvvvvvvvvvvvvvvvvv After Fork by parent vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n"); parent_use=my_get_physical_addresses(&global_a); printf("pid=%d: global variable global_a:\n", getpid()); prinft("******* Offset of logical address:[%p] Physical address:[%p]\n", &global_a,parent_use); printf("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n"); wait(); } else { /*child code*/ printf("llllllllllllllllllllllllll After Fork by child llllllllllllllllllllllllllllllll\n"); child_use=my_get_physical_addresses(&global_a); printf("******* pid=%d: global variable global_a:\n", getpid()); prinft("******* Offset of logical address:[%p] Physical address:[%p]\n", &global_a, child_use); printf("llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll\n"); printf("____________________________________________________________________________\n"); /*----------------------- trigger CoW (Copy on Write) -----------------------------------*/ global_a=789; printf("iiiiiiiiiiiiiiiiiiiiiiiiii Test copy on write in child iiiiiiiiiiiiiiiiiiiiiiii\n"); child_use=my_get_physical_addresses(&global_a); printf("******* pid=%d: global variable global_a:\n", getpid()); prinft("******* Offset of logical address:[%p] Physical address:[%p]\n", &global_a, child_use); printf("iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii\n"); printf("____________________________________________________________________________\n"); sleep(1000); } } ``` ## Question 2: ## 附錄 ### Vim 的操作技巧 - `gg` 移動至文件最上方 - `G` 移動至文件最下方 - `/[anything]+` 於文件中搜尋特定字串 - `enter` 後 `n` 查看下一個搜尋到的字串 - `:set number` 顯示行數