ジグソーパズル on unix/v6
私が住む立体長屋の入り口で、乳母車を引いた母子に出会った。以前その赤ちゃんを女の子 と間違えてしまったのだった。
今度は間違えないように、暫く見なかったら大きくなったねぇ。坊や幾つ? って、坊やに 聞いても、答えられないよ。おかーさんが、代弁してくれた。11ヶ月ですって。そして、もう 体重は、10kgになるとか。発育良好で、検診の時に褒められたと、プチ自慢ぎみ。
もう、歩けるのかな? こちらも代弁。ハイハイが出来て、気分がいいと掴まり立ちするそうな。しゃがんで 坊やの顔を見てたら、向こうもじっとこっちを見てる。そのうちに、顔が引きつって、泣き顔 モードに移行中。泣かせてしまったら、申し訳ないよなーと、そこそこと立ち去りましたよ。
おいらの顔って、そんなに泣かせる顔かしらん? はななだ疑問。そう言えば、散歩中、犬に よく吼えられるんよ。女房曰く、あんたの後ろには背後霊が居て、それを本能で感じ取って いるんじゃない、なんてぬかしおった。
そっか、おいらには、ダエモン君が背後にいるんだな。至極納得しました。
探す
前回は、ジグソーパズルのピースを埋め始めた。unix/v6はほとんどがC言語で書かれている。 C言語のソースの任意の所で止めて、どういう動きをしてるか見たいんだけど、gdbは勿論 使えんし。わずかに許されるのは、simでサポートしてる、アセンブラレベルのdebugger(と 言うか、monitorかな)、それとて、マシン語のアドレスで指定せにゃならん。
そんな訳で、既知のアドレスを元に、C言語上の関数のアドレスを探して行く。スタート地点は m40.s トラップルーチンあたりが、C言語への入り口になろう。 下記の、_trap のアドレスを知りたいね。
0755: trap: 0756: mov PS,-4(sp) 0757: tst nofault 0758: bne 1f 0759: mov SSR0,ssr 0760: mov SSR2,ssr+4 0761: mov $1,SSR0 0762: jsr r0,call1; _trap 0763: / no return 0764: 1: 0765: mov $1,SSR0 0766: mov nofault,(sp) 0767: rtt
mov PS,-4(sp) ってのは、m40.sでは、ここだけなので都合がよさそう。PSってのは、アセンブラ レベルでの定数なんで、調べてみると、177776って判明してる。
sim> eval mov 177776,-4(sp) 0: 016766 2: 177772 4: 177774
逆アセンブルして、命令パターンを教えてもらった。結果、3ワードに展開されるんだ。
sim> e =016766 300-3500 326: 016766 sim> e -m 326/100 326: MOV 177776,177774(SP) 334: TST 120334 340: BNE 372 342: MOV 177572,120336 350: MOV 177576,120342 356: MOV #1,177572 364: JSR R0,406 370: CMP (R5),-(R4) 372: MOV #1,177572 400: MOV 120334,(SP) 404: RTT 406: TST -(SP) 410: BIC #340,177776 416: BR 424 420: MOV 177776,-(SP) 424: MOV R1,-(SP)
命令のオペレーション部を、300番地から3500番地の範囲で検索してみた。その結果、326番地が 運よく一つだけ見つかった。そこで、その番地から、100byteを逆アセンブルしてみた。 m40.sと比べると、合っていそう。370番地は、_trap というアドレスデータのはずなんだけど、 命令として解釈しちゃって、変な事になっている。改めて、この番地を表示してみると
sim> e 370 370: 021544
つう事で、C言語側のtrap関数のアドレスが判明した。
sysentの攻略
で、後はtrap関数を丹念に追って、
100 case 6+USER: /* sys call */ 101 u.u_error = 0; 102 ps =& ~EBIT; 103 callp = &sysent[fuiword(pc-2)&077];
sysentの場所を突き止めれば、systemcallの実際の処理アドレスが判明するという、美味しい 状態になる訳である。が、へたれなおいらには、そんな元気が無い。人間版のccなんて勘弁勘弁である。
こういう時は、sim上で、メモリーパターンを検索しちゃうのが得策。例の本では、余り 面白くないソース、sysent.c なんて書いてあるけど、おいらにとっちゃ宝の山ですよ。
11 int sysent[] 12 { 13 0, &nullsys, /* 0 = indir */ 14 0, &rexit, /* 1 = exit */ 15 0, &fork, /* 2 = fork */ 16 2, &read, /* 3 = read */ 17 2, &write, /* 4 = write */
各行の最初の数字は、アリティー(引数の数)、続いて、処理アドレスとなっている。 これって、Lispの関数の登録簿にもよく出てくるね。
make_func("car", &car, 1); make_func("cdr", &cdr, 1); make_func("cons", &cons, 2); make_func("eq", &eq, 2);
このLispの場合は、Lisp上から使う関数名を文字列として登録してる。sysentの方は、配列の Index番号で、systemcallを区別してるんだな。
simで、複数のワードの検索が出来ればいいんだけど、ちと無理っぽい。しょうがないので、 saveってを試してみるかな。 これを使うとpdp11の状態をファイルに落とせる。ここから メモリーパターンを検索出来るだろうと思ったね。実際にファイルに落として、眺めてみると
00000000 56 33 2e 35 0a 50 44 50 2d 31 31 0a 33 32 62 20 |V3.5.PDP-11.32b | 00000010 64 61 74 61 0a 33 32 62 20 61 64 64 72 65 73 73 |data.32b address| 00000020 65 73 0a 45 74 68 65 72 6e 65 74 20 73 75 70 70 |es.Ethernet supp| 00000030 6f 72 74 0a 35 36 32 30 32 39 33 35 36 34 0a bc |ort.5620293564..| 00000040 df fe 4e 43 50 55 0a 0a 04 00 00 00 00 00 00 00 |..NCPU..........| 00000050 00 00 00 00 00 00 00 00 fe ff 3f 00 00 00 00 00 |..........?.....|
ファイルの頭に能書きとかが書いてあって、自分がイメージしてたのとちょっと違った。 simh/scp.cあたりを解析すれば、フォーマットが分かるだろうけど、それじゃ日が暮れて しまう。もっと手軽に出来ないかな?
simには、ログ機能が付いていて、simとのセッションをログに落とせる。いろいろなパターンで 検索したのをログに落として、そいつを眺めればいいじゃん。
sim> set log LOG Logging to file "LOG" sim> e =000000 0-177777 : 177432: 000000 sim> e =000001 0-177777 :
こんな風にして、アリティー4までの、メモリーパターンを検索した。後は、ログのごみを 取り除いた後、数値(アドレス)で、並べ替えておく
[sakae@cdr ~/UV6]$ cat LOG | sort -n > MYLOG [sakae@cdr ~/UV6]$ wc MYLOG 14120 28240 207394 MYLOG
さて次は、このMYLOGから、複数行のマッチだな。rubyの一行野郎で何とかなるかな? 特異的に表れているパターンは、1(times),4(prof),0(tiu)だな。(それぞれの数値は、アリティー数です)
[sakae@cdr ~/UV6]$ cat MYLOG | ruby -ne 'puts $& if /001.*004.*000000/m' [sakae@cdr ~/UV6]$
あれ? 一行野郎で、複数行マッチは無理か。見なかった事にしてね。そんじゃ、一回こっきりの スクリプトを書くよ。
a = File.open("./MYLOG").readlines a.size.times do |x| adr0,v0 = a[x].split(/\s/) adr1,v1 = a[x+1].split(/\s/) adr2,v2 = a[x+2].split(/\s/) if v0.to_i == 1 and v1.to_i == 4 and v2.to_i == 0 puts adr0 end end
やっつけなんです。終了判定はさぼってます。
[sakae@cdr ~/UV6]$ ./scan.rb 61174: ./scan.rb:7: private method `split' called for nil:NilClass (NoMethodError) from ./scan.rb:4:in `times' from ./scan.rb:4
エラーで止まったけど、アドレスが判明しました。60720のあたりなんですね。
sim> e 60720/400 60720: 000000 60722: 022570 nullsys 60724: 000000 60726: 033670 rexit 60730: 000000 60732: 034744 fork 60734: 000002 60736: 035442 read 60740: 000002 60742: 035462 write 60744: 000002 60746: 035726 open 60750: 000000 60752: 036362 close 60754: 000000 60756: 034310 wait 60760: 000002 60762: 036000 creat 60764: 000002 60766: 036720 link 60770: 000001 60772: 041562 unlink 60774: 000002 60776: 032134 exec 61000: 000001 61002: 041762 chdir 61004: 000000 61006: 041154 gtime 61010: 000003 61012: 037152 mknod 61014: 000002 61016: 042110 chmod 61020: 000002 61022: 042200 chown 61024: 000001 61026: 035104 sbreak 61030: 000002 61032: 037562 stat 61034: 000002 61036: 036426 seek 61040: 000000 61042: 041450 getpid 61044: 000003 61046: 040170 smount 61050: 000001 61052: 040604 sumount 61054: 000000 61056: 041254 setuid 61060: 000000 61062: 041326 getuid 61064: 000000 61066: 041204 stime 61070: 000003 61072: 024230 ptrace 61074: 000000 61076: 022552 nosys 61100: 000001 61102: 037520 fstat 61104: 000000 61106: 022552 nosys 61110: 000001 61112: 022570 nullsys 61114: 000001 61116: 047064 stty 61120: 000001 61122: 046754 gtty 61124: 000000 61126: 022552 nosys 61130: 000000 61132: 041506 nice 61134: 000000 61136: 037262 sslep 61140: 000000 61142: 041472 sync 61144: 000001 61146: 042370 kill 61150: 000000 61152: 041136 getswit 61154: 000000 61156: 022552 nosys 61160: 000000 61162: 022552 nosys 61164: 000000 61166: 040120 dup 61170: 000000 61172: 031144 pipe 61174: 000001 61176: 042546 times 61200: 000004 61202: 042612 profil 61204: 000000 61206: 022552 nosys 61210: 000000 61212: 041356 setgid 61214: 000000 61216: 041420 getgid 61220: 000002 61222: 042256 ssig :
ピースが大分埋まりましたね。後は、これを元に追っていけばいいのか。
sim> set break -e 021544 sim> c Breakpoint, PC: 021544 (JSR R5,3574) sim> sh cpu history=20 PC PSW src dst IR 010770 170000|177712 010770 MOV 42670(R5),16724 010776 170010|177712 010776 MOV 177770(R5),23722 011004 170000| 000002 TRAP 0 000326 030346|000326 141774 MOV 21626,435(SP) 000334 030340| 000334 TST 342 000340 030344| BNE 372 000342 030344|000342 000342 MOV 347,1377 000350 030340|000350 000350 MOV 356,21630 000356 030340|000356 000356 MOV #5016,5323 000364 030340|000002 000364 JSR R0,5406 000406 030340| 141772 TST -(SP) 000410 030340|000410 000410 BIC #4737,7054 000416 030000| BR 424 000424 030000|177740 141770 MOV R1,-(SP) 000426 030010| 141766 MFPI SP 000430 030010|141764 141764 MOV 0(SP),-(SP) 000434 030000|000434 141762 BIC #105267,(SP) 000440 030000|000440 000440 BIT #12716,447 000446 030000| BEQ 510 000450 030000|000450 000370 JSR PC,@(R0)+
さあ、長い旅が始まったよ。TRAPを境にPSWが変わっているのが新鮮。まるで神の国へ来た みたい。
(注) 上記で出てきたアドレスや行数は、私の環境でも実例です。みなさんが実行しても 多分私のとは違った結果になるでしょう。やってる事をご理解の上、お遊びください。 (釈迦に説法だったら、すまそ)
simhについて
そうそう、simを多用するようになると、simがコマンドヒストリーをサポートしてくれて いないため、ちと、いら管 ぎみになっちゃう。ports/devel/rlwrapを入れておいて、.bashrc にでも
alias pdp11='rlwrap pdp11'
と、書いておくと、楽土 になります。perl6って、楽土かどうかは知らんがな。
もうひとつ いら管 があって、それは、break -d が実装されていない事なんです。 これ、指定したアドレスがアクセスされたら break するって機能だと思います。unix的に 言えば、もっと細かく -r とか -w の方が(指定したアドレスを読み込もうとした、書き込もうとした) 嬉しいな。こういう機能が搭載されると、某スーパーミニコンみたいなマシンになっちゃい ますが。。誰か、pdp11を超マシンへと改造してくれませんかね。
改造出来るかと思ってコードを眺めてみた。そしたら、改造につぐ改造でコードが ぐちゃぐちゃ。しゃーないので、indent(配線の整理ですな)してから、簡単にトレース出来る ように、globalした。
でも、余りの複雑さに恐れおののいて います。昔のDECの職人さんは、pdp11が正しく動かなくてベル研に呼ばれた時、デニス・リッチィー さんの目の前で、基板上に残っていた空きゲートと白い配線材を使って改造してくれた そうです。おいらも昔は、こういう事だったら得意だったんですがね。歳と共に体力と 根気が無くなってしまったようです。
翻って、インテルの石には、アクセストラップの機能が搭載されてるようです。 Windows上で簡単に動かせるようなモニターもあるようですから、試してみる価値が あるかも知れません。 8086アセンブラプログラミング