# Semester Project This note includes the introduction and steps we've done for the semester project of the Linux course. ## Team Members 112552015 李昀磬<br> 112552024 胡淑旻<br> 111552012 周芊華 ## Goal * Add some system calls * Write a multi-thread program with three threads (main thread, thread 1, and thread 2) * Add new system calls to check which segments of a thread are shared by others * (optional) Obtain the the size, star address, and end address of each thread segment ## Overview * Built on macOS Monterey with **Apple M2** chip * Work based on ubuntu-22.04-LTS-arm64 * Kernel version 5.15.0-91-generic ## Prework ### Get the Needed App and Setup 1. Go to UTM's official website and install the app 2. Download [ubuntu server 22.04.3 LTS](https://cdimage.ubuntu.com/releases/22.04/release/ubuntu-22.04.3-live-server-arm64.iso?_ga=2.102778755.2002784953.1703913926-1870441787.1703913926) iso file 3. Launch UTM and click the plus sign to add a new VM 4. Click "Virtualize" then select the operating system "Linux" 5. Under "Boot ISO image", select the iso file you just downloaded 6. Use the default memory setting (4096 MB), set the number of CPU cores to 4 and click next 7. Set storage size to 64GB 8. Finish the rest of the settings and click save ### Install ubuntu 1. Launch the VM 2. Follow the steps to install ubuntu 3. Select "reboot the server" once its done, then the screen will turn black and seems like nothing running 4. Disconnect the server and go back to UTM 5. Scroll down to the bottom and empty the CD/DVD (in the dropdown menu, click clear) ### Install GUI (optional) 1. Launch your VM 2. Login by entering the user name and password you just set while installing ubuntu 3. In the terminal run the following commands ``` bash # update apt sudo apt update # install ubuntu Desktop sudo apt install ubuntu-desktop # reboot sudo reboot ``` 4. Re-launch the VM and you'll now able to use ubuntu desktop ## Get The Kernel Source Code ### Check kernel verion First, check your kernel version by typing the following: ``` bash uname -r ``` Mine is **5.15.0-91-generic**. ### Download the kernel source ``` bash wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.16.9.tar.xz ``` By downloading a kernal of a higher version, the kernel gets automatically updated when you reboot the system after compiling. ### Extract tar.xz file ``` bash unxz -v linux-5.16.9.tar.xz ``` ### Verify Linux kernel tartball with pgp ``` bash # First grab the PGP signature for linux-5.16.9.tar wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.16.9.tar.sign # Try to verify it gpg --verify linux-5.16.9.tar.sign ``` You'll get outputs like: ![Screen Shot 2024-01-01 at 10.30.17 AM EDIT](https://hackmd.io/_uploads/HJAh6hkO6.jpg) Grab the public key from the PGP keyserver in order to verify the signature ``` bash gpg --keyserver keyserver.ubuntu.com --recv-keys $(YOUR_RSA_KEY) gpg --no-default-keyring -a --export $(YOUR_RSA_KEY) | gpg --no-default-keyring --keyring ~/.gnupg/trustedkeys.gpg --import ``` Now verify gpg key again with the gpg command ``` bash gpg --verify linux-5.16.9.tar.sign ``` Extract the Linux kernel tarball using the tar command below ``` bash tar xvf linux-5.16.9.tar ``` :::warning :bulb: A digital signature certifies and timestamps a document. If the document is subsequently modified in any way, a verification of the signature will fail. A digital signature can serve the same purpose as a hand-written signature with the additional benefit of being tamper-resistant. The GnuPG source distribution, for example, is signed so that users can verify that the source code has not been modified since it was packaged. ::: ## Install the required compilers and other tools Since we're using Debian/Ubuntu: ``` bash sudo apt-get install build-essential libncurses-dev bison flex libssl-dev libelf-dev ``` ## Add New System Calls ### Define a new system call sys_my_syscall() You're currently under the path `/home/${name}/linux-5.16.9` Create a file ``` bash mkdir my_syscall vi my_syscall/my_syscall.c ``` Write the following code to the editor ``` c= #include <linux/sched.h> // For current pointer and task_struct #include <linux/printk.h> // For printk function #include <linux/syscalls.h> // For asmlinkage #include <linux/highmem.h> // For high memory management #include <linux/hugetlb.h> // For huge pages support // Function to convert a virtual address to a physical address unsigned long virtophys(unsigned int virt) { pgd_t *pgd; p4d_t *p4d; pud_t *pud; pmd_t *pmd; pte_t *pte; unsigned long page_addr, page_offset, phys; // Traverse the page tables to find the corresponding physical address pgd = pgd_offset(current->mm, virt); if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd))) goto out; p4d = p4d_offset(pgd, virt); if (p4d_none(*p4d) || unlikely(p4d_bad(*p4d))) goto out; pud = pud_offset(p4d, virt); if (pud_none(*pud) || unlikely(pud_bad(*pud))) goto out; pmd = pmd_offset(pud, virt); if (pmd_huge(*pmd)) goto out; if (pmd_none(*pmd) || unlikely(pmd_bad(*pmd))) goto out; pte = pte_offset_map(pmd, virt); if (!pte_present(*pte)) { pte_unmap(pte); goto out; } // Calculate the physical address page_addr = pte_val(*pte) & PAGE_MASK; page_offset = virt & ~PAGE_MASK; phys = page_addr | page_offset; return phys; out: return 0; } // Define a new system call using the SYSCALL_DEFINE macro SYSCALL_DEFINE1(my_syscall, unsigned int __user *, result) { struct mm_struct *mm; unsigned long start_phy, end_phy; if (current->mm) { mm = current->mm; printk(KERN_INFO "Hello mysyscall\n"); printk(KERN_INFO "Process name is %s (pid %d)\n", current->comm, current->pid); if (mm->mmap) { // For each memory segment, convert its start and end virtual addresses // to physical addresses and print them start_phy = virtophys(mm->start_stack); end_phy = virtophys(mm->start_stack + mm->stack_vm); printk(KERN_INFO "Stack segment virtual start=0x%lx, end=0x%lx\n", mm->start_stack, mm->start_stack + mm->stack_vm); printk(KERN_INFO "Stack segment physical start=0x%lx, end=0x%lx\n", start_phy, end_phy); // ... Repeat for other memory segments (heap, data, and text) // Convert the user-provided address to a physical address and copy it back to user space unsigned long physicalAddress = virtophys((unsigned long)result); if (copy_to_user(result, &physicalAddress, sizeof(physicalAddress))) { return -EFAULT; // Use standard error code } } } return 0; } ``` Create a Makefile in the my_syscall directory (beware of the uppercase) ``` bash vi Makefile ``` Add the following code to ensure that the my_syscall.c file is compiled and included in the kernel source code. ``` obj-y := my_syscall.o ``` Add `my_syscall/` to the kernel’s Makefile Go back to the parent directory and open "Makefile" ``` bash cd .. vi Makefile ``` Search for `core-y` in the document and add `my_syscall/` to the end of the line ``` core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ my_syscall/ ``` This is to tell the compiler that the source files of our new system call (sys_my_syscall()) are in present in the my_syscall directory. ### Add the new system call to the system call table Go to `syscalls` and edit `syscall_64.tbl` (for 64-bit) ``` bash vi arch/x86/entry/syscalls/syscall_64.tbl ``` And add the following to the last line ``` 548 common my_syscall sys_my_syscall ``` ![Screen Shot 2024-01-08 at 10.54.03 PM](https://hackmd.io/_uploads/r1RJ6YYda.png) ### Add to header file Go to `include/linux/` under the `linux-5.16.9/` ``` bash vi include/linux/syscalls.h ``` Edit Linux syscall interface and add the following right before the *#endif*. ``` c asmlinkage long sys_my_syscall(unsigned int __user *result); ``` ![Screen Shot 2024-01-08 at 10.54.21 PM](https://hackmd.io/_uploads/SJICntKdp.png) This defines the prototype of the function of our system call. “asmlinkage” is a key word used to indicate that all parameters of the function would be available on the stack. ## Compile The Kernel ### Make sure it is clean before starting The following command cleans up the directory. ``` bash sudo make mrproper ``` ### Configure the Linux kernel features and modules Before start building the kernel, one must configure Linux kernel features. You must also specify which kernel modules (drivers) needed for your system. Just simply copy the existing config file for now. ``` bash # go to the source code file cd linux-5.16.9 # copy config file cp -v /boot/config-$(uname -r) .config ``` If you just try to compile kernel, if outputs the error: ![Screen Shot 2024-01-01 at 1.46.47 PM](https://hackmd.io/_uploads/H1yyfR1dp.png) To fix this, open `.config`, comment out the `CONFIG_SYSTEM_TRUSTED_KEYS`, `CONFIG_MODULE` and `CONFIG_SYSTEM_REVOCATION_KEYS` ![Screen Shot 2024-01-01 at 2.02.24 PM](https://hackmd.io/_uploads/S1WhD0y_T.png) Type in the following to start kernel configuration ``` bash sudo make menuconfig ``` Tap the **tab** key to navigate to **\<Load\>** and load whatever config you want. After editing the configs, navigate to **\<Save\>** and save `.config` file. Run the make command to compile kernel ``` bash # -j specifies how many cores to use # -s to output the errors only sudo make -j4 -s ``` It outputs some warning about *the frame size is xxxx bytes larger than 1024*, so I edited the `.config` file, adjust `CONFIG_FRAME_WARN` from 1024 to 2048, and compile again. It ended up with the following error: ![Screen Shot 2024-01-01 at 3.06.36 PM](https://hackmd.io/_uploads/SydhVked6.png) Install **dwarves** to fix this. ``` bash sudo apt-get install dwarves ``` Compile the kernel again, and got another error: ![Screen Shot 2024-01-01 at 3.13.45 PM](https://hackmd.io/_uploads/SkaHL1e_6.png) Add the following code to `scripts/pahole-flags.sh` ``` sh if [ "${pahole_ver}" -ge "124" ]; then # see PAHOLE_HAS_LANG_EXCLUDE extra_pahole="${extra_paholeopt} --skip_encoding_btf_enum64" fi ``` Although I gave 64GB for the VM, it still ran out of space. After checking the space allocated, I found that ubuntu only used half of the space. The remainder is reserved in case the user needs to create another logical volume in the future. As a result, I extended the lvm by the following command: ``` bash # without --resizefs, the command will only extend the logical volume but not the filesystem inside it. sudo lvextend --resizefs -l +100%FREE ubuntu-vg/ubuntu-lv ``` ## Install The Linux Kernel Modules and Install Kernel ``` bash sudo make modules_install -j4 sudo make install -j4 ``` This will generate three files into `/boot` directory and modify the kernel grub configuration file. ![Screen Shot 2024-01-01 at 7.33.24 PM](https://hackmd.io/_uploads/B1YfXmgua.png) ## (optional) Update grub Config You need to modify Grub 2 boot loader configurations. The following commands are optional as make install does everything for your but included here for historical reasons only: ``` bash sudo update-initramfs -c -k 5.16.9 sudo update-grub ``` ## Reboot Your System Reboot the computer and verify new Linux kernel after reboot ``` bash reboot # check this after reboot uname -mrs ``` ![Screen Shot 2024-01-01 at 7.40.29 PM](https://hackmd.io/_uploads/BJeaN7gua.png) ## Test System Call Go to the home directory (`cd ~`), create a file called `userspace.c` and paste the following code. The code is a multi-threaded program that uses a custom system call (defined as number 548). ``` c= #include<stdio.h> #include<stdlib.h> #include<pthread.h> #include <unistd.h> #include <sys/syscall.h> #include <string.h> #define MEMORY_SIZE 1000000 #define __NR_my_syscall 548 void print_result(int *result, char * str) { for(int i = 0; i < MEMORY_SIZE; i += 2) { if(result[i + 1] != 0) { printf(" %s Virtual Address: 0x%x Physical Address: 0x%x\n", str , result[i], result[i + 1]); } } } void* child(void* arg) { int* result = (int *)arg; syscall(__NR_my_syscall, "child thread", result); pthread_exit(NULL); }; int main() { // allocate memory for the results int* result = calloc(MEMORY_SIZE, sizeof(int)); int* result_1 = calloc(MEMORY_SIZE, sizeof(int)); int* result_2 = calloc(MEMORY_SIZE, sizeof(int)); pthread_t thread1 , thread2; // main thread which makes the system call syscall(__NR_my_syscall, "I'm the main thread"); // creates two child threads pthread_create(&thread1, NULL, child, (void *)result_1); pthread_create(&thread2, NULL, child, (void *)result_2); // main thread and its results syscall(__NR_my_syscall, "Main thread", result); print_result(result, "main thread"); // wait for thread1 to finish and print its results pthread_join(thread1, NULL); print_result(result_1, "thread1"); // wait for thread2 to finish and print its results pthread_join(thread2, NULL); print_result(result_2, "thread2"); return 0; } ``` Compile and run the program ``` bash gcc userspace.c ./a.out ``` Check the kernel log buffer ``` bash sudo dmesg ``` ## Results We created three threads in total (main thread, thread 1, thread 2). According to the results, the threads share the Code, Lib, Data, and BSS segments. They don't share Stack and Heap. ## Reference [UTM | Virtual Machine for Mac](https://mac.getutm.app) [How to compile and install Linux Kernel 5.16.9 from source code - Vivek Gite](https://www.cyberciti.biz/tips/compiling-linux-kernel-26.html) [Adding a Hello World System Call to Linux Kernel - Anubhav Shrimal ](https://medium.com/anubhav-shrimal/adding-a-hello-world-system-call-to-linux-kernel-dad32875872) [Adding a New System Call - The Linux Kernel Documentation](https://docs.kernel.org/process/adding-syscalls.html) [Kernel Build Guide - By: Isaiah Anyimi, Ona Igbinedion, Moriah Scott](https://bjohnson.lmu.build/private/KernelBuildGuide.pdf) [gpg - sks-keyservers gone. What to use instead?](https://unix.stackexchange.com/questions/656205/sks-keyservers-gone-what-to-use-instead) [Ubuntu 22.04 编译 Linux 5.16.5 内核报错:FAILED: load BTF from vmlinux: Invalid argument](https://blog.csdn.net/woay2008/article/details/132748659) [process | OuyangSheng's Blog](https://0uyangsheng.github.io/2018/05/17/Linux-process/) [Linux进程地址管理之mm_struct](https://www.cnblogs.com/muahao/p/7462611.html) [C/C++ Linux pthread_join 用法與範例](https://shengyu7697.github.io/cpp-pthread_join/)