InstructionBreakPoint.java
     1: //========================================================================================
     2: //  InstructionBreakPoint.java
     3: //    en:Instruction break point -- It stops the MPU when the instruction at the specified address is executed the specified number of times.
     4: //    ja:命令ブレークポイント -- 指定されたアドレスの命令が指定された回数実行されたときMPUを止めます。
     5: //  Copyright (C) 2003-2019 Makoto Kamada
     6: //
     7: //  This file is part of the XEiJ (X68000 Emulator in Java).
     8: //  You can use, modify and redistribute the XEiJ if the conditions are met.
     9: //  Read the XEiJ License for more details.
    10: //  https://stdkmd.net/xeij/
    11: //========================================================================================
    12: 
    13: //----------------------------------------------------------------------------------------
    14: //  機能
    15: //    命令ブレークポイントが設定されたアドレスから始まる命令の実行回数を数えて、閾値以上のとき命令の実行前に停止する
    16: //    設定できる命令ブレークポイントの数は無制限
    17: //    1つのアドレスに設定できる命令ブレークポイントは1つに限る
    18: //      1つのアドレスに複数の命令ブレークポイントがあると表示しにくい
    19: //      1つのアドレスに複数の命令ブレークポイントがあるとすべての命令ブレークポイントの処理が終わるまで停止させられないので処理が冗長になる
    20: //      100回目で一旦止めてから200回目でもう一度止めるような作業を繰り返したいときは連続する命令にそれぞれ命令ブレークポイントを設定する
    21: //  命令ブレークポイントの検出
    22: //    命令ブレークポイントをデバイスで検出する
    23: //      多数の命令ブレークポイントが設定されていても命令ブレークポイントが存在しないページの命令のオーバーヘッドは0になる
    24: //      命令を実行する度にそのアドレスに命令ブレークポイントがあるかどうか確認する方法は無関係の命令のオーバーヘッドが大きくなる
    25: //      命令ブレークポイントが存在しているときだけ命令を実行する度にそのアドレスに命令ブレークポイントがあるかどうか確認する方法は、
    26: //      メインのループを2種類用意して命令ブレークポイント以外の部分を常に一致させ続けなければならないので面倒
    27: //      そもそもコードを分けてしまったら命令ブレークポイントがないほうのコードをデバッグしたことにならない
    28: //    停止するときデバイスがthrowする
    29: //      特別な例外を用意する必要がある
    30: //      停止するときデバイスが特別な命令を返す方法も考えられるが、停止するときだけなので命令コードを消費してまで高速化する必要はない
    31: //    第1オペコードのフェッチだけ命令ブレークポイント用のメモリマップを用いる
    32: //      アクセスが第1オペコードのフェッチかどうかを毎回確認する必要がない
    33: //      命令ブレークポイントが設定されているページでも第1オペコードのフェッチ以外のアクセスのオーバーヘッドは0になる
    34: //      デバイスにはリードワードゼロ拡張の処理だけあればよい
    35: //      ユーザモードとスーパーバイザモードを切り替えるときに命令ブレークポイント用のメモリマップを切り替える処理が加わる
    36: //        分岐ログの負荷と比べれば遥かに小さい
    37: //    アドレスのハッシュテーブルを用いる
    38: //      すべての命令ブレークポイントと比較してみるよりも高速
    39: //      多数の命令ブレークポイントが設定されていても大部分の命令のオーバーヘッドは最小になる
    40: //      すべての命令に命令ブレークポイントの有無のマークを付けるよりもメモリを食わない
    41: //      ハッシュコードはアドレスの下位のビット列をそのまま使う。最下位ビットは常に0なので除く
    42: //        プログラムカウンタはインクリメントが基本なので十分に分散するはず
    43: //  命令ブレークポイントのフィールド
    44: //    address    アドレス
    45: //    threshold  閾値。-1=インスタント
    46: //    value      現在値
    47: //    target     目標値
    48: //                 停止条件は現在値>=閾値だが、命令の実行前に停止するため、
    49: //                 続行しようとしたときに同じ閾値と比較したのでは最初の命令で再び止まってしまい続行することができない
    50: //                 そこで、停止する度に停止条件を現在値==閾値から現在値==閾値+1,現在値==閾値+2,…と増やしていくことにする。この右辺が目標値
    51: //    next       同じハッシュコードを持つ次の命令ブレークポイント
    52: //  リセットされたとき
    53: //    すべての命令ブレークポイントについて
    54: //      現在値を0にする
    55: //      インスタントのとき
    56: //        目標値を0にする
    57: //      インスタントでないとき
    58: //        目標値を閾値にする
    59: //  命令ブレークポイントを作ったとき
    60: //    ハッシュテーブルに加える
    61: //    ページの命令ブレークポイントを有効にする
    62: //  命令ブレークポイントを消したとき
    63: //    同じハッシュコードを持つ命令ブレークポイントのリストから取り除く
    64: //    同じページに他に命令ブレークポイントがなければページの命令ブレークポイントを無効にする
    65: //  命令ブレークポイントに遭遇したとき
    66: //    現在値が目標値と一致しているとき
    67: //      インスタントのとき
    68: //        取り除く
    69: //      インスタント化しているとき
    70: //        目標値を閾値に戻す
    71: //      インスタントでなくインスタント化していないとき
    72: //        目標値を1増やす
    73: //      停止する
    74: //    現在値が目標値と一致していないとき
    75: //      現在値を1増やす
    76: //      続行する
    77: //  ここまで実行
    78: //    命令ブレークポイントがないとき
    79: //      インスタント命令ブレークポイントを作る
    80: //    命令ブレークポイントがあるとき
    81: //      インスタント化する
    82: //        pcと一致しているとき
    83: //          目標値=現在値+1とする
    84: //        pcと一致していないとき
    85: //          目標値=現在値とする
    86: //    続行する
    87: //----------------------------------------------------------------------------------------
    88: 
    89: package xeij;
    90: 
    91: import java.lang.*;  //Boolean,Character,Class,Comparable,Double,Exception,Float,IllegalArgumentException,Integer,Long,Math,Number,Object,Runnable,SecurityException,String,StringBuilder,System
    92: import java.util.*;  //ArrayList,Arrays,Calendar,GregorianCalendar,HashMap,Map,Map.Entry,Timer,TimerTask,TreeMap
    93: 
    94: public class InstructionBreakPoint {
    95: 
    96:   public static final boolean IBP_ON = true;  //true=命令ブレークポイントの機能を用いる
    97: 
    98:   //ハッシュテーブル
    99:   public static final int IBP_HASH_BITS = 8;
   100:   public static final int IBP_HASH_SIZE = 1 << IBP_HASH_BITS;
   101:   public static final int IBP_HASH_MASK = IBP_HASH_SIZE - 1;
   102: 
   103:   //命令ブレークポイントがあるページを差し替えたメモリマップ
   104:   public static MemoryMappedDevice[] ibpUserMap;  //ユーザモード用
   105:   public static MemoryMappedDevice[] ibpSuperMap;  //スーパーバイザモード用
   106:   //第1オペコードのメモリマップ
   107:   public static MemoryMappedDevice[] ibpOp1UserMap;  //ユーザモード用。XEiJ.busUserMapまたはibpUserMap
   108:   public static MemoryMappedDevice[] ibpOp1SuperMap;  //スーパーバイザモード用。XEiJ.busSuperMapまたはibpSuperMap
   109:   public static MemoryMappedDevice[] ibpOp1MemoryMap;  //第1オペコードの現在のメモリマップ。XEiJ.regSRS!=0?ibpSuperMap:ibpUserMap
   110: 
   111:   //命令ブレークポイントレコード
   112:   public static class InstructionBreakRecord {
   113:     public int ibrLogicalAddress;  //論理アドレス
   114:     public int ibrPhysicalAddress;  //物理アドレス
   115:     public int ibrThreshold;  //閾値。-1=インスタント
   116:     public int ibrValue;  //現在値
   117:     public int ibrTarget;  //目標値
   118:     public InstructionBreakRecord ibrNext;  //同じ物理ハッシュコードを持つ次の命令ブレークポイント。null=終わり
   119:     public ExpressionEvaluator.ExpressionElement ibrScriptElement;
   120:   }  //class InstructionBreakRecord
   121: 
   122:   //論理アドレス→命令ブレークポイント
   123:   public static TreeMap<Integer,InstructionBreakRecord> ibpUserPointTable;  //ユーザモード用
   124:   public static TreeMap<Integer,InstructionBreakRecord> ibpSuperPointTable;  //スーパーバイザモード用
   125:   //物理ハッシュコード→その物理ハッシュコードを持つ命令ブレークポイントのリストの先頭
   126:   public static InstructionBreakRecord[] ibpUserHashTable;  //ユーザモード用
   127:   public static InstructionBreakRecord[] ibpSuperHashTable;  //スーパーバイザモード用
   128: 
   129:   //ibpInit ()
   130:   //  命令ブレークポイントを初期化する
   131:   public static void ibpInit () {
   132:     if (IBP_ON) {
   133:       ibpUserMap = new MemoryMappedDevice[XEiJ.BUS_PAGE_COUNT];
   134:       ibpSuperMap = new MemoryMappedDevice[XEiJ.BUS_PAGE_COUNT];
   135:       Arrays.fill (ibpUserMap, MemoryMappedDevice.MMD_NUL);
   136:       Arrays.fill (ibpSuperMap, MemoryMappedDevice.MMD_NUL);
   137:       ibpOp1UserMap = XEiJ.busUserMap;
   138:       ibpOp1SuperMap = XEiJ.busSuperMap;
   139:       ibpOp1MemoryMap = ibpOp1SuperMap;
   140:       ibpUserPointTable = new TreeMap<Integer,InstructionBreakRecord> ();
   141:       ibpSuperPointTable = new TreeMap<Integer,InstructionBreakRecord> ();
   142:       ibpUserHashTable = new InstructionBreakRecord[IBP_HASH_SIZE];
   143:       ibpSuperHashTable = new InstructionBreakRecord[IBP_HASH_SIZE];
   144:     }
   145:   }  //ibpInit()
   146: 
   147:   //ibpReset ()
   148:   //  すべての命令ブレークポイントをリセットする
   149:   public static void ibpReset () {
   150:     if (IBP_ON) {
   151:       for (InstructionBreakRecord r : ibpUserPointTable.values ()) {
   152:         r.ibrValue = 0;
   153:         r.ibrTarget = r.ibrThreshold < 0 ? 0 : r.ibrThreshold;
   154:       }
   155:       for (InstructionBreakRecord r : ibpSuperPointTable.values ()) {
   156:         r.ibrValue = 0;
   157:         r.ibrTarget = r.ibrThreshold < 0 ? 0 : r.ibrThreshold;
   158:       }
   159:     }
   160:   }  //ibpReset()
   161: 
   162:   //ibpInstant (logicalAddress, supervisor)
   163:   //  指定された論理アドレスにインスタント命令ブレークポイントを設定する
   164:   //  既に同じ論理アドレスに命令ブレークポイントが設定されているときはそれをインスタント化する
   165:   public static void ibpInstant (int logicalAddress, int supervisor) {
   166:     if (IBP_ON) {
   167:       TreeMap<Integer,InstructionBreakRecord> pointTable = supervisor != 0 ? ibpSuperPointTable : ibpUserPointTable;
   168:       InstructionBreakRecord r = pointTable.get (logicalAddress);
   169:       if (r == null) {
   170:         ibpPut (logicalAddress, supervisor, 0, -1, null);  //インスタント命令ブレークポイントを作る
   171:       } else {
   172:         r.ibrTarget = r.ibrLogicalAddress == logicalAddress ? r.ibrValue + 1 : r.ibrValue;  //既存の命令ブレークポイントをインスタント化する
   173:       }
   174:     }
   175:   }  //ibpInstant(int,int)
   176: 
   177:   //ibpPut (logicalAddress, supervisor, value, threshold, scriptElement)
   178:   //  指定されたアドレスに命令ブレークポイントを設定する
   179:   //  既に同じアドレスに命令ブレークポイントが設定されているときは閾値を変更する
   180:   //  threshold  -1=インスタント
   181:   public static void ibpPut (int logicalAddress, int supervisor, int value, int threshold,
   182:                              ExpressionEvaluator.ExpressionElement scriptElement) {
   183:     if (IBP_ON) {
   184:       int physicalAddress = MC68060.mmuTranslatePeek (logicalAddress, supervisor, 0);  //物理アドレスに変換する
   185:       if ((logicalAddress ^ physicalAddress) == 1) {  //変換できない
   186:         return;
   187:       }
   188:       TreeMap<Integer,InstructionBreakRecord> pointTable = supervisor != 0 ? ibpSuperPointTable : ibpUserPointTable;
   189:       InstructionBreakRecord r = pointTable.get (logicalAddress);  //探す
   190:       if (r == null) {  //指定されたアドレスに命令ブレークポイントが設定されていない
   191:         //命令ブレークポイントがなかったら命令ブレークポイントを有効にする
   192:         if (pointTable.isEmpty ()) {
   193:           if (supervisor != 0) {
   194:             ibpOp1SuperMap = ibpSuperMap;
   195:           } else {
   196:             ibpOp1UserMap = ibpUserMap;
   197:           }
   198:           ibpOp1MemoryMap = XEiJ.regSRS != 0 ? ibpOp1SuperMap : ibpOp1UserMap;  //ここはsupervisor!=0ではない
   199:         }
   200:         //新しい命令ブレークポイントを作る
   201:         r = new InstructionBreakRecord ();
   202:         r.ibrLogicalAddress = logicalAddress;
   203:         r.ibrPhysicalAddress = physicalAddress;
   204:         //命令ブレークポイントのマップに加える
   205:         pointTable.put (logicalAddress, r);
   206:         //同じ物理ハッシュコードを持つ命令ブレークポイントのリストに加える
   207:         {
   208:           InstructionBreakRecord[] hashTable = supervisor != 0 ? ibpSuperHashTable : ibpUserHashTable;
   209:           int h = physicalAddress >>> 1 & IBP_HASH_MASK;  //物理ハッシュコード
   210:           r.ibrNext = hashTable[h];
   211:           hashTable[h] = r;
   212:         }
   213:         //ページの命令ブレークポイントを有効にする
   214:         {
   215:           int p = physicalAddress >>> XEiJ.BUS_PAGE_BITS;  //ページ番号
   216:           if (supervisor != 0) {
   217:             ibpSuperMap[p] = MemoryMappedDevice.MMD_IBP;
   218:           } else {
   219:             ibpUserMap[p] = MemoryMappedDevice.MMD_IBP;
   220:           }
   221:         }
   222:       }
   223:       r.ibrValue = value;  //現在値
   224:       r.ibrTarget = threshold < 0 ? 0 : threshold;  //目標値
   225:       r.ibrThreshold = threshold;  //閾値
   226:       r.ibrScriptElement = scriptElement;
   227:     }
   228:   }  //ibpPut(int,int,int,int,ExpressionEvaluator.ExpressionElement)
   229: 
   230:   //ibpRemove (logicalAddress, supervisor)
   231:   //  指定された論理アドレスの命令ブレークポイントを取り除く
   232:   public static void ibpRemove (int logicalAddress, int supervisor) {
   233:     if (IBP_ON) {
   234:       int physicalAddress = MC68060.mmuTranslatePeek (logicalAddress, supervisor, 0);  //物理アドレスに変換する
   235:       if ((logicalAddress ^ physicalAddress) == 1) {  //変換できない
   236:         return;
   237:       }
   238:       TreeMap<Integer,InstructionBreakRecord> pointTable = supervisor != 0 ? ibpSuperPointTable : ibpUserPointTable;
   239:       InstructionBreakRecord r = pointTable.get (logicalAddress);  //探す
   240:       if (r != null) {  //指定された論理アドレスに命令ブレークポイントが設定されている
   241:         //同じ物理ハッシュコードを持つ命令ブレークポイントのリストから取り除く
   242:         {
   243:           InstructionBreakRecord[] hashTable = supervisor != 0 ? ibpSuperHashTable : ibpUserHashTable;
   244:           int h = physicalAddress >>> 1 & IBP_HASH_MASK;  //物理ハッシュコード
   245:           InstructionBreakRecord t = hashTable[h];
   246:           if (t == r) {  //先頭にあった
   247:             hashTable[h] = r.ibrNext;
   248:           } else {
   249:             for (; t.ibrNext != r; t = t.ibrNext) {  //必ずあるはずなのでnullのチェックを省略する
   250:             }
   251:             t.ibrNext = r.ibrNext;
   252:           }
   253:           r.ibrNext = null;
   254:         }
   255:         //同じ物理ページに他に命令ブレークポイントがなければ物理ページの命令ブレークポイントを無効にする
   256:         {
   257:           int p = physicalAddress >>> XEiJ.BUS_PAGE_BITS;  //物理ページ番号
   258:           boolean more = false;
   259:           for (InstructionBreakRecord t : pointTable.values ()) {
   260:             if (t.ibrPhysicalAddress >>> XEiJ.BUS_PAGE_BITS == p) {
   261:               more = true;
   262:               break;
   263:             }
   264:           }
   265:           if (supervisor != 0) {
   266:             ibpSuperMap[p] = more ? MemoryMappedDevice.MMD_IBP : XEiJ.busSuperMap[p];
   267:           } else {
   268:             ibpUserMap[p] = more ? MemoryMappedDevice.MMD_IBP : XEiJ.busUserMap[p];
   269:           }
   270:         }
   271:         //命令ブレークポイントのマップから取り除く
   272:         pointTable.remove (logicalAddress);
   273:         //命令ブレークポイントがなくなったら命令ブレークポイントを無効にする
   274:         if (pointTable.isEmpty ()) {
   275:           if (supervisor != 0) {
   276:             ibpOp1SuperMap = XEiJ.busSuperMap;
   277:           } else {
   278:             ibpOp1UserMap = XEiJ.busUserMap;
   279:           }
   280:           ibpOp1MemoryMap = XEiJ.regSRS != 0 ? ibpOp1SuperMap : ibpOp1UserMap;  //ここはsupervisor!=0ではない
   281:         }
   282:       }
   283:     }
   284:   }  //ibpRemove(int,int)
   285: 
   286: }  //class InstructionBreakPoint
   287: 
   288: 
   289: