移植(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.