付録F
.troオブジェクト・ファイルの形式
TATARAが書き出し、TANRENが読むオブジェクト・ファイルの拡張子は.troです。この付録では、そのファイルに何が入っていて、どのように並んでいるかを説明します。これを読み書きするプログラムを作りたい人や、TANRENの/Rが何を表示しているかを知りたい人のためのものです。説明するのは、バージョン1.2.0のTATARAとTANRENが使う、形式のバージョン2です。まずオブジェクト・ファイルが何を伝えなければならないかを説明し、次に実際のファイルを1バイトずつたどり、最後にリファレンスを示します。
F.1 オブジェクト・ファイルが伝えること
2章で、アセンブラーだけではプログラムを完成できない理由を説明しました。リンカーがそれぞれの部分をどこに置くかも、ほかのモジュールが定義する名前のアドレスもわからないからです。そこでオブジェクト・ファイルは、自分のモジュールについて次の4つのことをリンカーに伝えます。
- 部分 バイトを置くセグメント、それぞれのバイト数、そしてバイトそのもの。
- 提供する名前 公開するそれぞれの名前と、その値。値はあるセグメントの中のオフセットか、ただの数です。
- 必要な名前 それぞれの外部の名前。
- 穴 バイトの中の、セグメントを置く場所や外部の名前によって値が決まるすべてのワード。
穴は空のままにはなりません。ワードには値のうちすでにわかっている部分、つまりアデンド(addend)が入っていて、別のフィックスアップ(fixup)が、それに何を足すかを示します。フィックスアップは自分では数を持たず、何を足すかを示すだけで、数はいつもワードに入っています。countがデータ・セグメントのオフセット0000hにあるとき、ld hl,countのワードは0000hで、フィックスアップは「データ・セグメントのアドレスを足す」と示します。ld hl,count+2では、ワードはcountのオフセットに2を足した 0002hで、フィックスアップは同じです。外部の名前を呼び出すワードは 0000hで、そのフィックスアップは「その名前の値を足す」と示します。TANRENはバイトを所定の場所にコピーし、そのあとで、それぞれのフィックスアップが示すものを足します。
これらはどれもレコードとして保存されます。レコードは、タイプのバイト、そのあとに続くものの長さ、そして内容からなります。ファイルを読むプログラムは、必要のないレコードを長さだけで読み飛ばせます。
F.2 例
次のモジュールには、上のものが1つずつ入っています。
; OBJ.AS - a module with one of each thing an object file holds. public start,count extrn putc cseg start: ld hl,count ; a word in the data segment ld a,(hl) call putc ; an external ret dseg count: db 3 dseg scratch,transient group one buf1: ds 4 group two buf2: ds 2 end start
tatara obj.as obj.troで169バイトのファイルができます。図F.1はそれをレコードの並びとして示し、表F.1は1バイトずつたどります。オフセットとバイトは 16進数で、文字列のバイトはその文字で示します。
| オフセット |
バイト |
意味 |
| 00 |
54 52 4F 1A 02 |
ヘッダー:TRO、1Ah、バージョン2 |
| 05 |
01 05 00 |
MODNAME、5バイト: |
|
00 03 "obj" |
||
| 0D |
02 04 00 03 "one" |
GRPDEF:グループ00はone |
| 14 |
02 04 00 03 "two" |
GRPDEF:グループ01はtwo |
| 1B |
03 07 00 |
SEGDEF、7バイト:セグメント00 |
|
00 FF 08 00 02 " C" |
コード、グループなし、8バイト、デフォルトのコード・セグメント |
|
| 25 |
03 07 00 |
SEGDEF:セグメント01 |
|
01 FF 01 00 02 " D" |
データ、グループなし、1バイト、デフォルトのデータ・セグメント |
|
| 2F |
03 0C 00 |
SEGDEF:セグメント02 |
|
03 FF 00 00 07 "scratch" |
トランジェントなデータ、グループなし、0バイト:セグメントそのもの |
|
| 3E |
03 0C 00 |
SEGDEF:セグメント03 |
|
03 00 04 00 07 "scratch" |
トランジェントなデータ、グループ00、4バイト |
|
| 4D |
03 0C 00 |
SEGDEF:セグメント04 |
|
03 01 02 00 07 "scratch" |
トランジェントなデータ、グループ01、2バイト |
|
| 5C |
05 05 00 04 "putc" |
EXTDEF:外部0000はputc |
| 64 |
04 09 00 |
PUBDEF、9バイト: |
|
01 00 00 05 "count" |
セグメント01、オフセット0000h:count |
|
| 70 |
04 09 00 |
PUBDEF: |
|
00 00 00 05 "start" |
セグメント00、オフセット0000h:start |
|
| 7C |
06 0B 00 |
DATA、11バイト: |
|
00 00 00 |
セグメント00、オフセット0000h |
|
|
21 00 00 7E CD 00 00 C9 |
コード:ld hl,count、ld a,(hl)、call putc、ret。2つの穴は0000h |
|
| 8A |
06 04 00 01 00 00 03 |
DATA:セグメント01、オフセット0000h、バイト03 |
| 91 |
07 0C 00 |
RELOC、12バイト、フィックスアップ2つ: |
|
00 00 01 00 01 00 |
セグメント相対:セグメント00のオフセット0001hのワードに、セグメント01のアドレスを足す |
|
|
01 00 05 00 00 00 |
外部:セグメント00のオフセット0005hのワードに、外部0000の値を足す |
|
| A0 |
08 03 00 00 00 00 |
ENTRY:セグメント00、オフセット0000h |
| A6 |
FF 00 00 |
END |
次の点に注目してください。
- デフォルトのコード・セグメントとデータ・セグメントには、" C"と" D"という名前があります。空白と1文字で、ソースではセグメントの名前として書けない名前です。どのオブジェクト・ファイルにも、片方が空でも、両方があります。
- トランジェント・セグメントscratchには、グループもバイトもない自分自身のSEGDEFがあり、そのあとにグループごとに1つずつ、そのグループの大きさを持つSEGDEFがあります。
- モジュールの名前は、コマンド・ラインで入力したとおりのソースの名前です。
putcをret1つとして定義するPUTC.TROとリンクすると、TANRENはOBJのコードを0100hに、PUTCのコードをそのあとの 0108hに、データ・セグメントを0109hに置きます。バイトを所定の場所にコピーし、2つのフィックスアップを適用します。ld hl,countには 0000h + 0109h、call putcには0000h + 0108hです。プログラムOBJ.COMは、次の10バイトです。
21 09 01 7E CD 08 01 C9 C9 03
トランジェント・セグメントは010Ahにあり、2つのグループはどちらもそこから始まります。バイトを持たないので、ファイルには入りません。
F.3 ファイルの構成
ファイルには1つのモジュールが入ります。ファイルは5バイトのヘッダーで始まります。
| オフセット | バイト | 意味 |
| 0 | 54 52 4F | 文字TRO |
| 3 | 1A | MSX-DOSのファイルの終わりの印。type obj.troはTROを表示して止まる |
| 4 | 02 | 形式のバージョン |
そのあとに、ENDレコードまでレコードが続きます。レコードは、タイプのバイト、ペイロードの長さのワード、ペイロードからなります。全体を通して、次の規則があります。
- ワードは2バイトで、Z80と同じように下位バイトが先です。
- 文字列は、1から255までの長さのバイトと、その数の文字です。名前は書いたとおりに保存されます。大文字と小文字を区別したかどうかは、MODNAMEのフラグが示します。
- セグメント、グループ、外部の名前には、レコードの順に0から番号が付きます。セグメントとグループの番号は1バイトで、FFhは「なし」を意味します。セグメントではアブソリュートな値、グループではグループなしです。外部の名前の番号はワードです。
- レコードが使えるのは、それより前のレコードで定義されたセグメント、グループ、外部の名前の番号だけです。
- ENDレコードのあとのものは無視されます。TANRENはbytes follow the END record.でそれを知らせます(27.3節)。
TATARAは、MODNAME、GRPDEF、SEGDEF、EXTDEF、PUBDEF、DATA、RELOC、ENDにアドレスがあればENTRY、ENDの順にレコードを書き出します。
F.4 レコード
それぞれのレコードについて、ペイロードの形と、TANRENの/Rが表示する行を示します(21.3節)。/Rの行の値は 16進数です。
01h MODNAME:モジュール
ファイルの最初のレコードで、この種類のものは1つだけです。TANRENはモジュールの名前を、大文字と小文字を区別せずに比べます。/RはMODNAME flags 00 objと表示します。
02h GRPDEF:グループ
ペイロードはグループの名前の文字列です。グループには次の番号が付きます。同じ名前のグループは、このモジュールでもほかのモジュールでも同じグループです(10.6節)。/RはGRPDEF oneと表示します。
03h SEGDEF:セグメント
| フィールド | 大きさ | 意味 |
| flags | バイト | ビット0が1ならデータ、0ならコード。ビット1が1ならトランジェント |
| group | バイト | グループの番号。なしならFFh |
| size | ワード | このモジュールがセグメントに置くバイト数 |
| name | 文字列 | 名前。デフォルトのセグメントは" C"と" D" |
セグメントには次の番号が付きます。TANRENは、すべてのモジュールの同じ名前とグループのセグメントを、モジュールの順につなげ、トランジェント・セグメントのグループは重ねて置きます。あるモジュールではコードで別のモジュールではデータのセグメントや、一方ではトランジェントで他方ではそうでないセグメントはエラーです。/RはSEGDEF flags 03 group 00 size 0004 scratchと表示します。
04h PUBDEF:公開する名前
| フィールド | 大きさ | 意味 |
| segment | バイト | 値のセグメント。ただの数ならFFh |
| offset | ワード | 値。そのセグメントの中のオフセット、または数 |
| name | 文字列 | 名前 |
この3つのフィールドを繰り返してペイロードを埋めることもできますが、TATARAは1つのレコードに1つの名前を書きます。/RはPUBDEF seg 01 offset 0000 countと表示します。
05h EXTDEF:外部の名前
ペイロードは1つ以上の文字列で、それぞれが外部の名前です。名前には次の外部の番号が付きます。/RはEXTDEF putcと表示します。
06h DATA:バイト
| フィールド | 大きさ | 意味 |
| segment | バイト | セグメント。アブソリュート・セグメントならFFh |
| offset | ワード | セグメントの中でバイトが始まる位置、またはアドレス |
| bytes | 残り | バイト。穴にはアデンドが入っている |
レコードの長さには、バイトの前の3バイトも含まれるので、8バイトなら 11です。1つのセグメントのDATAレコードはいくつあってもよく、順序も自由です。/Rはバイト数そのものとバイトを表示します。DATA seg 00 offset 0000 len 0008 21 00 00 7E CD 00 00 C9
07h RELOC:フィックスアップ
| フィールド | 大きさ | 意味 |
| kind | バイト | 00h:セグメントのアドレスを足す。01h:外部の値を足す |
| segment | バイト | ワードのあるセグメント |
| offset | ワード | そのセグメントの中のワードの位置 |
| target | ワード | kindが00hならセグメントの番号、01hなら外部の番号 |
この6バイトがフィックスアップごとに繰り返されます。フィックスアップはどれもワード全体に対するもので、1バイトに対するフィックスアップはありません。1バイトのオペランドがアブソリュートでなければならないのはそのためです(8.6節)。/Rはその数を表示します。RELOC 2 fixups
08h ENTRY:開始アドレス
セグメントのバイトとオフセットのワードで、ENDの行のアドレスです。1つのモジュールに1つまでで、TANRENはモジュールの中で最初に見つけたものを使います(26.5節)。/RはENTRY seg 00 offset 0000と表示します。
7Fh COMMENT
どんなバイトでもよく、読むプログラムは読み飛ばします。TATARAはこのレコードを書きません。
FFh END
ペイロードは空です。モジュールの最後のレコードです。/RはENDと表示します。
F.5 TANRENがオブジェクト・ファイルを読む方法
TANRENは、どのオブジェクト・ファイルも始めから終わりまで2回読みます(27.2節)。図F.2に、F.2節の例のOBJ.TROとPUTC.TROについての 2つのパスを示します。
- 第1パスでは定義を読みます。モジュールの名前、グループとセグメントとその大きさ、公開する名前、外部の名前で、DATAとRELOCは長さで読み飛ばします。すべてのファイルを読み終えると、セグメントを配置し(24章)、公開するそれぞれの名前の値を求め、外部の名前がどれも定義されていることを確かめます。
- 第2パスでは、DATAレコードのバイトを所定の場所にコピーし、それぞれのワードにフィックスアップが示すものを足します。そのあと、TANRENは出力ファイルを書き出します。