實驗內容:
- 找一個系統呼叫,系統呼叫號為學號最后 2位相同的系統呼叫【即 97號系統呼叫】
- 通過匯編指令觸發該系統呼叫
- 通過 gdb 跟蹤該系統呼叫的內核處理程序
- 重點閱讀分析系統呼叫入口的保存現場、恢復現場和系統呼叫回傳,以及重點關注系統呼叫程序中內核堆疊狀態的變化
實驗環境:
VMWare虛擬機下的Ubuntu18.04.4,實驗采用的內核版本為linux-5.4.34,
1 環境準備
1.1 內核編譯
回退實驗一的補丁操作:
cd linux-5.4.34
patch -R -p1 < ../mykernel-2.0_for_linux-5.4.34.patch
make defconfig
修改內核編譯配置重新編譯:
#打開debug相關選項
Kernel hacking --->
Compile-time checks and compiler options --->
[*] Compile the kernel with debug info
[*] Provide GDB scripts for kernel debugging
[*] Kernel debugging
#關閉KASLR,否則斷點失敗
Processor type and features --->
[] Randomize the address of the kernel image (KASLR)
make menuconfig
make -j$(nproc)
啟動內核,此時內核無法正常運行,提示Kernel panic報錯:
qemu-system-x86_64 -kernel arch/x86/boot/bzImage
根據報錯提示,可以看出是缺少必要的根檔案系統,導致內核無法掛載,

1.2 制作根檔案系統
電腦加電啟動首先由bootloader加載內核,內核緊接著需要掛載記憶體根檔案系統,其中包含必要的設備驅動和工具,
為了簡化實驗環境,僅借助 BusyBox 制作極簡記憶體根檔案系統,提供基本的用戶態可執行程式,
首先從https://www.busybox.net下載 busybox源代碼解壓,解壓完成后,配置編譯并安裝,
axel -n 20 https://busybox.net/downloads/busybox-1.31.1.tar.bz2
tar -jxvf busybox-1.31.1.tar.bz2
配置編譯成靜態鏈接,不用元件,
cd busybox-1.31.1
make menuconfig


編譯安裝,默認會安裝到原始碼目錄下的 _install 目錄中,
make -j$(nproc) && make install
制作記憶體根檔案系統鏡像:
mkdir rootfs
cd rootfs
cp ../busybox-1.31.1/_install/* ./ -rf
mkdir dev proc sys home
sudo cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/
在根檔案系統目錄下添加init腳本檔案(rootfs/init),init內容如下:
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo "Wellcome MengningOS!"
echo "--------------------"
cd home
/bin/sh
給init腳本添加可執行權限:
chmod +x init
打包成記憶體根檔案系統鏡像:
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
測驗掛載根檔案系統,看內核啟動完成后是否執行init腳本:
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz

bootloader成功加載根檔案系統到記憶體中后,內核會將其掛載到根目錄下,
然后運行根檔案系統中 init 腳本執行一些啟動任務,最后才掛載真正的磁盤根檔案系統,
2 系統呼叫
2.1 查找系統呼叫
在 linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl 檔案中找到相應的系統呼叫:

2.2 觸發系統呼叫
getrlimit用于獲得每個行程能夠創建的各種系統資源的限制使用量,
在rootfs/home/目錄下新建getrlimit_test.c進行測驗:
#include <stdio.h>
#include <sys/resource.h>
int main()
{
struct rlimit limit;
int ret = getrlimit(RLIMIT_NOFILE, &limit);
printf("ret = %d,\tcur = %ld,\tmax = %ld\n",
ret, limit.rlim_cur, limit.rlim_max);
return 0;
}
函式執行成功回傳0,失敗回傳1,
其中,RLIMIT_NOFILE表示每個行程能打開的最多檔案數,
limit.rlim_cur為當前軟體限制,limit.rlim_max為最大硬體限制,
采用靜態編譯:
gcc -o getrlimit_test getrlimit_test.c -static
代碼測驗結果如下:

getrlimit測驗成功后,通過撰寫匯編代碼來觸發系統呼叫:
#include <stdio.h>
#include <sys/resource.h>
int main()
{
struct rlimit limit;
int ret = -1;
asm volatile(
"movq %2, %%rsi\n\t"
"movl %1, %%edi\n\t"
"movl $0x61, %%eax\n\t"
"syscall\n\t"
"movq %%rax,%0\n\t"
:"=m"(ret)
:"a"(RLIMIT_NOFILE), "b"(&limit)
);
printf("ret = %d,\tcur = %ld,\tmax = %ld\n",
ret, limit.rlim_cur, limit.rlim_max);
return 0;
}
2.3 跟蹤系統呼叫內核處理程序
重新制作根檔案系統:
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
純命令列啟動qemu:
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"
開啟新的terminal進行gdb除錯:
cd linux-5.4.34
gdb vmlinux
target remote:1234
c

添加斷點測驗:
b __x64_sys_getrlimit
發現斷點處無法停止,需要分析getrlimit反匯編的代碼:


之前的斷點是根據97號系統呼叫設定,但根據反匯編具體程式可以看到,此處實際呼叫的是0x12e也就是302號系統呼叫,
因此之前根據97號系統呼叫設定的斷點并沒有起到作用,這里應該按照302號系統呼叫進行斷點設定,302號系統呼叫對應的是__x64_sys_prlimit64,
重新設定斷點:
b __x64_sys_prlimit64
成功進入中斷:


觀察函式呼叫堆疊,可以找到系統呼叫入口 entry_SYSCALL_64:
ENTRY(entry_SYSCALL_64)
UNWIND_HINT_EMPTY
/*
* Interrupts are off on entry.
* We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON,
* it is too small to ever cause noticeable irq latency.
*/
swapgs
/* tss.sp2 is scratch space. */
movq %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp
/* Construct struct pt_regs on stack */
pushq $__USER_DS /* pt_regs->ss */
pushq PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /* pt_regs->sp */
pushq %r11 /* pt_regs->flags */
pushq $__USER_CS /* pt_regs->cs */
pushq %rcx /* pt_regs->ip */
GLOBAL(entry_SYSCALL_64_after_hwframe)
pushq %rax /* pt_regs->orig_ax */
PUSH_AND_CLEAR_REGS rax=$-ENOSYS
TRACE_IRQS_OFF
/* IRQs are off. */
movq %rax, %rdi
movq %rsp, %rsi
call do_syscall_64 /* returns with IRQs disabled */
用戶態程式通過系統呼叫進入內核態,程式執行到entry_SYSCALL_64時,先保存行程現場,然后call do_syscall_64執行:
#ifdef CONFIG_X86_64
__visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)
{
struct thread_info *ti;
enter_from_user_mode();
local_irq_enable();
ti = current_thread_info();
if (READ_ONCE(ti->flags) & _TIF_WORK_SYSCALL_ENTRY)
nr = syscall_trace_enter(regs);
if (likely(nr < NR_syscalls)) {
nr = array_index_nospec(nr, NR_syscalls);
regs->ax = sys_call_table[nr](regs);
#ifdef CONFIG_X86_X32_ABI
} else if (likely((nr & __X32_SYSCALL_BIT) &&
(nr & ~__X32_SYSCALL_BIT) < X32_NR_syscalls)) {
nr = array_index_nospec(nr & ~__X32_SYSCALL_BIT,
X32_NR_syscalls);
regs->ax = x32_sys_call_table[nr](regs);
#endif
}
syscall_return_slowpath(regs);
}
#endif
在do_syscall_64中,通過查詢sys_call_table得到具體的系統呼叫函式,本文被呼叫的核心代碼如下:
SYSCALL_DEFINE4(prlimit64, pid_t, pid, unsigned int, resource,
const struct rlimit64 __user *, new_rlim,
struct rlimit64 __user *, old_rlim)
{
struct rlimit64 old64, new64;
struct rlimit old, new;
struct task_struct *tsk;
unsigned int checkflags = 0;
int ret;
if (old_rlim)
checkflags |= LSM_PRLIMIT_READ;
if (new_rlim) {
if (copy_from_user(&new64, new_rlim, sizeof(new64)))
return -EFAULT;
rlim64_to_rlim(&new64, &new);
checkflags |= LSM_PRLIMIT_WRITE;
}
rcu_read_lock();
tsk = pid ? find_task_by_vpid(pid) : current;
if (!tsk) {
rcu_read_unlock();
return -ESRCH;
}
ret = check_prlimit_permission(tsk, checkflags);
if (ret) {
rcu_read_unlock();
return ret;
}
get_task_struct(tsk);
rcu_read_unlock();
ret = do_prlimit(tsk, resource, new_rlim ? &new : NULL,
old_rlim ? &old : NULL);
if (!ret && old_rlim) {
rlim_to_rlim64(&old, &old64);
if (copy_to_user(old_rlim, &old64, sizeof(old64)))
ret = -EFAULT;
}
put_task_struct(tsk);
return ret;
}
當系統呼叫函式運行結束后,函式呼叫堆疊先回傳到do_syscall_64處為回傳用戶態做準備,
回傳到entry_SYSCALL_64中后再完成用戶態程式的恢復作業,系統呼叫完畢,
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/69056.html
標籤:Linux
下一篇:Windows xp修改IP地址
