2016-02-28

PWS500auのシリアルコンソールが不調

PWS500auのシリアルコンソールが不調だったのが治ったようだったので昨年末にOpenVMSをインストールしましたが、問題となっていた現象が再び起きるようになりました。どうやらシリアルコンソールが問題なのではなく、マシンそのものが動かなくなってしまいます。

起動後数10分ほどすると、telnetで接続している端末の反応がなくなりますし、他のマシンからpingをかけ続けていても応答しなくなります。仕方ないので電源を落とし、直ちに電源を入れてもシリアルコンソールに表示が出てきません。1時間ほど待ってから電源を入れれば、また動くようになります。どうも調子が良くありませんが、そもそも昨年OSをインストールした時には何時間も動いていてくれたわけなので、どうにも困ったものだと思います。

やはり真っ先に疑うのはメモリかもしれません。DIMMが不良というだけなら交換すれば良いだけですが、マザーボードの何かが不良ということになると、どうしようもありません。

現状では状況がしっかり把握できていないので、訳も分からずマシンが固まっているだけとしか言いようがないのですが、何が悪いのか問題個所を絞り込んでいく方法はないものかと思っています。

2016-02-24

OpenVMSにおけるgzipの現状

U*IXで使い慣れたツールをOpenVMSでも利用したければ、何処からか探してくるか、自前で移植する必要があります。もし既に移植済みであったとしても自分で移植してみれば、OSの違いの影響などを学ぶことが出来るでしょうから、技術力向上に役立つと思います。そこでtarballを扱うために必要なgzipとtarを移植してみようと思います。どちらから手を付けようかと迷いましたが、gzipを先にすることにします。

gzip本体とOpenVMS版の現状を確認しておきましょう。
  1. gzipの最新版は2013年6月9日にリリースされた1.6です。ちなみに1.5は2012年6月17日に、1.4は2010年1月20日にリリースされました。
  2. OpenVMS版の情報は「Gzip for VMS - A File Compression/Expansion Utility」にあります。ここにあるのは2012年8月28日にリリースされた「Gzip 1.5 VMS-Ready Kits」なので、最新の1.6には対応していません。また「Gzip 1.5 for VMS (1.5b)」にはOpenVMS移植に関わる情報が記されています。
以前のgzipはVMSに対応していましたが、ChangeLogを見ると2011年8月10日に打ち切られています。
2011-08-10  Jim Meyering  <meyering@redhat.com>

        maint: remove amiga, atari, msdos, nt, os2, vms sub-directories,
        and all files therein.  This was proposed months prior, and no
        one objected.
OpenVMSに対応していた最終リリースであるgzip-1.4をまず完成させてみようと思います。その後でgzip-1.6の移植に取り掛かろうと考えています。

2016-02-16

UNIX V6で森田オセロ V6.1が動作した

UNIX V6上で森田オセロV6.1が動作しました。問題点を突き止めるため、リンクするオブジェクトファイルを1つずつ追加しながら、異常になるファイルを探しました。リンク時に未定義関数エラーが出ますが、スタブで対処しました。

その結果cell.cが怪しいと分かりました。『思考ゲームプログラミング』の178頁には「8ビット用のCでは2000~3000程度に変更する」という但書があります。PDP-11は8ビットCPUではありませんが、メモリ空間が64Kなので、実質的には8ビット用と同様の制約を受けるのかもしれません。

MAXCELLが7000だったので2000に変更したところ、無事に動作してくれました。幾つか気になっている個所が残っていますが、とりあえず一安心しました。
# ./a.out

MORITAN OTHELLO Ver 6.1
Copyright (C) 1986 by K.Morita


1.man-com 2.com-man 3.com-com
select = 1
Level = 3

  a b c d e f g h
1 . . . . . . . .
2 . . . . . . . .
3 . . . . . . . .
4 . . . 0 X . . .
5 . . . X 0 . . .
6 . . . . . . . .
7 . . . . . . . .
8 . . . . . . . .
black= 2 white= 2

Input your move ? f5

black:f5
  a b c d e f g h
1 . . . . . . . .
2 . . . . . . . .
3 . . . . . . . .
4 . . . 0 X . . .
5 . . . X X X . .
6 . . . . . . . .
7 . . . . . . . .
8 . . . . . . . .
black= 4 white= 1

white:f6
  a b c d e f g h
1 . . . . . . . .
2 . . . . . . . .
3 . . . . . . . .
4 . . . 0 X . . .
5 . . . X 0 X . .
6 . . . . . 0 . .
7 . . . . . . . .
8 . . . . . . . .
black= 3 white= 3

Input your move ?

2016-02-15

森田オセロのコンパイルエラーが無くなったものの、動かない

『思考ゲームプログラミング』に掲載されている森田オセロのUNIX V6に移植し、コンパイルエラーは出なくなりましたが、動きませんでした。
# ls -l a.out
-rwxrwxrwx  1 root    25112 Oct 10 19:58 a.out
# ./a.out
Bad system call -- Core dumped
# cdb
$
Bad system call
 0: savr5()
?
コンパイルが通ったとしても実行できるとは限らないのがC言語なので、何が問題なのかを今後探していこうと思います。

コンパイルが通るようになるまでに変更した箇所をまとめておきます。
  1. #includeを利用するため、ファイルの先頭に「#」を追加
  2. op=」を「=op」に変更
  3. 関数の引数に指定されている「register」を削除
  4. 関数名が7文字以下で一意にならないものがあったので変更
  5. 大域変数に対する「static」を削除
  6. typedef」を#defineで代用
  7. setjmp関連をUNIX V7から移植
  8. 変数を初期化する場合に「=」は不要
  9. 変数を初期化する場合に関数内に置くのは不可
  10. 構造体変数を初期化する場合に「{」と「}」のネストは不可
  11. 関数に対して「static」の指定は不可
  12. get()sscanf()が無かったのでgetchar()で代用し、処理を変更

2016-02-14

UNIX V6でcdbを使う

UNIX V6にはcdbというデバッガがついています。今日のデバッガからみると、かなりプリミティブです。マニュアルcdb(I)は2ページしかありませんし、あまり機能が充実しているとは言えませんが、使い慣れればデバッグに活躍するでしょう。

以下のプログラムを対象にしてcdbの使い方を紹介してみます。
char msg[]  "Hello world";

func(p)
char *p;
{
    printf("%s\n", p);
}

main()
{
    int i;

    for (i = 0; i < 10; i++)
        func(msg);
}
今日ではデバッグする時にはccに「-g」オプションをつけますが、当時のccには特別なオプションは要りません。実行ファイルがa.outであれば、cdbはデフォルトで読み込みます。まずmain()にブレークポイントをかけて、実行してみます。
# cdb
main%b
%r
Breakpoint: main+4
さらにfunc()にもブレークポイントをかけて、一時停止している実行を再開します。
func%b
%c
Breakpoint: func+4
func()で実行が一時停止していますので、スタックトレースを見てみます。
$
Trace/BTP
 0: func(01232)
 1: main()
func()の引数pはスタック上に割り当てられています。その内容はmsg[]の先頭アドレスです。
func:p=
0177744
func:p/
01232
func:p"
Hello world
 func()の先頭から逆アセンブルしてみます。またレジスタの値も見てみます。
func,5?
func:   jsr     r5,csv
        mov     04(r5),(sp)
        mov     $msg+12,-(sp)
        jsr     pc,*$printf
        tst     (sp)+
$r
Trace/BTP
ps      0170004
pc      036     func+06
sp      0177730
r5      0177740
r4      0
r3      0
r2      0
r1      0
r0      034     func+04
ブレークポイントは関数の先頭にかけることが多いかもしれませんが、任意の位置にかけることもできます。ただし今日のデバッガのように親切ではないので、かけ間違えても何のメッセージも出ませんし、うまくブレークポイントにかからないかもしれません。
 func+12%b
cdbのコマンド体系は、UNIX V6にもうひとつあるデバッガdbに似ています。しかしdbにはデバッガを抜けるためのコマンドが用意されているのに、cdbにはありません。しかたないので^Dでcdbを抜けるしかありません。

cdb(I)には以下のような記述があります。当時のマシン環境の常識が分からないので、意味がよく理解できないのですが、「no advance planning is necessary to use it」とわざわざ断っているのは、当時のデバッグでは何か特別な「planning」が必要だったということなのでしょうか。
An important feature of cdb is that even in the interactive case no advance planning is necessary to use it; in particular it is not necessary to compile or load the program in any special way nor to include any special routines in the object file.

setjmp()とlongjmp()のUNIX V6への移植

UNIX V6にはsetjmp()とlongjmp()が実装されていないので、どこからか探してきて移植しなければなりません。調べてみたらUNIX V7には入っていることが分かりました。
setjmp.hの実現方法を見ると、UNIX V7のC言語にはtypedef宣言が使えるようになっているようです。しかも「#include <setjmp.h>」のようなシステム・インクルードが出来るようにもなっていたようです。しかしUNIX V6では両者とも実現されていないので別の対応を考えなければなりません。setjmp.hでは「typedef int jmp_buf[3]」と定義され、レジスタの退避領域を確保しているので「jmp_buf env;」のような箇所は「int env[3];」のようにすれば良いでしょう。

setjmp.sにあるlongjmp()の実装では、最初にcsvというルーチンが、最後にcretというルーチンが呼ばれます。これは「C register save and restore」というものでUNIX V6にも存在します。しかしUNIX V6の実装とUNIX V7の実装は多少異なります。setjmp.sはUNIX V7で実現されているので、UNIX V6に移植するために何か変更する必要があるのか検討しておかなければなりません。

cretの方は、UNIX V6版とUNIX V7版とでレジスタの使い方が若干違いますが、同じものだと考えてよいと思います。csvの方は、UNIX V6版とUNIX V7版とでは、このルーチンからの戻り方の実装だけが異なります。

そもそもPDP-11では、サブルーチンの呼び出しはjsr命令で、そこから戻るにはrts命令を使うことが想定されています。ところがcsvではそうではなく、UNIX V6版では「tst -(sp); jmp (r0)」、UNIX V7版では「jsr pc,(r0)」となっています。実現方法が違いますが、実行される結果は等価であると考えて良いと思います。

以上より、UNIX V7のsetjmp.sをUNIX V6に持ってくれば、そのままsetjmp()とlongjmp()として利用できるようです。簡単なプログラムをを作ってみましたが、ちゃんと動作しているようです。

setjmp.sのロジックを追っていたら、longjmp()の処理として以下のような箇所がありました。
    mov    6(r5),r0
    bne    1f
    mov    $1,r0
1:
コメントが入っていないので何を意図しているのか分かりませんでしたが、BSD 2.11版のsetjmp.sにある「if (val == 0) r0 = 1」というコメントを見ると何がしたかったかが分かりました。

longjmp(a,v)を呼び出すと、vという戻り値でsetjmp()から戻ってきたような動作をします。setjmp(a)が呼ばれると戻り値として0を返す仕様になっているため、longjmp()でvに0が指定されると、setjmp()から戻ってきたのか、longjmp()の結果として戻ってきたのかが区別できなくなってしまうため、もしvに0が指定されていた場合は、強制的に1に置き換えてしまうというロジックになっているようです。

この考え方はわかりますが、その代わりにvに0を指定しても1を指定しても、setjmp()からは1が戻ってくるという副作用が生じています。longjmp()はそういうものだと思うしかないでしょう。

2016-02-08

C言語の奇妙な挙動に関するマニュアル上の説明

UNIX V6版Cにある妙な挙動は『C Reference Manual』の「12. Compiler control lines」で説明されていました。
In order to cause this preprocessor to be invoked, it is necessary that the very first line of the program begin with #.
要するに、ファイルの先頭にある「#」の有無でプリプロセッサを通すかどうかを決めているのでしょう。スマートな方法ではありませんが、当時の計算機資源の実情を考えると仕方ない判断だったのかもしれません。