上一篇文章和大家簡要說明了下kprobe到底應該怎樣用,那么現在我們就揭開kprobe神秘的面紗,刨根問底,一睹kprobe的廬山真面目。
kprobe的工作過程大致如下:
1)注冊kprobe。注冊的每個kprobe對應一個kprobe結構體,該結構中記錄着插入點(位置),以及該插入點本來對應的指令original_opcode;
2)替換原有指令。使能kprobe的時候,將插入點位置的指令替換為一條異常(BRK)指令,這樣當CPU執行到插入點位置時會陷入到異常態;
3)執行pre_handler。進入異常態后,首先執行pre_handler,然后利用CPU提供的單步調試(single-step)功能,設置好相應的寄存器,將
下一條指令設置為插入點處本來的指令,從異常態返回;
4)再次陷入異常態。上一步驟中設置了single-step相關的寄存器,所以originnal_opcode剛一執行,便會二進宮:再次陷入異常態,此時將single-step
清除,並且執行post_handler,然后從異常態安全返回。
步驟2),3),4)便是一次kprobe工作的過程,它的一個基本思路就是將本來執行一條指令擴展成執行kprobe->pre_handler ---> 指令 ---> kprobe-->post_hander這樣三個過程。下面詳細解釋每個過程:
指令替換過程:
上圖中藍色區域表示內存,紅色標明了地址,綠色部分代表一條指令,上圖的意思是,內存0xfffffc000162914處存放一條指令是0xa9bd7bfd。那么,現在我注冊了一個kprobe,探測點是sys_write函數,該函數的起始位置就是0xffffffc000162914,現在我要使能kprobe了,那么我要做的就是把0xffffffc000162914處原來的指令0xa9bd7bfd替換成一條BRK指令,即上圖所表示的一個移花接木過程。你可能會好奇原來的指令0xa9bd7bfd存在哪里?存在kprobe結構體的opcode域!這樣當不再使能kprobe的時候,我再恢復回去。
觸發BRK指令:
上面把人家指令給改了,那么CPU執行到BRK必然會引發內核陷入BRK異常狀態:
藍色部分依舊表示內存,綠色部分表示指令,紅色表示CPU,上圖表示CPU執行到0xffffffc000162914(sys_write)處,該處指令為BRK,於是內核陷入異常態。在異常態中,內核通過BRK指令的錯誤碼判斷這是一個kprobe異常,於是進入了kprobe處理函數。kprobe異常處理函數會根據發生異常的地址來找到對應的kprobe(kprobe的addr域記錄着地址),執行kprobe的pre_handler函數,然后設置single-step相關的寄存器,為下一步執行原指令時發生single-step異常作准備。那么緊接着就是設置原指令的地址了,我們知道0xffffffc000162914處已經被替換成了BRK指令,原指令保存在kprobe結構體中,怎么保證下一步執行到原指令呢?最簡單的做法是申請一塊內存,然后將原指令復制到這塊內存開始處,設置PC寄存器為該內存的首地址,這樣當代碼從異常態返回時,執行的第一條指令便是原指令了!
原指令得到執行,二進宮
經過上面一個步驟,pre_handler得到了執行,從異常態返回之后,原指令也得到了執行,但是由於設置了single-step模式,所以執行完原指令,馬上又陷入了異常態,二進宮:
這次進入異常態后,先清一下single-step相關的寄存器,確保下次從異常返回時的指令不會由於single-step發生三進宮,然后執行post_handler,最后將地址0xfffffc000162918寫入到PC寄存器,為什么是這個數值呢?它正是緊接着0xffffffc000162914的下一條指令的地址,有沒有發現,至此我們已經完成了pre_handler->原指令->post_handler這樣三個階段,也就是說kprobe要做的事情都做完了,此時的工作就是收拾下殘局,返回到正常的指令流程,我們的探測點在0xffffffc000162914處,下一條指令應該就是0xffffffc000162918了,所以把此值寫入PC寄存器,讓一切恢復正軌!
kprobe工作結束,走上正軌
上面把PC設置成了0xffffffc000162918,所以從異常態返回時,CPU就走上了正軌接着朝下面執行了,一個BRK指令引發的反應在此就告一段落了,但是每次當CPU執行到0xffffffc000162914處,都會觸發上面的一連串操作,kprobe的機制也就是從一個BRK指令開始了。
注:
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
由於kprobe涉及到程序指令的修改,這部分和體系結構相關,我選擇的體系結構ARM64,如本文的BRK指令等均是ARM64中的概念,
x86中INT3與之對應。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------