MPLABが出力するHEXファイルの見方

デバッガやシミュレータ等の開発実行環境を除き、どのアドレスにどんな命令やデータが配置されているか調べる方法として、リンク時に出力されたHEXファイルを参照する方法がある。そこでHEXファイルの見方を例を挙げてまとめておく(PIC24Fに必要な範囲だけ)。
ただしHEXファイルは初めてなので間違えるかも。

参考:HEXフォーマット)。

サンプルにしたアセンブラ・ソースは以下の通り。
バイナリを作成するためだけのいい加減コードなのでよろしく。
デバイスは見ての通りPIC24FJ64GA002。

	.include "p24FJ64GA002.inc"
	.global	_main
	.global __reset

	config __CONFIG1, 0x3350
	config __CONFIG2, 0xA907

	.text

__reset:
	mov		#0x1234,w15
	goto	_main

_main:
	mov		#0x4567,w0
	goto	_main

	.end

HEXファイルの出力は以下の通り。一部省略。


:020000040000fa
:080000000002040000000000f2
:020000040000fa
:020000040001f9
:0457f80007a90000fd
:020000040000fa
:020000040001f9
:0457fc005033000026
:020000040000fa
:100008000e0200000e0200000e0200000e020000a8
:100018000e0200000e0200000e0200000e02000098
...
:1001d8000e0200000e0200000e0200000e020000d7
:1001e8000e0200000e0200000e0200000e020000c7
:0801f8000e0200000e020000df
:020000040000fa
:100208000e0200000e0200000e0200000e020000a6
:100218000e0200000e0200000e0200000e02000096
...
:1003d8000e0200000e0200000e0200000e020000d5
:1003e8000e0200000e0200000e0200000e020000c5
:0803f8000e0200000e020000dd
:020000040000fa
:100400004f23210006020400000000007056240063
:080410000602040000000000d8
:020000040000fa
:0404180000000000e0
:020000040000fa
:04041c000000fe00de
:00000001FF

MAPファイル。これも必要そうな部分だけ。


Program Memory [Origin = 0x200, Length = 0xa9fc]

section address length (PC units) length (bytes) (dec)
------ ------- ----------------- --------------------
.text 0x200 0xc 0x12 (18)
.dinit 0x20c 0x2 0x3 (3)
.isr 0x20e 0x2 0x3 (3)

Total program memory used (bytes): 0x18 (24) <1%
External Symbols in Program Memory (by address):

0x000200 __reset
0x000206 _main
0x00020e __DefaultInterrupt

Memory Configuration

Name Origin Length Attributes
data 0x000800 0x002000 a !xr
reset 0x000000 0x000004
ivt 0x000004 0x0000fc
aivt 0x000104 0x0000fc
program 0x000200 0x00a9fc xr
config2 0x00abfc 0x000002
config1 0x00abfe 0x000002
*default* 0x000000 0xffffffff

HEXファイルの内容をフォーマットに合わせて分解すると以下の通り。行番号は説明用に付加したもの。一部省略。

行番号 ':' data length offset address record type data checksum
1 : 02 0000 04 0000 fa
2 : 08 0000 00 0002040000000000 f2
3 : 02 0000 04 0000 fa
4 : 02 0000 04 0001 f9
5 : 04 57f8 00 07a90000 fd
6 : 02 0000 04 0000 fa
7 : 02 0000 04 0001 f9
8 : 04 57fc 00 50330000 26
9 : 02 0000 04 0000 fa
10 : 10 0008 00 0e0200000e0200000e0200000e020000 a8
11 : 10 0018 00 0e0200000e0200000e0200000e020000 98
...
39 : 10 01d8 00 0e0200000e0200000e0200000e020000 d7
40 : 10 01e8 00 0e0200000e0200000e0200000e020000 c7
41 : 08 01f8 00 0e0200000e020000 df
42 : 02 0000 04 0000 fa
43 : 10 0208 00 0e0200000e0200000e0200000e020000 a6
44 : 10 0218 00 0e0200000e0200000e0200000e020000 96
...
72 : 10 03d8 00 0e0200000e0200000e0200000e020000 d5
73 : 10 03e8 00 0e0200000e0200000e0200000e020000 c5
74 : 08 03f8 00 0e0200000e020000 dd
75 : 02 0000 04 0000 fa
76 : 10 0400 00 4f232100060204000000000070562400 63
77 : 08 0410 00 0602040000000000 d8
78 : 02 0000 04 0000 fa
79 : 04 0418 00 00000000 e0
80 : 02 0000 04 0000 fa
81 : 04 041c 00 0000fe00 de
82 : 00 0000 01 FF

レコードタイプは3種類しか使われていない。

レコードタイプ04は拡張リニアアドレスレコードで、32bitアドレスの上位16bitを指定するもの。HEXファイル仕様作成当初16bitアドレスだったフォーマットを後から32bitに拡張するもので、後続のデータレコードの実アドレスはリニアドレスのdata×0x10000+リニアアドレスレコードのoffset address+データレコードのoffset addressの単純和である(後述の通り更に2で割ること)。
レコードタイプ00はデータレコードで、メモリ・データをdataに持つ。
レコードタイプ01はHEXファイルの終了を示す。
checksumは、data length〜dataの各バイト値の合計の2の補数。 2の補数は等大逆向きの値になるので、':'の次から改行手前までの全byteデータを8bit整数として加算すると0になる。0でなければその行のデータは信用できない。

PIC24ということで以下の点について注意すること。
dataがメモリ・データ(つまりレコードタイプ00)のとき、これらのデータはPIC24のプログラム・メモリに格納される。HEXフォーマットはバイト単位のビッグエンディアンで、PIC24はロング・ワード(4byte)単位のリトルエンディアンである(PIC24はPhantom Byte(無効8bit)+Instruction(24bit)の4byteで一組だから)。よってPIC24のデータに変換するときは、4byte単位で内部のバイトを並び替える。またPIC24のプログラム・メモリは上位8bitがファントムなので無効になり、bit23〜bit0が有効である。
offset addressや、アドレス値を持つdata(つまりレコードタイプ04)はそのまま読めばよい(エンディアン変換不要)。
実際の格納アドレスを計算したとき、そのアドレスはプログラムするとき命令アドレス・バスに乗るため23bit幅(bit22〜bit1は可変、bit0は0固定)だけが利用される(0xdef12345なら0x712344)。またPIC24の命令アドレスはワード(2byte)単位であるため、格納アドレスの計算結果を更に2で割る必要がある。

HEXファイルの先頭から順に見ていく。checksumは確認しない。

<<1〜2行目、GOTO InstructionとReset Address>>
1行目のレコードタイプ04でoffset addressで下位0x0000、dataで上位0x0000としている。2行目のレコードタイプ00ではoffset addressが0x0000なので、((0x0000<<16)+0x0000+0x0000)/2 → 0x00000000となり、そこに0002040000000000を配置している。
ここにはGOTO InstructionとReset Addressが配置されている。
先に書いたとおり、4bayte(32bit)毎にバイトの並び順を入れ替えればよい(0x11223344なら0x44332211)。0x0002040000000000 → 0x0004020000000000。32bitのうち上位8bitの無効部分(「PIC24F Reference Manuals」の「4.1 PROGRAM MEMORY ADDRESS MAP」参照)を削除すると、0x040200000000。
23bit固定アドレスのGOTO Instructionのエンコーディング(2進数)は以下の通り(1word=24bit)。


1st word 0000 0100 nnnn nnnn nnnn nnn0
2nd word 0000 0000 0000 0000 0nnn nnnn
nは飛び先lit23(23bit定数)アドレス(nが22個しかないことに注目)。「dsPIC30F/33F Programmer’s Reference Manual」ではnの並び順定義が見つからなかった(いいかげんだなあor私の見逃し)が、GOTO命令のDescriptionに「lit23<0> is ignored(23bitリテラルのbit0は無視される)」とあるので、1st wordのbit0がlit23<0>のことなんだろう。すると1st wordのbit18がlit23<15>、2nd wordのbit0がlit23<16>、2nd wordのbit6がlit23<22>と考えられる。従って040200000000のlit23は、0b000 0000 0010 0000 0000 0000 = 0x000200がリセット時の飛び先アドレスである。
リセット・ハンドラ__resetのアドレス0x000200と一致する。

<<3〜5行目、Configuration Word 2>>
3行目は不要。HEXファイルを出力するpic30-coff-bin2hex.exe(デフォルトcoff出力の場合)が、処理単位の先頭で決め打ち出力しているみたい。
アドレスを32bitに拡張すると、((0x0001<<16)+0x0000+0x57f8)/2 → 0x00ABFCでこれはConfiguration Word2のアドレスであり、ここに0x07a90000 → 0x00a907の値を配置している。Configuration Word2は、bit15〜bit0のワードが有効で、アセンブル・ソースでも__CONFIG2に0xA907を指定している。

<<6〜8行目、Configuration Word 1>>
Configuration Word 2と同様なので計算と確認だけ。
アドレス ((0x0001<<16)+0x0000+57fc)/2 → 0x00ABFE。
データ 0x50330000 → 0x003350。__CONFIG1の設定値と一致。Configuration Word 1はbit15〜bit0が有効。

<<9〜41行目、Interrupt Vector Table(IVT)>>
割込ベクタ・テーブルが並ぶ。アセンブル・ソースに記述はないが、リンカ・スクリプトが未定義となっている割込ベクタ・テーブルを__DefaultInterruptのアドレスで埋めてくれるらしい。
10行目のアドレスは、((0x0000<<16)+0x0000+0x0008)/2 → 0x000004、
41行目のアドレスは、((0x0000<<16)+0x0000+0x01f8)/2 → 0x0000FC、
41行目にはデータが8byte → 4wordあるから0x0000FC+0x4 → 0x000100が末尾+1のアドレスになる。IVTのアドレス範囲と一致する。
データは0x0e020000 → 0x00020eが繰り返し格納されている。これはmapファイルにある__DefaultInterruptのアドレスである。

<<42〜74行目、Alternate Interrupt Vector Table (AIVT)>>
43行目のアドレスは、((0x0000<<16)+0x0000+0x0208)/2 → 0x000104である。
これはAIVTの開始アドレスと一致していている。配置されているデータはIVTと同様なので割愛。

<<75〜77行目、__reset、_main>>
76行目のアドレスは、((0x0000<<16)+0x0000+0x0400)/2 → 0x000200である。
これは__resetの開始アドレスであり、コードが配置されていることが判る。
面倒なので__resetのコードを飛ばして、_mainのコードを探してみる。mapファイルから_mainの開始アドレスが0x000206であるから、下位16bitの0x0206×2=0x040Cを含むレコードタイプ00を探す。ここでは76行目が該当する。アドレス上位16bitも一致するか確認するため、行を遡ってレコードタイプ04を探し、offset addressとdataが0x0000であることも確認。
76行目のアドレスは、((0x0000<<16)+0x0000+0x0400)/2 → 0x000200であるから、_mainの開始位置まで0x000206-0x000200=6word=12byteスキップする必要がある。mapファイルから0x00020eの手前までが_mainなので、最大でも0x00020e-0x000206=8word=16byte分が_mainのコードである。ただし何らかの理由で次のシンボルまで間を設けていることあるので減少する可能性がある。
ということでとりあえず76〜77行目の705624000602040000000000が_mainのコード部分である。
これを4byte毎にエンディアン変換し、MSB側8bit(ファントム)を削除し、逆アセンブルすればよい。
705624000602040000000000 → 00245670 00040206 00000000 → 245670 040206 000000
「dsPIC30F/33F Programmer's Reference Manual」の「Table 6-2: dsPIC30F/dsPIC33F Instruction Encoding」を参照するとMSB側の数ビットでオペコードの種別が判る。


0x2 → 0b0010 → MOV
0x04 → 0b0000 0100 → GOTO
0x000000の先頭は0x0なのでNOPと勘違いしそうだが、これはGOTOのオペランドの一部だ(後述)。
同マニュアルの「5.4 Instruction Descriptions」で、各命令の詳細なEncodingを調べる。
複数のエンコードを持つMOV命令の内、先頭が0b0010のものを探す。

Syntax: {label:} MOV #lit16, Wnd
Encoding: 0010 kkkk kkkk kkkk kkkk dddd
bit19〜4がlit16の16bit代入値であり0x4567、bit3〜0が代入先Wレジスタ番号ありW0となる。
kとかdとかは同マニュアルの「Table 5-1: Instruction Encoding Field Descriptors」参照のこと。
つまり

MOV #0x4567, W0
である。
GOTOも2種類のエンコードがあるが、先頭が0b0000 0100の方である。

Syntax: {label:} GOTO Expr
Encoding: 1st word 0000 0100 nnnn nnnn nnnn nnn0
2nd word 0000 0000 0000 0000 0nnn nnnn
GOTOはリセット・ベクタで説明しているので、結果だけを書くと以下のようになる。

GOTO 0x000206
0x000206はシンボル_mainのアドレスなので

GOTO _main
となり、アセンブル・コードと一致する。

<<78〜79行目、.dinitセクション>>
79行目のアドレスは、((0x0000<<16)+0x0000+0x0418)/2 → 0x00020Cである。
mapファイルによると、.dinit(初期化データ)のセクションである。今回のプログラムでは定数も初期化する変数も無いし、ライブラリもリンクしていないのでこのセクションは不要なはずである。データは0x00000000でが、MSB8bitはファントムなので0x000000である。
たぶんリンク・スクリプトが決め打ちで出力しているようだ。

*追記(2009/05/25)
リンカLINK30のデフォルト動作でセクション.dinit(data-init)が出力されます。「Project」→「Build options...」→「project」でタブ「MPLAB LINK30」の「Don't initialize data sections」にチェックを付ければ、.dinitは出力されなくなります。もちろんrun-timeライブラリ(libpic30.a)を使えなくなります。

<<80〜81行目、__DefaultInterrupt>>
81行目のアドレスは、((0x0000<<16)+0x0000+0x041c)/2 → 0x00020Eで、これは__DefaultInterruptのアドレスである。
データは0x0000fe00 → 0xfe0000。
この0xfe → 0b1111 1110は、CLRWDT, PWRSAV, POP.S, PUSH.S, RESETの各命令が割り当てられている。後ろにコードが続いていないから、たぶんRESET。


Syntax: {label:} RESET
Encoding: 1111 1110 0000 0000 0000 0000
当たり。

RESET
__DefaultInterruptは予期せぬトラップや割込に対しリセットする。

<<82行目、終了>>



  • アドレスから調べるなら、mapファイルでコードなのかデータなのか調べる必要がある。
  • PIC24アドレスをHEXファイルのアドレスに変換するには、2倍する必要がある。
  • HEXファイルのアドレスをPIC24アドレスに変換するには、2で割る必要がある。
  • 命令や4byteデータなら、4byte単位でエンディアン変換が必要である。
  • プログラム・メモリに格納されるなら、4byteのMSB8bitはファントム・バイトであり、削除して考えた方が間違えにくい。
  • ただしファントム・バイトと2byteデータ以外の残り1byteについて、アセンブラでデータを配置したり、コンパイラがその部分を有効利用したりすることはあり得るので、何もデータが無いと決めつけてはいけないはず。