qemu (3)

gaucheの組み込みをFreeBSDでもと思ってやってみたよ。そしたら意味不なエラーが出てきた。

[sakae@secd ~/e-scheme]$ make
gcc -std=gnu99 -g -Wall -Werror -o a.out `gauche-config -I`  `gauche-config -L` `gauche-config -l`
/usr/lib/crt1.o: In function `_start1':
crt1_c.c:(.text+0xa3): undefined reference to `main'
*** [a.out] Error code 1

Stop in /usr/home/sakae/e-scheme.

良く見ると、gccのコマンドラインに、ソースファイルが表れていないじゃないですか。一つも ソースを指定しないと、こんなエラーが出るんだと感心しましたよ。BSD makeとGNU makeの 否互換性に端を発したエラーって事だな。

[sakae@secd ~/e-scheme]$ gmake
gcc -std=gnu99 -g -Wall -Werror -o a.out `gauche-config -I` main.c `gauche-config -L` `gauche-config -l`
[sakae@secd ~/e-scheme]$ ldd ./a.out
./a.out:
        libgauche-0.9.so.0.3 => /usr/local/lib/libgauche-0.9.so.0.3 (0x28069000)
        libcrypt.so.5 => /lib/libcrypt.so.5 (0x282b7000)
        libutil.so.9 => /lib/libutil.so.9 (0x282dc000)
        libm.so.5 => /lib/libm.so.5 (0x282ef000)
        libthr.so.3 => /lib/libthr.so.3 (0x28309000)
        libc.so.7 => /lib/libc.so.7 (0x28329000)

今度は旨くいったけど、参照してるGaucheのエンジンがLinuxのそれと違うな。これは何に起因する 違いなんだろう? 興味は尽きないな。

cpuid for xv6

前回はdebug用のqemuを作った。それを利用してqemuがどんな風に動いているか探検してみたい。 qemuを動かすには仮想の何かが必要になるんで、xv6上でcpuidを発行させよう。前回だったかの 使い回しで、cpuidを発行するCのソースを、cpuid.cとして用意し、Makefileに_cpuidを登録 しとく。走らせると、エラーも無く、xv6に同化してくれた。

[sakae@arch xv6]$ make qemu-nox
gcc -fno-pic -static -fno-builtin -fno-strict-aliasing -Wall -MD -ggdb -m32 -Werror -fno-omit-frame-pointer -fno-stack-protector   -c -o cpuid.o cpuid.c
ld -m    elf_i386 -N -e main -Ttext 0 -o _cpuid cpuid.o ulib.o usys.o printf.o umalloc.o
objdump -S _cpuid > cpuid.asm
objdump -t _cpuid | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > cpuid.sym
./mkfs fs.img README _cat _cpuid _echo _forktest _grep _init _kill _ln _ls _mkdir _rm _sh _stressfs _usertests _wc _zombie
 :
xv6...
cpu0: starting
init: starting sh
$ cpuid

一応アセンブル出力も出来ていたんで、確認。

int
main(int argc, char *argv[])
{
  41:   55                      push   %ebp
  42:   89 e5                   mov    %esp,%ebp
  44:   83 e4 f0                and    $0xfffffff0,%esp
  47:   83 ec 10                sub    $0x10,%esp
  do_cpuid(0x00000000, regs);
  4a:   c7 44 24 04 a8 0a 00    movl   $0xaa8,0x4(%esp)
  51:   00
  52:   c7 04 24 00 00 00 00    movl   $0x0,(%esp)
  59:   e8 a2 ff ff ff          call   0 <do_cpuid>
  exit();
  5e:   e8 69 02 00 00          call   2cc <exit>
  63:   90                      nop

ああ、コンパイル時にオプチマイズされなかったので、cpuidの入ったコンテナが呼び出されるように なってるのね。そんじゃ、当たりを付けておいた所に網を張って実行。

(gdb) b cpu_x86_cpuid
Breakpoint 3 at 0x81a391e: file /home/sakae/MINE/qemu-0.11.1/target-i386/helper.c, line 1607.
c
Continuing.

Breakpoint 3, cpu_x86_cpuid (env=0xa22bb60, index=0, count=8, eax=0xbfd4a55c, ebx=0xbfd4a558, ecx=0xbfd4a554, edx=0xbfd4a550) at /home/sakae/MINE/qemu-0.11.1/\
target-i386/helper.c:1607
1607        if (index & 0x80000000) {
(gdb) bt
#0  cpu_x86_cpuid (env=0xa22bb60, index=0, count=8, eax=0xbfd4a55c, ebx=0xbfd4a558, ecx=0xbfd4a554, edx=0xbfd4a550) at /home/sakae/MINE/qemu-0.11.1/target-i386/helper.c:1607
#1  0x08185350 in helper_cpuid () at /home/sakae/MINE/qemu-0.11.1/target-i386/op_helper.c:1943
#2  0xb0e00558 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)

残念ながら、ずっと上位へは辿って行けないみたいだ。しょうがないので、上から攻めてみると、 mainが呼ばれて、長い旅の末にmain-loopに入って、cpu_x86_exec辺りで回っているようなので、

(gdb) b cpu_x86_exec
Breakpoint 4 at 0x81579e5: file /home/sakae/MINE/qemu-0.11.1/cpu-exec.c, line 215.
c
Continuing.

Breakpoint 4, cpu_x86_exec (env1=0xa22bb60) at /home/sakae/MINE/qemu-0.11.1/cpu-exec.c:215
215     {
(gdb) bt
#0  cpu_x86_exec (env1=0xa22bb60) at /home/sakae/MINE/qemu-0.11.1/cpu-exec.c:215
#1  0x080536c6 in qemu_cpu_exec (env=0xa22bb60) at /home/sakae/MINE/qemu-0.11.1/vl.c:4198
#2  0x080537b2 in tcg_cpu_exec () at /home/sakae/MINE/qemu-0.11.1/vl.c:4229
#3  0x08053a8f in main_loop () at /home/sakae/MINE/qemu-0.11.1/vl.c:4342
#4  0x08057667 in main (argc=7, argv=0xbfd4aaa4, envp=0xbfd4aac4) at /home/sakae/MINE/qemu-0.11.1/vl.c:6142

止まった所で、env1なんて言うそれらしい名前のやつを検査すると

$6 = {
  regs = {0x0, 0x8010ff20, 0x24, 0x10074, 0x8010c5e0, 0x8010c608, 0x10074, 0x0}\
,
  eip = 0x80104737,
  eflags = 0x93,
  cc_src = 0x91,
  cc_dst = 0xfffffffd,
  cc_op = 0x1,
  df = 0x1,
  hflags = 0xb4,
  hflags2 = 0x1,
  segs = {{
      selector = 0x10,
      base = 0x0,
      limit = 0xffffffff,
      flags = 0xcf9300
   :
  cpuid_level = 0x2,
  cpuid_vendor1 = 0x756e6547,
  cpuid_vendor2 = 0x49656e69,
  cpuid_vendor3 = 0x6c65746e,
  cpuid_version = 0x633,
  cpuid_features = 0x781abfd,
  cpuid_ext_features = 0x80000001,
  cpuid_xlevel = 0x0,
  cpuid_model = {0x554d4551, 0x72695620, 0x6c617574, 0x55504320, 0x72657620, 0x6e6f6973, 0x312e3020, 0x312e31, 0x0, 0x0, 0x0, 0x0},
  cpuid_ext2_features = 0x0,
  cpuid_ext3_features = 0x0,
  cpuid_apic_id = 0x0,
  cpuid_vendor_override = 0x0,
   :

シュミレートするために必要な情報が満載されてましたよ。箱庭なCPUが出来上がっているんだな。 この巨大な構造物が何処で定義されてるかと思ったら、target-i386/cpu.hで、CPUX86Stateなんて 具合になってた。

そんじゃ、どこでこの巨大な構造物を作ってるのかな。ソース嫁もいいんだけど、私のオシロスコープgdbを 使って追ってみよう。

[sakae@arch xv6]$ gdb -q ./qemu
   :
(gdb) set args  -nographic -hdb fs.img xv6.img -m 384
(gdb) b main
Breakpoint 1 at 0x805447d: file /home/sakae/MINE/qemu-0.11.1/vl.c, line 4826.
(gdb) run
  :
Breakpoint 1, main (argc=7, argv=0xbffff644, envp=0xbffff664)
    at /home/sakae/MINE/qemu-0.11.1/vl.c:4826
4826        const char *gdbstub_dev = NULL;
(gdb) watch env
Hardware watchpoint 2: env
(gdb) maintenance i b
Num     Type             Disp Enb Address    What
1       breakpoint       keep y   0x0805447d in main
                                             at /home/sakae/MINE/qemu-0.11.1/vl.c:4826 inf 1
        breakpoint already hit 1 time
-1      shlib events     keep y   0xb7fed960 <_dl_debug_state> inf 1
-2      thread events    keep y   0xb7f66b30 <__nptl_create_event> inf 1
-3      thread events    keep y   0xb7f66b40 <__nptl_death_event> inf 1
-4      longjmp master   keep n   0xb7f6e280 <siglongjmp> inf 1
-5      longjmp master   keep n   0xb7f6e280 <siglongjmp> inf 1
-6      longjmp master   keep n   0xb79e65f0 <siglongjmp> inf 1
-7      longjmp master   keep n   0xb79e65f0 <siglongjmp> inf 1
-8      longjmp master   keep n   0xb79e65f0 <siglongjmp> inf 1
-9      watchpoint scope del  y   0xb79d1825 <__libc_start_main+245> inf 1
2       hw watchpoint    keep y              env inf 1
(gdb) c
Continuing.
Hardware watchpoint 2: env

Old value = (struct CPUX86State *) 0x0
New value = (struct CPUX86State *) 0x8471b60
0x08056dc7 in main (argc=7, argv=0xbffff644, envp=0xbffff664)
    at /home/sakae/MINE/qemu-0.11.1/vl.c:5957
5957        for (env = first_cpu; env != NULL; env = env->next_cpu) {

構造物をずっと監視してて、変化が有ったらトリガーかけて教えてねって設定をした。gdbには 隠れトリガーポイントもあるようで(そういうやつは、マイナス表示)、洗いざらい表示させてみた。 こういう使い方は、gdb 7.2の 説明書あたりを参照。ここ見てて、対象物を末梢する、killなんてのが有る事を知ったよ。 マニュアル見ると、いい事あるなあ。

中には、gdb hacksなんて、楽しい事を やってる方もおられます。おいらが現役時代はオシロの使い方を見るだけで、その人の技量が 分かったものですが、gdbの使い方を見るだけで、技量がひょっとしたら分かるんでしょうか。 qemuには、gdbとの通信装置も組み込まれているんで、後で点検してみよう。

何処で、cpuidの値を埋めているか調べてみたら、どうもこのあたりらしい。

Breakpoint 1, cpu_x86_register (env=0x8471b60, cpu_model=0x81ce41f "qemu32")
    at /home/sakae/MINE/qemu-0.11.1/target-i386/helper.c:459
459         x86_def_t def1, *def = &def1;
(gdb) bt
#0  cpu_x86_register (env=0x8471b60, cpu_model=0x81ce41f "qemu32")
    at /home/sakae/MINE/qemu-0.11.1/target-i386/helper.c:459
#1  0x081a3ffa in cpu_x86_init (cpu_model=0x81ce41f "qemu32")
    at /home/sakae/MINE/qemu-0.11.1/target-i386/helper.c:1830
#2  0x080a499d in pc_new_cpu (cpu_model=0x81ce41f "qemu32")
    at /home/sakae/MINE/qemu-0.11.1/hw/pc.c:1088
#3  0x080a4ac3 in pc_init1 (ram_size=402653184, boot_device=0xbffff473 "cad",
    kernel_filename=0x0, kernel_cmdline=0x81bc90d "", initrd_filename=0x0,
    cpu_model=0x81ce41f "qemu32", pci_enabled=1)
    at /home/sakae/MINE/qemu-0.11.1/hw/pc.c:1148
#4  0x080a56d5 in pc_init_pci (ram_size=402653184,
    boot_device=0xbffff473 "cad", kernel_filename=0x0,
    kernel_cmdline=0x81bc90d "", initrd_filename=0x0, cpu_model=0x0)
    at /home/sakae/MINE/qemu-0.11.1/hw/pc.c:1454
#5  0x08056dbb in main (argc=7, argv=0xbffff644, envp=0xbffff664)
    at /home/sakae/MINE/qemu-0.11.1/vl.c:5953

qemu32って名前のCPUを登録したいって事みたいだな。どういうルートを辿ってきたかが分かるので 、見るべきソースが絞られるのが有り難い。さしずめの興味はhw/pc.cの中にある、pc_init1あたりだな。 ここで、CPUの名前付けとかメモリーの手配、BIOS-ROMの選定なんかをやってるぞ。

  =>  if (cpu_model == NULL) {
  #ifdef TARGET_X86_64
          cpu_model = "qemu64";
  #else
          cpu_model = "qemu32";
  #endif

ここでi386な石を差し込んでいるんだな。

(gdb) p filename
$1 = 0x8497010 "/home/sakae/MINE/share/qemu/bios.bin"

これBIOS-ROMね。gdbをemacsの上から使ってると、BPを張るのも楽だし、検索もスイスイ出来て なかなか快適。

とまあ、いろいろ脈絡の無い事をやってきたけど、肝心要のマシンコードをどうやって実行するんねん? と言う事については手を出していなかった。参考文献を見つけたよ。

QEMU memo(1)

QEMU memo(2)

QEMU memo(3)

CPU から I/O 空間 (タイマー) へのアクセス

PITと割り込み 後は、qemu/tcg/READMEが、開発者Fabrice Bellardさん自身による説明書になってる。まずは、これを 嫁だな。

cpuid for FreeBSD

今度は本物のOSで、cpuidがどうなってるか追ってみる。

(gdb) c
Continuing.
[Thread 0xb3403b40 (LWP 582) exited]
[New Thread 0xb3403b40 (LWP 583)]

Program received signal SIGUSR2, User defined signal 2.
0x0817879e in tcg_out8 (s=0x845da80 <tcg_ctx>, v=44 ',')
    at /home/sakae/MINE/qemu-0.11.1/tcg/tcg.c:81
81          *s->code_ptr++ = v;

何やら、SIGUSR2信号を頻繁に受信して先に進めないぞ。こういう時はどうする? ぐぐったら、 gdb signal のを紹介されたよ。

(gdb) info signals SIGUSR2
Signal        Stop      Print   Pass to program Description
SIGUSR2       Yes       Yes     Yes             User defined signal 2
(gdb) handle SIGUSR2 nostop noprint
Signal        Stop      Print   Pass to program Description
SIGUSR2       No        No      Yes             User defined signal 2

これで、静かになったよ。(表面的には、、、かな?)

(gdb) c
Continuing.

Breakpoint 2, cpu_x86_cpuid (env=0xa00a740, index=2147483648,
    count=3217026276, eax=0xbffec34c, ebx=0xbffec348, ecx=0xbffec344,
    edx=0xbffec340) at /home/sakae/MINE/qemu-0.11.1/target-i386/helper.c:1607
1607        if (index & 0x80000000) {
(gdb) bt
#0  cpu_x86_cpuid (env=0xa00a740, index=2147483648, count=3217026276,
    eax=0xbffec34c, ebx=0xbffec348, ecx=0xbffec344, edx=0xbffec340)
    at /home/sakae/MINE/qemu-0.11.1/target-i386/helper.c:1607
#1  0x08185350 in helper_cpuid ()
    at /home/sakae/MINE/qemu-0.11.1/target-i386/op_helper.c:1943
#2  0xb6bdae5b in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)

mainまで辿って行けないのは、ちと寂しいなあ。スレッドはOSが介在してるのかな? スレッド無しのqemuを作るって手があるな。でも、多分実行スピードがガタ落ちだろうな。 今でも、FreeBSDの実行は遅くてかなわんから、これはもう諦めるしかないか。

最後に、懐かしいのに出くわしたので、記念にカキコ。 gdbの面白い使い方がないかと探してた時、たまたま gdb core なんてのを見つけた。昔々、このサイトにはお世話になってたなあ、と遠い目。