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日になるんやろか。

2019年10月5日土曜日

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

とりあえずCP/M-8000のリンカ ld8kは、Linux上に移植し動作するようになりました。
だれの役にも立たないでしょうが、移植のコツみたいなものを書き出しておきます。

もともとのC言語のソースコードはスタイルが古典すぎて、そのままではGCCでコンパイルできないので、まず今風に書き換える必要があります。
使われているコードのスタイルは次のようになっています。
func(a, b) int a;
{
    return(a + b);
}
  • 引数の型宣言は、関数の後ろ
  • 引数の型宣言は省略できる。型は int になる
  • 戻り値の宣言は省略できる。型は int になる
  • int のサイズは16bit
  結構ルーズな書き方も許されていて面食らうこともあります。
 
標準ライブラリも今時の標準と違うので、前後のコードと予想した動作から別のものに書き換える必要があります。
  • opena() --- テキストモードでファイルをオープン
  • openb() --- バイナリモードでファイルをオープン
  • seek() ------ ファイルシーク  
このあたりは、fopen(), fseek() で置き換えました。
 ファイルをオープンするモードや、シークポジションの基点などは、コードから推測して指定します。

Z8000がビッグエンディアンなので、スモールエンディアンと相互に変換するようコードに書き換えていく必要があります。これは、オブジェクトファイルから読みだす時と書き出す時に必要になります。
例えば、
unsigned int a;
read(infile, &a, 2);
上のような、ファイルから16bit値を変数 a にロードするコードは、下のように書き直します。
unsigned int a;
unsigned char buf[2];

fread(buf, 2, 1, infile);
a = buf[0] * 256 + buf[1];

CP/M-8000のCコンパイラは、関数名や変数名に8文字までしか使えないようで、命名が短縮され過ぎています。当時、短い名前は当たり前だったのでしょうが、名前から役割を推測しづらく、読み解くのは結構きついです。

なんやかんやでリンカは移植できたのですが、もとのCP/Mのライセンスの扱いがよくわからないので、コードは公開できないでしょう。 コードも分かりづらいし公開できるよう、できれば完全に書き直したいのですが、とりあえず先に進みます。

次の段階は、BIOSを書くことです。参考になるOlivetti M20用のBIOSのソースコードがあるのですが、アセンブリ言語とC言語で書かれており、CP/M-8000が動く環境がないとリビルドできません。なので、クロス開発することになるのですが、残念ながら XOUT フォーマットを吐いてくれる開発ツールは存在しないようです。GNU Binutils のクロスアセンブラは COFF を吐いてくれるので、これを XOUT に変換してリンクすることにします。

ということで、次はオブジェクトファイルの変換ツールを作ることが目標になります。

先ながいなぁ…