C#でintelのhexファイルフォーマットを解析する

C#でintelのhexファイルフォーマットの解析プログラムを作成したので,公開しておきます。

hexファイルフォーマットはアスキー形式の可変長テキストですが、シンプルな作りになっているので読み込みは簡単です。

ファイルフォーマットの詳細が知りたい場合は、以下のエントリで説明しています。興味があれば参考にしてください。
MPLABで作成されたhexファイルのフォーマットを解析する


ここで読み込んだ情報を元にPIC16F84A用のバイナリを逆アセンブルしてみる予定ですが、本クラス自体はターゲットデバイス非依存な作りです。
(pic以外用のROMデータでも解析できるはずです)


というわけで、解析処理のクラスです。

using System;
using System.Collections.Generic;
 
public class HexFileParser {
    /// <summary> hexファイル情報</summary>
    class HexFileData {
        public int    Offset  { get; set; } // オフセット   0~65536
        public int    Length  { get; set; } // データ長     0~255
        public string RecType { get; set; } // レコード種別 2byte
                                            // 00   データレコード
                                            // 01   ファイル終了レコード
                                            // 02   拡張セグメントアドレスレコード
                                            // 03   スタートセグメントアドレスレコード
                                            // 04   拡張リニアアドレスレコード
                                            // 05   スタートリニアアドレスレコード
        public string Data    { get; set; }
    }
 
    /// <summary> 開始アドレス</summary>
    public int StartAddr { get{ return startAddr; } }
 
    /// <summary> 終了アドレス</summary>
    public int EndAddr   { get{ return endAddr;   } }
 
    // 解析エラー情報を取得する
 
    public List<string>ParseErrorInfo { get{ return parseErrorInfo;} }
 
    public string DefaultValue { get; set; }
 
    /// <summary>開始アドレス</summary>
    private int startAddr;
    /// <summary>終了アドレス</summary>
    private int endAddr;
 
    /// <summary> Hexファイル情報リスト(元ファイル1行で1データ)</summary>
    private List<HexFileData> hexFileList = new List<HexFileData>();
 
 
    /// <summary>解析エラー情報</summary>
    private List<string> parseErrorInfo = new List<string>();
 
 
    //*********************************************************************
    /// <summary> コンストラクタ
    /// </summary>
    //*********************************************************************
    public HexFileParser() {
        DefaultValue = "00";
 
        clearParseInfo();
    }
 
 
    //*********************************************************************
    /// <summary>   解析情報をクリアする
    /// </summary>
    //*********************************************************************
    public void clearParseInfo() {
        startAddr    = 0xFFFF;
        endAddr      = 0x0000;
        parseErrorInfo.Clear();
        hexFileList.Clear();
    }
 
    //*********************************************************************
    /// <summary> 指定された行のデータを解析する
    /// </summary>
    /// <param name="lineNo">行番号(エラー出力用)</param>
    /// <param name="inData">hexファイル行データ</param>
    //*********************************************************************
    public void parseLine( int lineNo, string inData ) {
        string startMark;       // 1byte    スタートマーク(":"固定)
        string byteCount;       // 2byte    データ長
        string offsetAddress;   // 4byte    データオフセット(開始位置)
        string recType;         // 2byte    レコード種別 00:データ
        string data;            // variable データ
        string checkSum;        // 2byte    チェックサム
 
        //--------------------
        // レコード長チェック
        //--------------------
        if ( inData.Length < 11 ) {
            parseErrorInfo.Add( "行:" + lineNo + " レコード長が不正です" );
            return;
        }
 
        //------------------------------------
        // 入力レコードを、フィールド分割する
        //------------------------------------
        startMark     = inData.Substring( 0, 1 );
        byteCount     = inData.Substring( 1, 2 );
        offsetAddress = inData.Substring( 3, 4 );
        recType       = inData.Substring( 7, 2 );
        data          = inData.Substring( 9, inData.Length - 11 );
        checkSum      = inData.Substring( inData.Length - 2 );
 
        //--------------------
        // データチェック
        //--------------------
 
        // スタートマーク
        if ( !startMark.Equals( ":" ) ) {
            parseErrorInfo.Add( "行:" + lineNo + " スタートマークが':'では有りません[data=" + startMark + "]" );
            return;
        }
 
        // バイト数
        if ( !isHexaDecimal( byteCount ) ) {
            parseErrorInfo.Add( "行:" + lineNo + " バイトカウントが16進文字列では有りません[data=" + byteCount + "]" );
            return;
        }
 
        // オフセット
        if ( !isHexaDecimal( offsetAddress ) ) {
            parseErrorInfo.Add( "行:" + lineNo + " オフセットアドレスが16進文字列では有りません[data=" + offsetAddress + "]" );
            return;
        }
 
        // レコード種別
        if ( !isHexaDecimal( recType ) ) {
            parseErrorInfo.Add( "行:" + lineNo + " レコード種別が16進文字列では有りません[data=" + recType + "]" );
            return;
        }
        if ( !recType.Equals( "00" ) && !recType.Equals( "01" ) && !recType.Equals( "02" ) &&
             !recType.Equals( "03" ) && !recType.Equals( "04" ) && !recType.Equals( "05" ) ) {
            parseErrorInfo.Add( "行:" + lineNo + " レコード種別が00~05以外の値です[data=" + recType + "]" );
            return;
        }
 
        // データ
        if ( !isHexaDecimal( data ) ) {
            parseErrorInfo.Add( "行:" + lineNo + " データが16進文字列では有りません[data=" + data + "]" );
            return;
        }
 
        // チェックサム
        if ( !isHexaDecimal( checkSum ) ) {
            parseErrorInfo.Add( "行:" + lineNo + " チェックサムが16進文字列では有りません[data=" + checkSum + "]" );
            return;
        }
 
        //------------------------------
        // チェックサムの整合性チェック
        //------------------------------
 
        // スタートマークの次の位置(2byte目)~チェックサムの手前までを合算する
        uint calcValue = 0;
        for ( int loop = 1; loop < inData.Length - 2; loop +=2 ) {
            string curValue = inData.Substring( loop, 2 );
            calcValue += (uint)Convert.ToInt32( curValue, 16 );
        }
        calcValue = (~calcValue) + 1; // 2の補数を取る
        calcValue &= 0x000000FF;      // 下位16ビット分だけを抽出
        if ( Convert.ToInt32( checkSum, 16 ) != calcValue ) {
            parseErrorInfo.Add( "行:" + lineNo + " チェックサムが一致しません[calc=" + calcValue.ToString() + ", data=" + checkSum + "]" );
            return;
        }
 
        // 解析した値を覚える
        int start = Convert.ToInt32( offsetAddress, 16 );
        int len   = Convert.ToInt32( byteCount, 16 );
        storeValue( start, len, recType, data );
 
    }
 
 
    //*********************************************************************
    /// <summary> 指定されたアドレスの値を取得する
    /// </summary>
    /// <param name="address">  取得対象アドレス</param>
    /// <returns>               メモリ値</returns>
    //*********************************************************************
    public string getValue( int address ) {
        // 指定されたアドレスに対する定義を探す
        HexFileData targetData = null;
        foreach ( HexFileData curData in hexFileList ) {
            if ( curData.Offset <= address && curData.Offset + curData.Length > address ) {
                targetData = curData;
                break;
            }
        }
 
        // 指定されたアドレスの定義がない場合は、デフォルト値を返す
        if ( targetData == null ) {
            int wordLen = DefaultValue.Length / 2;  // 1ワードの長さを求める
            int wordPos = address % wordLen;        // 要求されたアドレスが、ワード中のどこにあたるかかのoffsetを求める
 
            return DefaultValue.Substring( wordPos * 2, 2 );
        }
 
        // 定義がある場合は、ファイルの内容を返す
        int pos = address - targetData.Offset;
        return targetData.Data.Substring( pos*2, 2 );
    }
 
 
    //*********************************************************************
    /// <summary>解析した文字列を保存する
    /// </summary>
    /// 
    /// <param name="recType">      レコード種別</param>
    /// <param name="data">         データ(空文字列の可能性もあり</param>
    //*********************************************************************
    private void storeValue( int start, int len, string recType, string data ) {
        // データレコード以外は保存しない
        if ( !recType.Equals( "00") ) {
             return;
        }
 
        //-------------------------------------------------------
        // このデータによって開始・終了アドレスが変わるかチェック
        //-------------------------------------------------------
        if ( start < startAddr ) {
            startAddr = start;
        }
        if ( start + len > endAddr ) {
            endAddr = start + len;
        }
 
        //-------------------------------------------------------
        // 追加すべきデータをセット
        //-------------------------------------------------------
        HexFileData hexFileData = new HexFileData();
        hexFileData.Offset  = start;
        hexFileData.Length  = len;
        hexFileData.RecType = recType;
        hexFileData.Data    = data;
 
        //-------------------------------------------------------
        // 一覧に追加
        //-------------------------------------------------------
        hexFileList.Add( hexFileData );
    }
 
    //*********************************************************************
    /// <summary>指定された値が16進文字列であるかをチェックする
    ///          (= '0'~'F'のみで構成されているか)
    /// </summary>
    /// 
    /// <param name="inData">   チェック対象データ</param>
    /// <returns>true:16進文字列である, false:16進文字列ではない</returns>
    //*********************************************************************
    private bool isHexaDecimal( string inData ) {
        foreach ( Char data in inData ) {
            if ( ( data >= '0' && data <= '9' ) ||
                 ( data >= 'A' && data <= 'F' ) ) {
                continue;
            }
 
            // "0-9A-F"以外->値がおかしい
            return false;
        }
 
        // チェックOK
        return true;
    }
}



このクラスの利用側は以下のような感じになります。

    string fileName ="c:\\test.hex";
    public HexFileParser parser = new HexFileParser();
 
    // デフォルト値の指定(pic16F84Aの場合"0x3FFF"だがlittle endianなのでFF3Fになる)
    parser.DefaultValue = "FF3F"; 
 
    using ( StreamReader reader = new StreamReader( fileName ) ) {
        int lineNo = 0;
        //-------------------------------
        // 全ての行を読み込むまで繰り返し
        //-------------------------------
        while ( true ) { 
            string line = reader.ReadLine();
            if ( line == null ) {
                break;
            }
 
            //-------------------------------------
            // Hexファイルのフォーマット解析を行う
            //-------------------------------------
            parser.parseLine( lineNo, line );
 
            // 解析したデータを画面に表示
            textBox1.AppendText( line + Environment.NewLine );
 
            lineNo++;
        }
    }
 
    //------------------------------------
    // hexファイルを元に,16進ダンプを行う
    //------------------------------------
    txtHexDump.Text = "";
 
    int loopCol = 0;
    int startAddr = parser.StartAddr;
    int endAddr   = parser.EndAddr;
    for ( int curAddr = startAddr; curAddr < endAddr; curAddr++ ) {
        string mCode = parser.getValue( curAddr );
        // 16進ダンプ値を表示
        textBox1.AppendText( mCode + " " );
 
 
        // 16byte単位で改行を入れる
        if ( loopCol++ >= 16 ) {
            loopCol = 0;
            textBox1.AppendText( Environment.NewLine );
        }
    }



次回は、このプログラムを元にPIC16F84のバイナリを逆アセンブルしてみます。
PIC16シリーズのバイナリを逆アセンブルする

関連記事

コメントを残す

メールアドレスが公開されることはありません。