ラベル Z8001 の投稿を表示しています。 すべての投稿を表示
ラベル Z8001 の投稿を表示しています。 すべての投稿を表示

2023年11月22日水曜日

UNIX V7 カーネル 

 ブートはどうにかなってきたので、そろそろカーネル本体に手を出していきたいのですが、どこから手を付けていいのか、正直途方に暮れています。

カーネルのコードは /usr/sys/ 以下にあり、/usr/sys/conf/ で make すれば、カーネルがビルドできるようです。その中の makefile を見る限るでは、l.s, mch.s, c.c あたりから見ていけば良さそうです。


この辺りは、カーネルのか解説本を見てもあまり参考になりません。解説本には、メモリ管理やプロセス管理のコードの説明がされていますが、移植を前提とした解説ではないので。この辺りは、PDP-11のハードに依存したコードが多いのも難しいところです。

暫くこの辺りのコードを眺めながら、突破口を探すことにします。

2023年7月29日土曜日

Z8001MBex その1

新しくZ8001CPUボードを作製しました。今までは、ラッピングワイヤを直接はんだ付けして配線していましたが、今回初めて、ユニバーサル基板にピンヘッダをたてワイヤラッピングで配線してみました。この方法は、ピンヘッダとICソケットとの間を配線するのが単調で根気がいります。とくにPLCCソケットは苦痛でした。ラッピングでの配線はやっていて楽しいのですが、間違いに気づいたときは修正が大変です。ほどきたい配線の上にすでに配線されていると、それもほどかないといけません。一度ほどくとワイヤが傷んでしまっているので、2回目巻きなおすと折れたりするので気を使います。年で眼も弱ってきているので、そろそろPCBが作れるようにならないとだめですね。


今回のボードは、MMUを搭載しCP/M-8000以外のOSも移植することを考えています。初代のボードでは、Z8010 MMUを使ってメモリー管理ができるように考えていたのですが、何故かうまく動作せず。結局、CPLDでセグメントアドレスを変換するロジックを組んで、CP/M-8000が動作するようにしました。しかし、この方法ではアドレス変換が固定で融通が利かず、CP/M-8000以外のOSを動かすのは難しくなります。

新しいボードでは、Z8010を使うことは諦めて、MMUを自前で組むことにしました。これに先立って、Z8002 CPUを使ったボードで、MMUを自作できそうか簡単な回路を組んで試してあったのですが、実際に作ってみると配線量の多さからかなり苦戦しました。MMU以外は、最初のボードとほぼ同じです。

自作MMUの仕様は下のようになっています。

  • 高速SRAMを使ってページテーブルを構成する
  • ページテーブルをメモリ空間に置く
  • ページサイズ 2Kバイト
  • 論理アドレス空間 1Mバイト(16セグメント)
  • 物理アドレス空間 1Mバイト
  • 命令とデータ空間を分離可能
  • システムとノーマルで空間を分離可能
手持ちで256kビットの高速SRAMを持っていたので、これを使ってページテーブルを構成しました。試験的に作ったZ8002ボードでは、I/O空間にページテーブルを置いたのですが、Z8001ではメモリ空間に置くことにしました。ページテーブルはSRAMの一部しか使わないので、未使用の部分が通常のメモリとして使えます。
また、メモリ空間だとブロック転送命令が使えるので、ページテーブルのコピーや移動が短いコードで行なえます。

ページサイズとアドレス空間のサイズは適当に決めたもので、これが最適なのかはわかりません。欲張ると配線量が増えるので、自作するには、これ位がちょうど良いかなと思っています。

命令とデータ空間の分離は、CP/M-8000を動かすためには必要な機能です。また、Z8001には8086のように8ビット命令がないため、コードが大きくなりがちです。64kバイトのセグメントサイズに縛られるZ8000では、データを別空間に置ける機能はあったほうが良さそうです。

システムとノーマルで空間を分離する機能は、UNIXのようなOSを移植するために付けておきました。一応、CP/M-8000でも使われているようなのですが、役には立っていないようです。

今回は次のような機能は見送っています。
  • ページを無効に設定できる
  • ページを書き込み禁止に設定できる
  • ページにアクセスがあったか記録する
最初の2つは、あとからでも実装できると思います。しかし、ページアクセスの記録は、別のメモリを積むか、高速SRAMにアドレス変換後に書き込む回路を追加する必要があり、実現が難しいので諦めています。

すでに、CP/M-8000の移植は完了しています。初代のボードとほとんど同じなので、MMUの初期化を追加し、初代のボードと同じセグメントアドレス変換になるよう合わせれば動きました。

そして今、UNIX V7を移植しようと挑んでます。

2022年8月11日木曜日

ZCCの注意点

YouTube に ZCCで "Hello, world" する動画を上げました。

"Hello, world" under CP/M-8000

 ZCCを使ってみて気づいた注意点を上げておきます。なにせK&R時代の古いCコンパイラなので、今の感覚でコードを書くと、思わぬところでエラーの嵐に遭遇します。

・=- に注意

 「x=-1」 は、「x = -1」ではなく「x -= 1」に解釈されます。コンパイル時にwarningがでますが、オブジェクトファイルが出力されリンクも正常にできるので注意が必要です。実行してみて意図した動作をしないことで気づきましたが、理由がわからずしばらく悩みました。 かなり古い(K&R以前?)の書き方のようです。

・関数のプロトタイプ宣言に引数の型を含めない & 戻り値がなくても int で宣言

 これもかなりハマりました。次のように宣言します。

         int foo(a, b);

         foo(a, b) int a, b;
         {
            /* 戻り値なしの処理 */
         }

・unsigned char が使えない?

 unsigned int は使えるのですが、unsigned char はサポートしていないようです。

・関数名、ラベル名、変数名は8文字まで 

 Cコンパイラは名前の前に "_" をつけるので、実質使えるのは7文字です。それ以上の文字は無視されるので、気を付けないと違う名前を付けたのに同じと判別されてしまいます。アセンブラとリンカが8文字までしかとらない仕様なので、これは仕方ありません。

・コンパイラの -D オプションが働かない

 これは、The Unofficial CP/M Web Siteに書かれていました。試したところ本当に働きません。ヘッダファイルに、#define で定義しておく必要があります。

・コンパイラのオプションがわからない

 ZCCのドキュメントが失われているようで、コンパイラーに指定できるオプションがわかりません。今のところわかっているのは、上の働かない-Dぐらいです。CP/M-68KのCコンパイラにはドキュメントが残っているようなので、参考になるかもしれません。

・改行コードと終端コードに注意

 改行コードがCR+LFで、テキストがEOFで終端されていないとエラーに悩まされます。これはZCCというよりCP/Mの仕様なのですが、意味のわからないエラーメッセージがでるので悩まされます。CP/M上でコードを編集する場合は大丈夫ですが、LinuxやWindows上で編集している場合に問題になります。改行コードは指定しておけば問題ないのですが、末尾にEOFをいれてくれるエディターはないようなので、意図的に入れる必要があります。VimだとCtrl+Zで入れられます。EOFのあとにテキストが入っていても無視されます。

癖がわかってくると、ZCCは結構使える気がします。アセンブリ言語で書くよりは格段に楽ですから、多少の制約は我慢できます。

2022年8月1日月曜日

CP/M-8000にFPUエミュレーションを追加

 CP/Mのライセンスに変化があったのと、夏休みに入って時間ができたこともあり、CP/M-8000に手を入れています。その一番目として、やり残していたFPUエミュレーションをCP/Mにリンクして使えるようにしました。

作業としては、fpe.o と fpedep.o をXOUTからCOFFに変換し、biostrap.sを書き換えるだけですんだので拍子抜けするほど簡単でした。fpe.oはZilogが出荷予定だった実数演算コプロセッサのZ8070をエミュレートするもので、Z8070の命令がくると例外処理で実数演算をソフトウェアで実行します。

これで、ZCC(ZilogのCコンパイラ)で、floatが扱えるようになりました。ためしに、レトロCPU界隈ではメジャーなマンデルブロ集合のASCIIARTをC言語に書き換えて実行してみました。コードは下のような感じです。


これをコンパイルして実行したのが下の結果です。

見慣れたアスキーアートが出ているので、問題なく動作しているようです。ただし実行時間は遅く、7分44秒とかなり期待はずれな結果です。結果はともかく、実数演算がC言語で使えるようになっただけで可能性は広がります。試せる人はかなり限られると思いますが、この成果はGitHubのリポジトリに反映しました。

今後は、開発環境を改善していく予定です。私にはCP/M標準のEDが全く馴染まないので、まずはエディタをどうにかしようと検討中です。

2021年12月27日月曜日

Z8K CP/Mボード その2

こんなものを試す人はかなりのハッカー基質の人だと思い、Githubの説明はあまり親切でないのと、英語があまりにひどいので、CP/M-8000を起動させるための補足をしておきます。

準備

作業はDebian系のディストリビューションで行うことを想定しています。ATMEGAのコードをビルドし書き込むために、gcc-avr, avr-libc, avrdude をインストールしておきます。Z8000のコードをアセンブルするために、GNUのサイトからbinutils をダウンロードして、 Z8001クロス開発環境 binutilsをビルドしてみるを参考にビルドしてください。私が確認しているは使っているのは、binutils-2.34 です。

ATMEGA164のヒューズビット設定 

Z8001MBと同じくZ8K CP/Mでは、Z8001を起動するためにATMEGA164Pが使われていますが、JTAGの禁止と外部クロック入力にするためヒューズビットの書き換えが必要です。値は、LFUSE : 0xE0, HFUSE : 0xD9 です。ATmega164PのJTAGを禁止するを参照し、avrdudeを使って書き換えてください。

マシンモニタの書き込み

z8kboot ディレクトリでmakeを実行してください。z8kbooter.elf が作られます。 チップへの書き込みは、make write で実行できます。

CP/M-8000のビルド

COFFに変換済みのcpmsys.o と libcpm.a が入れてあるので、cpm8kディレクトリで、makeするだけです。cpm8k.bin が作られます。

CP/M-8000のディスクイメージの作成

cpmtoolsをインストールし、/etc/cpmtools/diskdefsにcpm8kディレクトリにあるdiskdefsを追加します。diska/b/c/d ディレクトリにディスクイメージに含めたいファイルを入れて、make dskimg を実行します。disk.img   ができるので、dd でCFに書き込めばOKのはずです。ディスクイメージに入れるCP/M-8000のコマンド類は、 http://www.cpm.z80.de/download/cpm8k11.zip を解凍すると得られるDISKn.ZIPに含まれています。

こんな状況になると予想しておらず、慌てて書いたので間違っているかもしれません。

2021年12月26日日曜日

Z8K CP/Mボード

 私がGithubにあげているZ8001MBの回路図をもとに、tomi9さんがCP/M-8000を動作させることができるPCBを作成され、一枚送ってくださいました。


PCBだけでなくCFのソケットとGALも同封されており、あとは手持ちの部品だけで行ける状態でした。tomi9さん、ありがとうございます。自分もいつかはPCBを作ってみたいとは思っているのですが、なかなか勇気が出ません。ラッピングワイヤーでの手配線だと、すぐに回路の修正がききますが、PCBだとそう簡単にはできません。

久しぶり(中学高の技術の授業以来?)のPCBでの組み立てで、ちょっと一投目は緊張しましたが、最難関のCFソケットのはんだ付けを済ませ、Z8536以外の部品を組みつけて、現在、動かせるところまで来ています。

2020年9月30日水曜日

Z8530 SCC

 Z8530 SCCを制御するコードも上げておきます。

先ずは初期化のコードです。送受信の割り込みが処理できるように初期化しています。この石もかなり曲者で初期化手順を間違うとうまく動きません。

発生させる割り込み込みベクタは0x10からで、チャンネルAの送信が0x18で、受信が0x1cになります。

	.equ	SCCAC, 0x0009
	.equ	SCCAD, 0x000D

init_scc:
	ld	r2, #(scccmde - scccmds)	! initialize Z8530
	ld	r3, #SCCAC
	ld	r4, #scccmds
	otirb	@r3, @r4, r2
	ret

scccmds:
	.byte	9, 0xc0		! Reset
	.byte	4, 0x44		! x16, 1stop-bit, non-parity
	.byte	3, 0xe0		! Receive  8bit/char, rts auto         
	.byte	5, 0xe2		! Send 8bit/char, dtr rts

	.byte	2, 0x10		! Interrupt vector
	.byte	1, 0x12		! Interruprt on Rx All character and Tx Int
	.byte	9, 0x09		! MIE, VIS, Status=Low 

	.byte	11, 0x50	! BG use for receiver and transmiter
	.byte	12, 30		! 4800bps at 5MHz clock
	.byte	13, 00
	.byte	14, 0x02	! PCLK for BG
	.byte	14, 0x03	! BG enable

	.byte	3, 0xe1		! Receiver enable
	.byte	5, 0xea		! Transmiter enable

scccmde:

割り込みハンドラーは、次のようなコードです。リングバッファ処理部分は端折ってあります。Z8536と同じで割り込み処理の終了処理を正しく行わないと、優先順位の低い割り込みがかからなくなったりします。

scc_rxint:				! Rx InterruptHandler
	push	@r15, r0
	push	@r15, r1
1:	
	inb	rl0, #SCCAC
	andb	rl0, #0x01
	jr	z, 1b 
	inb	rh0, #SCCAD		! Read a data from Rx buffer
	
    !
	!
    
	ldb	rl0, #0x38		! Enable IUS
	outb	#SCCAC, rl0
	pop	r1, @r15
	pop	r0, @r15
	iret

scc_txint:				! Tx Interrupt Handler
	push	@r15, r0
	push	@r15, r1

	!
    !
    
	outb	#SCCAD, rl0 ! Write a data to Tx buffer
	
	ldb	rl0, #0x28		! Clear Tx interrupt pendinng 
	outb	#SCCAC, rl0
	ldb	rl0, #0x38		! Enable IUS
	outb	#SCCAC, rl0
	
    pop	r1, @r15
	pop	r0, @r15
	iret

2020年9月12日土曜日

Z8536 CIO

 Z8536の初期化手順と割込み処理のメモです。カウンタタイマ3で10mS周期の割り込みをかけます。

Z8536にはRESETピンがなく、ハードウェアリセットはRDピンとWRピンを同時にLにすることで行います。今回はハードウェアを手抜きしているので、ソフトウェアでリセットしています。A0, A1ピンをHにしてリードし、つづいて、レジスタ0に1を書き込んだのちレジスタ0に0を書き込みます。これで初期化されます。

レジスタのアクセスは、A0, A1ピンをHにしアクセスするレジスタ番号を書き込み、つづいて目的のレジスタに書き込みか読み込みをします。Z8536を初期化するのにレジスタ設定の順番を変えたりすると、なぜか動かなくなったりします。正しい手順はよく理解できていないのですが、手探りで見つけた手順が下のコードです。


	.equ	CIOPRTC, 0x0011
	.equ	CIOPRTB, 0x0015
	.equ	CIOPRTA, 0x0019
	.equ	CIOCTRL, 0x001d


init_cio:
	ld	r1, #CIOCTRL
	inb	rl0, #CIOCTRL
	lda	r2, ciocmds
	ld	r3, #(ciocmde - ciocmds)
	otirb	@r1, @r2, r3
	ret

ciointr:
	push	@r15, r0	! レジスタ保存
	push	@r15, r1

	! 割り込み処理
	!
	!
	
	ldb	rl0, #0x0c
	outb	#CIOCTRL, rl0
	ldb	rl0, #0x24
	outb	#CIOCTRL, rl0	! Clear IP and IUS
	
	pop	r1, @r15	! レジスタ復帰
	pop	r0, @r15
	iret

!------------------------------------------------------------------------------

ciocmds:
	.byte	0x00, 0x01		! Reset
	.byte	0x00, 0x00		! Clear reset
	.byte	0x01, 0x00		! Reset PortC and C/T3
	.byte	0x01, 0x10		! Enable PortC and C/T3
	.byte	0x1e, 0xc2		! Continuous, Ext Output, Square wave
	.byte	0x1a, 0x30		! Set timer constant
	.byte	0x1b, 0xd4		! Set timer constant
	.byte	0x00, 0x84		! MIE and VIS
	.byte	0x04, 0x00		! Set Interupt vector
	.byte	0x0c, 0xc0		! Set Interupt Enable bit
	.byte	0x0c, 0x06		! Gate and Triger
ciocmde:


割り込み処理は、最後にレジスタ12のIPとIUSをクリアして戻れば、続けて割り込みが発生します。これを忘れると、割り込みが単発になったり、他のチップからの割り込みが発生しなくなります。

Z8530もそうでしたが、Zilogの周辺チップはプログラムが難しい印象です。さらにデータシートが不親切で、読んだだけでは理解できないことが多々あります。ネット上にもあまり情報がないので、手探りがで動かすことになるのですが、動いた時の高揚感は格別です。

2020年5月16日土曜日

CP/M-8000公開

Z8001ボードにインストラクションとデータのメモリ空間を分離する機能の追加と、CP/M-8000のBIOSの対応を行いました。これで、アセンブラやCコンパイラなど、すべてのコマンドが使えるようになっています。


回路図とBIOSのコードをGitHUbに上げました。

https://github.com/4sun5bu/Z8001MB

今後、CP/M-8000を移植したい人が現れるかはわかりませんが、役に立つと思います。

2020年4月29日水曜日

CP/M-8000の移植とその先へ

暫くCP/M-8000のBIOSを書いていたのですが、ほぼ終わりました。
現在、CFに書き込んだCP/Mのディスクイメージから起動できるようになっています。コマンドも実行可能で、下はSTATを実行した例です。


CP/Mのことをほとんど知らないのに移植に挑んだのが原因ですが、Disk I/Oの部分を書くのにかなり手間取りました。いかに役に立ったサイトのリンクを載せておきます。
  • John Elliott氏のCP/M Main Page : CP/M全般の情報が集められています。ファイルシステムの構造やBIOSの情報が役に立ちました。
  • Grant Searle氏のGrant's homebuilt electronics : CP/MのドライブをCFに対応させるアイデアや、ディスクフォーマットの決め方を参考にさせてもらいました。その他の情報も盛りだくさんです。
  • neko Javaさん?のCP/Mコーナー : CP/Mのディスクイメージの作成方法の情報があります。

ここまでで「CP/M-8000の移植はできた」と言っていいとは思うのですが、実は問題があります。トランジェントコマンドの多くが、インストラクションとデータのメモリ空間が分けられるハードウェアを要求することです。
Z8001は8MBのアドレス空間を持ってはいますが、64kBのセグメントに分割されています。コマンドはノンセグメンテッドのコードで書かれているため、利用できるメモリー空間が64kBに制限されてしまいます。それを補うためインストラクションとデータとを別々の空間に置き、128kBまで扱えるようにしているのです。

今回作ったCPUボードでは空間の分離には対応していないので、殆どのコマンドが実行できません。EDやASすら実行できなので、テキストを入力してアセンブルすることすらできず正直何もできません。

動かないコマンドがあることは、移植を開始した早い段階で気づいていましたが、「移植できるのか」という技術的なことにしか興味がなかったので目をつむっていました。せっかくここまで来たので、ハードウェアを改造してインストラクションとデータ空間を分けられるようにしていきます。

2020年4月19日日曜日

CP/M-8000 目覚める

ここ暫くCP/M-8000のBIOSの実装を続けてきたのですが、やっとプロンプトが表示されるところまで来ました。まだプロンプトの表示が変で、謎の"1#"が表示されてはいますが、ビルトインコマンドをタイプすると実行できることを確認できました。
完全にBIOSが実装できておらず、特にディスクI/Oがまだ未実装なので何もできませんが、ゴールデンウィークを中に実装を進めるつもりです。


The Unofficial CP/M Web site には、「CP/M-8000を移植するには、CP/Mが動作するOlibetti-M20上でビルドすることを検討しろ」と書かれていたのですが、オブジェクトファイルをCOFFに変換するツールができたことで、LinuxやWindows上でクロス開発できるようになりました。LinuxやWindows上では高機能な開発環境が使えるので、作業がかなり楽になります。
このあと、私以外にCP/M-8000を移植しようとする人が現れるのかはわかりませんが、ものずきな人のために情報をまとめて公開していくつもりです。

2020年3月14日土曜日

XOUT to COFF 変換

ここ暫く作業を続けていた、CP/M-8000のXOUTファイルをCOFFに変換するツールができたので、まとめてGitHubで公開することにします。

xoutulis (GitHub)

当初、いつものようにCで書いていたのですが途中で行き詰ってしまい、気分転換と自分の勉強を兼ねてGoで書き直しました。Goでかいた最初のアプリになるので、かなり変なことをしているかもしれませんが、とりあえず動きます。

公開するツールは、XOUTからCOFFに変換する xout2coff と、ライブラリファイルをXOUTファイルに分割する xarch です。

使い方は単純で、GOPATHをxoututils/ に通して、そのディレクトリで、
$ go build xout2coff
$ go build xarch
とコマンドを打つと、それぞれビルドされて xoututils/ にコマンドができます。
$ xarch libXXX.a
で、ライブラリの展開。

$ xout2coff XXX.rel
で、COFFへのファイル変換ができます。

これで、CP/M-8000をLinuxやWindows上でクロス開発できるようになりました。今はOlivetti M20用のBIOSを読んで、自作Z8001ボード用に書き直す作業に入っています。

2019年10月23日水曜日

Z8k-COFF フォーマット 3

リロケーション情報

Z8k-COFFで拡張され、offsetとstuffが追加されている。
変数名はBFDのコードに合わせたが、用途は不明。
リロケーション情報は、r_vaddrが昇順になるよう並べないと、リンカがsegmentation fault を起こす。

 r_vaddr  (4 bytes)
  リロケーションを行う箇所のセクション先頭からのアドレス。

 r_symndx (4 bytes)
 シンボル番号


 offset (4 bytes)
 オフセット+シンボル中の e_value が、リロケーションを行う箇所の値になる。

 r_type (2 bytes)
 アドレスのタイプ。確認できているのは次の6種類。
 外部シンボルをPC相対で参照するケースがあるのか分からないが、
 アセンブリ言語でコードを書いてobjdumpで確認すると、これらのタイプが使われている。
 内部シンボルへのPC相対アドレスは解決できるので、リロケーション情報にはない。 
 16bitの短縮セグメントアドレスがあるかは不明。

 0x01 - 16bit オフセットアドレス (ノンセグメント)
 0x02 - 8bit PC相対 ( JR )
 0x04 - 16bit PC相対 (LDA, LDAR)
 0x05 - 12bit PC相対 ( CALR )
 0x11 - 32bit セグメントアドレス
 0x25 - 7bit PC相対 ( DJNZ )

 セクションデータ中のr_vaddrが指すアドレスには、各r_typeごとに次の値が書き込まれている。 
 
 0x01 : セクション先頭からの16 bitアドレス 
 0x02 : セクション先頭を指すPC相対アドレス (v_addrが指す次のアドレスが基点)
 0x04 : 0x0000が書かれている。
 0x05 : セクション先頭を指すPC相対アドレス (v_addrが指すアドレスが基点)
 0x11 : セクション先頭からのセグメント形式でない32bitアドレス
 0x25 : 何を指しているのか不明 

 stuff (2 bytes)
 不明 0x5343 "SC" が書かれている。

XOUTとCOFFの両フォーマットがなんとなく理解できたので、これで先に進めます。
まずはシンボルテーブルあたりから変換できそうか試してみるつもりです。

Z8k-COFF フォーマット 2

シンボルテーブル

シンボルが定義されたテーブル。
シンボル値は、シンボルクラスによって意味が変わったりするので複雑。

 e.e_name 
 シンボルネーム。8文字に満たない場合は、残りを0で埋められている。
 8文字の場合は、0で終端していない。
 先頭の4byteが0の場合、次の4byteは文字列テーブルへのオフセットになっている。

 e_value
 シンボルの値。e_scnume_typeによって意味が変わる。

 e_scnum
 シンボルが属しているセクション番号。セクションテーブルの先頭セクションは1になるので注意。
 0以下の値は特別の意味を持つ

  0 : 外部シンボル
 -1 : 即値 アセンブラで .equ 定義した値など、再配置で変化しない値
 -2 :  デバッギングシンボル

 e_type
 シンボルの型を表すことになっているが、オブジェクトファイルを見ると何故かいつも0x0000。
 実行ファイルには、何か値が入っている。
 e_sclass
 シンボルクラス。 DJGPP COFF SPEC の Symbol Table を参照 

 e_numaux
 現在のエントリに続く、補助エントリの数。シンボルクラスに応じて補助エントリの内容が変わる。

補助エントリ
 セクションクラスがファイル名とセクション定義の補助エントリは確認されるが、その他は不明。
 逆アセのスス乂の5.5.5 補助形式5:セクション定義を参照。

文字列テーブル

シンボルテーブルの後に続く、0で終端された文字列の配列。長いシンボル名などが収められる。
先頭4byteは、このテーブルのサイズ(先頭4byteを含む)。

2019年10月22日火曜日

Z8k-COFF フォーマット 1

つづいてZ8k-COFF

COFFの資料はネット上にいくらでも転がっているので、理解は楽だろうと踏んでいたのですが、x86のものや、WidowsのPEフォーマットのものばかりで、Z8000のCPU依存に関する情報はほとんどありません。
それでも基本構造は同じなので、機種依存はGNUのBFDライブラリのソースコードを参照しながら進めています。

参考にした情報のリンクを上げておきます。
 DJGPP COFF Spec
 逆アセのスス乂
 BFDソースコード

gccとasでテスト用のオブジェクトファイルを作り、バイナリエディタとobjdumpで変化を確認していく地道な作業を続けており、Z8000依存で判明した部分や忘れてしまいそうな事などをメモ代わりに書いていきます。変数名は、DJGPPのドキュメントに合わせてあります。

Z8k-COFF フォーマット

次の7パートからなっている。
 ・ ファイルヘッダ
 ・ オプショナルヘッダ
 ・ セクションテーブル
 ・ セクションデータ
 ・ リロケーション情報
 ・ シンボルテーブル
 ・ 文字列テーブル 

ファイルヘッダ

ファイルの全体的な構造が記されている

 f_magic 
 0x8000 - Z8001/2の識別はflagsによる。

 f_flags
 0x1000 - Z8001 セグメンテッドコード
 0x2000 - Z8002 非セグメンテッドコード 
 0x0200 - 不明 (ビッグエンディアンかも)
 0x0008 - ローカルシンボル情報なし 
 0x0004 - ライン番号情報なし
 0x0002 - 実行形式
 0x0001 - リロケーション情報なし

オプショナルヘッダ

実行ファイルの場合のみ付き、実行ファイルに必要な付加情報が入っている。
下の2つは詳細不明。

 magic
 0x0000 

 vstamp
 0x0000 

セクションテーブル

セクションの情報が記されたセクションヘッダの配列。

 s_flags
 0x80 - 初期地を持たないデータ(BSS)
 0x40 - 初期値をもつデータ
 0x20 - 実行コード
 

2019年10月19日土曜日

XOUTフォーマット 4

つづき

シンボルテーブル 12 byte

参照されるシンボルの情報(x_sym) の配列。

 x_sy_sg (1 byte)

 シンボルが含まれるセグメント番号(セグメント情報のインデックス)
 255の場合は、即値か外部参照を示している。

 x_sy_fl (1byte)

 シンボルのタイプ

 1 - ローカルシンボル (デバッグ用)
 2 - 未定義の外部シンボル (参照先が外部)
 3 - グルーバルシンボル (外部から参照可能なシンボル)
 4 - セグメント名

 x_sy_val (2 byte)
 
 シンボル値。セグメント番号とタイプによって、値の意味が変わる。
 
    セグメント番号 : 0-127, タイプが1, 3, 4の場合は、セグメント内でのオフセットアドレス
 セグメント番号 : 255, タイプ : 1 の場合は、16bit即値
 セグメント番号 : 255, タイプ : 2, 値 : 0 の場合は外部シンボル
 セグメント番号 : 255, タイプ : 2, 値 : 0 以外の場合は、BSSに確保するメモリースペース
 

 x_sy_name (8 byte)

 8文字のシンボル名
 8文字に満たない場合は、0で埋められる。8文字の場合は、0で終端されないので注意。

補足
コンスタントプール内のシンボルはローカルで、必ずしもシンボルテーブルに登録されない。
引数の文字列などはラベルがないので、リロケーション情報があるだけ。

セグメント番号がBSSを指すのは、明示的にBSSセクションを指定した場合のみらしい。
初期値のないグローバル変数は、セグメント番号 : 255, タイプ : 2 になっており、
複数のオブジェクトで定義されている場合は、その中から最大値がBSSに確保される。

XOUTフォーマット 3

つづき

再配置情報 6 byte

コードとデータ中にあるアドレス参照している箇所の情報 (x_rel) の配列。
リンカは、コードが配置されるアドレスとシンボルテーブルをもとに、そのアドレスを書き換える。

 x_rl_sgn (1 byte)

 アドレス参照箇所が含まれるセグメント番号 
 Z8001のセグメントではなく、セグメント情報のインデックスなので注意

 x_rl_flg (1 byte)

 アドレスの形式

 1 - 16 bit アドレス 
 2 - 16 bit ショート型アドレス 
 3 - 32 bit セグメント + オフセットのアドレス
 5 - 外部参照 16 bit アドレス 
 6 - 外部参照 16 bit ショート型アドレス
 7 - 外部参照 32 bit セグメント + オフセットのアドレス

 x_rl_loc (2 byte)

 アドレス参照している箇所
 セグメントの先頭からのオフセットになっている。

 x_rl_bas (2 byte) 

 ローカル参照の場合は、参照先があるセグメント番号
 外部参照の場合は、シンボルテーブルへのインデックス

ローカル参照の場合は、参照先があるセグメントの先頭アドレスと、参照箇所に書かれている値を足した値がアドレスになる。シンボルがローカルな場合はシンボルテーブルに存在しない。

XOUTフォーマット 2

つづき

セグメント情報  4 byte 

セグメントとなっているが、Z8001のセグメントとは直接関係していない。
COFFやELFのセクションに当たる。セクションをセグメントに対応させれば同じことなので、
完全に別物というわけでは無いのが紛らわしいところ。

ファイル中に含まれるセグメントの数だけ、次の構造 (x_sg) が並べられ配列になっている。

 x_sg_no (1 byte)

 セグメント番号 0 - 127か255が入る。
 Z80001のセグメントに対応していそうだが、ノンセグメンテッドのコマンドをみても、0 - 3の
 値が割り当てられており、ただの通し番号かも。 
 
 255の場合は、リンカがセグメント番号を割り当てることを示す。
 リンク可能なファイルには、この値が入っている。
 
 x_sg_type (1 byte)
 
 セグメントに含まれるデータのタイプ

 1 - 初期化されないデータ (BSS)  
 2 - スタック
 3 - 実行コード
 4 - 定数  
 5 - 初期値を持つデータ
 6 - コードとデータは混合 保護
 7 - コードとデータは混合 非保護 

 1,2は、ファイルには含まれず、メモリー上にロードされた時点で、領域が確保される。

 3は、実行コード本体。

 4は、関数への引数で渡される文字列など変更されないデータ。

 5は、初期値を持つグローバル変数や文字列。

 6,7はROMへの書き込み用らしい。

 x_sg_len (2 byte : unsigned)
 
 セグメントのサイズ

コード/データ


 ここには、命令コード、定数、初期値を持つデータが、セグメント情報の順番にまとめられている。
 各セグメントの開始位置は、ヘッダ情報から求まる。
 初期化されないデータ (BSS) とスタックは、ここに含まれない。

2019年10月14日月曜日

XOUTフォーマット 1

XOUTはCP/M-8000で使われている、実行とリンクが可能なオブジェクトファイルの形式です。
解析しているうちに頭がこんがらがってきたので、ファイル構造をメモ代わりに書いていきます。
参考にした情報は、CP/M-8000 Programmer's Guide (注 PDF)と P-CP/M-Z8k SOURCES (注 ZIP) にあるLD8Kリンカのソースコードです。

説明中の変数名は、リンカのソースコードで使われている名前をそのまま使っています。
ちょっとわかりにくい命名ですが、コードを読むときの助けになるよう、そのままにしておきます。

Z8000はビッグエンディアンなのでデータは最上位バイトから最下位バイトへの並びです。
各データ構造は2 byte境界で揃えられています。

XOUTフォーマット  

a.outフォーマットと似た構造になっており、5つのパートからなっている。
  1. ヘッダ
  2. セグメント情報
  3. コード/データ 
  4. 再配置情報
  5. シンボルテーブル

ヘッダ 16 byte

ここには、セグメントの数や各パートのサイズなどのファイルの全体的な構造が書かれている。

 x_magic (2 byte) 
 
 ファイルの形式を認識するための番号
 
 0xEE00 セグメンテッド    実行不可      
 0xEE01 セグメンテッド    実行可能
 0xEE02 ノンセグメンテッド 実行不可 
 0xEE03 ノンセグメンテッド 実行可能  命令コードとデータの空間は非共有
 0xEE07 ノンセグメンテッド 実行可能  命令コードとデータの空間は共有
 0xEE0B ノンセグメンテッド 実行可能  命令コードとデータの空間を分離 

 リンク可能なのは0xEE00か0xEE02で、セグメンテッドとノンセグメンテッドはリンクできない。
 リンカのソースでは、リンク可能な0xEE06と0xEE0Aも存在していることになっている。
 配布されているCP/Mのイメージに含まれるコマンドは、0xEE03か0xEE0Bだけで、
 0xEE01, 0xEE07は見当たらない。

 0xEE03と0xEE07の違いは不明だが、64k byteの空間に命令コードとデータを収める形式。
 マジックが0xEE07のコマンドが無いため、比較ができないので不明。
 
 0xEE0Bは、コードとデータの空間を分離して、64k byteづつの空間をそれぞれに割り当てる。
 1セグメントが64k byte の制約を緩和できる。CPUに外付けのMMUがないと実現できない。


 x_nseg (2 byte) 
 
 セグメントの数
 
 x_init (4 byte) 
 
 コード/データのバイト数

 x_reloc (4 byte) 
 
 再配置情報のバイト数
 値が0の場合は、ファイルはリンクされている。

 x_symb (4 byte)
 
 シンボルテーブルのバイト数
 値が0の場合は、ファイルからシンボルテーブルは取り除かれている。

2019年10月12日土曜日

CP/M-8000は移植できるのか リンカ 4

先週の段階では、COFF を CP/M-8000 の XOUT に変換して、CPMLDR.SYS をリンクしようとしていたが、ふと思い立って方針を変更することにした。

もし、XOUT を COFF に変換できれば、GNU Binutils の ld を使ってリンクできるし、 COFF オブジェクを XOUTに変換し直せれば、CP/Mの実行ファイルが得られるはず。

COFF to XOUT は比較的簡単そうな気がするが、逆の XOUT to COFF がややこしそう。リンカを移植した時、動かすことを優先して XOUTのフォーマットをあまり理解せずガシガシ進めたので、XOUTの解説とリンカのソースコードを頼りに試行錯誤することにする。

移植したリンカは権利が不明なので、公開できないが、これだと変換ツールのソースコードの公開が可能になる。、Linux上で作業できれば、CP/M-8000 が動く実機が無くても、物好きな人がCP/Mの移植に挑戦できるようになる。

リンカを移植するのに使った時間は無駄になるが、しょせんは趣味なので気にしないし、人生の暇つぶしににはもってこい。

ついでに。
リンカを移植していたとき、CPMLDR.REL と CPM.LIB をリンクしようとすると、Segmentation Fault が出て困っていたが原因がわかった。CPMLDR.RELの一部が壊れている。


0x251C からの 0xFF 0xFF の2 byte を 0x00 0x00 に書き換えればOK。おそらく、その前に10箇所ある 0x08 0xB6 も 0x00 0x00 に書き換えた方が良いと思うが、これは実害は無いので、そのままでも大丈夫。
 
今日、外は台風でえらいことになってる。これを書いている時点では、台風は未だ上陸はしていない。これから記憶に残る1日になるんやろか。