移植(3)
散歩シリーズも第3段になりました。朝の散歩は、りんご畑やぶどう畑の間をぬって行って いるんだけど、よく消毒車が作業してる所に出会う。そんな時はわざわざ、回り道してるよ。 女房には、あんたも時々消毒してもらえばいいのよ、なんて言われるが(蚤でも付いているんか?) 、あの農薬のシャワーを絶対に浴びたくは無い。
消毒車って都会の人には馴染みが無いと思うけど、どんな車かと言うと、農薬を入れる大きな タンクの後ろに、巨大な扇風機が付いたやつと思えば良い。勿論、運転席は車体の前の方に 申し訳程度に付いている。リンゴの樹の下をくぐりながら、農薬を撒き散らすので、車体は 非常に低く、運転席は開放型(室にはなっていない)である。
ほとんど毎日見てるから風景に溶け込んでしまっていて、さして興味も無かったんだけど、今朝 たまたま、給水で止まってる車に目が行った。注目だったのは、ナンバープレート。まあ、時々 公道を走っているんで、ナンバープレートが付いてて当たり前なんだけど。。。
そのプレートの色が今まで見た事が無い、青色をしてる事に気がついた。ナンバーって、白地とか 緑地、黄色地ぐらいしか知らないし、わは、レンタカーぐらいの知識しか無かったんだ。 で、早速、 ナンバープレートの雑学とか 日本のナンバープレートですよ。 しが死を連想するとか、へが屁と思っちゃうとか、駐留軍以外の車両では下2桁が「42」「49」のものはそれぞれ「死に」「死苦」「始終苦(しじゅうく)」 「轢く」などを連想させて縁起が悪いとされるため払い出されないとか、日本って面白い国だな。 朝の散歩が、思わぬ方向に向かって、今日一日プチ得した気分。
呑気に喜んでいたら、堂々と恐ろしいのが出てた。くわばらくわばら。
gdb attach
既に動いているプログラムにgdbを追加して、デバッグする機能がgdbに用意されている。 この機能を使うには、あらかじめ実行中のプログラムのプロセス番号(pid)を調べておき、gdbを起動後、 pidを引数にしてattachコマンドを発行する必要がある。面倒至極だなあ。
と言う訳で、上記を自動化するようにしてみた。そのスクリプトの名前はどうすべ? 3秒考えて、 bkに決定。bkって、アマチュア無線の電信略号でbreakって意味なんだ。交信してる人同士に 混ぜて貰いたい時に使うんだよ。横槍な訳だから、上手に使わないと無視されたり、怒られたり するけどね。(経験者は語る)
#!/bin/sh # gdb attach selected program # Usage: bk program pid=`ps awx | grep $1 | grep -v grep | grep -v $0 | awk '{print $1}'` if [ $pid ]; then gdb $1 $pid fi
例えば、今回移植しようとしてるsimpってプログラムで、セッション(会話)してる時、横槍を 入れて、そのセッションを乗っ取っちゃりする時に使う。まれにプログラムが暴走してて、どう しようも無い時にも出番が有ります。 使うには、別端末を起動して、割り込みたいプログラム名を指定するだけ。
[sakae@bang Simple]$ bk simp : Reading symbols from /home/sakae/Simple/simp...done. Attaching to program: /home/sakae/Simple/simp, process 662 Reading symbols from /lib/libm.so.6...(no debugging symbols found)...done. Loaded symbols for /lib/libm.so.6 Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done. Loaded symbols for /lib/ld-linux.so.2 0xb7754424 in __kernel_vsyscall () (gdb) bt #0 0xb7754424 in __kernel_vsyscall () #1 0xb76445c3 in __read_nocancel () from /lib/libc.so.6 #2 0xb75d976b in _IO_new_file_underflow () from /lib/libc.so.6 #3 0xb75dbc29 in _IO_default_uflow_internal () from /lib/libc.so.6 #4 0xb75dba3b in __uflow () from /lib/libc.so.6 #5 0xb75d63ac in getc () from /lib/libc.so.6 #6 0x0804c257 in gettoken () at main.c:1060 #7 0x0804d170 in read () at main.c:1507 #8 0x08048b7f in main () at main.c:47
こんな風に出てくるから、対象のプログラムが暴走してたって、どこら辺をうろちょろしてるか 一目瞭然。上記の例なら、mainの中のreadが呼ばれて、その中でgettokenが呼ばれて、、、 最後は、Linuxの中まで行ってるね。まあ、そこまで見なくても、readで入力待ちになってるって 事は分かるはな。
attachした時点で、制御権はgdbにあるんで、マイクをお返ししまーすとばかり、gdbからcontinue して、相手のセッションを継続させておく。(相手に制御が渡っても、gdb側から、CTRL-Cすれば gdbが制御権を取り戻せるので、BPを設定し忘れた時などには、とっても便利)
(gdb) continue Continuing. Program received signal SIGSEGV, Segmentation fault. 0xb75e2b56 in free () from /lib/libc.so.6 (gdb) bt #0 0xb75e2b56 in free () from /lib/libc.so.6 #1 0x08048c38 in main () at main.c:63 (gdb) f 1 #1 0x08048c38 in main () at main.c:63 63 free(memory[addr].name); (gdb) p addr $1 = 100000
相手側で、(exit)しました。セグフォで落ちて、gdbに制御権が回ってきました。mainの中で ご本尊様の名前エリアを開放しようとして、ちょいと回りすぎて(配列の範囲外を アクセスしてしまい)怒られたようです。(注 coreが大きくならないように、CELLSIZEを小さくしてる)
と言うことで、オフ・バイ・ワンのエラーを発見できましたよ。 あれ、sasagawa888さんの所では、 網走番外地を苦もなく見せて いるけど、運が良い人だなあ。今は亡きサンマイクロ社製の虫発見器、deadbeaf が植え付けてあると、 プンプン臭ってbugをあぶり出せて いいな。今度、殺虫剤を探してみるかな。夏も近い事だし!
虫さん、出ておいで
殺虫剤が手に入らないので地道に虫?を探して行く。FreeBSDで、(room)した時、セグフォに なるんだった。
(gdb) bt #0 0x0804fe90 in cdr (lis=675592640) at list.c:35 #1 0x08051117 in macronamep (sym=579) at list.c:511 #2 0x08049be6 in comp (expr=582, env=0, code=583, tail=5) at main.c:284 #3 0x08048eda in compile (expr=582) at main.c:117 #4 0x08048cc5 in main () at main.c:49 (gdb) f 1 #1 0x08051117 in macronamep (sym=579) at list.c:511 511 if (IS_MACRO(cdr(val))) (gdb) l 506 507 val = assq(sym, G); 508 if (val == 0) 509 return (0); 510 511 if (IS_MACRO(cdr(val))) 512 return (1); 513 else 514 return (0); 515 } (gdb) p val $1 = 675592640
assqの返値がおかしいな。assqって、どうなってる?
int assq(int obj, int lis) { if (nullp(lis)) return (BOOLF); else if (eqp(obj, caar(lis))) return (car(lis)); else assq(obj, cdr(lis)); }
ふむ、C言語で書いてあるって言っても、まるっきりSchemeだな。(eqpは、頭の中で、eq?って 読み替える事)
assqの呼び出し元で与えている G は、どうなってるかLinux上で観察してみると、
Simp> (reg 'G) ((= . #<subr =>) (>= . #<subr >=>) ... (room . #<subr room>) ... ldc stop (#f . #f) (#t . #t)) Simp> (addr room) 88 Simp> (dump 85 90) addr car cdr env tag val 0000085 F 0000000 0000000 0000000 Sym prof 0000086 F 0000085 0000084 0000000 Lis 0000087 F 0000000 0000000 0000000 Sym room 0000088 F 0000000 0000000 0000000 Sub 0x8050b28 0000089 F 0000087 0000088 0000000 Lis 0000090 F 0000089 0000086 0000000 Lis #<undef>
dumpに少々手を加えて、Subの場合その実行開始番地を表示するように、ちょっと悪戯してみた。 こういうの楽しいね。ちなみに、assqの効能をちょっと確認しとくか。
Simp> (assq 'room (reg 'G)) (room . #<subr room>) Simp> (assq 'hogefuga (reg 'G)) #f
そんじゃFreeBSDに戻って、地道に虫の居所を探っていく。assqの(お目当てが見つかった時の) 出口にBPを置いて実行。
Breakpoint 2, assq (obj=579, lis=90) at list.c:81 81 return (car(lis)); (gdb) p memory[90].car $1 = 89 (gdb) n macronamep (sym=579) at list.c:508 508 if (val == 0) (gdb) p val $2 = 675592640
ふむ、期待する出口で止まってくれた。返値を確認すると正しそう。次の命令実行で、いきなり 上位の呼び出し元に戻ってる。で、上位でのvalが化けてる。
比較の為に、Linuxでやってみると、
Breakpoint 2, assq (obj=579, lis=90) at list.c:81 81 return (car(lis)); (gdb) p memory[90].car $1 = 89 (gdb) n 84 } (gdb) n 84 } :
ちゃんと再帰呼び出しを巻き戻ししてるっぽい。呼び出し履歴を見ると
: #118 0x0804fa32 in assq (obj=579, lis=574) at list.c:83 #119 0x0804fa32 in assq (obj=579, lis=578) at list.c:83 #120 0x080507fb in macronamep (sym=579) at list.c:507 #121 0x08049a50 in comp (expr=582, env=0, code=583, tail=5) at main.c:284 #122 0x08048db8 in compile (expr=582) at main.c:117 #123 0x08048b9f in main () at main.c:49
期待通りになってる。FreeBSDは、どうも一気に巻き戻しているっぽい。勝手に最適化でもしてる んかな? ちょいと悪戯で、assqの出口に、deadbeaf を置いてみる。
Breakpoint 2, assq (obj=579, lis=90) at list.c:81 81 return (0xdeadbeaf); (gdb) bt #0 assq (obj=579, lis=90) at list.c:81 #1 0x0805002f in assq (obj=579, lis=94) at list.c:84 #2 0x0805002f in assq (obj=579, lis=98) at list.c:84 : #122 0x0805002f in assq (obj=579, lis=578) at list.c:84 #123 0x080510ea in macronamep (sym=579) at list.c:508 #124 0x08049be6 in comp (expr=582, env=0, code=583, tail=5) at main.c:284 #125 0x08048eda in compile (expr=582) at main.c:117 #126 0x08048cc5 in main () at main.c:49
ふむ、ちゃんと再帰してるな。そんじゃ、進めてみる。
(gdb) f 123 #123 0x080510ea in macronamep (sym=579) at list.c:508 508 val = assq(sym, G); (gdb) p val $1 = 0 (gdb) n macronamep (sym=579) at list.c:509 509 if (val == 0) (gdb) p val $2 = 675592640 (gdb) p/x val $3 = 0x2844b9c0 (gdb) bt #0 macronamep (sym=579) at list.c:509 #1 0x08049be6 in comp (expr=582, env=0, code=583, tail=5) at main.c:284 #2 0x08048eda in compile (expr=582) at main.c:117 #3 0x08048cc5 in main () at main.c:49
やっぱり、一気に戻ってて、assqから申し送られた deadbeaf が、変なデータに化けてるぞ。 これはもう、assqの内部を晒してみるしかないですかね。余り(絶対)見たくないですけど。。。。
(gdb) disassemble assq Dump of assembler code for function assq: 0x0804ffd0 <+0>: push %ebp 0x0804ffd1 <+1>: mov %esp,%ebp 0x0804ffd3 <+3>: sub $0x18,%esp 0x0804ffd6 <+6>: mov 0xc(%ebp),%eax 0x0804ffd9 <+9>: mov %eax,(%esp) 0x0804ffdc <+12>: call 0x8050db0 <nullp> 0x0804ffe1 <+17>: test %eax,%eax 0x0804ffe3 <+19>: je 0x804ffee <assq+30> 0x0804ffe5 <+21>: movl $0x5,-0x4(%ebp) 0x0804ffec <+28>: jmp 0x8050031 <assq+97> 0x0804ffee <+30>: mov 0xc(%ebp),%eax 0x0804fff1 <+33>: mov %eax,(%esp) 0x0804fff4 <+36>: call 0x804fe10 <caar> 0x0804fff9 <+41>: mov %eax,0x4(%esp) 0x0804fffd <+45>: mov 0x8(%ebp),%eax 0x08050000 <+48>: mov %eax,(%esp) 0x08050003 <+51>: call 0x80562d0 <eqp> 0x08050008 <+56>: test %eax,%eax 0x0805000a <+58>: je 0x8050015 <assq+69> 0x0805000c <+60>: movl $0xdeadbeaf,-0x4(%ebp) 0x08050013 <+67>: jmp 0x8050031 <assq+97> 0x08050015 <+69>: mov 0xc(%ebp),%eax 0x08050018 <+72>: mov %eax,(%esp) 0x0805001b <+75>: call 0x804fe80 <cdr> 0x08050020 <+80>: mov %eax,0x4(%esp) 0x08050024 <+84>: mov 0x8(%ebp),%eax 0x08050027 <+87>: mov %eax,(%esp) 0x0805002a <+90>: call 0x804ffd0 <assq> 0x0805002f <+95>: jmp 0x8050037 <assq+103> 0x08050031 <+97>: mov -0x4(%ebp),%eax 0x08050034 <+100>: mov %eax,-0x8(%ebp) 0x08050037 <+103>: mov -0x8(%ebp),%eax 0x0805003a <+106>: leave 0x0805003b <+107>: ret End of assembler dump.
真ん中辺の所に deadbeaf が埋め込まれているな。こうなったら、マシン語で追ってくか。leave って、どんな動きするんだっけ?簡単な解説が有ったよ。
Breakpoint 2, assq (obj=579, lis=90) at list.c:81 81 return (0xdeadbeaf); (gdb) display/i $pc 1: x/i $pc => 0x805000c <assq+60>: movl $0xdeadbeaf,-0x4(%ebp) (gdb) stepi 0x08050013 81 return (0xdeadbeaf); 1: x/i $pc => 0x8050013 <assq+67>: jmp 0x8050031 <assq+97> : => 0x805003a <assq+106>: leave (gdb) 0x0805003b 85 } 1: x/i $pc => 0x805003b <assq+107>: ret (gdb) assq (obj=579, lis=94) at list.c:85 85 } 1: x/i $pc => 0x805002f <assq+95>: jmp 0x8050037 <assq+103> (gdb) 0x08050037 85 } 1: x/i $pc => 0x8050037 <assq+103>: mov -0x8(%ebp),%eax (gdb) p/x $eax $1 = 0xdeadbeaf : => 0x805002f <assq+95>: jmp 0x8050037 <assq+103> (gdb) 0x08050037 85 } 1: x/i $pc => 0x8050037 <assq+103>: mov -0x8(%ebp),%eax (gdb) p/x $eax $3 = 0x804fe29 (gdb) p $eax $4 = 134544937
確か関数からの戻り値はeaxレジスタに入る約束だったな。retした後の巻き戻し(飛び先)がおかしくて、変なデータを拾ってきちゃうのね。これって、gccの虫? だとしたら、万策尽きる? まてまて、Linux側は行け行けドンドンがデフォだから、FreeBSD側も それに対抗しよう。-O2 ぐらいを付けて、再コンパイル。
残念ながら、gdbでvalの値を確認出来なくなっちゃった。まあ、そうだわな。Linuxでちゃんと valを確認出来たって事は、最適化がOFFだったって事だな。もう、移植諦めるか。最後の悪あがきで、 腐った牛を除去した上で、最適化をかけて再コンパイル。
[sakae@secd ~/Simple]$ make gcc -g -O1 -c list.c gcc main.o cell.o list.o function.o compute.o -o simp -lm exctags *.[ch] [sakae@secd ~/Simple]$ ./simp educational Scheme compiler Simple Ver0.17.4 (written by sasagawa888) Simp> (room) F = 999408 G = 164 S = 0 E = 0 C = 1 D = 0 #<undef>
あらま、不思議な事に、動いちゃったよ。f_loadの行をイネーブルにして、マクロが有効に なるようにしても、動いてる。FreeBSDのgccの虫認定って事で宜しいですかね。参考に 巻末にassqの魚拓を置いておきます。
ガベコレ(本物)
以前、ガベコレ(もどき)ってお題をやったけど、今回は本物をみます。
main.cの中にあるmarkoblistを使って、SECDGの各レジスタから辿れるご本尊様に、使ってまーす印を 付けておく。更にそこから繋がってるのにもマークを付ける。それが終わったら、gbcsweep で、全ご本尊様をなめながら、印を頼りに使ってないものを回収してく。
伝統的な方法によるごみ集めでした。新しい方式のごみ集めも計画してるそうなので、今から 楽しみです。
[sakae@secd ~/Simple]$ ./simp educational Scheme compiler Simple Ver0.17.4 (written by sasagawa888) Simp> (gbc #t) Simp> (gbc) enter GBC free= 984645 exit GBC free= 997658 Simp> (- 997658 984645) 13013
ちょっとした事で、大量にご本尊様を消費する富豪的な構成になっていますから、ガベコレが 短時間で済むのは、matzさん共々も嬉しい訳ですよ。
gcc -O1 ...
わざわざgdbを起動しなくても、gcc -S -O1 -c xxx.c してから xxx.s を見ても いいんだけど。。。
Dump of assembler code for function assq: 0x0804f570 <+0>: push %ebp 0x0804f571 <+1>: mov %esp,%ebp 0x0804f573 <+3>: push %ebx 0x0804f574 <+4>: sub $0x14,%esp 0x0804f577 <+7>: mov 0xc(%ebp),%ebx 0x0804f57a <+10>: mov %ebx,(%esp) 0x0804f57d <+13>: call 0x804ee80 <nullp> 0x0804f582 <+18>: mov $0x5,%edx 0x0804f587 <+23>: test %eax,%eax 0x0804f589 <+25>: jne 0x804f5d0 <assq+96> 0x0804f58b <+27>: mov %ebx,(%esp) 0x0804f58e <+30>: call 0x804e970 <caar> 0x0804f593 <+35>: mov %eax,0x4(%esp) 0x0804f597 <+39>: mov 0x8(%ebp),%eax 0x0804f59a <+42>: mov %eax,(%esp) 0x0804f59d <+45>: call 0x8056d90 <eqp> 0x0804f5a2 <+50>: test %eax,%eax 0x0804f5a4 <+52>: je 0x804f5b2 <assq+66> 0x0804f5a6 <+54>: mov %ebx,(%esp) 0x0804f5a9 <+57>: call 0x804e950 <car> 0x0804f5ae <+62>: mov %eax,%edx 0x0804f5b0 <+64>: jmp 0x804f5d0 <assq+96> 0x0804f5b2 <+66>: mov %ebx,(%esp) 0x0804f5b5 <+69>: call 0x804e990 <cdr> 0x0804f5ba <+74>: mov %eax,0x4(%esp) 0x0804f5be <+78>: mov 0x8(%ebp),%eax 0x0804f5c1 <+81>: mov %eax,(%esp) 0x0804f5c4 <+84>: call 0x804f570 <assq> 0x0804f5c9 <+89>: jmp 0x804f5d2 <assq+98> 0x0804f5cb <+91>: nop 0x0804f5cc <+92>: lea 0x0(%esi,%eiz,1),%esi 0x0804f5d0 <+96>: mov %edx,%eax 0x0804f5d2 <+98>: add $0x14,%esp 0x0804f5d5 <+101>: pop %ebx 0x0804f5d6 <+102>: pop %ebp 0x0804f5d7 <+103>: ret End of assembler dump.