VAXでのアセンブラ短歌を説明します. VAXは可変長命令で,さらに奇数長命令を持っています. マイコン系以外(x86の命令セットは,わたし的にはマイコン系の分類) でアセンブラ短歌ができる,数少ない(kozos.jp/books/asmの中では唯一の)存在です.
なおここではFreeBSDでの例を主に説明します.UbuntuやCentOSなどの GNU/Linuxディストリビューションでも,同様の手順でできるかと思います. apt-getなどでインストールできるツールを探してそれを利用するのが楽で いいでしょう.
開発環境は以下のcross-20121216.tgzをインストールすることで, VAX向け開発環境が利用できる.
インストールすると /usr/local/cross/bin/vax-netbsdelf-gcc が利用できる ようになる.% /usr/local/cross/bin/vax-netbsdelf-gcc -v Reading specs from /usr/local/cross/lib/gcc/vax-netbsdelf/3.4.6/specs Configured with: ../../../toolchain/gcc-3.4.6/configure --target=vax-netbsdelf --prefix=/usr/local/cross --disable-werror --disable-nls --disable-threads --disable-shared --enable-languages=c Thread model: single gcc version 3.4.6 %
FreeBSDのportsにsimhというシミュレータがあるみたいなのでそれを利用する. VAX向けのエミュレータみたいなのは,simh以外には見あたらなかった.
simhをインストールすると「vax」というコマンドが利用可能になります. 他にも「pdp11」とかのコマンドがあって,アーキテクチャごとにコマンドが 分かれているみたい.
vaxを起動してhでヘルプを出すと,loadというコマンドがあってこれでなんらかの バイナリ?をロードできるみたいに思える.
% vax VAX simulator V3.8-1 sim> h r{eset} {ALL|} reset simulator e{xamine} examine memory or registers ... l{oad}
{ } load binary file ... h{elp} type help for command ! execute local command interpreter ! execute local host command sim>
simhのソースコードは /usr/ports/emulators/simh で make fetch することで ダウンロードできる.(/usr/ports/distfiles/simhv38-1.zip)
展開して(カレントディレクトリにいきなりファイルが作成されるので注意. ディレクトリ掘ってそこで展開する),grep load でloadコマンドの処理箇所を 探すと以下のようなのが見つかる.
~>% mkdir simh ~>% cd simh ~/simh>% unzip /usr/ports/distfiles/simhv38-1.zip ~/simh>% cd VAX ~/simh/VAX>% grep load * ... vax_syslist.c:t_stat sim_load (FILE *fileref, char *cptr, char *fnam, int flag) ... ~/simh/VAX>%vax_syslist.c の sim_load() の箇所を見てみるとコメントとかがあって, 以下のようなことがわかる.
実際にhaltで終了するような簡単なプログラムをアセンブラで書いて, ベタバイナリにしてロードして実行してみよう.
vax.S を以下の内容で作成する.最後にhaltで終了するだけの簡単なもの.
.section .text .globl _start .type _start, @function _start: halt ret
ld.scr を以下の内容で作成する.アドレスをROMBASEの 0x20040000 に合わせてある.
ENTRY("_start") SECTIONS { .text 0x20040000 : { *(.text .text.*) } }
以下のようにしてアセンブルしてベタバイナリを作成する.
% /usr/local/cross/bin/vax-netbsdelf-gcc -fno-builtin -nostdinc -nostdlib -static -O -Wall -g -o vax.o -c vax.S % /usr/local/cross/bin/vax-netbsdelf-gcc -fno-builtin -nostdinc -nostdlib -static -O -Wall -g -Wl,-Tld.scr -o vax.x vax.o % /usr/local/cross/bin/vax-netbsdelf-objdump -d vax.x > vax.d % /usr/local/cross/bin/vax-netbsdelf-objcopy -O binary vax.x vax.bin
以下のようにして実行する.
~/simple>% vax VAX simulator V3.8-1 sim> load -r vax.bin sim> boot cpu HALT instruction, PC: 20040001 (RET) sim> quit Goodbye ~/simple>%haltで終了できたので,プログラム実行はできているみたい.
アセンブラ短歌のために,simhのソースコードを調べて文字出力の方法を探る.
まずは grep putchar とかいろいろやってみると,VAX/vax_stddev.c に 以下のようなのが見つかる.
t_stat tto_svc (UNIT *uptr) { int32 c; t_stat r; c = sim_tt_outcvt (tto_unit.buf, TT_GET_MODE (uptr->flags)); if (c >= 0) { if ((r = sim_putchar_s (c)) != SCPE_OK) { /* output; error? */tto_unit.bufというバッファの内容を出力しているみたい. grep tto_unit.buf でバッファを操作している場所を探ると, VAX/vax_stddev.c に以下のようなのが見つかる.
void txdb_wr (int32 data) { tto_unit.buf = data & 0377; tto_csr = tto_csr & ~CSR_DONE; CLR_INT (TTO); sim_activate (&tto_unit, tto_unit.wait); return; }で,この txdb_wr() の呼び出しを探ると,以下のような手順で呼ばれているのが わかる.
(VAX/vax_cpu.c:sim_instr()) case MTPR: cc = (cc & CC_C) | op_mtpr (opnd); ↓ (VAX/vax_cpu1.c:op_mtpr()) default: WriteIPR (prn, val); /* others */ break; ↓ (VAX/vax_sysdev.c:WriteIPR()) case MT_TXDB: /* TXDB */ txdb_wr (val); break;ということでmtprという命令でTXDBというレジスタ?に書き込みすれば文字出力 できるみたい.ただ実際には単に書き込みだけではダメだった. tto_svc()が呼び出されるところを探っても,深くて追いきれなくてよくわからない.
他にも,VAX/vax_sysdev.c に cso_svc() っていう似たような出力処理があって putc()を使っていて同様にたどれるのだけど,上記TXDBのほうがコンソール出力の ようなので,とりあえずこちらは保留.
よくわからんのでNetBSD/VAXのコンソール出力処理を見てみる. NetBSDをsimhで動かすみたいは話がネット上で検索するといっぱい出てくるので, simhで動作するはずだ. TXDBというキーワードでgrepして探すと, sys/arch/vax/vax/gencons.c:gencnputc() に以下のようなのが見つかる.
while ((mfpr(PR_TXCS) & GC_RDY) == 0) /* Wait until xmit ready */ ; mtpr(ch, PR_TXDB); /* xmit character */ if(ch == 10) gencnputc(dev, 13); /* CR/LF */どうやらTXCSのRDYビットというのを見張って,それが立ったらTXDBに書き込んで いいみたい.RDYはおそらくreadyの意味で,レディービットなのだろう.
ということで以下のような待ち合わせ処理をすることで,文字出力ができた. VAXのアセンブラは慣れないので,while (!(TXCS & RDY)) {} みたいなCのソースを コンパイル・逆アセンブルして出てきたアセンブラコードを参考にして書いてみた.
ちなみに mtpr での出力文字の設定後,TXCS を見張ってウェイトするという処理を 入れないとsimhでは出力されなかった.おそらく実際の出力前にhaltでシミュレータが 終了してしまうからだろう.なので通常はウェイト後にmtprという順番にすべきだが, 以下の例では順番が逆になっていて,これはシミュレータ向けのコードと言える.
#define TXCS_RDY (1<<7) #define TXCS 34 #define TXDB 35 .section .text .globl _start .type _start, @function _start: 1: mfpr $TXCS, %r0 bicb2 $~TXCS_RDY, %r0 tstb %r0 bneq 2f brb 1b 2: mtpr $'W', $TXDB 1: mfpr $TXCS, %r0 bicb2 $~TXCS_RDY, %r0 tstb %r0 bneq 2f brb 1b 2: halt ret
このままでは関数呼び出しを行うと正常に動作しない. 以下のようにしてスタックポインタを適当に設定してみる.
(ld.scrの.textの後に以下を追加) .stack ALIGN(16) : { . += 1024; _estack = .; } (vax.Sの_startの直後に以下のスタックポインタの設定を追加) movl $_estack, %sp
でもダメ. たぶんスタックの扱いがおかしいせいだろう.
VAX/vaxmod_defs.h では以下のような判定をしている. なので現状のld.scrを流用してスタックが.textのすぐ後ろに配置されるようにすると, スタックがROM上になってしまうみたいだ.
#define ROMBASE 0x20040000 /* ROM base */ #define ADDR_IS_ROM(x) ((((uint32) (x)) >= ROMBASE) && \ (((uint32) (x)) < (ROMBASE + ROMSIZE + ROMSIZE)))で,VAX/vaxmod_defs.h を眺めてみると,以下の定義がある. どうもゼロ番地付近をRAMとして利用できるみたい.
#define MAXMEMWIDTH 26 /* max mem, std KA655 */ #define MAXMEMSIZE (1 << MAXMEMWIDTH) /* max mem size */ #define MAXMEMWIDTH_X 29 /* max mem, KA655X */ #define MAXMEMSIZE_X (1 << MAXMEMWIDTH_X) #define INITMEMSIZE (1 << 24) /* initial memory size */ #define MEMSIZE (cpu_unit.capac) #define ADDR_IS_MEM(x) (((uint32) (x)) < MEMSIZE)ということで,ld.scrでスタックを 0x00004000 付近に移動したら正常に 関数呼出しできた.
で,作ってみたアセンブラ短歌は以下のようなもの.VAXの3文字が出力される.
#define TXCS_RDY (1<<7) #define TXCS 34 #define TXDB 35 .section .text .globl _start .type _start, @function _start: .word 0x0101 movl $_estack, %sp calls $0x0, startmain halt ret .globl startmain .type startmain, @function startmain: .word 0x0000 nop /* fall through */ .globl main /* no specify type for ignore top word */ main: /*5*/ movzwl $'V', %r0 /*7*/ calls $0x0, putc /*5*/ movzwl $'A', %r0 /*7*/ calls $0x0, putc /*7*/ mtpr $'X', $TXDB /* fall through */ .globl wait .type wait, @function wait: .word 0x0101 /* dummy bitmap as nop for fall through */ 1: /* while (!(TXCS & RDY)) {} */ mfpr $TXCS, %r0 bicb2 $~TXCS_RDY, %r0 tstb %r0 bneq 2f brb 1b 2: ret .globl putc .type putc, @function putc: .word 0x0000 mtpr %r0, $TXDB calls $0x0, wait retポイントは以下.
% /usr/local/cross/bin/vax-netbsdelf-objdump -w -d vax.x ...(中略)... 20040015 <main>: 20040015: 3c 8f 56 00 50 movzwl $0x0056,r0 2004001a: fb 00 ef 23 00 00 00 calls $0x0,20040044 <putc> 20040021: 3c 8f 41 00 50 movzwl $0x0041,r0 20040026: fb 00 ef 17 00 00 00 calls $0x0,20040044 <putc> 2004002d: da 8f 58 00 00 00 23 mtpr $0x00000058,$0x23 ...(後略)...うーん美しい.
ビルドして以下で実行できる.
~/asm>% vax VAX simulator V3.8-1 sim> load -r vax.bin sim> boot cpu VAX HALT instruction, PC: 20040011 (RET) sim> quit Goodbye ~/asm>%ちなみに実行前に以下をすることで,telnetで接続してそっちに出力することも可能.
sim> set console telnet=12345