qemu
今年に入って、浮世離れしたxv6なんてのをずっとやってるんで、たまにはこういうのも いいかなと思ったぞ。
組み込みC言語プログラマのためのmruby入門(前編) ―― Rubyとmruby,何が違う? どう違う?
組み込みC言語プログラマのためのmruby入門(中編) ―― mrubyをお手軽に体験する!
組み込みC言語プログラマのためのmruby入門(後編) ―― mrubyの組み込み方とJavaとの違い
誰か、mrubyを焼きこんだ石を作りませんかね。H8でもPICでもWelcomeだぞ。どちらにしても 金がかかるな。スポンサー急募!!
それとも、こちらに流れるか。これがshiroさんの秘密兵器だろうね。多分。
普段Gaucheに触れると言ったら、goshだからね。goshは、Gaucheのエンジンを快適に使うための 乗り物なんだな。mrubyも同じ事で、本当はRiteVMってのがエンジンで、それを使ってrubyスクリプトを 動かすmrubyとか、replである、mirbってのを(参考で)供給してるんだ。
qemu monitor
前回だったか前々回だったか、OpenBSD上でqemuを使ってintelの石を手なづける実験をしてた時、 パニクッて、石の内臓を曝け出してくれた。記念の標本を取り忘れてしまったんで惜しい事を したなとずっと思ってた。
でも冷静に思い出してみると、あれは info registerで出てくる案内だったんじゃないかなと。 そこでだ、qemuのソースを腑分けして、どんな事をやってるか探ってみよう。
確か、CR0とかGDTとか表示されたな。それらを頼りに探ってみると、target-i386/helper.cの中に ある、cpu_dump_stateでやってるっぽい。 どんな具合になってるかと言うと、
cpu_fprintf(f, "GDT= %08x %08x\n", (uint32_t)env->gdt.base, env->gdt.limit); cpu_fprintf(f, "IDT= %08x %08x\n", (uint32_t)env->idt.base, env->idt.limit); cpu_fprintf(f, "CR0=%08x CR2=%08x CR3=%08x CR4=%08x\n", (uint32_t)env->cr[0], (uint32_t)env->cr[2], (uint32_t)env->cr[3], (uint32_t)env->cr[4]);
このルーチンがあちこちから呼ばれているんで、きっとパニクッた時にも証拠保全で曝け出して くれるんだろう。あちこちからって書いたけど、qemuのmonitorモードからは、info registersで も呼び出されている。
info registersした時に、画面の上部が切れてしまっていたな。遡って表示する機能も付いて いないし、何とかして全容を見れないかな。いろいろ探し回ったら QEMU Tipsなんてのを 見つけた。そこにある、nograpticsモードの隠れコマンドを試してみる。
qemu -nographic -hdb fs.img xv6.img -m 384 xv6... cpu0: starting init: starting sh $ ### C-a h C-a h print this help C-a x exit emulator C-a s save disk data back to file (if -snapshot) C-a t toggle console timestamps C-a b send break (magic sysrq) C-a c switch between console and monitor C-a C-a sends C-a ### C-a c QEMU 0.11.1 monitor - type 'help' for more information (qemu)
おお、ちゃんとTips通りに動いてるぞ。そんじゃ、helpでも覗いてみるか
(qemu) help help|? [cmd] -- show the help commit dinfo [subcommand] -- show various information about the system state q|quit -- quit the emulator eject [-f] device -- eject a removable medium (use -f to force it) stop -- stop emulation c|cont -- resume emulation gdbserver [device] -- start gdbserver on given device (default 'tcp::1234'), stop with 'none' x /fmt addr -- virtual memory dump starting at 'addr' xp /fmt addr -- physical memory dump starting at 'addr' p|print /fmt expr -- print expression value (use $reg for CPU register access) i /fmt addr -- I/O port read o /fmt addr value -- I/O port write sendkey keys [hold_ms] -- send keys to the VM (e.g. 'sendkey ctrl-alt-f1', default hold time=100 ms) system_reset -- reset the system system_powerdown -- send system power down event memsave addr size file -- save to disk virtual memory dump starting at 'addr' of size 'size' pmemsave addr size file -- save to disk physical memory dump starting at 'addr' of size 'size'
まだまだ一杯有ったけど、おいらが使いそうなやつだけ列挙したよ。
infoコマンドは面白そうだなあ。
(qemu) info block ide0-hd1: type=hd removable=0 file=fs.img ro=0 drv=raw encrypted=0 ide0-hd0: type=hd removable=0 file=xv6.img ro=0 drv=raw encrypted=0 ide1-cd0: type=cdrom removable=1 locked=0 [not inserted] floppy0: type=floppy removable=1 locked=0 [not inserted] sd0: type=floppy removable=1 locked=0 [not inserted]
ふーん、qemuを起動する時、fs.imgとxv6.imgが併記されたけど、こんな風にハードディスクに 割付られていたのね。hd0がカーネルの居場所で、hd1がユーザーから見えるファイルシステムとな。
続いて、待望のinfo registersを
EAX=8010c608 EBX=00010074 ECX=8010ff20 EDX=8010ff2c ESI=00010074 EDI=00000000 EBP=8010c5b8 ESP=8010c5a8 EIP=80104c1b EFL=00000816 [-O--AP-] CPL=0 II=0 A20=1 SMM=0 HLT=0 ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-] SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] FS =0018 8010f9d4 00000fff 00c09300 DPL=0 DS [-WA] GS =0018 8010f9d4 00000fff 00c09300 DPL=0 DS [-WA] LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT TR =0030 8010f928 00000067 00408900 DPL=0 TSS32-avl GDT= 8010f990 00000037 IDT= 80111ea0 000007ff CR0=80010011 CR2=00000000 CR3=003ff000 CR4=00000010 DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 DR6=ffff0ff0 DR7=00000400 EFER=0000000000000000 FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80 FPR0=0000000000000000 0000 FPR1=0000000000000000 0000 FPR2=0000000000000000 0000 FPR3=0000000000000000 0000 FPR4=0000000000000000 0000 FPR5=0000000000000000 0000 FPR6=0000000000000000 0000 FPR7=0000000000000000 0000 XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000 XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000 XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000 XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
おいらの知らないレジスターが一杯あるなあ。やっぱり、cpuidを見ておくべきかなあ。複雑な糞石の マイクロ命令が、helper.cにC言語で書いたあった。
host_cpuidの中にこんなのが、
#ifdef __x86_64__ asm volatile("cpuid" : "=a"(vec[0]), "=b"(vec[1]), "=c"(vec[2]), "=d"(vec[3]) : "0"(function), "c"(count) : "cc"); #else asm volatile("pusha \n\t" "cpuid \n\t" "mov %%eax, 0(%2) \n\t" "mov %%ebx, 4(%2) \n\t" "mov %%ecx, 8(%2) \n\t" "mov %%edx, 12(%2) \n\t" "popa" : : "a"(function), "c"(count), "S"(vec) : "memory", "cc"); #endif
どこかで見たな。こやつは、ユーザー側の利用手続きか。CPU側の解釈系は、cpu_x86_cpuidだな。
case 0x80000008: /* virtual & phys address size in low 2 bytes. */ /* XXX: This value must match the one used in the MMU code. */ if (env->cpuid_ext2_features & CPUID_EXT2_LM) { /* 64 bit processor */ #if defined(CONFIG_KQEMU) *eax = 0x00003020; /* 48 bits virtual, 32 bits physical */ #else /* XXX: The physical address space is limited to 42 bits in exec.c. */ *eax = 0x00003028; /* 48 bits virtual, 40 bits physical */ #endif } else { #if defined(CONFIG_KQEMU) *eax = 0x00000020; /* 32 bits physical */ #else if (env->cpuid_features & CPUID_PSE36) *eax = 0x00000024; /* 36 bits physical */ else *eax = 0x00000020; /* 32 bits physical */ #endif
糞石の拡張につぐ拡張で、QEMUの真似ルーチンも苦労させられてますってのが、ありありと表れて いますよ。
info mem tlb
関心事はメモリーにも及びます。なんたってgdbは不親切(オィ)ですからね。
info mem -- show the active virtual memory mappings info tlb -- show virtual to physical memory mappings info mtree -- show memory tree info roms -- show roms
取れる情報は、これぐらいですかね。
(qemu) info roms fw=genroms/kvmvapic.bin size=0x002400 name="kvmvapic.bin"
romの内容を見せてくれるかと思ったら、qemuに刺さってるROMを教えてくれるだけなのね。ROMの 交換は、どうやるの? BIOSのアップデートは出来ないのかな?
(qemu) info mtree : I/O 0000000000000000-000000000000ffff (prio 0, RW): io 0000000000000020-0000000000000021 (prio 0, RW): pic 0000000000000040-0000000000000043 (prio 0, RW): pit 0000000000000060-0000000000000060 (prio 0, RW): i8042-data 0000000000000061-0000000000000061 (prio 0, RW): elcr 0000000000000064-0000000000000064 (prio 0, RW): i8042-cmd 0000000000000070-0000000000000071 (prio 0, RW): rtc 000000000000007e-000000000000007f (prio 0, RW): kvmvapic 0000000000000092-0000000000000092 (prio 0, RW): port92 00000000000000a0-00000000000000a1 (prio 0, RW): pic 0000000000000170-0000000000000177 (prio 0, RW): alias ide @ide 0000000000000170-0000000000000177 00000000000001f0-00000000000001f7 (prio 0, RW): alias ide @ide 00000000000001f0-00000000000001f7 0000000000000376-0000000000000376 (prio 0, RW): alias ide @ide 0000000000000376-0000000000000376 0000000000000378-000000000000037f (prio 0, RW): alias parallel @parallel 0000000000000378-000000000000037f 00000000000003f1-00000000000003f5 (prio 0, RW): alias fdc @fdc 00000000000003f1-00000000000003f5 00000000000003f6-00000000000003f6 (prio 0, RW): alias ide @ide 00000000000003f6-00000000000003f6 00000000000003f7-00000000000003f7 (prio 0, RW): alias fdc @fdc 00000000000003f7-00000000000003f7 00000000000003f8-00000000000003ff (prio 0, RW): serial 00000000000004d0-00000000000004d0 (prio 0, RW): elcr 00000000000004d1-00000000000004d1 (prio 0, RW): elcr 0000000000000510-0000000000000511 (prio 0, RW): fwcfg 0000000000000cf8-0000000000000cfb (prio 0, RW): pci-conf-idx 0000000000000cfc-0000000000000cff (prio 0, RW): pci-conf-data 0000000000005658-0000000000005658 (prio 0, RW): vmport 000000000000c000-000000000000c03f (prio 1, RW): e1000-io 000000000000c040-000000000000c04f (prio 1, RW): piix-bmdma-container :
糞石の特徴、MEMORYとIOが別になってまーすってのの、I/O空間の概説です。IBMのオリジナル 設計をしっかり引きずってますってのが、見て取れる。e1000ってひょっとしてインテルの 通信チップかな。FreeBSDだとem0って具合にNICが生えてくるやつだな。
(qemu) info mem 0000000080000000-0000000080100000 0000000000100000 -rw 0000000080100000-0000000080109000 0000000000009000 -r- 0000000080109000-000000008e000000 000000000def7000 -rw 00000000fe000000-0000000100000000 0000000002000000 -rw
これ、石から見たアドレス範囲が、どの物理メモリーに対応してるかって説明だな。xv6が 一生懸命に作り上げたんだな。
(qemu) info tlb : 000000008dffe000: 000000000dffe000 ---DA---W 000000008dfff000: 000000000dfff000 ---DA---W 00000000fe000000: 00000000fe000000 --------W 00000000fe001000: 00000000fe001000 --------W :
これら、どういうデータを見せてくれているか、大体想像付くけど、正確を期すため、ちゃんと 追ってみる。monitor.cの中に、infoのコマンドテーブルが用意されてた。もろな名前が付いて ますなあ。
#if defined(TARGET_I386) || defined(TARGET_SH4) { "tlb", "", tlb_info, "", "show virtual to physical memory mappings", }, #endif #if defined(TARGET_I386) { "mem", "", mem_info, "", "show the active virtual memory mappings", }, { "hpet", "", do_info_hpet, "", "show state of HPET", }, #endif
mem_info の方は、最大1024x1024回、下記を呼び出している。
static void mem_print(Monitor *mon, uint32_t *pstart, int *plast_prot, uint32_t end, int prot) { int prot1; prot1 = *plast_prot; if (prot != prot1) { if (*pstart != -1) { monitor_printf(mon, "%08x-%08x %08x %c%c%c\n", *pstart, end, end - *pstart, prot1 & PG_USER_MASK ? 'u' : '-', 'r', prot1 & PG_RW_MASK ? 'w' : '-'); } if (prot != 0) *pstart = end; else *pstart = -1; *plast_prot = prot; } }
tlb_infoの方も最大1024x1024回、下記を呼び出している。
static void print_pte(Monitor *mon, uint32_t addr, uint32_t pte, uint32_t mask) { monitor_printf(mon, "%08x: %08x %c%c%c%c%c%c%c%c\n", addr, pte & mask, pte & PG_GLOBAL_MASK ? 'G' : '-', pte & PG_PSE_MASK ? 'P' : '-', pte & PG_DIRTY_MASK ? 'D' : '-', pte & PG_ACCESSED_MASK ? 'A' : '-', pte & PG_PCD_MASK ? 'C' : '-', pte & PG_PWT_MASK ? 'T' : '-', pte & PG_USER_MASK ? 'U' : '-', pte & PG_RW_MASK ? 'W' : '-'); }
more play with qemu
上記の、tlb_info のログを取る時、結果表示が長いんで、ちと苦労した。だって、1024x1024行 でっせ。screenrcで、過去へ遡る行数を大きくしたけど、まあ、限度があるわな。
こういうのは、qemuを改造して、ログにでも落としてしまえって戦略が見えてきますな。 いきなりの改造は怖いので、qemu trace なんてのが手始めに良いかも知れません。
そして、最終手段として、qemuをgdbの手のひら上で動かすってのも有りかもしれません。 そんな時には、 qemu and gdb が、役に立ちそうです。