Copyright (c) 2012 陳韋任 (Chen Wei-Ren)
這裡以 linux-0.11 當範例,請至 [1][2] 下載代碼和硬盤映像編譯。我們觀察以 guest pc 0xe4c0 為開頭的 basic block。使用 QEMU 0.13 [3] 運行 linux-0.11。
$ mkdir build; cd build
$ ../qemu-0.13.0/configure --prefix=$INSTALL --target-list=i386-softmmu
--enable-debug --extra-cflags=”–save-temps”
$ make install
$ gdb qemu
(gdb) r -boot a -fda Image -hda hdc-0.11-new.img -vnc 0.0.0.0:1 -d in_asm,op,out_asm
登入後,下 `ls`。觀察 qemu.log 並定位至 0xe4c0。首先,我們可以看到如下內容:
IN: 0x0000e4c0: sub $0x4,%esp 0x0000e4c3: mov 0x8(%esp),%eax 0x0000e4c7: mov %al,(%esp) 0x0000e4ca: movzbl (%esp),%eax 0x0000e4ce: mov 0xc(%esp),%edx 0x0000e4d2: mov %al,%fs:(%edx) 0x0000e4d5: add $0x4,%esp 0x0000e4d8: ret
OP: —- 0xe4c0 movi_i32 tmp1,$0x4 mov_i32 tmp0,esp sub_i32 tmp0,tmp0,tmp1 mov_i32 esp,tmp0 mov_i32 cc_src,tmp1 mov_i32 cc_dst,tmp0
… 略 …
OUT: [size=450] 0x40bbeff0: mov 0x10(%r14),%ebp 0x40bbeff4: sub $0x4,%ebp
… 略 …
0x4011813b: callq 0x54d38a 0x40118140: mov 0x10(%r14),%ebp
… 略 …
這是 QEMU 第一次遇到尚未翻譯,以 guest pc 0xe4c0 開頭的 basic block 時所產生的輸出,這包括 guest binary (IN: 以下內容)、TCG IR (OP: 以下內容) 和 host binary (OUT: 以下內容)。
再繼續往下搜尋 0xe4c0,會看到以下內容:
IN: 0x0000e4c0: sub $0x4,%esp 0x0000e4c3: mov 0x8(%esp),%eax 0x0000e4c7: mov %al,(%esp) 0x0000e4ca: movzbl (%esp),%eax 0x0000e4ce: mov 0xc(%esp),%edx 0x0000e4d2: mov %al,%fs:(%edx) 0x0000e4d5: add $0x4,%esp 0x0000e4d8: ret
OP: —- 0xe4c0 movi_i32 tmp1,$0x4 mov_i32 tmp0,esp sub_i32 tmp0,tmp0,tmp1 mov_i32 esp,tmp0 mov_i32 cc_src,tmp1 mov_i32 cc_dst,tmp0
… 略 …
RESTORE: 0x0000: 0000e4c0 0x0007: 0000e4c3 0x000d: 0000e4c7 0x0011: 0000e4ca 0x0015: 0000e4ce 0x001b: 0000e4d2 spc=0x4011813f pc_pos=0x1b eip=0000e4d2 cs_base=0
這裡就是重點了。spc 指的是發生例外的 host pc,eip 指的是與其相對映發生例外的 guest pc。這邊請注意,由於我們將 guest binary 翻譯成 host binary 並執行,真正發生例外的是 host binary (位於 code cache),但是我們必須將它映射回 guest pc,查出哪一條 guest 指令發生例外,並做後續處理。我們看一下第一次翻譯 0xe4d2 所得的 host binary。
0x4011813b: callq 0x54d38a 0x40118140: mov 0x10(%r14),%ebp
我們可以看到 spc 0x4011813f == 0×40118140 – 1,也就是 callq 0x54d38a 下一條指令所在位址減去 1。這裡做點弊,我們在 gdb 下 print __stb_mmu。
(gdb) print __stb_mmu $1 = {void (target_ulong, uint8_t, int)} 0x54d38a
可以得知,我們在呼叫 __stb_mmu 的時候發生例外。__{ld,st}{b,w,l,q}_{cmmu,mmu} 是用來存取 guest 內存的 helper function。它們首先會查找 TLB (env1->tlb_table) 試圖取得 guest virtual address 相對映的 host virtual address。如果 TLB 命中,可直接利用該 host virtual address 存取 guest 內存內容。如果 TLB 不命中,則會呼叫 tlb_fill (target-i386/op_helper.c)。tlb_fill 會呼叫 cpu_x86_handle_mmu_fault 查找客戶頁表。如果命中,代表該 guest virtual address 所在的頁已存在,tlb_fill 會將該頁項目填入 TLB 以便後續查找。如果不命中,代表發生頁缺失,QEMU 會回復 guest CPUState,並拉起 guest exception index (env->exception_index) 通知 guest 頁缺失發生。最後交由 guest 頁缺失 handler 將該頁載入。
我先給出一個 precise exception handling 的大致流程,之後再透過閱讀代碼有更深的體會。底下給出關鍵的資料結構 TranslationBlock,它負責掌管 guest binary 和 host binary 的關係,其中 pc 代表此 basic block 起始的 guest pc,tc_ptr 指向翻譯好的 host binary 在 code cache 中的位址。
code cache
guest binary TranslationBlock (host binary)
0x0000e4c0: sub $0x4,%esp 0x40bbeff0: mov 0x10(%r14),%ebp 0x0000e4ca: movzbl (%esp),%eax 0x40bbeff4: sub $0x4,%ebp 0x0000e4ce: mov 0xc(%esp),%edx 0x0000e4d2: mov %al,%fs:(%edx) … 略 … 0x0000e4d5: add $0x4,%esp 0x0000e4d8: ret 0x4011813b: callq 0x54d38a (3) offset –> 0x40118140: mov 0x10(%r14),%ebp (1)
... 略 ...
當 QEMU 發現例外是發生在 code cache 裡,這代表需要處理 precise exception。首先,QEMU 會透過 host pc (0x4011813f) 查出相對應的 TranslationBlock – (1)。接下來,QEMU 會回復 guest CPUState。主要概念是這樣,透過之前查找到的 TranslationBlock 的 pc,我們從該 pc 所指的 guest binary 重新再翻譯一次,同時產生額外的資訊以便回復 guest CPUState – (2)。那要如何得知我們已經翻譯到發生 exception 的 guest binary? 這裡的重點在於,我们重新翻译 guest binary,直到 host binary 地址到达了出现异常的位置,这个时候 guest pc 就是产生异常的指令。我們便可以回復 guest CPUState – (3)。
接下來各小節的主題分別是:
* Precise Exception Handling 3/5 – (1)
* Precise Exception Handling 4/5 – (2) (3)
* Precise Exception Handling 5/5 – 驗證我們對 QEMU 的理解。
接下來,我們來看代碼。;)
[1] http://www.oldlinux.org/oldlinux/viewthread.php?tid=13681&extra=page%3D1
[2] http://oldlinux.org/Linux.old/bochs/linux-0.11-devel-060625.zip
[3] http://wiki.qemu.org/download/qemu-0.13.0.tar.gz