前一則日記「小聊 qemu 的 CPUState」提到:
相關應用,例如,我們只要觀察 r15(pc)暫存器的值,就可以知道現在這台機器的程式執行位置。實際上的應用,像是 qemu 所實作的 gdbserver 即是透過此 object 的資訊,來回覆 gdb client 的命令。
以下展示實際的操作畫面。
下圖是利用 jk2410-emulator 模擬 Jollen-Kit! Pro. 的開機畫面,使用的 kernel 是 2.6.20.4:
在 kernel 檢查 root filesystem 是否為 initramfs 的時候,我們啟動了 gdb client,並透過 socket 連到 qemu 的 gdbserver;為了方便進行 source-level debug,我們使用 cgdb 來操作,下圖是 debug 畫面:
我們可以看到,此時 kernel 正執行到 lib/inflate.c 裡的第 59x 行程式,我們利用 next 做單步執行。這種「一邊跑 kernel,一邊看 kernel 跑到哪一個原始檔的哪一行」正是使用 qemu 做「整體系統」除錯的關鍵突破之一。
回到 qemu,找到 gdbserver 的實作 'gdbstub.c';當 host 的 gdb(gdb client)端對 target 端 gdb server 下命令時(透過 gdb remote protocol),qemu 實作的 gdb server 會先由 CPUState 讀取 register 的內容,再回應給 gdb client。gdb remote debug protocol 顯然不是我們目前的重點,所以先找到 'gdbstub.c' 關於「讀取 ARM 處理器 register」的主要程式片斷如下:
#elif defined (TARGET_ARM) static int cpu_gdb_read_registers(CPUState *env, uint8_t *mem_buf) { int i; uint8_t *ptr; ptr = mem_buf; /* 16 core integer registers (4 bytes each). */ for (i = 0; i < 16; i++) { *(uint32_t *)ptr = tswapl(env->regs[i]); ptr += 4; } /* 8 FPA registers (12 bytes each), FPS (4 bytes). Not yet implemented. */ memset (ptr, 0, 8 * 12 + 4); ptr += 8 * 12 + 4; /* CPSR (4 bytes). */ *(uint32_t *)ptr = tswapl (cpsr_read(env)); ptr += 4; return ptr - mem_buf; }
gdb server 回傳給 host 端的資訊,已經由 host 端的 gdb 為我們解析,並根據 debug sections 來找到 $pc 對應的原始程式位置,即 source-level debug。
由於這種除錯模式,是對「整粒 ARM 處理器」做除錯,概念上很像是 jtag debug,因此會比用 kgdb 或 kdb 來得實用。原理上來看,gdb 單純是對 qemu 做除錯,而 qemu 是一個可以讓「完整 Linux 系統」在上面執行的 'system emulator'(for various processors),所以我們可以對模擬器裡頭的程式概念上「直接」進行除錯,因為對 host 端的 gdb 來說,這是通透的(transparent)。
再者,對除錯的二大重要工作:source-level debug 或是「單步執行」來說,qemu 單純只回應 $pc 給 gdb client,qemu 並不會知道現在跑的程式是什麼,同時也不需再加入(或修改)gdb stub 至 kernel 或 application,這樣的通透性,讓此種除錯模式可說是非常「萬能」的。不過,在實務上仍有一些不足的地方,例如,我們需要在 host 端編譯出具備除錯資訊的程式,才能搭配 qemu 來除錯,並且,也要小心注意 host 端的 loaded ELF image 與 run-time 的程式是否相同。
高通透性並非全然都是好的,例如,即使 loaded kernel image(vmlinux)與 run-time kernel 不同,我們還是會看到「目前跑到 source code 什麼地方」,因此目前的 source code 位置與 $pc 的位址是對不起來的。實務上,host gdb 透過 qemu 來除錯 kernel 的高度通透性,也帶來了一些小問題,有機會再做整理分享,或許可在 [OrzLab] 的讀書會上提出討論。
不過,如果是因為忘了重新打包 kernel,就直接跑 emulator 進行除錯,而發生上述的錯誤,實在是非戰之罪。
來源: Jollen's Blog
Jollen's Blog 使用 Github issues 與讀者交流討論。請點擊上方的文章專屬 issue,或 open a new issue
您可透過電子郵件 jollen@jollen.org,或是 Linkedin 與我連絡。更歡迎使用微信,請搜尋 WeChat ID:jollentw