qemu

今年に入って、浮世離れしたxv6なんてのをずっとやってるんで、たまにはこういうのも いいかなと思ったぞ。

組み込みC言語プログラマのためのmruby入門(前編) ―― Rubyとmruby,何が違う? どう違う?

組み込みC言語プログラマのためのmruby入門(中編) ―― mrubyをお手軽に体験する!

組み込みC言語プログラマのためのmruby入門(後編) ―― mrubyの組み込み方とJavaとの違い

誰か、mrubyを焼きこんだ石を作りませんかね。H8でもPICでもWelcomeだぞ。どちらにしても 金がかかるな。スポンサー急募!!

それとも、こちらに流れるか。これがshiroさんの秘密兵器だろうね。多分。

C言語からGaucheを使おう! (10) まとめ

普段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 が、役に立ちそうです。