ジグソーパズル 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アセンブラプログラミング