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 なんてのを見つけた。昔々、このサイトにはお世話になってたなあ、と遠い目。