最近在弄移植大作業,目標是將學校課程中提供的一個操作系統內核(mips)移植到RISCV64位架構上。
我的qemu版本是5.2.0,OpenSBI版本是0.8。(最新的QEMU已經把OpenSbi更新到了0.9,不過無傷大雅)
設想之中的流程很簡單:qemu上電跳轉到bios->OpenSBI在M態完成boot第一部分->mret到S態,交給我的內核。
(實際上這個流程是我搞完這一堆才總結出來的…………)
從其他一些博客上參考了各色各樣的運行指令后,我最終使用了以下指令來跑起我的內核:
qemu -S -s -machine virt -m 64 -bios default -nographic -kernel /path/to/kernel.elf
這樣我就使用了qemu自帶的opensbi,跑起了我心愛的RISCV內核。
問題的出現
由於之后內核的迭代,需要建立起頁表系統並且將內核的運行位置從實地址換到虛地址上;因此,我修改了我的鏈接腳本,將程序地址換到了虛空間中;但我想再次嘗試運行時,qemu模擬器在打印出OPENSBI的一些基本信息后,就什么也不肯輸出了。
不過,這里的錯誤是明顯的。我將我的內核的基址從0x80200000更改到了0xffffffff80200000;在qemu中使用xp指令觀察ram中的內容,可以發現0x80200000處空無一物;由此可以推斷由於地址的更改,在使用-kernel選項后QEMU不會再將內核裝載到正常的區域了。
那么既然-kernel選項不能再使用,我便上網查找了各式資料,最后找到了THU的一個RISCV64內核的教程作為參考。里面給出的指令如下:
qemu -machine virt -m 64 -bios default -nographic -s -S -device loader,file=/path/to/kernel.bin,addr=0x80200000
其中qemu的版本為5.0.0, OpenSBI版本0.6。
這個教程應該是肯定沒錯的,畢竟還有一堆THU的同學們上了這門課程;但是到了我這里完全行不通。
我試着用xp指令讀取0x80200000,沒錯,內核確實被加載上去了;但是qemu就是運行不到那個地址。
萬般無奈之下,我去咨詢一位一周半就完成了內核移植任務,對OS有着豐富經驗的學長;但他也沒能從截圖里看出什么端倪。
走投無路之下,我只好git clone opensbi, 開始啃代碼。
問題原因的探索
當然,作業那么多,我不可能自己讀完所有代碼;因此我去網上簡單搜索了一下,想借助別人的成果快速定位我想要找到的代碼段;
別說,還真叫我找到兩個:
RISC-V OpenSBI啟動過程, 一個大致的總結,還附帶一些環境配置啥的,對於我的需求來說基本夠了;
OpenSBIの內部実裝(boot~linux kernelを実行するまで)Google到的,總結的很全面,甚至直接定位到代碼,雖然需要發揮想象力塞翻;
借助這兩篇博文,以及我clone下來的opensbi原碼,以及加載了qemu/pc-bios/opensbi-riscv64-generic-fw_dynamic.elf的gdb,我開始了我的探索。
具體的運行過程可以看上面的兩篇博文,總之最后,我的目光鎖定在了一段代碼上:
里面的一些函數具體如下圖所示:(實際上在qemu結合了不知道什么payload編譯之后,這些函數都被展開了)
可以看到,這是一段從a2寄存器的地址中讀取一些boot設置的代碼。而運行到這里的代碼自始至終就沒動過a2寄存器,這個值又是從哪里來的呢?
實際上,QEMU上電后會跳轉到0x1000,經過幾行代碼之后才會跳轉到0x80000000處的OpenSBI,而這幾行代碼中就包括了設置a2為0x1028的指令。
在這里面讀取的所有值中,我最關心的就是next_addr:這塊內存會被加載到一個scatch結構體里,而那個結構體的next_addr屬性就是在單hart、支持S模式下的OpenSBI的跳轉地址。(之所以這么說是因為我沒有看其他條件下if分支的走向,因此無法確定跳轉地址是不是也存在那里,不代表一定不是;這段代碼在init_cold_boot函數里)
加上偏移量之后,我們可以得到next_addr最開始的地址:0x00001038。這段地址在ROM里面,說明這應該是由Qemu負責注入的啟動信息。
驗證這一點很簡單:xp 0x00001038.在使用-kernel選項下這個位置儲存的是我再熟悉不過、再想念不過的數字:0x80200000。而在換成-loader選項之后,這個位置卻只留下一片死寂的0x00000000.
那么,問題就出在Qemu的初始化上。
那么怎么修改呢?我已經花費了太多時間,實在不想再花費時間去讀Qemu的長得要命源碼了(之前谷歌了很久也沒找到相關訊息);-loader也更改不了rom上的值;我思來想去,最后決定從我目前最為熟悉的地方開始:OpenSBI。
行動開始。
破局
- 對qemu/pc-bios/opensbi-riscv64-generic-fw_dynamic.elf使用readelf和objdump,找出對應的源代碼:
圖中圈出的四行代碼就是加載地址的核心代碼。閱讀源代碼可知紅圈上面的幾行匯編是在檢查信息是否正確加載;雖然會對安全性造成損失,不過在qemu的情況下,我們是可以犧牲掉這幾行代碼的。
- 對qemu/pc-bios/opensbi-riscv64-generic-fw_dynamic.elf使用vim和xxd,找到對應的代碼段;
- 對代碼段進行編輯后保存;
在編輯之后我以兩行自檢代碼為代價,硬生生地將0x80200000hard-code到了OpenSBI里面。 - 對修改后的elf使用objcopy --strip-all -O binary ,將其轉化為二進制文件;
- 使用以下指令啟動qemu:
qemu -S -s -machine virt -m 64 -bios /path/to/qemu/pc-bios/opensbi-riscv64-generic-fw_dynamic.bin -nographic -device loader,file=/path/to/kernel.bin,addr=0x80200000
問題解決。
后記
雖然修改二進制代碼來hack掉OpenSBI的感覺很爽,但是這顯然不是正確的方法。希望之后我能找到正確加載的方法。
我在如何讓qemu運行起來我的內核上花的時間比移植花的時間還要多……各種百度谷歌gayhub,最后實在沒招了才出此下策。
無敵的ida Pro 64在我試圖用它分析RISC-V代碼時還是倒了,從側面證明了RISC-V體系結構的優秀的安全性。(霧
如果還使用-bios default選項的話,我們修改過的bios並不會被調用。所以說qemu到底把bios文件藏到哪里去了……
由於不是正常解決問題的方法,並且花費了大量時間搗鼓,因此起名為踩坑記。上一篇踩坑記還是我忘了MySQL密碼,而網上只有老版本修改密碼對應的記錄和新版本的密碼加密方式的博文,最后逼得我用老版本修改記錄的方法將密碼更新為了新版本密碼加密方式加密的'123456',最后用123456登錄進去時寫的…………
2021.7.24新增:或許可以把Qemu內置的設備樹導出來修改一番然后讓Qemu強制加載該設備樹?
2022.6.25新增:其實最簡單的方法就是自己寫個類似於bootstrap的東西,把內核從指定位置拷過來吧…… 不過都到這地步了直接用uboot不好嗎