2-1 課堂練習2.1:外部中斷
本實訓分析 Linux 0.11 對外部中斷的響應和處理過程。在每條指令執行的末尾,如果沒有關中斷,CPU 會檢查是否收到了外部中斷信號,如果有信號,則 CPU 就切換到核心態去執行對應的中斷處理程序,在處理完畢后,會執行 iret 這個中斷返回指令,回到原狀態(一般是用戶態),繼續執行原程序。
第1關時鐘中斷的發生
任務描述
?本關任務:通過實際操作回答在輸出第一行 0/1 字符的過程中(如下圖所示),共發生了幾次時鐘中斷?
相關知識
為了完成本關任務,你需要掌握: 1.設置版本 1 內核為分析對象; 2.開始用 gdb 調試內核; 3.跟蹤分析時鐘中斷。
設置版本1內核為分析對象
首先解壓版本1內核源碼。使用cp
命令將/data/workspace/myshixun/exp1
中的1.tgz
復制到~/os/
目錄下;
切換到~/os/linux-0.11-lab
目錄下,將1.tgz
解壓到當前目錄下;
然后調整cur的指向。先使用rm -rf cur
將cur
刪除,再使用ln
命令創建符號鏈接。
現在可以編譯和測試版本 1 內核。首先進入1/linux
目錄下編譯內核;
確認內核映像文件Image
已經生成;
然后回到目錄~/os/linux-0.11-lab
,并使用./run
啟動虛擬機檢測內核是否正常;
如果正常虛擬機在加載完畢之后將會出現如下畫面。
第2關第一次時鐘中斷
?任務描述
分析版本1內核,找到第一次時鐘中斷的恢復點地址。
相關知識
為了完成本關任務,你需要掌握: 1.用 gdb 調試內核; 2.跟蹤分析時鐘中斷; 3.中斷/異常的恢復點分析。
準備階段
本關卡的分析對象是版本1內核,可以基于前一關卡環境進行后續實驗,如果重置實驗環境則需要重新將版本1內核設置為分析對象,詳見第一關的相關知識。
使用 gdb 調試內核
啟動兩個終端,在一個終端里切換到目錄~/os/linux-0.11-lab
,然后運行腳本 rungdb,以啟動 bochs 虛擬機并等待 gdb 連接;
在另一個終端里切換到目錄~/os/linux-0.11-lab
,然后執行腳本./mygdb
,以啟動 gdb 并讀入符號信息,跟蹤到 main 函數入口。
跟蹤分析時鐘中斷
在函數 do_timer(由時鐘中斷的處理函數 timer_interrupt 調用)處設置斷點; 跟蹤到該斷點第 1 次出現;
中斷/異常的恢復點分析
當一個中斷/異常被 gdb 捕獲時,通常正在運行中斷處理程序,這時可以繼續跟蹤,直至回到恢復點指令。以時鐘中斷為例,為了從函數 do_timer 跟蹤到恢復點,可以如下操作:
由上圖可見時鐘中斷處理程序的入口是 timer_interrupt 函數。 跟蹤到當前函數(do_timer)執行完畢返回到 timer_interrupt 函數;
跟蹤到 timer_interrupt 函數(用匯編語言寫的)末尾的 iret 指令;
通過單步執行命令 si 來執行該 iret 指令,返回到恢復點;
再通過反匯編命令:disas ,分析恢復點指令的地址。
關閉 gdb 調試: 在評測通關之后為了保證環境的正常,不影響下一關的操作,需要先輸入 kill 指令關閉虛擬機,然后輸入 quit 退出 gdb 調試。
答案
第3關第六次時鐘中斷
?任務描述
本關任務:通過相關知識以及實驗回答:版本 1 內核的第 6 次時鐘中斷發生時,斷點和恢復點(指令地址)分別是多少?此時 bochs 虛擬機輸出的 0/1 字符串是什么?(忽略空格)
相關知識
為了完成本關任務,你需要掌握: 1.使用 gdb 調試內核; 2.跟蹤分析時鐘中斷。
準備階段
本關卡基于前面關卡環境進行后續實驗,如果重置實驗環境請從第一關重新開始。
使用 gdb 調試內核
啟動兩個終端,在一個終端里切換到目錄~/os/linux-0.11-lab
,然后運行腳本 rungdb,以啟動 bochs 虛擬機并等待 gdb 連接;
在另一個終端里切換到目錄~/os/linux-0.11-lab
,然后執行腳本./mygdb
,以啟動 gdb 并讀入符號信息,跟蹤到 main 函數入口。
跟蹤分析時鐘中斷
開始用 gdb 調試內核,跟蹤到 main 函數入口。方法與第 1 關的步驟(2)一樣。 (點擊右下角上一關可以直接查看上一關內容,不會對關卡造成影響,但是不要點擊評測,會改變環境)
捕獲第 6 次時鐘中斷的發生
方法與第 2 關的步驟(3)類似,跟蹤到斷點 do_timer 第 6 次出現時即可,此時 jiffies 的值也是 6 。
中斷/異常的恢復點分析
當一個中斷/異常被 gdb 捕獲時,通常正在運行中斷處理程序,這時可以繼續跟蹤,直至回到恢復點指令。以時鐘中斷為例,為了從函數 do_timer 跟蹤到恢復點,可以類似如下操作:
由上圖可見時鐘中斷處理程序的入口是 timer_interrupt 函數。 跟蹤到當前函數(do_timer)執行完畢返回到 timer_interrupt 函數;
跟蹤到 timer_interrupt 函數(用匯編語言寫的)末尾的 iret 指令;
使用調試命令 si 來執行該 iret 指令,返回到恢復點;
然后通過反匯編命令:disas ,來分析恢復點指令的地址。
斷點指令和恢復點指令的分析
對于外部中斷而言,恢復點指令是斷點指令的后一條。需要說明的是,loop 指令的功能是先將 ecx 寄存器減一,然后檢查其值,如果其值非 0 ,則繼續循環,否則中止循環,執行下一條指令。以如下指令為例:
其功能是:在地址 0x7977 處循環,每次 ecx 寄存器都減一,直到其值為 0 。因此,loop 指令的上一條有可能是它自己。 在 gdb 中查看寄存器值的命令是 info reg:
斷點指令和恢復點指令的分析
對于外部中斷而言,恢復點指令是斷點指令的后一條。需要說明的是,loop 指令的功能是先將 ecx 寄存器減一,然后檢查其值,如果其值非 0 ,則繼續循環,否則中止循環,執行下一條指令。以如下指令為例:
其功能是:在地址 0x7977 處循環,每次 ecx 寄存器都減一,直到其值為 0 。因此,loop 指令的上一條有可能是它自己。 在 gdb 中查看寄存器值的命令是 info reg:
答案
?
2-2 課后作業2.1:外部中斷
第1關修改版本 1 內核源碼,使得每次時鐘中斷發生時,都在屏幕上輸出字符 ‘t’
任務描述
本關任務:修改版本 0 內核,使得每發生 100 次時鐘中斷,就在屏幕上輸出一個字符‘t’和當時的進程號,如“t(0)”表示0號進程運行時發生了時鐘中斷。
相關知識
為了完成本關任務,你需要掌握: 1.內核態下的字符輸出; 2.判斷已經發生了多少次時鐘中斷。
內核態下的字符輸出
在版本 0 內核里,可以使用函數printk
來輸出字符,其用法類似于printf
。 注: 在版本 0 內核中,沒有實現用int 0x81
指令輸出字符這個功能。
printk
用法示例:
printk("trying to free inode with count=%d\n",inode->i_count);
判斷已經發生了多少次時鐘中斷
已經發生的時鐘中斷的次數記錄在全局變量 jiffies 中,每發生一次時鐘中斷,該變量的值就增加 1 。
編程要求
根據相關知識,以及上課內容,對版本 0 內核進行修改,使得每發生 100 次時鐘中斷,就在屏幕上輸出一個字符‘t’和當時的進程號。
使用VScode打開1/linux文件夾,獲取版本1內核源代碼?
在timer_interrupt函數開頭添加輸出字符t的匯編代碼:
movb $116, %al
int $0x81
回到命令行中重新編譯1/linux,再啟動./rungdb和./mygdb虛擬機即可過關
第2關修改版本 0 內核
任務描述
本關任務:修改版本 0 內核,使得每發生 100 次時鐘中斷,就在屏幕上輸出一個字符‘t’和當時的進程號,如“t(0)”表示0號進程運行時發生了時鐘中斷。
相關知識
為了完成本關任務,你需要掌握: 1.內核態下的字符輸出; 2.判斷已經發生了多少次時鐘中斷。
內核態下的字符輸出
在版本 0 內核里,可以使用函數printk
來輸出字符,其用法類似于printf
。 注: 在版本 0 內核中,沒有實現用int 0x81
指令輸出字符這個功能。
printk
用法示例:
printk("trying to free inode with count=%d\n",inode->i_count);
判斷已經發生了多少次時鐘中斷
已經發生的時鐘中斷的次數記錄在全局變量 jiffies 中,每發生一次時鐘中斷,該變量的值就增加 1 。
編程要求
根據相關知識,以及上課內容,對版本 0 內核進行修改,使得每發生 100 次時鐘中斷,就在屏幕上輸出一個字符‘t’和當時的進程號。
通過vscode找到do_timer
?在函數中添加
if(jiffies%100==0)
prink("t(%d),sys_getpid());
2-3 課堂練習2.2:中斷/異常的處理過程

第1關除零異常分析
任務描述
分析版本 1.1 內核,回答下列問題: 1.在函數 main 的語句jiffies = jiffies/0;
所對應的匯編指令片段中,有一個 idiv 指令,此指令的地址是多少? 2.在該 idiv 指令執行之前,當前指令位置(CS:EIP)和棧位置(SS:ESP)分別是多少? 3.使用 si 命令執行了該指令后,新指令位置和棧位置分別是多少?此時棧中保存的恢復點位置和用戶棧位置分別是多少?
相關知識
為了完成本關任務,你需要掌握: 1.如何設置某版本的內核為分析對象; 2.如何開始用 gdb 調試內核; 3.查看 C 語句編譯之后對應的匯編指令片段; 4.分析響應中斷/異常時,CPU 做了哪些工作; 5.查看當前寄存器的狀態; 6.查看當前棧頂的狀態。
實驗準備
本關卡使用版本 1.1 內核作為分析對象,內核文件存放在/data/workspace/myshixun/exp1
文件夾中,可以將其解壓到linux-0.11-lab
下使用。
如何設置某版本的內核為分析對象
下面以版本1內核為例進行講解。
首先解壓版本1內核源碼。使用cp
命令將/data/workspace/myshixun/exp1
中的1.1.tgz
復制到~/os/
目錄下;
切換到~/os/linux-0.11-lab
目錄下,將1.1.tgz
解壓到當前目錄下;
然后調整cur的指向。先使用rm -rf cur
將cur
刪除,再使用ln
命令創建符號鏈接。
如何開始用 gdb 調試內核
先關閉bochs虛擬機,然后打開兩個終端,其中一個終端在linux-0.11-lab
目錄下運行rungdb
腳本,以啟動 bochs 虛擬機并等待 gdb 連接;
在另一個終端里切換到目錄~/os/linux-0.11-lab/
,然后啟動腳本./mygdb
,這個命令會啟動 gdb 并讀入內核符號信息,同時會通過執行0.gdb
中的調試命令來連接到 bochs 虛擬機,并進而跟蹤到 main 函數入口。
查看 C 語句編譯之后對應的匯編指令片段
如果要查看某條 C 語句編譯之后對應的匯編指令片段,可以在該 C 語句處設置斷點,并跟蹤到該斷點,然后反匯編,所看到的當前指令之后的一段匯編指令就對應于該 C 語句。
例如,jiffies = jiffies/0;
是文件 main.c 的第 147 行,可以如下方式查看:
上面顯示的匯編指令中,有一行前面有箭頭標識,此即為當前指令,即馬上將要執行的指令。
分析響應中斷/異常時,CPU 做了哪些工作
-
切換到核心棧,并在其中保存中斷現場。
-
轉到中斷處理程序去運行,并切換到核心態。
如下圖所示:
上圖顯示了棧中中斷現場的結構,(OLD SS:OLD ESP) 描述了用戶棧頂的位置,(OLD CS:OLD EIP) 描述了恢復點的位置。
如何查看當前寄存器的狀態
使用 gdb 調試命令 info registers 即可,如下所示:
也可以單獨查看某一個寄存器:
如何查看當前棧頂的狀態
可以使用命令 x 來查看:
上面顯示了棧頂的 5 個長字,是某異常發生時的中斷現場,其中存儲的用戶棧頂的位置是 0x17:0x2573c ,存儲的恢復點的位置是 0xf:0x7967 。需要注意的是,x86 中棧是從高地址向低地址方向增長的,這里的棧頂位置是 0x1fa0c 。
編程要求
根據相關知識,回答問題:(將答案填寫在/data/workspace/myshixun/第一關.txt
中) 1.在函數 main 的語句jiffies = jiffies/0;
所對應的匯編指令片段中,有一個 idiv 指令,此指令的地址是多少? 2.在該 idiv 指令執行之前,當前指令位置(CS:EIP)和棧位置(SS:ESP)分別是多少? 3.使用 si 命令執行了該指令后,新指令位置和棧位置分別是多少?此時棧中保存的恢復點位置和用戶棧位置分別是多少?