Tatara

付録F
.troオブジェクト・ファイルの形式

TATARAが書き出し、TANRENが読むオブジェクト・ファイルの拡張子は.troです。この付録では、そのファイルに何が入っていて、どのように並んでいるかを説明します。これを読み書きするプログラムを作りたい人や、TANRENの/Rが何を表示しているかを知りたい人のためのものです。説明するのは、バージョン1.2.0のTATARAとTANRENが使う、形式のバージョン2です。まずオブジェクト・ファイルが何を伝えなければならないかを説明し、次に実際のファイルを1バイトずつたどり、最後にリファレンスを示します。

F.1 オブジェクト・ファイルが伝えること

2章で、アセンブラーだけではプログラムを完成できない理由を説明しました。リンカーがそれぞれの部分をどこに置くかも、ほかのモジュールが定義する名前のアドレスもわからないからです。そこでオブジェクト・ファイルは、自分のモジュールについて次の4つのことをリンカーに伝えます。

  1. 部分 バイトを置くセグメント、それぞれのバイト数、そしてバイトそのもの。
  2. 提供する名前 公開するそれぞれの名前と、その値。値はあるセグメントの中のオフセットか、ただの数です。
  3. 必要な名前 それぞれの外部の名前。
  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進数で、文字列のバイトはその文字で示します。

PIC

図 F.1: レコードの並びとしてのOBJ.TROと、1つのレコードの形。箱の幅はバイト数を表す。

表 F.1: OBJ.TROの1バイトずつ
オフセット

バイト

意味

00

54 52 4F 1A 02

ヘッダー:TRO、1Ah、バージョン2

05

01 05 00

MODNAME、5バイト:

00 03 "obj"

フラグ00(/Cなし)、名前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

次の点に注目してください。

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レコードまでレコードが続きます。レコードは、タイプのバイト、ペイロードの長さのワード、ペイロードからなります。全体を通して、次の規則があります。

TATARAは、MODNAME、GRPDEF、SEGDEF、EXTDEF、PUBDEF、DATA、RELOC、ENDにアドレスがあればENTRY、ENDの順にレコードを書き出します。

F.4 レコード

それぞれのレコードについて、ペイロードの形と、TANRENの/Rが表示する行を示します(21.3節)。/Rの行の値は 16進数です。

01h MODNAME:モジュール

フィールド 大きさ

意味

flags バイト

ビット0が1なら/C付きでアセンブルした。ほかのビットは0

name 文字列

モジュールの名前

ファイルの最初のレコードで、この種類のものは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. 第1パスでは定義を読みます。モジュールの名前、グループとセグメントとその大きさ、公開する名前、外部の名前で、DATAとRELOCは長さで読み飛ばします。すべてのファイルを読み終えると、セグメントを配置し(24章)、公開するそれぞれの名前の値を求め、外部の名前がどれも定義されていることを確かめます。
  2. 第2パスでは、DATAレコードのバイトを所定の場所にコピーし、それぞれのワードにフィックスアップが示すものを足します。そのあと、TANRENは出力ファイルを書き出します。

PIC

図 F.2: TANRENがOBJ.TROとPUTC.TROで行うこと。上は第1パスで、セグメントと名前の表を作る。下は第2パスで、バイトをコピーし、穴(網掛け)を埋める。