《Linux內核分析》MOOC課程:http://mooc.study.163.com/course/USTC-1000029000
第五講 扒開系統調用的三層皮(下)
@2015.04
通過gdb我們可以給系統調用內核處里程序如sys_write, sys_time設置斷點,並讓程序停在斷點處,進行斷點跟蹤系統調用處裡過程。由於system_call是完全用匯編寫就一個的函數,雖然我們也可以在system_call處設置斷點,但卻無法讓系統停在system_call處,所以也無法通過單步跟蹤學習其處裡流程。但system_call是所有系統調用的入口,也是程序由用戶態轉入內核態執行時無法越過的一個函數,其重要性不言而喻,所以我們跟隨老師簡化的彙編代碼以及源代碼學習其主要的流程。
實驗環境是使用本課程配備的實驗樓虛擬機環境,打開命令行客戶端,cd LinuxKernel目錄,使用命令rm -rf menu刪除原來的代碼,使用git clone https://github.com/mengning/menu.git獲取menu的最新代碼,然後 cd menu 進入menu子文件夾,使用gedti test.c打開文件,講我上週實驗的代碼拷貝改寫成為menu的兩個菜單項,主要代碼部分如下:
int SayHello(int argc, char *argv[])
{
char* msg = "Hello World";
printf("%s", msg);
printf("\n");
return 0;
}
int SayHelloAsm(int argc, char *argv[])
{
char* msg = "Hello World";
int len = 11;
int result = 0;
__asm__ __volatile__("movl %2, %%edx;\n\r"
"movl %1, %%ecx;\n\r"
"movl $1, %%ebx;\n\r"
"movl $4, %%eax;\n\r"
"int $0x80"
:"=m"(result)
:"m"(msg),"r"(len)
:"%eax");
printf("\n");
return 0;
}
int main()
{
PrintMenuOS();
SetPrompt("MenuOS>>");
MenuConfig("version","MenuOS V1.0(Based on Linux 3.18.6)",NULL);
MenuConfig("quit","Quit from MenuOS",Quit);
MenuConfig("time","Show System Time",Time);
MenuConfig("time-asm","Show System Time(asm)",TimeAsm);
<strong> MenuConfig("sayhello","Say Hello World",SayHello);
MenuConfig("sayhello-asm","Say Hello World",SayHelloAsm);</strong>
ExecuteMenu();
}
這裡主要就是加了兩個菜單項 sayhello 和 sayhello-asm 及其對應的實現函數,保存 test.c 函數,使用 make rootfs 編譯運行 menuos 系統,輸入help 可以看到我們新加入的菜單:
使用 gdb 跟蹤相關的函數過程,在我前述的博客中有筆記詳細的描述,有興趣的可以參考下:http://blog.csdn.net/sunyeyi/article/details/44517109,這裡主要就是說明下,我們可以在 gdb 裡使用 b system_call 設置斷點,但是卻無法讓程序停在這個地方。當然網上也有資料提供了其他技術來跟蹤這個函數,由於個人時間能力所限,沒有去研究!
先看下系統調用機制的初始化,在start_kernel函數中,trap_init函數就是完成系統調用初始化的。
/init/main.c start_kernel
trap_init();
在trap_init()函數中,關鍵代碼如下,通過中斷向量,將system_call函數和0x80綁定: /arch/x86/kernel/traps.c
#ifdef CONFIG_X86_32
set_system_trap_gate(SYSCALL_VECTOR, &system_call);
set_bit(SYSCALL_VECTOR, used_vectors);
#endif
據此,用戶態代碼只要執行 int 0x80 中斷,就會由 system_call 函數來處裡。 再來看看system_call源代碼,http://codelab.shiyanlou.com/xref/linux-3.18.6/arch/x86/kernel/entry_32.S,490行處。
根據這段源代碼,參考老師在視頻中的講述,我們可以得到system_call的簡化流程圖如下:
這裡我們需要特別注意的就是,在系統調用system_call返回之前,會檢查當前進程是否需要執行syscall_exit_work,如果不需要就restore_all,然後中斷返回了;如果需要處裡,那麼就在syscall_exit_work流程中查看是否有進程信號需要處理,若有就處裡,是否需要進程調度,若有就調用schedule,切換到其他進程執行;然後才restore_all,中斷返回。
通過中斷向量表,int 0x80和system_call關聯起來;system_call又是通過系統調用號,將每一個系統調用和特定的系統調用服務例程關聯起來;在system_call返回用戶態之前,會執行syscall_exit_work,work_notifysig,schedule等函數以應對可能的進程信號處理和進程調度。