源碼級調試的XNU內核


i春秋翻譯小組-FWorldCodeZ

源碼級調試的XNU內核

無論你是在開發內核擴展,進行漏洞研究,還是還有其他需要進入macOS / iOS內核,XNU,有時你需要附加調試器。當你這樣做時,使用源代碼執行它是非常好的。Damien DeVilleSnare和其他人都寫過這個過程。以下是他們的一些文章:

事情已經發生了一些變化。雖然你可以從以前的工作中完成所有工作,但有些事情沒有得到解決。所以讓我們從頭開始詳細介紹。

以下是我們的目標:

  • 從macOS環境調試macOS 10.13.6內核(10.14內核源尚未發布)
  • 使用虛擬機目標,這樣我們就不必拖動兩個單獨的Mac
  • 不僅有內核符號,還有內核源代碼
  • 能夠從目標機器的內存中漂亮地打印結構
  • 在斷點處,顯示:
    • 來源清單
    • 注冊內容
    • 回溯
    • 當前線程堆棧

購物清單

在我們做之前,雖然這里有你需要的東西的購物清單。

虛擬機

創建你的macOS來賓計算機。

  • 我推薦一個簡單的用戶名和密碼,如“admin”和“a”
  • 你需要啟用SSH並可能自動用戶登錄。
  • 請務必在guest虛擬機中安裝VMware工具
  • 檢查操作系統版本sw_vers。我們正在尋找17G65和xnu版本xnu-4570.71.2:

 

​                                          檢查macOS構建和內核版本

為了便於SSH連接到機器,你可能需要使用VMware的DHCP以及主機名來為其提供靜態IP地址/etc/hosts。這是如何做。

  1. 獲取虛擬機網絡接口的MAC地址(通常en0)。我的是00:0c:29:a5:fd:3a
  2. 編輯/Library/Preferences/VMware Fusion/vmnet8/dhcpd.confvmnet1如果你沒有使用VMware的NAT,請替換)
  3. 為VM的靜態DHCP租約添加一個節:
  1.  
    ####### VMNET DHCP Configuration. End of "DO NOT MODIFY SECTION" #######
  2.  
     
  3.  
    host gargleblaster{
  4.  
            hardware ethernet 00:0c:29:a5:fd:3a;
  5.  
            fixed-address 192.168.44.10;
  6.  
    }
  1. 將VM的主機名添加到/etc/hosts主機上
  2. 如果你願意,可以在Sharing.prefpane以及命令行中使用設置來賓VM的主機名 scutil --set HostName
  3. 使用ssh密鑰將ssh密鑰復制到vm ssh-copy-id
  4. 關閉VM,退出並重新啟動VMware,然后啟動VM以測試所有內容。
  5. 將VM引導至恢復模式並使用禁用SIP csrutil

VMware的GDB stub

調試XNU的官方方法是使用內置的調試存根,使用內核調試協議或KDP進行通信。它可以在各種傳輸上工作,包括串行,FireWire,我相信Thunderbolt。但是對於調試VM,你要使用UDP,這對於內核調試來說是超級好的。lldb經常不同步或失去與調試服務器的聯系,並且內核處於永久停止狀態。我認為這是因為調試服務器是內核本身的一部分,結合了UDP不可靠的特性。所以內核停止,停止與調試器通信,然后lldb放棄。

更可靠的機制是VMware提供的“硬件”調試工具。這使VM可以模擬內核下的硬件調試器。在這種情況下,內核在自己的調試中不起作用; 它甚至沒有“意識到”它正在被調試。這種方法不是100%可靠,但它通常比KDP更穩定。你也可以(通常)使用^ C中斷,就好像你已連接到正常的用戶空間進程一樣。設置很簡單:

繼續關閉VM。然后編輯.vmx.vmwarevm捆綁包中找到的文件。將以下第1行添加到文件中:

  1.  
    debugStub.listen.guest32 = "TRUE"
  2.  
    debugStub.listen.guest64 = "TRUE"

如果要從主機以外的計算機(例如另一個來賓VM)進行調試,則可以添加遠程偵聽器:

  1.  
    debugStub.listen.guest32.remote = "TRUE"
  2.  
    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內核:

  1.  
    admins-Mac:~ admin$ uname -a
  2.  
    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 4中設置各種調試標志,但不需要它們。它們不會以任何方式影響VMware的gdb服務器存根。但是,如果你在VMWare的調試存根之外使用kdp,它們可能會很有用。標志是一個位域,其值與OR一起。例如,debug=0x1告訴操作系統在啟動時暫停並等待調試器。可能一組有用的標志開頭是debug=0x141。Apple 在這里有部分調試標志列表,如果找不到滿足你需求的調試標志,osfmk/kern/debug.c可能是你的下一個最佳參考。你還可以在內核源中檢查調試引導arg的其他位置grep其他:

  1.  
    -==< zach@endor :~/src/xnu-4570.71.2 >==-
  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腳本應該類似於:

  1.  
    #幫助lldb弄清楚我們正在調試x86_64
  2.  
    settings set plugin.process.gdb-remote.target-definition-file ~/.lldb/x86_64_target_definition.py
  3.  
     
  4.  
    #使用合理的反匯編語法
  5.  
    settings set target.x86-disassembly-flavor intel
  6.  
     
  7.  
    #告訴加載隱藏在.dSYM文件中的任何lldb腳本和宏
  8.  
    settings set target.load-script-from-symbol-file true
  9.  
     
  10.  
    #告訴lldb源目錄到底在哪里
  11.  
    settings set target.source-map  /BuildRoot/Library/Caches/com.apple.xbs/Sources/xnu/xnu-4570.71.2 /Users/zach/src/xnu-4570.71.2
  12.  
     
  13.  
    #這應該在我們設置目標可執行文件時自動加載
  14.  
    #命令腳本導入
  15.  
    "/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development.dSYM/Contents/Resources/Python/lldbmacros/xnu.py"
  16.  
     
  17.  
    #這似乎沒有自動加載,所以我們在這里加載它。
  18.  
    command script import "/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development.dSYM/Contents/Resources/Python/lldbmacros/memory.py"
  19.  
     
  20.  
    # 加載我們將要調試的內核二進制文件。
  21.  
    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分離,請執行以下操作:

  1.  
    (lldb) c
  2.  
    Process 1 resuming
  3.  
    (lldb) detach
  4.  
    Process 1 detached
  5.  
    (lldb) target delete
  6.  
    1 targets deleted.
  7.  
    (lldb) quit

我發現我的lldb命令歷史記錄無法可靠地保存5,除非我經歷了分離,刪除目標和退出的整個過程。在分離之前,你可能希望清除任何斷點breakpoint delete。分離應該清除斷點,但根據我的經驗它並不總是,然后你的目標可以隨機掛起。

漂亮的Printing 結構

如果你能夠按名稱打破函數並顯示源代碼,那么你應該將所有設置為漂亮的Printing結構和來自內核內存的其他對象。這對於具有大量C宏和條件定義的非常大的結構特別有用。打印它們可以lldb讓你輕松查看結構的實際組成方式。

這是一個簡單的例子。設置斷點dofileread()

  1.  
    (lldb) breakpoint set -n dofileread                                                            
  2.  
    Breakpoint 1: where = kernel`dofileread + 51 at sys_generic.c:359, address = 0xffffff8015f46eb3
  3.  
    (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它們作為指針進行解釋並取消引用它們:

  1.  
    (lldb) print ctx
  2.  
    (vfs_context_t) $ 44 = 0xffffff8ce861bf00
  3.  
    (lldb) print *(vfs_context_t)ctx
  4.  
    (vfs_context) $ 45 = {
  5.  
      vc_thread = 0xffffff802a029a10
  6.  
      vc_ucred = 0xffffff8024d14520
  7.  
    }
  8.  
    (lldb) print fp
  9.  
    (fileproc *) $ 46 = 0xffffff8028c332e8
  10.  
    (lldb) print *(fileproc *)fp
  11.  
    (fileproc) $ 47 = {
  12.  
      f_flags = 0
  13.  
      f_iocount = 1
  14.  
      f_fglob = 0xffffff802ffc9960
  15.  
      f_wset = 0x0000000000000000
  16.  
    }

這是它的實際截圖:

 

建立Voltron

所以我們用符號和源代碼成功調試內核。但是lldb並沒有給我們太多的用戶界面。如果我們能夠看到更多的上下文,如寄存器,堆棧,指令指針的反匯編,當前線程的回溯,那就太好了。你懂。調試器做的事情。那么,而不是用戶界面,lldb為你提供API。我想如果我必須在半實現的用戶界面和非常好的API之間做出選擇,我會選擇后者。這就是我們如何獲得Snare的Voltron。

如果你還沒有,請從https://github.com/snare/voltron獲取Voltron 。你很想安裝它pip,但不要。使用包含的install.shshell腳本代替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運行中,可以查看在每個斷點處更新的寄存器。這是幫助輸出:

  1.  
    $ voltron view -h
  2.  
    usage: voltron view [-h]
  3.  
                        {backtrace,t,bt,back,registers,r,reg, register,breakpoints,b,bp,break,command,c,cmd,memory,m,mem,disasm,d,dis,stack,s,st}
  4.  
                        ...
  5.  
     
  6.  
    optional arguments:
  7.  
      -h, --help            show this help message and exit
  8.  
     
  9.  
    views:
  10.  
      valid view types
  11.  
     
  12.  
      {backtrace,t,bt,back,registers,r,reg, register,breakpoints,b,bp,break,command,c,cmd,memory,m,mem,disasm,d,dis,stack,s,st}
  13.  
                             additional help
  14.  
        backtrace (t,bt,back)
  15.  
                            backtrace view
  16.  
        registers (r,reg,register)
  17.  
                             register values
  18.  
        breakpoints (b,bp,break)
  19.  
                            breakpoints view
  20.  
        command (c,cmd)     run a command each time the debugger host stops
  21.  
        memory (m,mem)      display a chunk of memory
  22.  
        disasm (d,dis)      disassembly view
  23.  
        stack (s,st)        display a chunk of stack memory

這是我的設置。為巨型截圖道歉。

 

就是這樣。你正在使用符號,源和Voltron調試macOS內核。

務必向我發推文任何評論或更正。


  1. 我相信你真的只需要64位調試存根,但我添加了兩者。

  2. 如果要卸載開發內核並返回發布內核,則需要:

    1. 刪除以下內容/System/Library/
    • Kernels/kernel.development
    • PrelinkedKernels/prelinkedkernel.development
    • Caches/com.apple.kext.caches/Startup/kernelcache.development
    1. 像以前一樣使內核緩存失效
  3. 你將嘗試啟用不可屏蔽中斷或NMI調試標志,以便你可以通過按鍵暫停內核。我會救你的麻煩。它不適用於VM。你的主機每次都會捕獲NMI。我驚慌失措地試圖解決這個問題。我認為沒有辦法模擬VM中的NMI。

  4. 調試標志不會以任何方式影響VMware的調試存根。同樣,內核甚至都不知道它。它們僅配置內核自己的KDP調試存根。也就是說,它們可以很有用,因為它們為你提供了第二種附加調試器的方法。例如,如果系統在斷點之間發生混亂,你通常無法從VMware存根中反省恐慌情境。但是你可以將第二個lldb會話附加到KDP存根以查看恐慌情況。

  5. 一旦你弄清楚各種lldb咒語,你就不想再想出來了。所以你想要你的命令歷史。

  6. 他親自告訴我這件事。我認為他讓Voltron保持在PyPI只是為了搞砸新手。

作者:shadowfile

翻譯:I春秋翻譯小組-FWorldCodeZ

責任編輯:F0rmat

翻譯來源:https://shadowfile.inode.link/blog/2018/10/source-level-debugging-the-xnu-kernel

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM