戰(zhàn):從下載到跑通全流程)
簡(jiǎn)介這是一份面向操作系統(tǒng)內(nèi)核研究者和Windows驅(qū)動(dòng)開發(fā)學(xué)習(xí)者的ReactOS 0.3.15源碼包可用于分析類Windows系統(tǒng)架構(gòu)、對(duì)比開源實(shí)現(xiàn)并開展內(nèi)核調(diào)試實(shí)驗(yàn)。包內(nèi)共2000個(gè)文件以C語(yǔ)言源代碼551個(gè).c和頭文件1376個(gè).h為主輔以少量txt說(shuō)明文檔和1個(gè)cpp文件壓縮后約83.32MB。描述提到該版本在VS2012環(huán)境下可成功編譯出ntoskrnl.exe與ntoskrnl.pdb意味著讀者能夠借助源碼完成有限度的內(nèi)核級(jí)調(diào)試適合具備一定C/C基礎(chǔ)并希望深入操作系統(tǒng)底層的人群。目前已有174人學(xué)習(xí)下載在同類開源系統(tǒng)源碼中具備一定參考價(jià)值。通過閱讀這份代碼可以理解Windows NT內(nèi)核的關(guān)鍵模塊劃分、系統(tǒng)調(diào)用流程以及驅(qū)動(dòng)接口實(shí)現(xiàn)為后續(xù)參與操作系統(tǒng)移植或內(nèi)核安全研究打下基礎(chǔ)。 拿到這個(gè) ReactOS-0.3.15-REL-src.zip 的時(shí)候相信不少人跟我一樣第一眼就被一長(zhǎng)串縮寫弄得有點(diǎn)懵。其實(shí)拆分下來(lái)很直白R(shí)eactOS 是項(xiàng)目名0.3.15 是版本號(hào)REL 表示這是正式發(fā)布版src 是 source 的縮寫zip 就是最常見的壓縮格式。這整套組合在一起就是一款開源、號(hào)稱追求 Windows 兼容的操作系統(tǒng)的完整源代碼快照。我這次寫作前專門把這個(gè)老包又翻出來(lái)重新解壓、編譯、跑了幾遍中間踩了幾個(gè)歷史遺留的坑也把當(dāng)年一直沒太捋清的目錄關(guān)系重新理了一遍。這篇文章就把整個(gè)過程和核心思路寫透給想研究 ReactOS 源碼的讀者一份可以直接照著做的參考。1. 先搞清楚這個(gè)壓縮包到底值不值得下1.1 一句話定義一個(gè)追求 Windows 兼容的開源系統(tǒng)源碼ReactOS 這個(gè)項(xiàng)目跟 Linux 其實(shí)沒有直接關(guān)系。它是從零開始、以兼容 Windows 應(yīng)用程序和設(shè)備驅(qū)動(dòng)為目標(biāo)的一整套獨(dú)立操作系統(tǒng)。注意這里說(shuō)的是“兼容”不是“基于”也不是“使用”。整個(gè)系統(tǒng)的代碼都是重新寫的目的是讓 Windows 的 exe、dll、驅(qū)動(dòng)等二進(jìn)制文件能在這套系統(tǒng)里以接近原生的方式運(yùn)行起來(lái)。那這個(gè) zip 包里裝的就是 0.3.15 這個(gè)時(shí)間點(diǎn)上所有源碼的集合。解壓之后你能看到內(nèi)核、圖形子系統(tǒng)、驅(qū)動(dòng)框架、系統(tǒng)工具、開發(fā)庫(kù)這些東西的完整實(shí)現(xiàn)。整套代碼規(guī)模不算小但比起現(xiàn)代操作系統(tǒng)動(dòng)輒幾千萬(wàn)行的體量它又足夠輕量適合一個(gè)人或者一個(gè)小團(tuán)隊(duì)通讀關(guān)鍵模塊。這也是它長(zhǎng)期被當(dāng)作操作系統(tǒng)課程實(shí)踐素材的原因之一。下載這個(gè)包的人大概率是三類一類是學(xué)操作系統(tǒng)原理的學(xué)生想看看真正的內(nèi)核代碼長(zhǎng)什么樣一類是研究 Windows 內(nèi)部機(jī)制的開發(fā)人員想通過一個(gè)可運(yùn)行、可修改、可斷點(diǎn)調(diào)式的開源系統(tǒng)來(lái)驗(yàn)證自己的想法還有一類純粹好奇想看看號(hào)稱“Windows 克隆”的玩意兒到底做到了什么程度。無(wú)論你是哪一類0.3.15 都是一個(gè)很合適的入口版本。1.2 為什么特意去找 0.3.15 這個(gè)老版本你可能會(huì)問ReactOS 現(xiàn)在版本號(hào)都到 0.4.x 甚至更新了為什么還有人翻 0.3.15 這種老版本我當(dāng)初也是無(wú)意中在一個(gè)鏡像站點(diǎn)看到這個(gè)名字才下載的。事后復(fù)盤老版本有幾個(gè)不可替代的價(jià)值。第一0.3.15 處在 0.3 系列的中后段代碼結(jié)構(gòu)已經(jīng)相對(duì)穩(wěn)定但又沒有后來(lái) 0.4 系列那么大動(dòng)干戈的重構(gòu)。對(duì)整個(gè)項(xiàng)目來(lái)說(shuō)它更像是從“勉強(qiáng)能跑”到“初步可用”的一個(gè)過渡節(jié)點(diǎn)研究這個(gè)版本能清楚看到設(shè)計(jì)上的取舍。第二老版本代碼量更小編譯更快對(duì)硬件要求也更低普通虛擬機(jī)開 512M 內(nèi)存就能流暢跑起來(lái)用來(lái)學(xué)習(xí)內(nèi)核和子系統(tǒng)非常友好。第三它記錄了一個(gè)真實(shí)的歷史狀態(tài)你在閱讀代碼時(shí)能看到很多后來(lái)被替代掉的設(shè)計(jì)思路這種“考古”視角對(duì)新項(xiàng)目設(shè)計(jì)很有啟發(fā)。當(dāng)然如果你追求的是能運(yùn)行更多 Windows 軟件那肯定要去找更新版本。0.3.15 時(shí)代能穩(wěn)定運(yùn)行的基本局限在記事本、命令行、部分管理工具這類輕量程序復(fù)雜軟件還是會(huì)頻繁崩潰。所以選它的人目標(biāo)不是日用而是學(xué)習(xí)。2. 源碼包內(nèi)部結(jié)構(gòu)拆解那些目錄到底管什么2.1 頂層目錄地圖從哪一層看起把 zip 解壓之后首先面對(duì)的就是根目錄下十幾個(gè)一級(jí)目錄。我第一次打開的時(shí)候也感覺有點(diǎn)暈但理清之后會(huì)發(fā)現(xiàn)層次其實(shí)很清楚。大致會(huì)看到這些關(guān)鍵目錄ntoskrnl放的是內(nèi)核主體win32ss是 Win32 子系統(tǒng)drivers是各種驅(qū)動(dòng)hal是硬件抽象層boot是啟動(dòng)引導(dǎo)相關(guān)sdk是開發(fā)工具鏈和頭文件庫(kù)subsystems是環(huán)境子系統(tǒng)modules存放一些可選的應(yīng)用組件tools則是構(gòu)建輔助工具。另外還有reactos這個(gè)名字的目錄在部分版本里會(huì)出現(xiàn)通常存放應(yīng)用層程序比如桌面、控制面板、命令解釋器之類的用戶態(tài)組件??辞宄@些目錄之后讀代碼就有路線圖了。我習(xí)慣把整個(gè)系統(tǒng)拆成三層理解最底層是硬件抽象和內(nèi)核中間是系統(tǒng)服務(wù)和驅(qū)動(dòng)最上面是 Win32 子系統(tǒng)和用戶態(tài)程序。源碼目錄正好也按這個(gè)邏輯排布所以你完全可以從下往上讀也可以反過來(lái)從上往下追調(diào)用鏈。有一點(diǎn)要提醒解壓時(shí)留意路徑長(zhǎng)度問題。源碼里目錄層級(jí)很深Windows 系統(tǒng)默認(rèn)的 MAX_PATH 限制在某些機(jī)器上會(huì)觸發(fā)解壓報(bào)錯(cuò)。我建議直接用 7-Zip 解壓并放到一個(gè)短路徑下比如D:\ros不要嵌套在超長(zhǎng)目錄里否則后續(xù)編譯工具鏈容易莫名其妙出錯(cuò)。2.2 內(nèi)核、Win32 子系統(tǒng)和 HAL 各管哪攤事我先說(shuō)ntoskrnl這是整個(gè)系統(tǒng)的心臟負(fù)責(zé)內(nèi)存管理、進(jìn)程線程調(diào)度、對(duì)象管理、IRP 分發(fā)這些操作系統(tǒng)最核心的機(jī)制。0.3.15 的內(nèi)核已經(jīng)實(shí)現(xiàn)了不少 Windows 內(nèi)核語(yǔ)義比如進(jìn)程環(huán)境塊、線程環(huán)境塊、對(duì)象命名空間、系統(tǒng)服務(wù)分發(fā)表。你如果之前在 Windows 內(nèi)核編程的書上看到某些原語(yǔ)想看看開源實(shí)現(xiàn)長(zhǎng)什么樣翻這個(gè)目錄就對(duì)了。再看win32ss這是 ReactOS 相對(duì)獨(dú)特的模塊也是它跟普通類 Unix 系統(tǒng)最不一樣的地方。這個(gè)子系統(tǒng)負(fù)責(zé)實(shí)現(xiàn) Win32 API 的用戶態(tài)和內(nèi)核態(tài)部分包含win32k這類內(nèi)核態(tài)圖形驅(qū)動(dòng)以及 user32、gdi32 等用戶態(tài)庫(kù)的入口邏輯。簡(jiǎn)單理解你在 Windows 上調(diào)用 CreateWindow、DrawText 這些 APIReactOS 里最終的實(shí)現(xiàn)源頭就在這個(gè)目錄。圖形輸出、窗口管理、消息循環(huán)這些機(jī)制都整合在這一塊代碼風(fēng)格和內(nèi)核部分有明顯差異讀起來(lái)需要點(diǎn)耐心。hal相對(duì)小眾一些但地位不低。它把硬件差異隔離在底層讓內(nèi)核上層的代碼不需要關(guān)心具體的主板芯片組和中斷控制器型號(hào)。0.3.15 里的 HAL 已經(jīng)支持多種硬件平臺(tái)配置編譯時(shí)可以針對(duì)不同機(jī)器形態(tài)選擇不同 HAL。對(duì)做嵌入式或硬件相關(guān)開發(fā)的朋友來(lái)說(shuō)這個(gè)目錄是一個(gè)直觀的教材能學(xué)會(huì)怎么在操作系統(tǒng)層面抽象硬件能力。還有一個(gè)容易被忽略的sdk目錄里面是包括 NDK、DDK、WDK 風(fēng)格的頭文件和導(dǎo)入庫(kù)。我強(qiáng)烈建議初學(xué)者先花時(shí)間翻這個(gè)目錄下的頭文件它能讓你快速知道系統(tǒng)對(duì)外暴露了哪些接口相當(dāng)于整個(gè)系統(tǒng)的“API 地圖”。我在讀源碼時(shí)經(jīng)常遇到一個(gè)函數(shù)搞不清類型定義直接一查頭文件就通透了。3. 從源碼到可引導(dǎo)鏡像的完整構(gòu)建流程3.1 構(gòu)建環(huán)境準(zhǔn)備裝 RosBE 而不是自己拼工具鏈在 0.3.15 那個(gè)年代官方推薦的構(gòu)建環(huán)境是 RosBE全稱 ReactOS Build Environment。這個(gè)東西非常省心它把交叉編譯器、鏈接器、make、nasm 匯編器、必要的基礎(chǔ)庫(kù)都打包好了你只要按對(duì)應(yīng)平臺(tái)裝好再進(jìn)入它的命令行環(huán)境就能開始編譯。我當(dāng)時(shí)的做法是找一臺(tái) Linux 虛擬機(jī)在純凈系統(tǒng)上裝 RosBE for Unix。如果你只有 Windows 機(jī)器也可以裝 RosBE for Windows原理一樣。關(guān)鍵點(diǎn)是不要圖省事直接用系統(tǒng)自帶的 gcc 去編因?yàn)?ReactOS 的代碼對(duì)編譯器版本和特定頭文件有強(qiáng)依賴RosBE 里的工具鏈?zhǔn)枪俜津?yàn)證過的組合換成其他版本容易觸發(fā)各種莫名其妙的兼容問題。啟動(dòng) RosBE 環(huán)境后先建議跑一遍make -v和nasm -v確認(rèn)匯編器和 make 都可用。這個(gè)習(xí)慣很重要我后來(lái)很多次編譯失敗最后都發(fā)現(xiàn)是環(huán)境沒進(jìn)對(duì)壓根沒調(diào)用到 RosBE 里的工具。3.2 配置和編譯記住關(guān)鍵命令確認(rèn)環(huán)境沒問題之后進(jìn)入源碼根目錄先跑一下 configure 生成構(gòu)建系統(tǒng)需要的配置cd reactos-0.3.15 ./configure這個(gè)步驟會(huì)檢測(cè)宿主機(jī)環(huán)境、生成 Makefile并把構(gòu)建相關(guān)的配置參數(shù)保存下來(lái)。默認(rèn)配置適合大部分場(chǎng)景官方文檔一般還建議生成 bootcd 鏡像所以后面直接用默認(rèn)就行。接下來(lái)就是最核心的命令make bootcd這里要解釋一下bootcd是構(gòu)建目標(biāo)的名字意思是“生成一個(gè)可啟動(dòng) CD 鏡像”。整個(gè)流程會(huì)先編內(nèi)核再編各個(gè)子系統(tǒng)、驅(qū)動(dòng)和應(yīng)用程序最后打包成bootcd.iso。我在老機(jī)器上實(shí)測(cè)全量編譯大概需要兩三個(gè)小時(shí)如果只改了局部代碼可以用make bootcd配合make -j參數(shù)并行加速。但要注意0.3.15 對(duì)并行編譯的兼容性一般我建議先老老實(shí)實(shí)單核跑一遍全流程確認(rèn)無(wú)誤后再用 2 線程或 4 線程增量編譯否則很容易在鏈接階段出現(xiàn)內(nèi)存不足或者詭異報(bào)錯(cuò)。編譯過程中屏幕上會(huì)持續(xù)滾動(dòng)大量輸出看到error:開頭的行就要停下來(lái)看。比較常見的有兩類一類是缺頭文件通常是你沒有用 RosBE 環(huán)境導(dǎo)致的另一類是內(nèi)存不足我在后面問題清單里會(huì)細(xì)說(shuō)。3.3 用 QEMU 把系統(tǒng)跑起來(lái)鏡像生成之后接下來(lái)就是最激動(dòng)人心的時(shí)刻把它跑起來(lái)。我強(qiáng)烈推薦用 QEMU它輕量、跨平臺(tái)、支持各種硬件模擬調(diào)試也比較方便。在 RosBE 環(huán)境外另開一個(gè)終端執(zhí)行qemu-system-i386 -m 512 -cdrom bootcd.iso -boot d這條命令的意思是分配 512M 內(nèi)存用 bootcd.iso 作為光驅(qū)并且從光驅(qū)引導(dǎo)。首次啟動(dòng)你會(huì)看到 ReactOS 的引導(dǎo)畫面然后進(jìn)入圖形安裝界面也可以選擇從 LiveCD 方式直接進(jìn)入桌面。如果你的虛擬機(jī)上鼠標(biāo)鍵盤沒反應(yīng)別急著懷疑系統(tǒng)壞了。0.3.15 對(duì) USB 設(shè)備的支持還不夠完善優(yōu)先模擬 PS/2 鍵鼠或者給 QEMU 加-usb -usbdevice mouse -usbdevice keyboard這類參數(shù)多數(shù)情況能解決。我最初測(cè)試時(shí)因?yàn)橹鳈C(jī)沒有 PS/2 接口又沒傳設(shè)備參數(shù)進(jìn)入桌面后鼠標(biāo)完全卡死折騰了好一陣才排查清楚這個(gè)坑值得提前記住。跑起來(lái)之后你可以試試打開記事本、命令行也可以在系統(tǒng)里看一下系統(tǒng)屬性里顯示的版本號(hào)。0.3.15 的桌面已經(jīng)很有早期 Windows 的影子了雖然遠(yuǎn)談不上流暢但一個(gè)從零實(shí)現(xiàn)的系統(tǒng)能走到這一步還是挺讓人興奮的。4. 編譯與運(yùn)行中的常見坑和排查技巧4.1 編譯期問題速查表為了讓你少走彎路我把實(shí)際操作中容易踩的編譯期問題整理成了表格現(xiàn)象常見原因處理方法找不到頭文件或工具鏈命令沒有進(jìn)入 RosBE 環(huán)境重新打開 RosBE 命令行確認(rèn) make/nasm 路徑鏈接時(shí)內(nèi)存耗盡宿主機(jī)內(nèi)存不足關(guān)閉多余程序增加 swap/虛擬內(nèi)存降低-j并行數(shù)zip 解壓報(bào)路徑錯(cuò)誤解壓路徑過長(zhǎng)或權(quán)限不足用 7-Zip 解壓到短路徑目錄下編譯到一半報(bào)語(yǔ)法錯(cuò)誤源碼被非正常中斷過建議刪除源碼目錄重新解壓不要只做增量修復(fù)生成的 iso 無(wú)法引導(dǎo)構(gòu)建目標(biāo)不對(duì)或鏡像是舊的清理干凈后重新執(zhí)行make bootcd這里特別說(shuō)下內(nèi)存不足的問題。0.3.15 的構(gòu)建過程雖然整體不夸張但鏈接器在鏈接內(nèi)核和幾個(gè)大模塊時(shí)會(huì)一次性加載大量中間文件。我試過在 2G 內(nèi)存的機(jī)器上用并行編譯結(jié)果好幾個(gè)模塊在鏈接階段直接報(bào) out of memory。后來(lái)我把并行數(shù)調(diào)回 1并且打開交換分區(qū)問題就消失了。如果你看到類似virtual_alloc0: fatal error: out of memory這樣的輸出先別慌優(yōu)先懷疑的就是內(nèi)存資源不夠而不是代碼錯(cuò)誤。4.2 啟動(dòng)期問題黑屏、藍(lán)屏和慢啟動(dòng)編譯成功只是第一步跑到系統(tǒng)里才是真正測(cè)功底。0.3.15 時(shí)代啟動(dòng)階段的問題主要集中在黑屏和驅(qū)動(dòng)加載失敗。如果是純黑屏連引導(dǎo)畫布都沒出現(xiàn)多半是 QEMU 的顯卡模擬和系統(tǒng)自帶的顯卡驅(qū)動(dòng)不兼容。這時(shí)可以試試切換顯示方式比如加-vga std參數(shù)或者改用 VirtualBox 內(nèi)置的顯卡模擬往往會(huì)好很多。我曾經(jīng)在某個(gè)版本的 QEMU 上一直卡在黑屏換參數(shù)后一次就過了。如果能看到引導(dǎo)畫面但進(jìn)入系統(tǒng)后崩潰那就是驅(qū)動(dòng)層面的問題。0.3.15 對(duì)老式 IDE 硬盤、Realtek 8139 網(wǎng)卡這類設(shè)備支持相對(duì)好一些對(duì)新型硬件則基本無(wú)力。我建議在虛擬機(jī)里把硬件模型盡量調(diào)舊比如硬盤接口用 IDE 而不是 SATA網(wǎng)卡模型選 rtl8139這樣可以顯著提高成功率。還有一個(gè)容易被忽略的點(diǎn)是啟動(dòng)速度。在 0.3.15 上從引導(dǎo)到進(jìn)入桌面花兩三分鐘很正常如果配置了調(diào)試串口輸出還會(huì)更慢。不要因?yàn)榻缑骈L(zhǎng)時(shí)間沒反應(yīng)就以為死機(jī)了可以把系統(tǒng)日志輸出到文件或者加-serial stdio觀察啟動(dòng)過程能判斷系統(tǒng)是卡在哪個(gè)驅(qū)動(dòng)上。4.3 源碼閱讀建議從哪里讀起效率最高拿到源碼之后不建議一頭扎進(jìn)ntoskrnl的內(nèi)核細(xì)節(jié)里那很容易被底層的鏈表、內(nèi)存描述符和各種旋鎖勸退。我個(gè)人的閱讀順序是先從sdk/include里的頭文件入手理解系統(tǒng)暴露出來(lái)的核心數(shù)據(jù)結(jié)構(gòu)和 API然后去win32ss看窗口和消息機(jī)制這部分直觀能建立一個(gè)“操作系統(tǒng)到底怎么跟程序打交道”的整體印象最后再回到ntoskrnl讀內(nèi)存管理和進(jìn)程調(diào)度這時(shí)候你會(huì)發(fā)現(xiàn)自己已經(jīng)有了上下文讀起來(lái)順暢很多。如果時(shí)間有限可以考慮只挑一個(gè)子系統(tǒng)精讀。比如只跟蹤C(jī)reateProcess這個(gè) API 的完整調(diào)用鏈從用戶態(tài)的 kernel32 出發(fā)一路看到ntoskrnl里的進(jìn)程創(chuàng)建函數(shù)再看到win32ss里如何初始化進(jìn)程的窗口環(huán)境。這一條線走通你對(duì)一個(gè)操作系統(tǒng)的進(jìn)程管理、對(duì)象管理、環(huán)境子系統(tǒng)分工就能建立起很扎實(shí)的認(rèn)知框架。這一路的體會(huì)是ReactOS 這種項(xiàng)目最適合的用法就是“按圖索驥”。系統(tǒng)本身雖然做不到完全兼容 Windows但它把 Windows 的設(shè)計(jì)思路以一整套可編譯、可調(diào)試的開源代碼呈現(xiàn)出來(lái)了。隨便改一行窗口繪制代碼重新編譯就能在虛擬機(jī)里看到變化這種正反饋是讀任何紙面資料都給不了的。最后再分享一個(gè)小技巧調(diào)試 ReactOS 源碼時(shí)在 QEMU 里開啟調(diào)試輸出會(huì)讓效率提升不少。啟動(dòng)參數(shù)里帶上-serial file:serial.log再把系統(tǒng)的調(diào)試信息配置打開代碼里所有 DPRINT 輸出都會(huì)落到文件中。我在追蹤某個(gè)窗口消息處理問題時(shí)就是靠這個(gè)日志直接定位到 win32k 里一條分支寫錯(cuò)了參數(shù)省去了大量猜測(cè)的時(shí)間。3.0.15 雖然老但整套研究路徑放在今天依然值得復(fù)制。本文還有配套的精品資源點(diǎn)擊獲取