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を張るのも楽だし、検索もスイスイ出来て なかなか快適。
とまあ、いろいろ脈絡の無い事をやってきたけど、肝心要のマシンコードをどうやって実行するんねん? と言う事については手を出していなかった。参考文献を見つけたよ。
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 なんてのを見つけた。昔々、このサイトにはお世話になってたなあ、と遠い目。