i春秋翻譯小組-FWorldCodeZ
源碼級調試的XNU內核
無論你是在開發內核擴展,進行漏洞研究,還是還有其他需要進入macOS / iOS內核,XNU,有時你需要附加調試器。當你這樣做時,使用源代碼執行它是非常好的。Damien DeVille,Snare和其他人都寫過這個過程。以下是他們的一些文章:
事情已經發生了一些變化。雖然你可以從以前的工作中完成所有工作,但有些事情沒有得到解決。所以讓我們從頭開始詳細介紹。
以下是我們的目標:
- 從macOS環境調試macOS 10.13.6內核(10.14內核源尚未發布)
- 使用虛擬機目標,這樣我們就不必拖動兩個單獨的Mac
- 不僅有內核符號,還有內核源代碼
- 能夠從目標機器的內存中漂亮地打印結構
- 在斷點處,顯示:
- 來源清單
- 注冊內容
- 回溯
- 當前線程堆棧
購物清單
在我們做之前,雖然這里有你需要的東西的購物清單。
- 來自Mac App Store的Xcode(主要用於lldb)
- macOS High Sierra 10.13.6安裝程序
- Kernel Debug Kit build 17G65
- XNU source code for xnu-4570.71.2
- 最近的版本VMware Fusion。其他虛擬化工具可能有效,但我們在這里使用VMware Fusion Pro 11。
- Snare的excellent Voltron lldb UI
- lldb 的x86_64 target definition file
虛擬機
創建你的macOS來賓計算機。
- 我推薦一個簡單的用戶名和密碼,如“admin”和“a”
- 你需要啟用SSH並可能自動用戶登錄。
- 請務必在guest虛擬機中安裝VMware工具
- 檢查操作系統版本
sw_vers
。我們正在尋找17G65和xnu版本xnu-4570.71.2:
檢查macOS構建和內核版本
為了便於SSH連接到機器,你可能需要使用VMware的DHCP以及主機名來為其提供靜態IP地址/etc/hosts
。這是如何做。
- 獲取虛擬機網絡接口的MAC地址(通常
en0
)。我的是00:0c:29:a5:fd:3a
- 編輯
/Library/Preferences/VMware Fusion/vmnet8/dhcpd.conf
(vmnet1
如果你沒有使用VMware的NAT,請替換) - 為VM的靜態DHCP租約添加一個節:
-
####### VMNET DHCP Configuration. End of "DO NOT MODIFY SECTION" #######
-
-
host gargleblaster{
-
hardware ethernet 00:0c:29:a5:fd:3a;
-
fixed-address 192.168.44.10;
-
}
- 將VM的主機名添加到
/etc/hosts
主機上 - 如果你願意,可以在Sharing.prefpane以及命令行中使用設置來賓VM的主機名
scutil --set HostName
- 使用ssh密鑰將ssh密鑰復制到vm
ssh-copy-id
- 關閉VM,退出並重新啟動VMware,然后啟動VM以測試所有內容。
- 將VM引導至恢復模式並使用禁用SIP
csrutil
VMware的GDB stub
調試XNU的官方方法是使用內置的調試存根,使用內核調試協議或KDP進行通信。它可以在各種傳輸上工作,包括串行,FireWire,我相信Thunderbolt。但是對於調試VM,你要使用UDP,這對於內核調試來說是超級好的。lldb
經常不同步或失去與調試服務器的聯系,並且內核處於永久停止狀態。我認為這是因為調試服務器是內核本身的一部分,結合了UDP不可靠的特性。所以內核停止,停止與調試器通信,然后lldb
放棄。
更可靠的機制是VMware提供的“硬件”調試工具。這使VM可以模擬內核下的硬件調試器。在這種情況下,內核在自己的調試中不起作用; 它甚至沒有“意識到”它正在被調試。這種方法不是100%可靠,但它通常比KDP更穩定。你也可以(通常)使用^ C中斷,就好像你已連接到正常的用戶空間進程一樣。設置很簡單:
繼續關閉VM。然后編輯.vmx
在.vmwarevm
捆綁包中找到的文件。將以下第1行添加到文件中:
-
debugStub.listen.guest32 = "TRUE"
-
debugStub.listen.guest64 = "TRUE"
如果要從主機以外的計算機(例如另一個來賓VM)進行調試,則可以添加遠程偵聽器:
-
debugStub.listen.guest32.remote = "TRUE"
-
debugStub.listen.guest64.remote = "TRUE"
內核調試工具包
從Apple Developer Portal下載內核調試工具包。下載與VM中的macOS構建相匹配的KDK構建版本至關重要。我相信你需要使用Apple ID登錄開發人員門戶網站,但我認為你不需要為開發者帳戶付費。
你需要在主機和來賓中安裝KDK。從技術上講,只需將開發內核復制到guest虛擬機即可,但只需簡單地安裝整個KDK即可。
在guest虛擬機中,將開發內核從KDK位置復制到內核所在的目錄:
$ sudo cp /Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development /System/Library/Kernels/
由於系統實際上並不啟動內核,而是預先鏈接的內核緩存,因此需要使現有內核緩存無效,從而導致重建。該kextcache
命令執行此操作。它有很多選項,但為了簡單起見,你可以告訴它“重新啟動你在啟動卷上所知道的一切” 2:
$ sudo kextcache -i /
值得在KDK中尋找安裝它的東西。看看/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk
。在其中,你會發現很多內核和kexts的符號包,這非常好。不過,你不必擔心它們。LLDB將使用Spotlight通過UUID找到它們,並在需要時加載它們。真正有趣的是內核dSYM。其中有大量的Python lldb宏。LLDB加載其中一些,但大多數不加載。它們大部分都沒有記錄,但有些非常有用。我們稍后會看幾下。
引導args
有些指南會讓你在訪客中設置各種啟動參數,例如kcsuffix
。根據我的經驗,你無需執行任何特殊操作即可啟動開發內核。只要它存在(或者更重要的是內核緩存存在),它將優先於發布內核。重新啟動VM並檢查內核版本以確保啟動了Development內核:
-
admins-Mac:~ admin$ uname -a
-
Darwin admins-Mac. local 17.7.0 Darwin Kernel Version 17.7.0: Thu Jun 21 22:53:14 PDT 2018; root:xnu-4570.71.2~1/DEVELOPMENT_X86_64 x86_64
你還可以在debug=
引導arg 3 4中設置各種調試標志,但不需要它們。它們不會以任何方式影響VMware的gdb服務器存根。但是,如果你在VMWare的調試存根之外使用kdp,它們可能會很有用。標志是一個位域,其值與OR一起。例如,debug=0x1
告訴操作系統在啟動時暫停並等待調試器。可能一組有用的標志開頭是debug=0x141
。Apple 在這里有部分調試標志列表,如果找不到滿足你需求的調試標志,osfmk/kern/debug.c
可能是你的下一個最佳參考。你還可以在內核源中檢查調試引導arg的其他位置grep其他:
-
-==< zach@endor :~/src/xnu-4570.71.2 >==-
-
( 0) $ grep -rn 'PE_parse_boot_argn\(\"debug\"' .
設置LLDB
為了lldb
理解我們正在調試的東西,我們需要給它一些配置。如果你還沒有,請創建一個~/.lldb
目錄來保存某些lldb
特定文件。~/.lldbinit
如果你還沒有空文件,也要創建一個空文件。
把x86_64_target_definition.py
你先前下載的內容放在這里。當你進行任何其他一般的lldb調整或python腳本時,你也可以進入這里。然后你可以從你的來源獲取它們.lldbinit
。
有一些構建/內核版本特定的配置,所以它不能都是共同的.lldbinit
。我喜歡有一個git repo來跟上我的各種lldb init腳本,但是現在,讓我們假設你正在創建~/.lldb/kernel-debugging
。
首先,我們需要告訴lldb
我們正在調試x86_64目標。lldb
非常靈活,可以調試各種目標架構,甚至是之前從未聽說過的架構。目標定義文件描述了該體系結構。不應該lldb
知道開箱即用的x86_64?是的,它通常會,但不幸的是,我們要連接的遠程gdb存根不能告訴我們的調試器它正在調試什么架構。所以我們提前告訴調試器。將此添加到特定於內核的lldb init腳本:
settings set plugin.process.gdb-remote.target-definition-file ~/.lldb/x86_64_target_definition.py
說到x86,你可能想要將反匯編的風格設置為英特爾,而不是AT&T。你懂。因為你是一名專業人士:
settings set target.x86-disassembly-flavor intel
還記得dSYM包中的那些python腳本嗎?現在我們需要告訴lldb
他們自動加載它們(或者至少是它所需要的那些加載它們)。
settings set target.load-script-from-symbol-file true
KDK參考內核中的dSYM源自Apple構建內核時的任何位置。這通常就像是/BuildRoot/Library/Caches/com.apple.xbs/something/something
。當然lldb
無法在該路徑上找到內核源代碼(除非你把它們放在那里),所以我們需要告訴它要翻譯。以下設置適用於此構建,但其他內核的路徑可能不同。查找來自的錯誤消息lldb
。
settings set target.source-map /BuildRoot/Library/Caches/com.apple.xbs/Sources/xnu/xnu-4570.71.2 /Users/zach/src/xnu-4570.71.2
我們需要lldb
從內核dSYM加載一些超級有用的宏。應該拿起來xnu.py
,但還有更多memory.py
。
command script import "/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development.dSYM/Contents/Resources/Python/lldbmacros/memory.py"
你可以配置許多其他設置lldb
,其中大多數默認為合理的設置。檢查help settings
列表。請特別注意名稱中帶有“darwin”的設置。在某些情況下,可能很難找出可用於設置的可能值。在這種情況下,咨詢lldb
來源可能是最容易的。
此時,你的.lldb/kernel-debugging
腳本應該類似於:
-
-
settings set plugin.process.gdb-remote.target-definition-file ~/.lldb/x86_64_target_definition.py
-
-
-
settings set target.x86-disassembly-flavor intel
-
-
-
settings set target.load-script-from-symbol-file true
-
-
-
settings set target.source-map /BuildRoot/Library/Caches/com.apple.xbs/Sources/xnu/xnu-4570.71.2 /Users/zach/src/xnu-4570.71.2
-
-
-
-
"/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development.dSYM/Contents/Resources/Python/lldbmacros/xnu.py"
-
-
-
command script import "/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development.dSYM/Contents/Resources/Python/lldbmacros/memory.py"
-
-
-
target create /Library/Developer/KDKs/KDK_10 .13.6_17G65.kdk/System/Library/Kernels/kernel.development
啟動VM后,如果運行lldb
(沒有目標二進制文件),則會得到基本(lldb)
提示。現在使用以下內容獲取內核調試腳本
command source ~/.lldb/kernel-debugging
並留意任何錯誤:
如果你已經正確設置了一切,你應該能夠連接gdb-remote
命令:
(lldb) gdb-remote 8864
你應該進入內核,可能是在空閑線程的中間。假設lldb
發現內核,符號和源代碼沒問題,你應該在斷點處看到一個簡短的源代碼片段:
我不確定是什么決定了調試器進入的線程。我懷疑這只是機會。如果你的計算機繁忙,你可能會進入一個非空閑的線程,甚至可能在內核擴展中執行。在這種情況下,你將在一個沒有源代碼的地方。嘗試在經常調用的內核函數上設置斷點,例如dofileread()
並繼續。
(lldb) breakpoint set -n dofileread
如果你正確地破壞了內核而不是擴展,那么你應該看到源代碼。
此時應凍結VM。試着c
繼續跑步; VM應該再次交互。看看^ C是否中斷。
要從VM分離,請執行以下操作:
-
(lldb) c
-
Process 1 resuming
-
(lldb) detach
-
Process 1 detached
-
(lldb) target delete
-
1 targets deleted.
-
(lldb) quit
我發現我的lldb命令歷史記錄無法可靠地保存5,除非我經歷了分離,刪除目標和退出的整個過程。在分離之前,你可能希望清除任何斷點breakpoint delete
。分離應該清除斷點,但根據我的經驗它並不總是,然后你的目標可以隨機掛起。
漂亮的Printing 結構
如果你能夠按名稱打破函數並顯示源代碼,那么你應該將所有設置為漂亮的Printing結構和來自內核內存的其他對象。這對於具有大量C宏和條件定義的非常大的結構特別有用。打印它們可以lldb
讓你輕松查看結構的實際組成方式。
這是一個簡單的例子。設置斷點dofileread()
:
-
(lldb) breakpoint set -n dofileread
-
Breakpoint 1: where = kernel`dofileread + 51 at sys_generic.c:359, address = 0xffffff8015f46eb3
-
(lldb) c
調試器應該立即打到你的斷點; 文件讀取是一種超常用的操作。當它發生時,你應該看到lldb
函數原型的視圖:
kernel`dofileread(ctx=0xffffff8ce861bf00, fp=0xffffff8028c332e8, bufp=140465093751296, nbyte=65536, offset=-1, flags=0, retval=<unavailable>)
嘗試用print
命令打印一些函數參數。你會看到它ctx
是類型vfs_context_t
(實際上是一個typedefed指針),並且fp
是類型fileproc *
。要打印這些結構,你需要將lldb
它們作為指針進行解釋並取消引用它們:
-
(lldb) print ctx
-
(vfs_context_t) $ 44 = 0xffffff8ce861bf00
-
(lldb) print *(vfs_context_t)ctx
-
(vfs_context) $ 45 = {
-
vc_thread = 0xffffff802a029a10
-
vc_ucred = 0xffffff8024d14520
-
}
-
(lldb) print fp
-
(fileproc *) $ 46 = 0xffffff8028c332e8
-
(lldb) print *(fileproc *)fp
-
(fileproc) $ 47 = {
-
f_flags = 0
-
f_iocount = 1
-
f_fglob = 0xffffff802ffc9960
-
f_wset = 0x0000000000000000
-
}
這是它的實際截圖:
建立Voltron
所以我們用符號和源代碼成功調試內核。但是lldb
並沒有給我們太多的用戶界面。如果我們能夠看到更多的上下文,如寄存器,堆棧,指令指針的反匯編,當前線程的回溯,那就太好了。你懂。調試器做的事情。那么,而不是用戶界面,lldb
為你提供API。我想如果我必須在半實現的用戶界面和非常好的API之間做出選擇,我會選擇后者。這就是我們如何獲得Snare的Voltron。
如果你還沒有,請從https://github.com/snare/voltron獲取Voltron 。你很想安裝它pip
,但不要。使用包含的install.sh
shell腳本代替6。此腳本指出你安裝的調試器以及它們使用的Python版本。它還有助於解決Voltron的six
依賴與使用Python系統安裝的依賴之間的沖突。
完成安裝后,它應該在你的附加中添加類似於以下內容的行.lldbinit
:
command script import /Users/zach/Library/Python/2.7/lib/python/site-packages/voltron/entry.py
確保它在那里。還要確保將bin
上面使用的任何Python路徑下的目錄添加到shell中$PATH
。例如:
export PATH=$PATH:$HOME/Library/Python/2.7/bin
現在,在單獨的終端窗口(或tmux
窗格或其他)中,你可以啟動單獨的Voltron視圖。在主窗格中,lldb
正常啟動。然后根據你的選擇配置你的voltron窗格。voltron view registers
例如,從shell運行中,可以查看在每個斷點處更新的寄存器。這是幫助輸出:
-
$ voltron view -h
-
usage: voltron view [-h]
-
{backtrace,t,bt,back,registers,r,reg, register,breakpoints,b,bp,break,command,c,cmd,memory,m,mem,disasm,d,dis,stack,s,st}
-
...
-
-
optional arguments:
-
-h, --help show this help message and exit
-
-
views:
-
valid view types
-
-
{backtrace,t,bt,back,registers,r,reg, register,breakpoints,b,bp,break,command,c,cmd,memory,m,mem,disasm,d,dis,stack,s,st}
-
additional help
-
backtrace (t,bt,back)
-
backtrace view
-
registers (r,reg,register)
-
register values
-
breakpoints (b,bp,break)
-
breakpoints view
-
command (c,cmd) run a command each time the debugger host stops
-
memory (m,mem) display a chunk of memory
-
disasm (d,dis) disassembly view
-
stack (s,st) display a chunk of stack memory
這是我的設置。為巨型截圖道歉。
就是這樣。你正在使用符號,源和Voltron調試macOS內核。
務必向我發推文任何評論或更正。
-
我相信你真的只需要64位調試存根,但我添加了兩者。
-
如果要卸載開發內核並返回發布內核,則需要:
- 刪除以下內容/System/Library/
Kernels/kernel.development
PrelinkedKernels/prelinkedkernel.development
Caches/com.apple.kext.caches/Startup/kernelcache.development
- 像以前一樣使內核緩存失效
-
你將嘗試啟用不可屏蔽中斷或NMI調試標志,以便你可以通過按鍵暫停內核。我會救你的麻煩。它不適用於VM。你的主機每次都會捕獲NMI。我驚慌失措地試圖解決這個問題。我認為沒有辦法模擬VM中的NMI。
-
調試標志不會以任何方式影響VMware的調試存根。同樣,內核甚至都不知道它。它們僅配置內核自己的KDP調試存根。也就是說,它們可以很有用,因為它們為你提供了第二種附加調試器的方法。例如,如果系統在斷點之間發生混亂,你通常無法從VMware存根中反省恐慌情境。但是你可以將第二個
lldb
會話附加到KDP存根以查看恐慌情況。 -
一旦你弄清楚各種
lldb
咒語,你就不想再想出來了。所以你想要你的命令歷史。 - 他親自告訴我這件事。我認為他讓Voltron保持在PyPI只是為了搞砸新手。
作者:shadowfile
翻譯:I春秋翻譯小組-FWorldCodeZ
責任編輯:F0rmat
翻譯來源:https://shadowfile.inode.link/blog/2018/10/source-level-debugging-the-xnu-kernel