# Add system call to Linux 6.6 and test it in QEMU
## 系統環境
```
OS: Ubuntu Server 22.04
Arch: AMD64
Kernel Source Version: 6.6
```
## 編譯 Kernel Source Code
```shell
# 安裝 git
sudo apt update && sudo apt install -y git
# 用 git clone 下載 source code
git clone --depth=1 --branch=v6.6 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
# 安裝編譯 kernel 需要用到的工具
sudo apt install make gcc libncurses-dev flex bison
# 產生 minimal kernel 編譯設定
make allnoconfig
```
> Kernel 的編譯有非常多參數,在編譯前會要求先建立設定檔。執行 `make allnoconfig` 時會自動生成一個 `.config` 檔案來放置 minimal kernel 的編譯參數。
由於 minimal kernel 無法在 QEMU 內執行,我們還必須調整部分參數。
```shell
# 開啟 kernel 編譯設定編輯器
make menuconfig
```
以下是需要的編譯設定
```
64-bit kernel -> Enable
Executable file formats -> Enable all
Device Drivers > Character devices > Serial drivers and 8250/16550 and compatible serial support -> Enable
Device Drivers > Character devices > Console on 8250/16550 and compatible serial port -> Enable
General Setup > Initial RAM filesystem and RAM disk (initramfs/initrd) support -> Enable
```




```shell
# Kernel source code 有附設一些工具,需要這兩個 library
sudo apt install libssl-dev libelf-dev
# 編譯 kernel 為 bzImage
make -j <num_cpu> bzImage
```
編譯好的 bzImage 會位於 `arch/x86/boot/bzImage`,同時會建立一個位於 `arch/x86_64/boot/bzImage` 的 symbolic link。
## 建立 Root FS
只有 Kernel 是無法正常啟動的,如果用 QEMU 嘗試啟動應該會看到沒有 rootfs 的相關錯誤導致 kernel panic。在此我們採用 BusyBox 來快速建立 rootfs。
```shell
# 回到上層目錄
cd ..
# 下載 BusyBox 的 source code
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
# 解壓縮
tar -xf busybox-1.36.1.tar.bz2
# 進入 BusyBox 的 source code 資料夾
cd busybox-1.36.1
# 調整編譯設定
make menuconfig # 要選取 Build static binary
# 編譯並安裝至 _install 資料夾
make -j <num_cpu> install
# 進入 _install 資料夾
cd _install
# 建立 rootfs 需要的資料夾
mkdir -p lib lib64 proc sys etc etc/init.d
# 寫入開機後執行的腳本
cat > ./etc/init.d/rcS << EOF
#!/bin/sh
# Mount the /proc and /sys filesystems
mount -t proc none /proc
mount -t sysfs none /sys
# Populate /dev
/sbin/mdev -s
EOF
# 設定 rcS 腳本的執行權限
chmod +x etc/init.d/rcS
# 建立 rootfs 的 image
find . | cpio -o --format=newc | gzip > ../../linux/rootfs.img.gz
```
> 需要把 BusyBox build 成 static binary 的原因是 rootfs 中缺少 `ld.so` 和 `libc`,在缺乏這些 share library 的情況下我們只能採用靜態連結。然而靜態連結也並非萬能,實際上 `glibc` 對靜態連結的支援不友好,實際上可能會發生問題,在此之下,有開發者發展了 `musl`,可以完整的支援靜態連結。
> [glibc static linking problem](https://stackoverflow.com/questions/57476533/why-is-statically-linking-glibc-discouraged)
## 使用 QEMU 執行 Kernel
```shell
# QEMU 安裝
sudo apt install qemu qemu-kvm
# 回到 linux source code 資料夾
cd ../../linux
# 執行 kernel
qemu-system-x86_64 -kernel arch/x86/boot/bzImage -nographic -initrd rootfs.img.gz -append "root=/dev/ram rdinit=/sbin/init console=ttyS0"
```
成功開機後按 `Enter` 可進入 shell,若要退出 QEMU,可按 `Ctrl + A + X`。
## 加入 System Call
首先要將 system call 的宣告加入清單中。System call 的清單位於 `arch/x86/entry/syscalls/syscall_64.tbl`。
```
#
# 64-bit system call numbers and entry vectors
#
# The format is:
# <number> <abi> <name> <entry point>
#
# The __x64_sys_*() stubs are created on-the-fly for sys_*() system calls
#
# The abi is "common", "64" or "x32" for this file.
#
0 common read sys_read
1 common write sys_write
2 common open sys_open
```
檔案內有介紹如何新增 system call 宣告,我們將要新增的 system call 置於 453 後即可。
```
454 common my_syscall sys_my_syscall
```
這個檔案會在 compile 階段被讀取後轉為 header file(`arch/x86/include/generated/asm/syscalls_64.h`)
`include/linux/syscalls.h`
在 942 行後加入宣告。
```c
asmlinkage long sys_map_shadow_stack(unsigned long addr, unsigned long size, unsigned int flags); // line 942
asmlinkage long sys_my_syscall();
```
最後需要撰寫 system call 的實作,可先將撰寫的檔案置於 `kernel/my_syscall.c`。
```c
#include <linux/syscalls.h>
SYSCALL_DEFINE0(my_syscall)
{
printk("Hello Linux");
return 0;
}
```
> `SYSCALL_DEFINE0` macro 可定義無參數的 system call,`SYSCALL_DEFINE1` 可定義一個參數,依此類推。
> e.g.
> ```c
> SYSCALL_DEFINE1(my_syscall, long, arg1)
> ```
最後將其加入 makefile。
`kernel/Makefile`
```
obj-y = fork.o exec_domain.o panic.o \
cpu.o exit.o softirq.o resource.o \
sysctl.o capability.o ptrace.o user.o \
signal.o sys.o umh.o workqueue.o pid.o task_work.o \
extable.o params.o \
kthread.o sys_ni.o nsproxy.o \
notifier.o ksysfs.o cred.o reboot.o \
async.o range.o smpboot.o ucount.o regset.o ksyms_common.o \
my_syscall.o
```
## 測試編寫完的 System Call
由於 rootfs 內沒有 tool chain 讓我們編譯程式碼,因此可以先在外部編譯靜態連結的測試程式再將其加入 rootfs。
```c
#include <unistd.h>
long my_syscall()
{
return syscall(454);
}
int main()
{
my_syscall();
return 0;
}
```
> 若有參數可置於 `syscall` 後。
> e.g.
> ```c
> long my_syscall()
> {
> return syscall(454, 123);
> }
> ```