ここでは、インテル HEX 形式ファイルの作成方法について説明します。HEX ファイルは、プログラムイメージ(コードとデータを表すの 16 進数列)をテキストで表現したファイルで、主に ROM 化プログラムを作成する目的で使用されます。
これは米国インテル社が規定したファイル形式で、現在、ROM (Read Only Memory) に焼き付けるプログラムコードを表現する方法として、世界中で使用されています。
ヲLASM/LC が生成する HEX ファイル
Light Macro Assembler および Light C (for x86 および for Z80/KC160) で作成される .HEX ファイルは、このインテル HEX 形式に準拠しています。したがって、市販の多くのローダ、デバッガ、エミュレータ環境などで読み込むことができます。
HEX ファイルは、次のレコードを復帰/改行コード (0Dh, 0Ah) で区切って並べたものです。
各レコードは、次の共通形式を持ちます。各行はコロン (:) で始まり、いくつかの 16 進数が続きます。
:llaaaattdddd...ddddss\r\n
■各レコードの意味
LIL が生成する HEX ファイルの先頭には、データが 0 以外のアドレスから始まる場合、アドレス レコードが出力されます。アドレス レコードには ESA と ELA の 2 種類があります。これらのレコードの詳細については、後方の「アドレス レコード」を参照してください。
:02 0000 02 aaaa ss ESA レコード :02 0000 04 aaaa ss ELA レコード ※実際のファイルに空白は挿入されません。 ※aaaa は 10h 単位(ESA)または10000h 単位(ELA)のアドレスです。 ※ss はチェックサムです。
次に、いくつかのデータ レコードが続きます。データ レコードが実際のプログラムコードとデータの内容を表します。
:nn aaaa 00 data... ss データ レコード ※nn はデータのバイト数です (最大 10h)。 ※data... には実際のデータが nn 個続きます。
ソースコードの END 文で開始アドレスを指定した場合は、最後にタイプ 3 の開始アドレス レコードが作成されます。
:04 0000 03 seg-offs ss 開始アドレス レコード ※seg-offs は 4 バイトのセグメント:オフセット アドレスです。
最後に、必ずタイプ 1 のファイル終了レコードが出力されます。
:00 0000 01 ss 終了レコード
■コードの書き方
アセンブラおよび C 言語ののソースコードの書き方は、通常の実行ファイル (EXE, COM) を作成する場合と同じです。
ROM 化するプログラムでは、セグメントの絶対アドレスを指定する場合が多くなります。それには、ソースコード中で SEGMENT 文に AT 属性を指定する (アセンブラの場合) か、またはリンク時に LIL の /S オプションを使用します。
■アセンブル、コンパイル、リンク方法
Light Macro Assembler の場合 アセンブル方法は通常と同じですが、リンク時に LIL に対して /HEX オプションを指定する必要があります。その結果、EXE や COM の代わりに HEX ファイルが生成されます。/E オプションで HEX ファイル名を指定することもできます。デフォルトでは、コマンドラインで一番最初に指定されたオブジェクト ファイルのベース名に拡張子「.HEX」を付けた名前になります。
Light C の場合 コンパイル オプション /HEX を指定します。または、コンパイルとリンクを個別に実行して、リンク時に /HEX オプションを指定します。
■セグメント アドレスを指定する
必要に応じてリンカ オプション /S を追加し、セグメントのアドレスを指定します。構文は次のとおりです。
LIL /S<seg>=[<addr>][:<place>]
<seg> にはソースコード内のセグメント名を指定し、<addr> にはその絶対アドレスを 4 桁で指定します。<place> については後述します。/S オプションは /HEX オプションと一緒に使用する必要があります。
<addr> には、FFFFh 以下の 16 進数を指定します。この値はパラグラフ (16) 単位のアドレスを表します。たとえば、次のように指定します。
LIL /HEX /SCODE=2050 SAMPLE.OBJ
このコマンドラインは、セグメント「CODE」のセグメント アドレスを 2050h に指定しています。これは、アセンブラのソースコードに次のように記述することと同等です。
CODE SEGMENT AT 2050h
この結果、CODE はセグメント アドレスの 2050h、つまり 8086 なら実アドレスの 20500h に置かれることになります。同じセグメントに AT 属性と /S オプションの両方を指定すると、/S オプションの値が優先されます。
AT 属性も /S オプションも指定しなかった場合、そのセグメントはソースコード上で直前にあるセグメントの直後のアドレスに置かれます。そのセグメントが最初のセグメントである場合は、アドレス 0 に置かれます。
■セグメントの論理アドレスと実際の配置アドレスを異なる値に指定する
セグメントの論理的なアドレス (実行時のアドレス) と、そのセグメントを ROM 上に配置するアドレスを異なる値にすることもできます。その場合は、<addr> に論理的な実行時のアドレスを指定し、<place> に静的な配置アドレスを指定します。<addr> には 16 ビットのセグメント値を指定し、<place> には絶対的なアドレス値 (最大 32 ビット) を指定します。
このようなセグメントは、<place> に配置するように HEX ファイルに記述されますが、そのセグメント内のラベルへの参照は、<place> ではなく <addr> の値を使って行われます。
次に例を示します。
LIL /HEX /SCODE=2050:80000 SAMPLE.OBJ
これは、アセンブラのソースコードに次のように記述することと同等です。
CODE SEGMENT AT 2050h PLACE_AT 80000h
この場合、セグメント「CODE」への参照のフィックスアップは、このセグメントが 2050h にあるものとして行われます。たとえば、「mov ax,CODE」というコードは「mov ax,2050h」というコードとして解釈されます。しかし、HEX ファイル中で (ESA または ELA レコードによって) 示されるされるセグメントの配置アドレスは、2050h ではなく 80000h になります。
/S オプションでコロン (:) と <place> を省略すると、<addr> の 16 倍の値が配置アドレスとして使用されます。
<addr> を省略し、<place> だけを指定することもできます。たとえば、次のように指定します。
LIL /HEX /SCODE=:80000 SAMPLE.OBJ
これは、アセンブラのソースコードに次のように記述することと同等です。
CODE SEGMENT PLACE_AT 80000h
この場合、セグメント CODE の論理アドレスは、AT 指定があればそのアドレスに、なければ直前のセグメントの直後のアドレスになります。配置アドレスは 80000h になります。
いずれかのセグメントに対して配置アドレスを指定し、/M オプションでマップファイルを作成すると、「1. セグメント」のセクションの「始点」列には、セグメントの論理アドレスが表示されます。実際の配置アドレスは、「配置」列に表示されます。
■特定のセグメントを HEX ファイルに出力しない
<place> に "NO" を指定することにより、特定のセグメントを HEX ファイルに出力しないようにすることができます。たとえば、次のように指定します。
LIL /HEX /SCODE=:NO SAMPLE.OBJ
これは、アセンブラのソースコードに次のように記述するのと同等です。
CODE SEGMENT PLACE_AT NO (または) CODE SEGMENT PLACE_AT -1 (または) CODE SEGMENT PLACE_AT 0FFFFFFFFh
この場合、セグメント「CODE」は出力されません。
■直前のセグメントに続けて配置する
<place> に "CNT" を指定することにより、セグメントの配置アドレスを直前のセグメントの末尾に設定できます。たとえば、次のコードを考えます。
seg1 segment db 1 seg1 ends seg2 segment db 2 seg2 ends end
このコードを次のようにリンクします。
LIL A /SSEG1=1000:80000 /SSEG2=:CNT /HEX /M
この場合、SEG1 は 80000h に、SEG2 は後続の 80010h に配置されます。
配置アドレスの決定にもセグメントのアラインが影響しますが、PARA が下限です。配置アドレスはパラグラフ (16 バイト) 単位でしか指定できないため、アラインが PARA 未満の場合は、PARA でアラインされます。したがって、"CNT" を使って配置するセグメントには、PARA 以上のアラインを指定してください。Light C の場合は -csp オプションも参照してください。
■アドレス レコード
LIL が生成する HEX ファイルには、10000h 以上の配置アドレスを示すために、拡張セグメント アドレス (ESA) レコードと拡張リニア アドレス (ELA) レコードの一方または両方が出力される場合があります。
ォZ80:Z80 の場合は、セグメントが存在しないため、アドレスレコードは使用されません。≫
データ配置アドレスは、ESA レコード、ELA レコード、およびデータ レコードで決まります。この 3 種類のレコードには、いずれも 16 ビットの配置アドレス指定フィールドがあります。各レコードに記入された配置アドレスは、ESA レコードでは 10h バイト単位、ELA レコードでは 10000h バイト単位、データ レコードでは 1 バイト単位で解釈されます。
実際にアドレスを使用するのはデータ レコードです。各データ レコードのデータが実際に配置されるアドレスは、そのデータ レコード自身が指定する 16 ビットアドレスと、直前の ESA および ELA レコードの指定アドレスを加算した値になります。
たとえば、直前の ESA レコードが 0002h、ELA レコードが 0003h を指定している場合、データ レコードの配置アドレスには、2h * 10h + 3h * 10000h = 30020h が加算されます。
----------------------- 桁 7 6 5 4 3 2 1 0 ----------------------- ELA L L L L ESA S S S S DATA D D D D ----------------------- ADDR A A A A A A A A ----------------------- ※注 DATA : データ レコードで指定されているアドレス ADDR : 実効アドレス (32 bit)
ヲESA と ELA の選択
ESA レコードは、10 バイトという細かい単位でアドレスを指定できますが、100000h 以上の大きなアドレスを表現できません。これに対して、ELA レコードは、大きなアドレスでも表現できますが、10000h 単位でしかアドレスを指定できません。したがって、セグメント アドレスの表現に使用されるアドレス レコードは、一意に決まる場合があります。
しかし、ESA レコードと ELA レコードのどちらでも表現できるセグメント アドレスもあります。たとえば、70000h というセグメント アドレスは、ESA レコード値の 7000h で表すことも、ELA レコード値の 0007h で表すこともできます。
LIL の場合、デフォルトでは ESA レコードが使用され、ESA レコードだけで表現できない場合にのみ ELA レコードが使用されます。つまり、ESA レコードですべての配置アドレスを表現できるならば、ELA レコードは生成されません。
ただし、LIL のオプション /RL を指定すると、ELA レコードが優先的に使用されます。たとえば、次のようになります。
LIL /HEX /SCODE=2050:70000 /RL SAMPLE.OBJ
このように指定すると、0007h を示す ELA レコードが生成されます。/RL を指定しないと、7000h を示す ESA レコードが生成されます。どちらの場合も、論理的な実効アドレスは 70000h で変わりません。このオプションは、HEX ファイルを読み込むローダやエミュレータに合わせて HEX ファイルの形式を調整するために用意されています。
■インテル HEX 形式ファイルの例
次は HEX ファイルの一例です。見やすいようにスペースを入れてありますが、実際の HEX ファイルでは 16 進数が連続して並んでいます。
:02 0000 02 2000 DC :10 0000 00 B870178ED8BB2000C607AAEA00000120 EE :02 0000 02 2001 DB :0E 0000 00 43C607BBC6063412CCEA01008813 C3 :02 0000 02 1388 61 :04 0000 00 909090F4 58 :02 0000 02 1770 75 :04 0020 00 01020304 D2 :04 0000 03 20000000 D9 :00 0000 01 FF
ォ-Z80:次は、上の HEX ファイルのソースコードです。
assume cs:code code segment start: mov ax, seg data mov ds, ax mov bx, offset d1 mov byte ptr [bx], 0AAh jmp far ptr step2 code ends assume cs:code2 code2 segment step2: inc bx mov byte ptr [bx], 0BBh mov byte ptr ds:[1234h], 0CCh jmp far ptr step3 code2 ends assume cs:code3 code3 segment at 5000 nop step3: nop nop hlt code3 ends data segment at 6000 org 20h d1 db 1,2,3,4 data ends end start
アセンブル、リンク方法は次のとおりです。
lasm sample.asm lil /hex /scode=2000 sample.obj
サ