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 #.
要するに、ファイルの先頭にある「#」の有無でプリプロセッサを通すかどうかを決めているのでしょう。スマートな方法ではありませんが、当時の計算機資源の実情を考えると仕方ない判断だったのかもしれません。

2016/02/05

UNIX V6のC言語の妙な挙動

UNIX V6のC言語には思いもよらない動きをすることを知りました。現象を再現するために以下の3つのファイル(test.c, v6.c, test.h)を使用します。

まずtest.cを示します。
     1  /*
     2   * test.c
     3   */
     4
     5  #include "test.h"
     6
     7  test()
     8  {
     9          return 0;
    10  }
次にv6.cを示しますが、test.cとの違いは1行目に「#」があることだけです。
     1  #
     2  /*
     3   * test.c
     4   */
     5
     6  #include "test.h"
     7
     8  test()
     9  {
    10          return 0;
    11  }
インクルードしているtest.hを示しますが、内容に意味があるわけではありません。
     1  /* test.h */
UNIX V6のC言語で各々をコンパイルしようとすると、驚くような挙動を示します。
# cc -c test.c
5: Unknown character
7: Expression syntax
8: External definition syntax
# cc -c v6.c
# diff test.c v6.c
0a1
. #
test.cでは#includeが現れた行で「Unknown character」というエラーが出て、それに引きずられるようにエラーが続きます。ところがv6.cでは何事もなくコンパイルが出来ます。相違点は1行目の「#」の有無だけです。

どうしてこういうことになるのか理解していませんが、UNIX V6のC言語はこういうものなのだと思うしかないでしょう。/usr/source/c/c00.cなどを見ても、1行目に「#」があります。『Lions' Commentary on UNIX』でも、和訳のアスキー版でも86頁にあるmain.cの最初の行(1500行目となっている個所)を見ると、さりげなく「#」があります。うっかりすると見過ごしてしまいそうですが。


2016/02/03

外部環境からUNIX V6にファイルを持ち込む方法

SIMHのPDP-11上で動作するUNIX V6に外部からファイルを持ち込む方法を考えてみました。まず外部からファイルを持ち込むために仮想テープデバイスが使えることは確認しました。残っている問題は、複数のファイルを如何にして持ち込むかという点です。

tarが使えれば、転送元でファイルをまとめておけるので、それを仮想テープデバイス経由でコピーしてからUNIX V6上で展開すれば終わりなのですが、残念ながらtarはまだ登場していません。最近はあまり見かけませんが、shar形式を使えばよいかとも考えました。しかしsedが無いし、当時のシェルではヒアドキュメントも出来ないようなので、これも駄目です。

shar形式の発想を参考にして、ed(1)を利用してファイルを展開するための手順を考えてみました。以下に示すed(1)コマンドを組み合わせてファイルを作成します。仮にこれをedar形式としておきます。このファイルをUNIX V6に持ち込んで、ed(1)の標準入力に与えると期待した結果が得られそうです。
  1. 「e ファイル名」コマンドで、対象となるファイルを宣言する。
  2. 「a」コマンドでファイルの内容を取り込む。
  3. 「1,$s/^X//」コマンドでshar風にするため付加した文字を取り去る。 
  4. 「w」コマンドでファイルに書き出す。
  5. 「1,$d」コマンドで取り込んだ内容をすべて消去し、次のファイルに備える。

上述したファイルを作成するために「v6edar」というスクリプトを作成しました。
#! /bin/sh
# v6edar -- bind files like using shar

warn () {
    echo "$PROGNAME:$*"
}

oops () {
    warn "$*"
    exit $EXIT_FAILURE
}

usage () {
    cat <<*EOF*
Usage: $PROGNAME [-hvx] files ...
    -h        print this message.
    -v        set shell option verbose.  Useful for debugging.
    -x        set shell option xtrace.  Useful for debugging.
*EOF*
    exit $EXIT_FAILURE
}

EXIT_SUCCESS=0
EXIT_FAILURE=1

PROGNAME=$(basename $0)

while getopts hvx OPT; do
    case $OPT in
        h) usage;;
        v) set -o verbose;;
        x) set -o xtrace;;
        ?) oops "This option isn't accepted.";;
    esac
done
shift $(expr $OPTIND - 1)

for i in $*; do
    [ $i != $(basename $i) ] && warn "$i:Not allowed to use path specification." 1>&2
    echo "e $(basename $i)"
    echo 'a'
    sed -e 's/^.*$/X&/' $i
    echo '.'
    echo '1,$s/^X//'
    echo 'w'
    echo '1,$d'
done
echo 'q'

# That's all
exit $EXIT_SUCCESS
# $Id: v6edar,v 1.2 2016-02-03 05:03:17 furusawa Exp $
またSIMHの仮想テープデバイスで使うための変換をおこなうツールも作成しました。これはBob Supnik, "SIMH Magtape Representation and Handling", Aug. 30, 2006を参考にしました。このツールを「simhtape」とします。
#!/usr/local/bin/python

# simhtape -- convert to the SIMH Magtape Representation format
# Reference: Bob Supnik, "SIMH Magtape Representation and Handling", Aug. 30, 2006

import sys

def main():
    blksiz   = 512
    reclen   = bytearray([0, 2, 0, 0])
    tapemark = bytearray([0, 0, 0, 0])

    while True:
        blk = sys.stdin.read(blksiz)
        if len(blk) == 0: break

        sys.stdout.write(reclen)
        sys.stdout.write(blk)
        if len(blk) != blksiz:
            sys.stdout.write("".join(["\0" for i in xrange(blksiz-len(blk))]))
        sys.stdout.write(reclen)
    sys.stdout.write(tapemark)

if __name__ == "__main__":
    main()
# $Id: simhtape,v 1.3 2016-02-03 05:03:17 furusawa Exp $
これらのツールを使って、以下のようにして転送元でファイルを作成します。
v6edar aaa.c bbb.c | simhtape > working.tap
 作成されたファイルを仮想テープデバイスに割り当てて、UNIX V6側で取り込みます。そしてddでファイルを取り出し、edの標準出力に与えると、ファイルが取り出せるはずです。
# dd if=/dev/mt0 of=a.edar
read: I/O error
4+0 records in
4+0 records out
# cat a.edar | ed -
?
?
# ls -l
total 8
-rw-rw-rw-  1 root     2048 Oct 10 13:12 a.edar
-rw-rw-rw-  1 root      651 Oct 10 13:13 aaa.c
-rw-rw-rw-  1 root      891 Oct 10 13:13 bbb.c
 ddを実行した時に「read: I/O error」というエラーが出ています。何故このようになるのか不明です。edを実行した時には「?」というエラーが出ていますが、これは「e」コマンドを指定した時に与えられているファイル名の実体が無い時に出るようです。これは仕方ないでしょう。

これで複数のファイルを外部から取り込むことが出来るようになったようです。

2016/02/02

SIMH上のUNIX V6とのファイル交換

SIMHのPDP-11上で動作するUNIX V6にファイルを送りこむため、仮想テープデバイスの動作を確認しておきます。仮想テープデバイスを有効にするには、構成ファイルに「attach tm0 working.tap」などと記述しておけば良いのですが、問題となるのは(この例なら)working.tapの方です。事前にファイルが存在しなければ、勝手に空ファイルとして作成されるのですが、それではテープに対する操作をした時にエラーが出ます。
# dd if=/etc/rc of=/dev/mt0
write: I/O error
0+1 records in
0+1 records out
SIMHが期待しているマーカーを入れたファイルを用意しておけばエラーにはならないようです。このファイルを作成するには、何かスクリプトを書いても良いし、コマンドラインからprintf(1)を使用して「printf "\0\2\0\0"」としても良いでしょう。
#!/usr/local/bin/python
def main():
        with open("working.tap", "wb") as fout:
                fout.write(bytearray([0,2,0,0]))

if __name__ == "__main__":
        main()
いずれにせよ、次のようなファイルが出来れば問題ありません。
furusawa% ll working.tap
-rw-r--r--  1 furusawa  furusawa  4  2月  2 08:32 working.tap
furusawa% od -tx1 working.tap
0000000    00  02  00  00
0000004
このファイルがあれば、エラーにはならなくなります。
# dd if=/etc/rc of=/dev/mt0
0+1 records in
0+1 records out
ddで書きこんだ結果、このファイルは次のようになっています。
furusawa% ll working.tap
-rw-r--r--  1 furusawa  furusawa  524  2月  2 08:52 working.tap
furusawa% od -tx1 working.tap
0000000    00  02  00  00  72  6d  20  2d  66  20  2f  65  74  63  2f  6d
0000020    74  61  62  0a  2f  65  74  63  2f  75  70  64  61  74  65  0a
0000040    2f  65  74  63  2f  6d  6f  75  6e  74  20  2f  64  65  76  2f
0000060    72  6b  31  20  2f  75  73  72  2f  73  6f  75  72  63  65  0a
0000100    2f  65  74  63  2f  6d  6f  75  6e  74  20  2f  64  65  76  2f
0000120    72  6b  32  20  2f  75  73  72  2f  64  6f  63  0a  00  00  00
0000140    00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
*
0001000    00  00  00  00  00  02  00  00  00  00  00  00
0001014
仮想テープデバイスを経由すれば、UNIX V6と外部との間でファイル交換が出来そうです。ただし複数のファイルを交換したい場合には、どうすれば良いでしょうか。V6当時はtarは無かったようですし、sharを使おうにも、sedが無いようです。

最悪の場合1ファイルずつ上述した手順を地道に繰り返すしかないのですが、もうすこし効率的な方法を考えてみようと思います。

2016/02/01

オセロゲームにおけるGUIと思考エンジン分離

コンピュータ将棋やチェスの世界には、USI(Universal Shogi Interface)とかUCI(Universal Chess Interface)というプロトコルがあり、GUIと思考エンジンを分離して通信することができるのだそうです。見た目と思考ロジックを分けることが出来れば、いろいろと面白い応用が拡がると思います。

このようなプロトコルがオセロゲームにも欲しいところですが、今のところ実現していないようです。仮にUniversal Othello Interface(UOI)が出来れば、思考エンジンを開発する方向とユーザーインターフェイスを開発する方向が独立して発展できることになります。さらには思考エンジン同士の対戦もできるでしょうし、Webブラウザをユーザーインターフェイスとして利用するような応用も考えられます。

さらに気になっている事ですが、オセロゲームの棋譜を表現するための標準的なファイル形式というものは存在しているのでしょうか?『思考ゲームプログラミング』にあるオセロゲームの思考エンジンは序盤を定石で勝負しようとしますが、その情報はプログラム内部に持っています。これを外部に切り出せれば、プログラムを変えなくても定石の進化に対応できるのではないかと考えています。

チェス、将棋、オセロのようなコンピュータゲームにおいては思考エンジンの強化に意識が集中する面が強いですが、周辺技術にも気を配る必要があるのではないかと思っています。