--- nono/debugger/debugger.cpp 2026/04/29 17:04:28 1.1.1.1 +++ nono/debugger/debugger.cpp 2026/04/29 17:04:51 1.1.1.8 @@ -1,139 +1,23 @@ // // nono -// Copyright (C) 2019 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // -#include "header.h" -#include "bus.h" -#include "consio.h" -#include "cvprompt.h" -#include "disasm.h" -#include "m68030core.h" -#include "memdump.h" -#include "mpu.h" -#include "mystring.h" +#include "console.h" +#include "debugger_private.h" +#include "debugger_m680x0.h" +#include "debugger_m88xx0.h" +#include "mainapp.h" +#include "mythread.h" +#include "scheduler.h" +#include #include -class Debugger -{ - private: - // 型 - typedef void (Debugger::*cmdfunc_t)(); - typedef struct { - const char *name; - cmdfunc_t func; - } cmddef_t; - - // ブレークポイント - typedef struct { - bool enable; - bool ismemory; - uint32 addr; - uint32 count; - } breakpoint_t; - static const int MAX_BREAKPOINTS = 8; - - public: - Debugger(); - virtual ~Debugger() { } - - void Init(); - void ThreadRun(); - bool Check(); - - private: - bool MainLoop(); - cmddef_t *ParseCmd(); - // コマンド名は大文字小文字を区別する関係でスネークスタイル。 - void cmd_b(); - void cmd_b_list(); - void cmd_brhist(); - void cmd_bx(); - void cmd_c(); - void cmd_d(); - void cmd_dt(); - void cmd_D(); - void cmd_h(); - void cmd_L(); - void cmd_m(); - void cmd_mt(); - void cmd_M(); - void cmd_minus(); - void cmd_n(); - void cmd_q(); - void cmd_r(); - void cmd_ra(); - void cmd_rf(); - void cmd_rm(); - void cmd_ro(); - void cmd_s(); - void cmd_so(); - void cmd_show(); - void cmd_t(); - void cmd_unknown(); - - int AddBreakpoint(uint32 addr, bool ismemory); - void Continue(); - bool GetAddr(uint32& addr, uint32& lastaddr); - void cmd_d_common(MemdumpMode mode); - void cmd_m_common(MemdumpMode mode); - bool ParseAddr(const char *arg, uint32_t *addrp); - const char *OPccCondStr(uint32); - bool IsOPcc(uint16); - bool IsDBcc(uint16); - bool IsCond(uint16); - bool SetNBreakpoint(); - - void HelpMain(); - - void ShowRegMain(); - void ShowRegFPU(); - void ShowRegMMU(); - void ShowRegOther(); - void ShowMonitor(TextScreen& monitor); - - m68kcpu *cpu = NULL; // CPU コア - m68kreg prev {}; // 前回のレジスタセットのコピー - - Consio *cons = NULL; // 入出力 - char cmdbuf[256] {}; // 現在のコマンドライン - char last_cmdbuf[256] {}; // 直前のコマンドライン - int ac = 0; - char *av[10] {}; - - uint32 pc = 0; - uint32 nextpc = 0; - breakpoint_t bpoint[MAX_BREAKPOINTS] {}; - bool bc_enable = false; - uint32 bc_addr = 0; - uint32 d_last_addr = 0; - uint32 m_last_addr = 0; - bool n_enable = false; - bool n_breakenable = false; - uint32 n_breakaddr = 0; - uint32 n_count = 0; - bool s_enable = false; - uint32 s_count = 0; - bool so_enable = false; - uint32 so_a7 = 0; - uint16 so_sr = 0; - bool t_enable = false; - uint32 t_count = 0; - - // 一度VMを実行して再びプロンプトに来たら true - bool is_continued = false; - - static cmddef_t cmdtable[]; - static cmddef_t cmdtable_unknown; -}; - -Debugger *gDebugger; +static Debugger *gDebugger; +BreakpointMonitor *gBreakpointMonitor; CVPrompt *gCVPrompt; -// コマンドラインオプション -bool debug_on_start; -uint32 debug_breakaddr; - static void *debugger_run(void *); // 初期化。 @@ -144,6 +28,7 @@ debugger_init() gDebugger = new Debugger(); gDebugger->Init(); + gBreakpointMonitor = new BreakpointMonitor(); gCVPrompt = new CVPrompt(); // デバッガスレッド起動 @@ -155,7 +40,7 @@ debugger_init() static void * debugger_run(void *dummy) { - pthread_setname_np("Debugger"); + PTHREAD_SETNAME("Debugger"); pthread_detach(pthread_self()); gDebugger->ThreadRun(); @@ -165,7 +50,13 @@ debugger_run(void *dummy) // コンストラクタ Debugger::Debugger() { - cpu = gMPU->GetCPU(); + if (gMPU680x0) { + md = new DebuggerMD_m680x0(this, gMPU680x0->GetCPU()); + } else if (gMPU88xx0) { + md = new DebuggerMD_m88xx0(this, gMPU88xx0->GetCPU()); + } else { + throw "unknown mpu"; + } } // コマンドライン引数によって動作を決めるところ。 @@ -174,14 +65,33 @@ void Debugger::Init() { // -d なら CPU 起動時点で停止してプロンプトを待つ - if (debug_on_start) { - cpu->atomic_reqflag |= CPU_REQ_PROMPT; + if (gMainApp.debug_on_start) { + md->ReqSet(CPU_REQ_PROMPT); } // -b ならブレークポイント設定 - if (debug_breakaddr != 0xffffffff) { - cpu->atomic_reqflag |= CPU_REQ_TRACE; - AddBreakpoint(debug_breakaddr, false); + for (auto& str : gMainApp.debug_breakaddr) { + breakpoint_t bp; + + md->ReqSet(CPU_REQ_TRACE); + + // , で分離できたら を取り出す + int pos = str.find(','); + if (pos != std::string::npos) { + std::string skipstr = str.substr(pos + 1); + bp.skip = atoi(skipstr.c_str()); + + // XXX これ出来るんだっけ? + str[pos] = '\0'; + } + // そしてどちらにしても を取り出す + if (!ParseAddr(str.c_str(), &bp.addr)) { + warnx("\"%s\": Invalid breakpoint address", str.c_str()); + return; + } + // 登録 + bp.type = BreakpointType::Address; + AddBreakpoint(bp); } // どこでやるべか @@ -193,94 +103,234 @@ void Debugger::ThreadRun() { // XXX どこかでコンソールの選択とパラメータの取得 - cons = new ConsioTCP(); + if (gMainApp.debug_on_console) { + cons = new ConsoleStdio(); + } else { + cons = new ConsoleTCP(); + } if (cons->Init() == false) { + delete cons; return; } + // disp_regs の初期値設定。 + // 再接続でも継続してていいような気がするのでループ外で初期化。 + disp_regs.clear(); + disp_regs.push_back("r"); + for (;;) { - cons->Open(); - cons->Print("This is debugger console\n"); + // 着信待ち + if (Accept() == false) { + break; + } - // 接続ごとに初期化する値 - // XXX もうちょっときれいにしたい - d_last_addr = 0xffffffff; - m_last_addr = 0xffffffff; - n_enable = false; - s_enable = false; - t_enable = false; - is_continued = true; - - // コンソールが繋がっている間がトレースオン - // コンソールが繋がるとプロンプトになる - cpu->atomic_reqflag |= CPU_REQ_TRACE | CPU_REQ_PROMPT; - while (MainLoop() == true) - ; - // コンソールを抜ける時はCPUの停止を解除。 - cpu->atomic_reqflag &= ~CPU_REQ_TRACE; - gCVPrompt->NotifyRelease(); + cons->InitEditLine("> "); + + // デバッガプロンプトにいる間はトレースオン + md->ReqSet(CPU_REQ_TRACE); + + bool first = true; + for (;;) { + // 接続後の1回目だけ実行するもの。 + if (first) { + first = false; + + // greeting はプロンプトが取れる前にもう表示したい。 + // 何らかの事故でプロンプトが取れなくても、ここまでは接続 + // できてることが分かるように。 + cons->Print("This is debugger console\n"); + + // 接続ごとに初期化する値 + n_enable = false; + s_enable = false; + t_enable = true; + } + + // プロンプトが取れるのを待つ + if (AcquirePrompt() == false) { + break; + } + + // 実時間を停止 + gRealtime.Stop(); + + // d/m をいきなり引数なしで実行した時のため、現在地にしておく。 + pc = md->GetPC(); + m_last_addr.Set(pc, md->IsSuper(), true); + d_last_addr.Set(pc, md->IsSuper(), false); + // プロンプトに来た時に表示するいつものやつ + cmd_minus(); + + // コマンド処理のメインループ + auto action = MainLoop(); + + // 次回との差分のため、今のレジスタセットをバックアップ + md->BackupRegs(); + + // 実時間を再開 + gRealtime.Start(); + + // メインループから戻ったということは Leave か Quit なので + // どちらにしても、ここでプロンプトを手放す。 + gCVPrompt->NotifyRelease(); + + // quit ならここでループを1段抜ける + if (action == CommandAction::Quit) { + break; + } + } + + // デバッガを抜けるのでトレースオフ + md->ReqClr(CPU_REQ_TRACE); cons->Close(); + if (gMainApp.debug_on_console) { + break; + } } delete cons; } -// プロンプト処理のメイン部分。 -// false を返すとコンソールをクローズする。 +// コンソールに接続されるのを待つ。 +// うーん、なんだこれ。 +// +// +--- debug_on_console (-D オプション) +// | +- debug_on_start (-d オプション) +// | | 動作 +// 0 0 TCP、Accept で待機。 +// 0 1 TCP、Accpet で待機。 +// 1 0 stdio、起動時にプロンプトで止まらない -> キー入力待ち。 +// 1 1 stdio、起動時にプロンプトで止まる。 bool -Debugger::MainLoop() +Debugger::Accept() { - // プロンプトが取れるのを待つ - gCVPrompt->WaitAcquire(cons); + if (gMainApp.debug_on_console == false) { + // TCP なら accept が成功するのを待つ + cons->Accept(); + } else { + if (gMainApp.debug_on_start == false) { + // stdio、起動時にプロンプトで止まらない。 + // この場合キー入力かブレークポイントなどで止まった時点で + // プロンプトを出したいのでちょっと面倒なことに。 + for (;;) { + bool is_prompt = gCVPrompt->WaitAcquire(200); + if (is_prompt) { + break; + } - // VM 実行からプロンプトに来たとき - if (is_continued) { - is_continued = false; - - pc = RegPC; - d_last_addr = pc; - // プロンプトに来た時に表示するいつものやつ - cmd_minus(); - } - - cons->Print("> "); - cons->Flush(); - if (cons->Gets(cmdbuf, sizeof(cmdbuf)) < 1) { - return false; + int r = cons->Poll(200); + if (r < 0) { + cmd_q(); + return false; + } + if (r > 0) { + break; + } + } + } else { + // stdio、起動時にプロンプトで止まる。 + // この場合は無条件に成功でよい。 + } } - // chomp - for (int i = 0; cmdbuf[i] != '\0'; i++) { - if (cmdbuf[i] == '\r' || cmdbuf[i] == '\n') { - cmdbuf[i] = '\0'; + return true; +} + +// プロンプトが取れるのを待つ +// 失敗すれば false を返す +bool +Debugger::AcquirePrompt() +{ + md->ReqSet(CPU_REQ_PROMPT); + + for (;;) { + bool is_prompt = gCVPrompt->WaitAcquire(200); + if (is_prompt) break; + + // コンソールを定期観測して、入力があればブレーク要求 + if (cons->Poll()) { + // この入力は drop してみる + if (cons->Gets(cmdbuf) == false) { + // EOF or Error + cmd_q(); + return false; + } + md->ReqSet(CPU_REQ_PROMPT); } } + return true; +} - if (cmdbuf[0] != '\0') { - // コマンドが入力されれば次回のために保存 - strcpy(last_cmdbuf, cmdbuf); - } else { - // 空行が入力されれば直前のコマンドをもう一度 - // 前行がなければ何もせずもう一度プロンプトを表示するかね - if (last_cmdbuf[0] == '\0') - return true; - strcpy(cmdbuf, last_cmdbuf); - } +// コマンド処理のメインループ。 +// コマンドが Stay ならこの中で処理を継続。 +// コマンドが Leave か Quit ならその値を持って戻る (呼び出し側で処理する)。 +Debugger::CommandAction +Debugger::MainLoop() +{ + for (;;) { + // ブレークポイント到達メッセージがあればここで表示 + if (bpointmsg.empty() == false) { + cons->Print("%s", bpointmsg.c_str()); + bpointmsg.clear(); + } - // 行を cmd, ac, av に分解 - cmddef_t *cmd = ParseCmd(); - if (cmd == NULL) { - // NULL は知らないコマンドではなく終了コマンド - return false; - } + cons->Prompt(); + if (cons->Gets(cmdbuf) == false) { + // EOF or Error + return CommandAction::Quit; + } - // 前回の値が有効なのはコマンドが連続した時だけ + string_rtrim(cmdbuf); - // コマンド実行 - (this->*(cmd->func))(); - return true; + if (!cmdbuf.empty()) { + // コマンドが入力されれば次回のために保存 + last_cmdbuf = cmdbuf; + } else { + // 空行が入力されれば直前のコマンドをもう一度 + // 前行がなければ何もせずもう一度プロンプトを表示するかね + if (last_cmdbuf.empty()) + continue; + cmdbuf = last_cmdbuf; + } + + // 行を args に分解 + ParseCmdbuf(); + + // 前回の値が有効なのはコマンドが連続した時だけ + + // コマンドをテーブルから探す + auto it = std::find_if(cmdtable.begin(), cmdtable.end(), + [=](auto x) { return strcmp(args[0].c_str(), x.name) == 0; }); + if (it != cmdtable.end()) { + auto cmd = *it; + + // 見付かれば実行 + (this->*(cmd.func))(); + + // Stay なら引き続きコマンド処理 + if (cmd.action == CommandAction::Stay) { + continue; + } + // それ以外ならメインループ終了 + return cmd.action; + } + + // (コマンドテーブルになくて) "r" から始まっていればレジスタ表示系 + if (args[0][0] == 'r') { + // ShowRegister() は処理したら true を返す。 + // "r" 系コマンドはすべて Stay。 + if (md->ShowRegister(cons, args)) { + continue; + } + } + + // 知らないコマンドも Stay 相当。 + cons->Print("%s: unknown command\n", args[0].c_str()); + continue; + } + __unreachable(); } // ブレークポイントとかを調べる。 @@ -296,184 +346,703 @@ debugger_check() bool Debugger::Check() { - if (t_enable) { - t_count--; - if (t_count == 0) { - t_enable = false; - return true; + bool is_break = false; + + // ブレークポイントは他のチェックとは併用になるので先に調べる。 + if (CheckAllBreakpoints()) { + is_break = true; + } + // アドレス指定付き continue もブレークポイントと似た動作なのでこっち。 + // XXX ブレークポイントとして実装するかどうか + if (bc_enable) { + if (bc_addr == md->GetPC()) { + bc_enable = false; + is_break = true; } } - if (s_enable) { + // XXX 残りは排他動作のはず + + // いずれの場合も、ステップ実行が終了条件にマッチしたら、ブレークポイント + // の成否に関わらず true を返せばいい。 + // 終了条件が来ないうちに先にブレークポイントに到達した場合でも、ブレーク + // ポイントによりプロンプトに降りるわけなので、実行中のステップ実行を + // キャンセルする。この場合もブレークポイント側が true なので true を + // 返せばよい。 + + // t (トレース表示) が enable なら、終了条件にマッチしなくてもここで + // レジスタを表示。終了条件にマッチする場合に表示するとプロンプトで + // もう一回表示されて二重になってしまうので注意。 + + if (s_enable) { // ステップ実行が.. s_count--; - if (s_count == 0) { + if (s_count == 0) { // 成立 + s_enable = false; + return true; + } else if (is_break) { // 非成立だがブレークが成立 s_enable = false; return true; + } else if (t_enable) { // どちらも非成立で trace on + cmd_minus(); } - } - // n による1命令実行のブレークポイントか - if (n_enable && n_breakenable && n_breakaddr == RegPC) { - n_breakenable = false; - return true; - } + } else if (so_enable) { // ステップアウトが.. + if (md->IsStepOut()) { // 成立 + so_enable = false; + return true; + } else if (is_break) { // 非成立だがブレークが成立 + so_enable = false; + return true; + } else if (t_enable) { // どちらも非成立で trace on + cmd_minus(); + } - // アドレス - for (int i = 0; i < countof(bpoint); i++) { - breakpoint_t *bp = &bpoint[i]; - if (bp->enable) { - if (bp->addr == RegPC) { - bp->count++; - return true; + } else if (n_enable) { // 次命令まで実行が.. + if (n_breakaddr != 0xffffffff) { + // ステップインをスキップ中 + if (n_breakaddr == md->GetPC()) { + n_count--; } + } else { + // 1命令実行 + n_count--; + } + if (n_count == 0) { // 成立 + n_enable = false; + return true; + } else if (is_break) { // 非成立だがブレークが成立 + n_enable = false; + return true; + } + if (t_enable) { // どちらも非成立で trace on + cmd_minus(); + } + + // スキップ中でなければ、ステップインが起きるか都度調べる。 + // すでにスキップ中なら到達するまでは何もしない。 + if (n_breakaddr == 0xffffffff) { + SetNBreakpoint(); } } - // ステップアウト - if (so_enable) { - if (RegA(7) > so_a7 || (RegSR & 0x3000) != so_sr) { - so_enable = false; - return true; + // ステップ実行系がなければブレークポイントの成否だけ + return is_break; +} + +// ブレークポイントがどれかでも成立するかを調べる。 +// 1つ以上成立してブレークするなら true を返す。 +// 1つも成立しておらずブレークしないなら false を返す。 +// このルーチン内では Console があることを期待してはいけない (プロンプトが +// ない時でも呼ばれるので)。 +bool +Debugger::CheckAllBreakpoints() +{ + bool is_break = false; + + // 命令ごとにクリアする + bi_inst = 0; + bi_inst_bytes = 0; + + // 1つの条件でマッチしても(そこでブレークすること自体は確定するのだが) + // 残りの他の条件も成立すればカウントを進める必要があるため、 + // 全部処理した上でどれか一つでもブレークしたかで判断する必要がある。 + for (int i = 0, end = bpoint.size(); i < end; i++) { + auto& bp = bpoint[i]; + + switch (bp.type) { + case BreakpointType::Address: + if (bp.addr != md->GetPC()) { + continue; + } + break; + + case BreakpointType::Memory: + if (!md->CheckLEA(bp.addr)) { + continue; + } + break; + + case BreakpointType::Exception: + if (bv_vector >= 0) { + if (bp.vec1 <= bv_vector && bv_vector <= bp.vec2) { + break; + } + } + continue; + + case BreakpointType::Instruction: + if (CheckBreakpointInst(bp) == false) { + continue; + } + break; + + default: + continue; + } + + // 条件は成立したのでカウントとスキップ + bp.matched++; + + // skip == -1 は常にブレークしない (カウントするだけ) + if (bp.skip < 0) { + continue; + } + + // skip が 1 以上なら、remain を減算 + if (bp.skip > 0 && --bp.skipremain > 0) { + continue; } + + // ブレーク成立 + is_break = true; + + std::string desc; + switch (bp.type) { + case BreakpointType::Address: + desc = string_format("addr $%08x", bp.addr); + break; + case BreakpointType::Memory: + desc = string_format("mem $%08x", bp.addr); + break; + case BreakpointType::Exception: + { + desc = string_format("excp $%02x", bv_vector); + const char *name = md->GetExceptionName(bv_vector); + if (name != NULL && name[0] != '\0') { + desc += string_format(" \"%s\"", name); + } + break; + } + case BreakpointType::Instruction: + desc = string_format("inst %0*x", + bi_inst_bytes * 2, + bi_inst >> ((4 - bi_inst_bytes) * 8)); + break; + default: + assert(false); + break; + } + + // 到達メッセージを作成。 + // この時点ではまだコンソールを取得していない可能性があるので + // (-D なしで起動した場合とか)、表示せず用意するだけ。 + // コンソールが取得できたところで表示する。 + bpointmsg += string_format("breakpoint #%d (%s) reached\n", + i, desc.c_str()); } - // アドレス指定付き continue - if (bc_enable) { - if (bc_addr == RegPC) { - bc_enable = false; - return true; + // 例外通知は通過ごとに常に下ろしておく + bv_vector = -1; + + // ブレークポイントが1つでも成立したかどうかを返す + return is_break; +} + +// 命令ブレークポイントが成立するか調べる。 +bool +Debugger::CheckBreakpointInst(breakpoint_t& bp) +{ + // uint32 bi_inst が現在の PC 位置の命令データ (左詰め) + // bi_inst_bytes が読み込んだバイト数(bi_inst の左からの有効バイト数)。 + // bi_need_bytes が現在のブレークポイントで読み込む必要のあるバイト数。 + + saddr_t laddr(md->GetPC(), md->IsSuper()); + DebuggerMemoryStream mem(md, laddr); + + // 必要なバイト数に達するまで読み足す + bi_inst = 0; + while (bi_inst_bytes < bi_need_bytes) { + uint64 data = mem.FetchInst(); + if ((int64)data < 0) { + return false; } + + bi_inst <<= md->inst_bytes * 8; + bi_inst |= data; + bi_inst_bytes += md->inst_bytes; } + // 左詰めにする + bi_inst <<= (4 - bi_inst_bytes) * 8; + + // 必要なバイト数取得できたので比較 + if ((bi_inst & bp.mask) == bp.inst) { + return true; + } return false; } +// 例外通知 (MPU からの連絡用) +void +debugger_notify_exception(int vector) +{ + gDebugger->NotifyException(vector); +} + +// 例外通知 (本体) +void +Debugger::NotifyException(int vector) +{ + bv_vector = vector; +} + // コマンド一覧。 -// quit コマンドは ParseCmd() 側で処理してある。 -Debugger::cmddef_t Debugger::cmdtable[] = { - { "bx", &Debugger::cmd_bx, }, - { "b", &Debugger::cmd_b, }, - { "brhist", &Debugger::cmd_brhist }, - { "c", &Debugger::cmd_c, }, - { "d", &Debugger::cmd_d, }, - { "dt", &Debugger::cmd_dt, }, - { "D", &Debugger::cmd_D, }, - { "h", &Debugger::cmd_h, }, - { "L", &Debugger::cmd_L, }, - { "m", &Debugger::cmd_m, }, - { "mt", &Debugger::cmd_mt, }, - { "M", &Debugger::cmd_M, }, - { "n", &Debugger::cmd_n, }, - { "r", &Debugger::cmd_r, }, - { "ra", &Debugger::cmd_ra, }, - { "rf", &Debugger::cmd_rf, }, - { "rm", &Debugger::cmd_rm, }, - { "ro", &Debugger::cmd_ro, }, - { "s", &Debugger::cmd_s, }, - { "so", &Debugger::cmd_so, }, - { "show", &Debugger::cmd_show, }, - { "t", &Debugger::cmd_t, }, - { "-", &Debugger::cmd_minus }, -}; -Debugger::cmddef_t Debugger::cmdtable_unknown = { - .name = "", - .func = &Debugger::cmd_unknown, +// "r" から始まるレジスタ表示系コマンドはコード中で別処理してある。 +/*static*/ std::vector Debugger::cmdtable = { + { "bi", &Debugger::cmd_bi, Debugger::CommandAction::Stay }, + { "bm", &Debugger::cmd_bm, Debugger::CommandAction::Stay }, + { "bv", &Debugger::cmd_bv, Debugger::CommandAction::Stay }, + { "bx", &Debugger::cmd_bx, Debugger::CommandAction::Stay }, + { "b", &Debugger::cmd_b, Debugger::CommandAction::Stay }, + { "brhist", &Debugger::cmd_brhist, Debugger::CommandAction::Stay }, + { "c", &Debugger::cmd_c, Debugger::CommandAction::Leave }, + { "d", &Debugger::cmd_d, Debugger::CommandAction::Stay }, + { "dt", &Debugger::cmd_dt, Debugger::CommandAction::Stay }, + { "D", &Debugger::cmd_D, Debugger::CommandAction::Stay }, + { "disp", &Debugger::cmd_disp, Debugger::CommandAction::Stay }, + { "exhist", &Debugger::cmd_exhist, Debugger::CommandAction::Stay }, + { "hb", &Debugger::cmd_hb, Debugger::CommandAction::Stay }, + { "hr", &Debugger::cmd_hr, Debugger::CommandAction::Stay }, + { "h", &Debugger::cmd_h, Debugger::CommandAction::Stay }, + { "help", &Debugger::cmd_h, Debugger::CommandAction::Stay }, + { "L", &Debugger::cmd_L, Debugger::CommandAction::Stay }, + { "m", &Debugger::cmd_m, Debugger::CommandAction::Stay }, + { "mt", &Debugger::cmd_mt, Debugger::CommandAction::Stay }, + { "M", &Debugger::cmd_M, Debugger::CommandAction::Stay }, + { "n", &Debugger::cmd_n, Debugger::CommandAction::Leave }, + { "nt", &Debugger::cmd_nt, Debugger::CommandAction::Leave }, + { "q", &Debugger::cmd_q, Debugger::CommandAction::Quit }, + { "quit", &Debugger::cmd_q, Debugger::CommandAction::Quit }, + { "reset", &Debugger::cmd_reset, Debugger::CommandAction::Leave }, + { "s", &Debugger::cmd_s, Debugger::CommandAction::Leave }, + { "st", &Debugger::cmd_st, Debugger::CommandAction::Leave }, + { "so", &Debugger::cmd_so, Debugger::CommandAction::Leave }, + { "sot", &Debugger::cmd_sot, Debugger::CommandAction::Leave }, + { "show", &Debugger::cmd_show, Debugger::CommandAction::Stay }, + { "t", &Debugger::cmd_t, Debugger::CommandAction::Leave }, + { "-", &Debugger::cmd_minus, Debugger::CommandAction::Stay }, }; // ヘルプ +// h : 一覧を表示 +// h : 単独コマンドの詳細を表示 void Debugger::cmd_h() { - if (ac == 1) { - // 引数なし - HelpMain(); - return; - } else { - // 引数あり(今の所対応していない) - HelpMain(); + if (args.size() < 2) { + // 引数なしなら一覧表示。 + ShowHelpList(HelpListMain); return; } + + // 引数があれば個別の詳細 + bool try_again = false; + std::string cmd = args[1]; + do { + // レジスタ系のヘルプを MD から取得して、ローカル変数で足す + auto details = HelpDetails; + for (const auto& dict : md->GetHelpReg()) { + details.push_back(dict); + } + + // そこから検索 + for (const auto& dict : details) { + if (cmd == dict.first) { + const auto& desc = dict.second; + if (desc[0] == '=') { + // "=" 形式なら を探しなおす。 + // 別名で同じヘルプを指すシンボリックリンクみたいなもの。 + cmd = desc.substr(1); + try_again = true; + break; + } + // そうでなければこれを表示して終了 + std::string disp = HelpConvert(dict.second); + cons->Print("%s", disp.c_str()); + return; + } + } + } while (try_again); + cons->Print("invalid command name: %s\n", args[1].c_str()); } +// hb : ブレークポイント系コマンドの一覧を表示 +// こいつだけ結構占めるので別階層。 void -Debugger::HelpMain() +Debugger::cmd_hb() { - cons->Print(" b ブレークポイント一覧表示\n"); - cons->Print(" b #n ブレークポイント n を削除\n"); - cons->Print(" b $ メモリブレークポイント設定\n"); - cons->Print(" b ブレークポイント設定\n"); - cons->Print(" bx ブレークポイント全削除\n"); - cons->Print(" brhist ブランチ履歴表示\n"); - cons->Print(" c [] 実行再開(continue)\n"); - cons->Print(" d [ [] 逆アセンブル(論理、テーブルサーチなし)\n"); - cons->Print(" dt [ [] 逆アセンブル(論理、テーブルサーチあり)\n"); - cons->Print(" D [ [] 逆アセンブル(物理)\n"); - cons->Print(" h ヘルプ(help)\n"); - cons->Print(" L = ログレベル設定\n"); - cons->Print(" m [ [] メモリダンプ(論理、テーブルサーチなし)\n"); - cons->Print(" mt [ [] メモリダンプ(論理、テーブルサーチあり)\n"); - cons->Print(" M [ [] メモリダンプ(物理)\n"); - cons->Print(" n ステップ実行 (サブルーチンを飛ばす)\n"); - cons->Print(" q quit\n"); - cons->Print(" r [a|f|m|o] レジスタ表示\n"); - cons->Print(" so ステップアウト\n"); - cons->Print(" show モニター表示\n"); - cons->Print(" t トレース実行\n"); -} - -// 知らないコマンドを処理するというコマンド -void -Debugger::cmd_unknown() -{ - cons->Print("%s: unknown command\n", av[0]); -} - -// cmdbuf を cmd, ac, av に分解する。 -// cmdbuf は破壊されるので以後は cmd, ac, av で参照のこと。 -// コマンド(の1ワード目)が一致しなければ cmd_unknown を返し、 -// 終了コマンドと一致したら NULL を返す。 -Debugger::cmddef_t * -Debugger::ParseCmd() + ShowHelpList(HelpListBreakpoints); +} + +// hr : レジスタ表示系コマンドの一覧を表示 +// CPU ごとに違うので。 +void +Debugger::cmd_hr() { - char *p; - cmddef_t *cmd; + ShowHelpList(md->GetHelpListReg()); +} - p = cmdbuf; - ac = 0; - memset(&av, 0, sizeof(av)); +// ヘルプ一覧を表示。 +void +Debugger::ShowHelpList(const HelpMessages& msgs) +{ + cons->Print("Type \"help \" for indivisual details.\n"); + for (const auto& pair : msgs) { + cons->Print(" %-16s %s\n", pair.first.c_str(), pair.second.c_str()); + } +} - // 引数を空白で分解 - for (; ac < countof(av); ) { - // 先頭の空白は取り除く - while (isspace((int)*p)) - p++; - if (*p == '\0') - break; +// 個別ヘルプメッセージを出力用に置換。 +// 1. 行頭の連続する改行 (通常は1つのはず) を取り除く。 +// 2. 末尾の連続するタブ (通常は1つのはず) を取り除く。 +// 3. (各行頭の) タブを空白4つに置換する。 +std::string +Debugger::HelpConvert(const std::string& src) +{ + int start = 0; + int len = src.size(); - av[ac++] = p; - // 次の空白まで進める - for (; *p != '\0'; p++) { - if (isspace((int)*p)) - break; + // 末尾の連続するタブをカウント + while (src[len - 1] == '\t') { + len--; + } + // 先頭の連続する改行をカウント + while (src[start] == '\n') { + start++; + len--; + } + std::string stripped = src.substr(start, len); + + // 面倒なので行頭に関わらず全タブを置換 + const char *c_stripped = stripped.c_str(); + std::string dst; + for (const char *s = c_stripped; *s; s++) { + if (*s == '\t') { + dst.append(" "); + } else { + dst.append(1, *s); } - if (*p != '\0') - *p++ = '\0'; } + return dst; +} - // コマンドを探す - for (int i = 0; i < countof(cmdtable); i++) { - cmd = &cmdtable[i]; +/*static*/ const HelpMessages +Debugger::HelpListMain = { + { "b*", "Set/show Breakpoints (Type \"hb\" for details)" }, + { "brhist", "Show branch history" }, + { "c", "Continue" }, + { "d/dt/D", "Disassemble" }, + { "disp", "Set register group to show" }, + { "exhist", "Show exception history" }, + { "help", "Show this message" }, + { "L", "Set log level" }, + { "m/mt/M", "Memory dump" }, + { "n/nt", "Step until next instruction (Skip subroutines)" }, + { "quit", "Quit" }, + { "r*", "Show registers (Type \"hr\" for details)" }, + { "reset", "Reset the VM" }, + { "s/st", "Step an instruction (Step in)" }, + { "so", "Step out" }, + { "show", "Show monitor" }, +}; - // デバッガから抜ける(終了する)コマンドだけここで処理 - if (strcmp(av[0], "q") == 0 || strcmp(av[0], "quit") == 0) - return NULL; +/*static*/ const HelpMessages +Debugger::HelpListBreakpoints = { + { "b", "Show all breakpoints" }, + { "b arg..","Set/Delete breakpoint" }, + { "bm", "Set memory breakpoint" }, + { "bi", "Set instruction breakpoint" }, + { "bv", "Set exception breakpoint" }, + { "bx", "Delete all breakpoints" }, +}; - if (strcmp(av[0], cmd->name) == 0) - return cmd; - } +// 各コマンドの詳細。"コマンド名" => "説明文" の形式。 +// 説明文は C+11 の生文字リテラル機能を使って R"**( )**" で囲む。 +// 1つのエントリは +// { "cmdname", R"**( +// cmdname [argument...] +// +// Description... +// )**" }, +// のようになる。説明文本文はインデント1つ(TAB 幅 4) で字下げした状態で +// 80桁以内に収めること。出力の際にタブを空白4つに置き換えることでソース上 +// での見た目と実際の出力とを揃えてある。 +// またソースコード上の見栄えの問題として、文字列の開始記号、終了記号を本文 +// とは別の行に書いているため、オブジェクトとしての文字列には先頭に改行、 +// 末尾にタブが余計に入っているが、これは表示の際にコードで取り除く。 +// +// 説明文の書き方は、まずコマンド書式を1行ずつ書く。 +// コマンド書式と本文との間は1行あける。 +/*static*/ const HelpMessages +Debugger::HelpDetails = { + //----- + { "b", R"**( + Command: b + Command: b
[] + Command: b #n + + The first form (with no arguments) shows all breakpoints. + The second form sets a new breakpoint on
. + XXX skipcount + If is -1, it will never match. It's useful to count the + number of times you have passed this address. + The third form deletes a breakpoint specified by number. + )**" }, + + //----- + { "bm", R"**( + Command: bm
[] + + Sets a memory breakpoint on
. + XXX skipcount + If is -1, it will never match. It's useful to count the + number of times you have passed this address. + )**" }, + + //----- + { "bi", R"**( + Command: bi [:] [] + + Sets an instruction breakpoint. must be 16 bits or 32 bits on + m68k and must be 32 bits on m88k. can specify the mask. Its + length must be the same as . If the is ommited, all bits + of is used to compare. + For example, + "bi 4e75" on m68k will stop at 'rts' instruction. + "bi 70004e4f:fff0ffff" on m68k will stop at any of 'IOCS #0'..'IOCS #15'. + + XXX skipcount + If is -1, it will never match. It's useful to count the + number of times you have passed this address. + )**" }, + + //----- + { "bv", R"**( + Command: bv [-] [] + + Sets an exception breakpoint. must be specified in hex. + ranges from 0 to ff (255 in decimal) on m68k, and 0 to 1ff (511 + in decimal) on m88k. If is specified, it matches the range + from to (including themselves). + For example, "bv 1-1ff" on m88k means all exceptions but reset. + XXX skipcount + If is -1, it will never match. It's useful to count the + number of times you have passed this address. + )**" }, + + //----- + { "bx", R"**( + Command: bx + + Deletes all breakpoints. + )**" }, + + //----- + { "brhist", R"**( + Command: brhist [] + + Show branch history. specifies the maximum number of lines + to display. If is negative value, sort in reverse order. + )**" }, + + //----- + { "c", R"**( + Command: c [
] + + Continue (until
if specified). + )**" }, + + //----- + { "d", R"**( + Command: D
] [] + Command: d [[:]
] [] + Command: dt [[:]
] [] + + Shows disassemble. + The first form ("D") interprets
as a physical address. + The second and third form ("d", "dt") interprets
as a logical + address if the address translation is enabled. Normally,
is + interpreted in the current privilege and current address space. You can + change it by modifier. + On m68k, can be specified either by function code number directly + ('1', '2', '5', and '6) or by one or more following modifiers: + 's' .. Supervisor mode 'u' .. User mode + 'd' .. Data space 'p' or 'i' .. Program space + On m88k, can be specified by using combination of privilege + modifier ('s' or 'u', same as above) and following CMMU identifiers: + 'd' .. Data CMMU 'i' or 'p' .. Instruction CMMU + Or CMMU can also be identified by CMMU number (like '6' or '7'). + + "d" only looks up in ATC (on m68k) or in BATC/PATC (on m88k). + "dt" will simulate to search the page table in addition to that. + )**" }, + { "dt", "=d" }, + { "D", "=d" }, + + //----- + { "disp", R"**( + Command: disp + + Sets the registers list to be shown in the trace. + is comma-separated list and each of which is a command + name that shows registers. See "hr". + The default is "r" and thus it only shows general registers in the trace. + For example, if you set "disp r,rf", it will show general registers and + FPU registers in every trace. + )**" }, + + //----- + { "exhist", R"**( + Command: exhist [] + + Show exception history. specifies the maximum number of lines + to display. If is negative value, sort in reverse order. + )**" }, + + //----- + { "help", R"**( + Command: help [] + Command: hb + Command: hr + + The "help" command without arguments shows the list of commands. + The "help" command with an argument shows 's detailed help. + "h" is a synonym of "help". + The "hb" command shows the list of breakpoint-related commands. + The "hr" command shows the list of register-related commands. + )**" }, + { "h", "=help" }, + { "hb", "=help" }, + { "hr", "=help" }, + + //----- + { "L", R"**( + Command: L + + Set loglevel. XXX To be written... + )**" }, + + //----- + { "m", R"**( + Command: M
] [] + Command: m [[:]
] [] + Command: mt [[:]
] [] + + Shows memory dump. + The first form ("M") interprets
as a physical address. + The second and third form ("m", "mt") interprets
as a logical + address if the address translation is enabled. Normally,
is + interpreted in the current privilege and current address space. You can + change it by modifier. + On m68k, can be specified either by function code number directly + ('1', '2', '5', and '6) or by one or more following modifiers: + 's' .. Supervisor mode 'u' .. User mode + 'd' .. Data space 'p' or 'i' .. Program space + On m88k, can be specified by using combination of privilege + modifier ('s' or 'u', same as above) and following CMMU identifiers: + 'd' .. Data CMMU 'i' or 'p' .. Instruction CMMU + Or CMMU can also be identified by CMMU number (like '6' or '7'). + + "m" only looks up in ATC (on m68k) or in BATC/PATC (on m88k). + "mt" will simulate to search the page table in addition to that. + )**" }, + { "mt", "=m" }, + { "M", "=m" }, + + //----- + { "n", R"**( + Command: n [] + Command: nt [] + + Step one (or ) instructions. Unlike "s" command, "n" skips + subroutine. + If "t" is suffixed, it shows a trace for each instruction (including + while skipping). + )**" }, + { "nt", "=n" }, + + //----- + { "quit", R"**( + Command: quit (or q) + + Quit the debugger console. This continues the execution, as same as "c". + )**" }, + { "q", "=quit" }, + + //----- + { "reset", R"**( + Command: reset + + Reset the VM. Currently this does the hardware reset. + )**" }, + + //----- + { "s", R"**( + Command: s [] + Command: st [] + + Step one (or ) instructions. Unlike "n" command, "s" steps in + subroutine. + If "t" is suffixed, it shows a trace for each instruction. + + "t" command is a synonym of "st" for backward compatibility. + )**" }, + { "st", "=s" }, + + //----- + { "so", R"**( + Command: so + Command: sot + + Step out this sub routine. + If "t" is suffixed, it shows a trace for each instruction. + )**" }, + + //----- + { "show", R"**( + Command: show + + Shows the specified monitor. + )**" }, + + //----- + { "t", "=s" }, +}; + +// cmdbuf を args... に分解する。 +void +Debugger::ParseCmdbuf() +{ + int pos; + int start; - // 見つからなければ cmd_unknown コマンドを実行。 - return &cmdtable_unknown; + pos = 0; + args.clear(); + + // 引数を空白で分解 + for (;;) { + // 先頭の空白はスキップ + for (; cmdbuf[pos] != '\0'; pos++) { + if (!isspace((unsigned int)cmdbuf[pos])) + break; + } + if (cmdbuf[pos] == '\0') + break; + + // 終わりを探す + start = pos; + for (; cmdbuf[pos] != '\0'; pos++) { + if (isspace((unsigned int)cmdbuf[pos])) + break; + } + args.push_back(cmdbuf.substr(start, pos - start)); + } + // デバッグ表示 + if (0) { + for (int i = 0 ; i < args.size(); i++) { + printf("args[%d]=|%s|\n", i, args[i].c_str()); + } + } } // @@ -481,85 +1050,330 @@ Debugger::ParseCmd() // // ブレークポイント +// b ... 一覧表示 +// b #n ... #n を削除 +// b [] ... 設定 void Debugger::cmd_b() { // 引数なしなら一覧表示 - if (ac < 2) { + if (args.size() < 2) { return cmd_b_list(); } // 引数取得 - if (av[1][0] == '#') { + if (args[1][0] == '#') { // # 形式なら、指定番号のブレークポイントを削除 - int i = atoi(&av[1][1]); - if (i < 0 || i >= MAX_BREAKPOINTS) { - cons->Print("invaild break point number: #%d\n", i); + int i = atoi(&args[1][1]); + if (i < 0 || i >= bpoint.size()) { + cons->Print("invaild breakpoint number: #%d\n", i); return; } - breakpoint_t *bp = &bpoint[i]; - if (bp->enable) { - cons->Print("%sbreakpoint #%d (%08x) removed\n", - bp->ismemory ? "memory " : "", i, bp->addr); - bp->enable = false; - } else { - cons->Print("breakpoint #%d not enabled\n", i); + auto& bp = bpoint[i]; + if (bp.type == BreakpointType::Unused) { + cons->Print("invalid breakpoint number: #%d\n", i); + return; } + cons->Print("breakpoint #%d (%08x) removed\n", + i, bp.addr); + bp.type = BreakpointType::Unused; + // 今登録されている命令ブレークの必要命令長を再計算 + RecalcInstMask(); return; } - bool ismemory; - uint32 addr; - if (av[1][0] == '$') { - // $ 形式ならメモリブレークポイント - ismemory = true; - addr = strtoul(&av[1][1], NULL, 16); + return cmd_b_set(BreakpointType::Address); +} + +// メモリブレークポイントの設定 +// bm [] +void +Debugger::cmd_bm() +{ + // XXX m68k では未サポート + if (md->arch == Arch::M680x0) { + cons->Print("bm not supported yet on m68k\n"); + return; + } + cmd_b_set(BreakpointType::Memory); +} + +// type が違うだけの各種ブレークポイント設定の共通部分。 +void +Debugger::cmd_b_set(BreakpointType type) +{ + breakpoint_t bp; + + if (args.size() < 2) { + cons->Print("usage: %s []\n", args[0].c_str()); + return; + } + + // アドレス + if (!ParseAddr(args[1].c_str(), &bp.addr)) { + return; + } + // あればスキップカウント + if (args.size() > 2) { + bp.skip = atoi(args[2].c_str()); + } + + // 空いてるところにセット + // (よく似たエントリがあっても干渉しない) + bp.type = type; + int bi = AddBreakpoint(bp); + if (bi == -1) { + cons->Print("no free breakpoints\n"); + } else { + cons->Print("breakpoint #%d added\n", bi); + } +} + +// 命令ブレークポイントの設定 +// bi [:] [] +void +Debugger::cmd_bi() +{ + breakpoint_t bp; + std::string inststr; + std::string maskstr; + int instlen; + int masklen; + + if (args.size() < 2) { + cons->Print("usage: bi [:] []\n"); + return; + } + + // 引数をまず分離 + auto pos = args[1].find(':'); + if (pos == std::string::npos) { + // マスク指定なし + inststr = args[1]; + instlen = inststr.size(); + masklen = -1; + } else { + // マスク指定あり + inststr = args[1].substr(0, pos); + instlen = inststr.size(); + maskstr = args[1].substr(pos + 1); + masklen = maskstr.size(); + } + + // 命令部チェック + if (ParseVerbHex(inststr.c_str(), &bp.inst) == false) { + cons->Print("%s: invalid instruction value\n", args[1].c_str()); + return; + } + if (instlen % (md->inst_bytes * 2) != 0) { + cons->Print("%s: invalid instruction length\n", args[1].c_str()); + return; + } + + // マスク部チェック + bp.mask = 0xffffffff; + if (masklen != -1) { + if (ParseVerbHex(maskstr.c_str(), &bp.mask) == false) { + cons->Print("%s: invalid mask value\n", args[1].c_str()); + return; + } + if (masklen != instlen) { + cons->Print("%s: inst:mask must be the same length\n", + args[1].c_str()); + return; + } + } + // 8バイト未満なら左詰め。 + if (md->inst_bytes < 4 && instlen < 8) { + bp.inst <<= 32 - instlen * 4; + bp.mask <<= 32 - instlen * 4; + } + + // あればスキップカウント + bp.skip = 0; + if (args.size() > 2) { + bp.skip = atoi(args[2].c_str()); + } + + // 空いてるところにセット + bp.type = BreakpointType::Instruction; + int bi = AddBreakpoint(bp); + if (bi == -1) { + cons->Print("no free breakpoints\n"); } else { - // そうでなければアドレス指定 - ismemory = false; - addr = strtoul(av[1], NULL, 16); + cons->Print("breakpoint #%d added\n", bi); + } + + // 今登録されている命令ブレークの必要命令長を再計算 + RecalcInstMask(); +} + +// 例外ブレークポイントの設定 +// bv [-] [] +void +Debugger::cmd_bv() +{ + breakpoint_t bp; + + if (args.size() < 2) { + cons->Print("usage: be [-] []\n"); + return; } - // すでにあればそのブレークポイントを削除 - for (int i = 0; i < MAX_BREAKPOINTS; i++) { - breakpoint_t *bp = &bpoint[i]; - if (bp->enable && bp->ismemory == ismemory && bp->addr == addr) { - bp->enable = false; - cons->Print("%sbreakpoint #%d (%08x) removed\n", - bp->ismemory ? "memory " : "", i, bp->addr); + auto pos = args[1].find('-'); + if (pos == std::string::npos) { + // ベクタ番号が1つなら vec1, vec2 を同値にしておく。 + if (ParseVerbHex(args[1].c_str(), (uint32 *)&bp.vec1) == false) { + cons->Print("%s: invalid vector number\n", args[1].c_str()); + return; + } + bp.vec2 = bp.vec1; + } else { + // ベクタ番号(範囲指定 [vec1, vec2]) + std::string str1 = args[1].substr(0, pos); + std::string str2 = args[1].substr(pos + 1); + + if (ParseVerbHex(str1.c_str(), (uint32 *)&bp.vec1) == false) { + cons->Print("%s: invalid first vector number\n", args[1].c_str()); + return; + } + if (ParseVerbHex(str2.c_str(), (uint32 *)&bp.vec2) == false) { + cons->Print("%s: invalid last vector number\n", args[1].c_str()); return; } } + // 範囲チェック + if (bp.vec1 < 0 || bp.vec1 >= md->vector_max) { + cons->Print("$%x: invalid vector number\n", bp.vec1); + return; + } + if (bp.vec2 < 0 || bp.vec2 >= md->vector_max) { + cons->Print("$%x: invalid last vector number\n", bp.vec2); + return; + } + + // 大小が逆なら入れ替える? + if (bp.vec1 > bp.vec2) { + int tmp; + tmp = bp.vec1; + bp.vec1 = bp.vec2; + bp.vec2 = tmp; + } + + // あればスキップカウント + bp.skip = 0; + if (args.size() > 2) { + bp.skip = atoi(args[2].c_str()); + } + // 空いてるところにセット - int bi = AddBreakpoint(addr, ismemory); + bp.type = BreakpointType::Exception; + int bi = AddBreakpoint(bp); if (bi == -1) { - cons->Print("no free breakpoints!\n"); + cons->Print("no free breakpoints\n"); } else { - breakpoint_t *bp = &bpoint[bi]; - cons->Print("%sbreakpoint #%d (%08x) added\n", - bp->ismemory ? "memory " : "", bi, bp->addr); + cons->Print("breakpoint #%d added\n", bi); } + + // すでに来ている例外をクリア。 + // ブレークポイント設定の有無に関わらず例外が起きたら CPU 側から常に + // 通知されている。これをクリアするのは CheckAllBreakpoints() で、これは + // 命令間(命令前)に呼ばれるやつ、なのでこうなる。 + // 1. 例外が起きると bv_vector がセットされる + // 2. 例外ブレークポイントを設定していないとこれがクリアされない + // 3. bv コマンドで例外ブレークを新たに設定すると、次の命令境界で + // 1.のベクタが反応してしまう。 + // 命令ごととかにクリアしてもいいかも知れないが、ここでブレークポイントを + // 設定したのだから、それ以前の事象には反応すべきでない、という意味では + // ここでもいいか? + bv_vector = -1; } // ブレークポイント一覧表示 void Debugger::cmd_b_list() { - int found = 0; + ShowMonitor(*gBreakpointMonitor); +} + +// ブレークポイント一覧 (モニタ) +void +Debugger::MonitorBreakpoint(TextScreen& monitor) +{ + // 0 1 2 3 4 5 6 + // 0123456789012345678901234567890123456789012345678901234567890 + // No Type Parameter Matched Skip + // #0 addr $01234567 123456789 123456789/123456789 + // #1 inst 00000000/00000000 + // #2 excp $00-$00 + + monitor.Clear(); + monitor.Print(0, 0, "No Type Parameter"); + monitor.Print(26, 0, "Matched"); + monitor.Print(37, 0, "Skip"); + + for (int i = 0; i < bpoint.size(); i++) { + const auto& bp = bpoint[i]; + int y = i + 1; + + monitor.Print(0, y, "#%d", i); + + // 種別ごとの表示 + switch (bp.type) { + case BreakpointType::Unused: + continue; + + case BreakpointType::Address: + monitor.Print(3, y, "addr $%08x", bp.addr); + break; + case BreakpointType::Memory: + monitor.Print(3, y, "mem $%08x", bp.addr); + break; - for (int i = 0; i < MAX_BREAKPOINTS; i++) { - breakpoint_t *bp = &bpoint[i]; - if (bp->enable) { - cons->Print(" #%d %s%08x %d\n", - i, - bp->ismemory ? "memory $" : "", - bp->addr, bp->count); - found = 1; + case BreakpointType::Exception: + monitor.Print(3, y, "excp $%02x", bp.vec1); + if (bp.vec2 != bp.vec1) { + monitor.Print(11, y, "-$%02x", bp.vec2); + } + break; + + case BreakpointType::Instruction: + monitor.Print(3, y, "inst"); + if (md->inst_bytes == 4) { + if (bp.mask == 0xffffffff) { + monitor.Print(8, y, "%08x", bp.inst); + } else { + monitor.Print(8, y, "%08x:%08x", bp.inst, bp.mask); + } + } else { + if (bp.mask == 0xffffffff) { + monitor.Print(8, y, "%08x", bp.inst); + } else if (((bp.inst | bp.mask) & 0x0000ffff) != 0) { + monitor.Print(8, y, "%08x:%08x", bp.inst, bp.mask); + } else if (bp.mask == 0xffff0000) { + monitor.Print(8, y, "%04x", bp.inst >> 16); + } else { + monitor.Print(8, y, "%04x:%04x", + bp.inst >> 16, bp.mask >> 16); + } + } + break; + + default: + monitor.Print(3, y, "type=%d", (int)bp.type); + continue; + } + + // マッチ回数 + monitor.Print(26, y, "%d", bp.matched); + + // スキップ + if (bp.skip < 0) { + monitor.Print(37, y, "forever"); + } else if (bp.skip > 0) { + monitor.Print(37, y, "%d / %d", (bp.skip - bp.skipremain), bp.skip); } - } - if (found == 0) { - cons->Print(" No breakpoints enabled\n"); } } @@ -567,38 +1381,132 @@ Debugger::cmd_b_list() void Debugger::cmd_bx() { - for (int i = 0; i < MAX_BREAKPOINTS; i++) { - breakpoint_t *bp = &bpoint[i]; - bp->enable = false; + for (auto& bp : bpoint) { + bp.type = BreakpointType::Unused; } cons->Print(" All breakpoints disabled\n"); + + // 今登録されている命令ブレークの必要命令長を再計算 + RecalcInstMask(); } // ブレークポイントを設定。 -// 設定できればその番号、できなければ -1 を返す -// TODO 重複の処理とか +// new_bp のうち matched, skipremain はこちらで初期化する。 +// それ以外を埋めてから呼ぶこと。 +// 設定できればその番号、できなければ -1 を返す。 int -Debugger::AddBreakpoint(uint32 addr, bool ismemory) +Debugger::AddBreakpoint(const breakpoint_t& new_bp) { - for (int i = 0; i < countof(bpoint); i++) { - breakpoint_t *bp = &bpoint[i]; - if (!bp->enable) { - bp->enable = true; - bp->addr = addr; - bp->ismemory = ismemory; - bp->count = 0; + for (int i = 0; i < bpoint.size(); i++) { + auto& bp = bpoint[i]; + if (bp.type == BreakpointType::Unused) { + bp = new_bp; + bp.matched = 0; + if (bp.skip > 0) { + bp.skipremain = bp.skip; + } else { + bp.skipremain = 0; + } + return i; } } return -1; } -// ブランチ履歴表示 +// すべての命令ブレークのマスクのうち最長のものを再計算する。 +// 命令ブレークポイントの追加/削除のたびに呼び出すこと。 +void +Debugger::RecalcInstMask() +{ + uint32 mask; + bi_need_bytes = 0; + + // 登録されている命令ブレークの最長マスクを求める + mask = 0; + for (const auto& bp : bpoint) { + if (bp.type == BreakpointType::Instruction) { + mask |= bp.mask; + } + } + + // mask の Number of Trailing Zero を求める。 + // (x & -x) で x の最も下の立ってるビットだけを立てる、 + // (x & -x) -1 でそれより下の全ビットを立てる、 + // それを popcount で数えるので、$fffffff0 なら ntz = 4 になる。 + int ntz = __builtin_popcount((mask & -(int32)mask) - 1); + // 上位側から数えたマスクに必要なビット数 + int mlen = 32 - ntz; + // 命令語単位に切り上げる + mlen = roundup(mlen, md->inst_bytes * 8); + // バイト数に変換 + mlen /= 8; + + if (mlen > bi_need_bytes) { + bi_need_bytes = mlen; + } +} + +// ブランチ履歴、例外履歴表示 +// brhist [] +// exhist [] +// は表示する最大エントリ数。 +// コンソールではデフォルトで下を新しいの順とする。 を負数にすると +// (行数は絶対値して) 並び順を逆にしてモニタウィンドウと同じ上を新しいの順に +// する。 void Debugger::cmd_brhist() { - gMPUBrHist->MonitorUpdate(); - ShowMonitor(gMPUBrHist->monitor); + cmd_hist_common(md->GetBrHist(), 0); +} +void +Debugger::cmd_exhist() +{ + cmd_hist_common(md->GetExHist(), BranchHistory::ExHist); +} + +// ブランチ履歴、例外履歴表示の共通部分。 +void +Debugger::cmd_hist_common(BranchHistory& hist, uint64 flag) +{ + // 表示最大行数(と向き) + // 向きは bottom_to_top = true が新しいほうを下とする方向。 + int maxlines = 20; + bool bottom_to_top = true; + if (args.size() > 1) { + maxlines = atoi(args[1].c_str()); + if (maxlines < 0) { + bottom_to_top = false; + maxlines = -maxlines; + } + + if (maxlines < 1) + maxlines = 1; + if (maxlines > 256) + maxlines = 256; + } + + // コンソールでは空エントリの行は表示したくないので、 + // 先にエントリ数を調べる。 + int used = hist.GetUsed(); + + // 表示行数 + 1行はヘッダ分 + int lines = std::min(used, maxlines) + 1; + + // MonitorUpdate() は TextScreen 高さに合わせて出力してくれる。 + auto size = hist.GetMonitorSize(); + TextScreen tscr; + tscr.Init(size.width, lines); + + // コンソールでは下が新しいの順のほうがいい + if (bottom_to_top) { + flag |= BranchHistory::BottomToTop; + } + tscr.userdata = flag; + + // 表示 + hist.MonitorUpdate(tscr); + ShowTextScreen(tscr); } // 実行再開(continue): c [] @@ -606,9 +1514,9 @@ Debugger::cmd_brhist() void Debugger::cmd_c() { - if (ac > 1) { + if (args.size() > 1) { // 引数があれば - if (!ParseAddr(av[1], &bc_addr)) { + if (!ParseAddr(args[1].c_str(), &bc_addr)) { return; } // 偶数番地に丸める @@ -616,95 +1524,244 @@ Debugger::cmd_c() bc_enable = true; } - Continue(); } -// 実行再開 -void -Debugger::Continue() +// 引数などからアドレスを取得するごった煮共通ルーチン。 +// +// 引数がなければ sa->addr には書き込まずに true で帰る (ので、呼び出し側が +// 事前に適切なデフォルト値をセットしておくと、それがそのまま使われる)。 +// 引数が1つ(以上)あれば args[1] をアドレスとしてパースする。 +// アドレスには空間修飾子を前置可能、省略の場合はいずれも現在値を使う。 +// エラーならメッセージを表示して false を返す。 +// sa は in/out パラメータ。 +bool +Debugger::GetAddr(saddr_t *sa, bool default_data) { - is_continued = true; + if (args.size() < 2) { + // 引数なしなら、呼び出し側が sa にセットした前回値をそのまま使う。 + return true; + } - // 今のレジスタセットを次回との差分のためにバックアップしておく - prev = cpu->reg; + // 引数ありならパースする。アドレスが明示的に指定されたので、 + // ここから先の省略箇所は前回値ではなくデフォルト値で補完する。 - gCVPrompt->NotifyRelease(); -} + std::string& str = args[1]; + uint32 addr; -// 引数などからアドレスを取得する共通ルーチン。 -// 引数が1つ(以上)あれば av[1] をアドレスとしてパースする。 -// なければ、lastaddr が指定されていればそれを使う。 -// それもなければ、RegPC を使う。 -bool -Debugger::GetAddr(uint32& addr, uint32& lastaddr) -{ - if (ac > 1) { - // 1つ目の引数があれば、開始アドレス - if (ParseAddr(av[1], &addr) == false) { + // ':' があればその前はアドレス空間修飾子 + int mod_super = -1; // 'u'/'s' + int mod_prog = -1; // 'd'/'p' or 'd'/'i' + int mod_num = -1; // '0'..'7' + auto addrpos = str.find(':'); + if (addrpos == std::string::npos) { + // なければ先頭からアドレス + addrpos = 0; + } else { + // ':' が途中にあれば、その前が修飾子。 + // ':' が先頭なら修飾子が空文字列という扱いでいいか。 + std::string mod = str.substr(0, addrpos); + addrpos++; + for (auto ch : mod) { + if (ch == 'u') { + if (mod_super == 1) + goto error; + mod_super = 0; + } else if (ch == 's') { + if (mod_super == 0) + goto error; + mod_super = 1; + } else if (ch == 'd') { + if (mod_prog == 1) + goto error; + mod_prog = 0; + } else if (ch == 'p' || ch == 'i') { + // m68k では 'P'rogram space、m88k では 'I'nstruction CMMU… + if (mod_prog == 0) + goto error; + mod_prog = 1; + } else if ('0' <= ch && ch <= '7') { + int num = ch - '0'; + if (md->arch == Arch::M680x0) { + // m68k では FC 指定は Super/User 指定を含んでおり、 + // 値指定は他の修飾子とは同時に指定できない。 + if (mod_super >= 0 || mod_prog >= 0 || + (mod_num >= 0 && mod_num != num)) { + goto error; + } + // 有効な値は 1,2,5,6 のみ + if (num == 0 || num == 3 || num == 4 || num == 7) { + cons->Print("%c: invalid address space\n", ch); + } + mod_num = num; + mod_super = mod_num >> 2; // FC2 + mod_prog = (mod_num & 3) - 1; // FC1,FC0 + } else if (md->arch == Arch::M88xx0) { + // m88k では CMMU 指定と、Super/User 指定は独立。 + if (mod_prog >= 0 || + (mod_num >= 0 && mod_num != num)) { + goto error; + } + mod_num = num; + mod_prog = (mod_num & 1); // CMMU7 が Inst + } + } else { + cons->Print("%c: unknown address space modifier\n", ch); + return false; + } + continue; + + error: + cons->Print("%s: address space modifier '%c' conflicts\n", + mod.c_str(), ch); return false; } - // 偶数番地からに丸める - addr &= 0xfffffffe; - } else if (lastaddr != 0xffffffff) { - // 引数なしで前回値があれば前回の続き - addr = lastaddr; - } else { - // そうでなければ、今の PC - addr = RegPC; + + // 数値指定を機種ごとに読み替える + if (mod_num >= 0) { + if (md->arch == Arch::M680x0) { + // m68k なら値指定で全部確定する + // "1:" -> User/Data + // "2:" -> User/Program + // "5:" -> Super/Data + // "6:" -> Super/Program + mod_super = (mod_num & 4) ? true : false; + mod_prog = (mod_num & 3) - 1; + } else if (md->arch == Arch::M88xx0) { + // m88k では mod_num を CMMU ID とする(?)。 + // XXX まだ CPU は1つしかないので雑に処理。 + // "6" -> Data (CPU#0) + // "7" -> Program (CPU#0) + if (mod_num < 6) { + cons->Print("%d: CMMU#%d not installed\n", + mod_num, mod_num); + return false; + } + mod_prog = mod_num - 6; + } + } + } + + // アドレス空間修飾子のうち省略箇所はデフォルト状態で補完 + // - 's'/'u' いずれもなければ現在値。 + // - 'd'/'p'/'i' いずれもなければ、d/m コマンドごとに自然なほう。 + if (mod_super < 0) { + mod_super = md->IsSuper(); + } + if (mod_prog < 0) { + mod_prog = !default_data; + } + sa->SetSuper(mod_super); + sa->SetData(!mod_prog); + + // アドレス + if (ParseAddr(str.c_str() + addrpos, &addr) == false) { + return false; } + // 命令境界に丸める + sa->SetAddr(addr & ~(md->inst_bytes - 1)); + return true; } -// 逆アセンブル: d [ []] (論理、テーブルサーチなし) +// 逆アセンブル: d [[SU:] []] (論理、テーブルサーチなし) void Debugger::cmd_d() { - cmd_d_common(MemdumpMode::Logical_atc); + cmd_d_common(MemoryMode::Logical, MMULookupMode::False); } -// 逆アセンブル: dt [] []] (論理、テーブルサーチあり) +// 逆アセンブル: dt [[SU:]] []] (論理、テーブルサーチあり) void Debugger::cmd_dt() { - cmd_d_common(MemdumpMode::Logical_search); + cmd_d_common(MemoryMode::Logical, MMULookupMode::True); } // 逆アセンブル: D [ []] (物理) void Debugger::cmd_D() { - cmd_d_common(MemdumpMode::Physical); + cmd_d_common(MemoryMode::Physical, MMULookupMode::None); } // 逆アセンブル共通 void -Debugger::cmd_d_common(MemdumpMode mode) +Debugger::cmd_d_common(MemoryMode access_mode, MMULookupMode lookup_mode) { - uint32 addr; + saddr_t saddr; int cnt; cnt = 10; // 開始アドレス - if (GetAddr(addr, d_last_addr) == false) { + saddr = d_last_addr; + if (GetAddr(&saddr, false) == false) { return; } // 2つ目の引数があれば行数 - if (ac > 2) { - cnt = strtol(av[2], NULL, 10); + if (args.size() > 2) { + cnt = strtol(args[2].c_str(), NULL, 10); } - DisasmLine dis(cpu, addr, mode); + DebuggerMemoryStream mem(md, saddr, access_mode, lookup_mode); + for (int i = 0; i < cnt; i++) { - std::string buf = dis.Output(); - cons->Print("%s\n", buf.c_str()); - // バスエラーならこの行で終了。 - // XXX バスエラーかどうかを文字列判定してるのはさすがにどうかと - if (buf.find("BusErr") != std::string::npos) { + std::string addrbuf; + std::string mnemonic; + std::string mnembuf; + std::vector local_ir; + + // アドレス + if (mem.FormatAddr(addrbuf) == false) { + // アドレス変換でバスエラーならここで終了 + cons->Print("%s\n", addrbuf.c_str()); + break; + } + + // 逆アセンブル + md->Disassemble(mem, mnemonic, local_ir); + // オフライン用に整形 + mnembuf = md->FormatDisasm(mnemonic, local_ir); + + cons->Print("%-18s %s\n", addrbuf.c_str(), mnembuf.c_str()); + + // 変換できなければここで終了 + if (local_ir.size() == 0) break; + } + + // アドレスと Super/User 状態を次回継続用に保存 + d_last_addr = mem.laddr; +} + +// 表示レジスタ選択 +// disp 現在の内容を表示 +// disp ... 設定 +void +Debugger::cmd_disp() +{ + if (args.size() < 2) { + // 表示 + bool first = true; + for (const auto& r : disp_regs) { + cons->Print("%s%s", (first ? "" : ","), r.c_str()); + first = false; } + cons->Print("\n"); + return; + } + + // 設定 (引数を分解する) + char buf[256]; + char *p; + char *last; + strlcpy(buf, args[1].c_str(), sizeof(buf)); + disp_regs.clear(); + for (p = strtok_r(buf, ",", &last); p; p = strtok_r(NULL, ",", &last)) { + disp_regs.push_back(std::string(p)); } - d_last_addr = dis.GetNextAddr(); + + // XXX チェック } // ログレベル設定: L @@ -714,23 +1771,22 @@ Debugger::cmd_L() const char *name; int level; - if (ac < 3) { + if (args.size() < 3) { cons->Print("L \n"); return; } - name = av[1]; - level = atoi(av[2]); + name = args[1].c_str(); + level = atoi(args[2].c_str()); // 短縮形とかエイリアスとか // lib/mainapp.cpp に同じものがあるのでどうにかしたほうがいい if (strcmp(name, "sch") == 0) name = "scheduler"; - if (strcmp(name, "pio") == 0) - name = "ppi"; if (strcmp(name, "scc") == 0) name = "sio"; + // 複数のオブジェクトが同じ logname を持ってもよい (CMMUとか)。 // all はどうするか std::string sname = name; bool found = false; @@ -739,7 +1795,6 @@ Debugger::cmd_L() obj->loglevel = level; cons->Print("%s=%d\n", name, obj->loglevel); found = true; - break; } } // 見付からない場合はエラー @@ -752,70 +1807,142 @@ Debugger::cmd_L() void Debugger::cmd_m() { - cmd_m_common(MemdumpMode::Logical_atc); + cmd_m_common(MemoryMode::Logical, MMULookupMode::False); } // メモリダンプ: mt [ []] (論理、テーブルサーチあり) void Debugger::cmd_mt() { - cmd_m_common(MemdumpMode::Logical_search); + cmd_m_common(MemoryMode::Logical, MMULookupMode::True); } // メモリダンプ: M [ []] (物理) void Debugger::cmd_M() { - cmd_m_common(MemdumpMode::Physical); + cmd_m_common(MemoryMode::Physical, MMULookupMode::None); } // メモリダンプ共通 void -Debugger::cmd_m_common(MemdumpMode mode) +Debugger::cmd_m_common(MemoryMode access_mode, MMULookupMode lookup_mode) { - uint32 addr; + saddr_t saddr; int cnt; cnt = 10; // 開始アドレス - if (GetAddr(addr, m_last_addr) == false) { + saddr = m_last_addr; + if (GetAddr(&saddr, true) == false) { return; } // 2つ目の引数があれば行数 - if (ac > 2) { - cnt = strtol(av[2], NULL, 10); + if (args.size() > 2) { + cnt = strtol(args[2].c_str(), NULL, 10); } - MemdumpLine mem(cpu, addr, mode); + DebuggerMemoryStream mem(md, saddr, access_mode, lookup_mode); + for (int i = 0; i < cnt; i++) { - cons->Print("%s\n", mem.Output().c_str()); + std::string addrbuf; + std::string dumpbuf; + std::string charbuf; + + // アドレス + if (mem.FormatAddr(addrbuf) == false) { + // アドレス変換でバスエラーならここで終了 + cons->Print("%s\n", addrbuf.c_str()); + break; + } + + for (int j = 0; j < 8; j++) { + for (int k = 0; k < 2; k++) { + uint64 b = mem.Fetch8(); + + if ((int64)b < 0) { + dumpbuf += "--"; + charbuf += " "; + } else { + uint32 c = (uint32)b; + dumpbuf += string_format("%02x", c); + charbuf += string_format("%c", + (0x20 <= c && c <= 0x7e) ? c : '.'); + } + } + dumpbuf += " "; + } + + cons->Print("%-18s %-40s %s\n", + addrbuf.c_str(), dumpbuf.c_str(), charbuf.c_str()); } - m_last_addr = mem.GetNextAddr(); + + // アドレスと Super/User 状態を次回継続用に保存 + m_last_addr = mem.laddr; } // プロンプトに来た最初に表示するいつものやつを再表示する void Debugger::cmd_minus() { - cmd_r(); + // 指定のレジスタ群を表示 + // disp_regs はレジスタ群のリスト { "r", "rf" } のような感じ。 + // 一方 ShowRegisters() の第2引数は1つのコマンドラインを空白で区切った + // 文字列リスト { arg[0], arg[1], .. }。型が同じで意味が違うので注意。 + for (const auto& reg : disp_regs) { + std::vector tmpargs; + tmpargs.push_back(reg); + md->ShowRegister(cons, tmpargs); + } + + // 現在の PC の位置を逆アセンブル。ここで ir を更新。 + std::string addrbuf; + std::string mnemonic; + std::string mnembuf; + ir.clear(); + + // 論理アクセス時、ATC ミスが分かったほうが便利だと思うので lookup なし + saddr_t laddr(pc, md->IsSuper()); + DebuggerMemoryStream mem(md, laddr); + // アドレス + if (mem.FormatAddr(addrbuf) == false) { + cons->Print("%s\n", addrbuf.c_str()); + return; + } + // 逆アセンブル + md->Disassemble(mem, mnemonic, ir); + // ライブ表示用に整形 + mnembuf = md->FormatDisasmLive(mnemonic, ir); - // 論理アクセス時、ATC ミスが分かったほうが便利だと思う - MemdumpMode mode = (cpu->mmu_enable) - ? MemdumpMode::Logical_atc - : MemdumpMode::Physical; - DisasmLine dis(cpu, pc, mode); - cons->Print("%s%s\n", dis.Output().c_str(), OPccCondStr(pc)); - nextpc = dis.GetNextAddr(); + cons->Print("%-18s %s\n", addrbuf.c_str(), mnembuf.c_str()); } -// ステップ実行: n[?=1] (サブルーチンを飛ばす) +// サブルーチンとループを飛ばしながら count 命令ステップ実行 +// n [?:1] (途中レジスタを表示しない) void Debugger::cmd_n() { - if (ac > 1) { + cmd_n_common(false); +} + +// サブルーチンとループを飛ばしながら count 命令ステップ実行 +// nt [?:1] (1命令ずつレジスタを表示する) +void +Debugger::cmd_nt() +{ + cmd_n_common(true); +} + +// n と nt の共通部分 +void +Debugger::cmd_n_common(bool trace) +{ + t_enable = trace; + + if (args.size() > 1) { int count; - count = strtol(av[1], NULL, 10); + count = strtol(args[1].c_str(), NULL, 10); if (count < 1) { cons->Print(" invalid step count: %d\n", count); return; @@ -827,116 +1954,58 @@ Debugger::cmd_n() } n_enable = true; SetNBreakpoint(); - Continue(); } -// n コマンド用のサブルーチンを飛ばすブレークポイントを設定。 -// 戻り値は、プロンプトをスキップするなら true。 -bool +// n コマンド用のブレークポイントを設定する。 +// この命令語によって仕掛けるブレークポイントが変わるので、都度都度 +// 呼び出すこと。 +void Debugger::SetNBreakpoint() { - // n コマンドは、通常はブレークポイントを次の命令に仕掛けながら - // 実行するが、いくつかの命令ではブレークポイントではなく、 - // 命令トレース (t コマンド) のように動作させる。 - // トレース動作する命令は以下の通り。 - // Bcc, BRA - // JMP - // RTD, RTR, RTS - // RTE - // FBcc - // FDBcc (2ワード目無視) - // (RTM は 68020 専用なので対応しない) -#define IsBcc(x) ((((x) & 0xf000) == 0x6000) && (((x) & 0xff00) != 0x6100)) -#define IsJMP(x) (((x) & 0xffc0) == 0x4ec0) -#define IsRTD(x) (((x) & 0xffff) == 0x4e74) -#define IsRTE(x) (((x) & 0xffff) == 0x4e73) -#define IsRTR(x) (((x) & 0xffff) == 0x4e77) -#define IsRTS(x) (((x) & 0xffff) == 0x4e75) -#define IsFBcc(x) (((x) & 0xf180) == 0xf080) -#define IsFDBcc(x) (((x) & 0xf1f8) == 0xf048) - - uint16 op; - - // XXX この辺統一的にしたほうがいい - // XXX logical peek のような気がする - op = (((uint16)m68030_phys_peek_8(pc)) << 8) - | m68030_phys_peek_8(pc + 1); - - if (IsBcc(op) - || IsJMP(op) - || IsRTD(op) - || IsRTE(op) - || IsRTR(op) - || IsRTS(op) - || IsFBcc(op) - || IsFDBcc(op) - ) { - n_breakenable = false; - s_enable = true; - s_count = 1; - return false; - } else { - n_breakenable = true; - n_breakaddr = nextpc; - return true; - } -} + saddr_t laddr(md->GetPC(), md->IsSuper()); + DebuggerMemoryStream mem(md, laddr); -// レジスタ表示 -// rf: FPU レジスタを表示 -// rm: MMU レジスタを表示 -// r<それ以外>: 通常レジスタを表示? -void -Debugger::cmd_r() -{ - if (ac > 1) { - int arg = av[1][0]; - switch (arg) { - case 'a': - ShowMonitor(gMPUATC->monitor); - return; - case 'f': - ShowRegFPU(); - return; - case 'm': - ShowRegMMU(); - return; - case 'o': - ShowRegOther(); - return; + // 命令がサブルーチンやトラップなどステップインできる命令か。 + if (md->IsOpStepIn(mem)) { + // この次の命令にブレークをかける + if (md->inst_bytes_fixed != 0) { + // 固定長命令 + n_breakaddr = (uint32)mem.laddr; + } else { + // 可変長なら逆アセンブルしてみるしか + std::string mnemonic; + std::vector v; + // XXX 失敗したらどうすべ + mem.ResetAddr(md->GetPC()); + md->Disassemble(mem, mnemonic, v); + n_breakaddr = (uint32)mem.laddr; } + } else { + // そうでなければ1命令進める。 + // 0xffffffff は常にアドレスとして不正なのでこれをフラグに使う。 + n_breakaddr = 0xffffffff; } - ShowRegMain(); } -// ショートカット -void -Debugger::cmd_ra() -{ - gMPUATC->MonitorUpdate(); - ShowMonitor(gMPUATC->monitor); -} -void -Debugger::cmd_rf() -{ - ShowRegFPU(); -} +// デバッガ終了コマンド void -Debugger::cmd_rm() +Debugger::cmd_q() { - ShowRegMMU(); + cons->Print("quit debugger console.\n"); } + +// VM リセット void -Debugger::cmd_ro() +Debugger::cmd_reset() { - ShowRegOther(); + gScheduler->RequestResetHard(); } // モニター表示 void Debugger::cmd_show() { - if (ac != 2) { + if (args.size() != 2) { cons->Print("usage: show \n"); for (auto& d : gObjects) { if (d->logname != "?") { @@ -946,17 +2015,10 @@ Debugger::cmd_show() return; } - std::string name(av[1]); + std::string name = args[1]; for (auto& d : gObjects) { if (d->logname == name) { - // ここで更新して.. - if (d->MonitorUpdate()) { - // 表示 - ShowMonitor(d->monitor); - } else { - // モニターがない場合 - cons->Print("show: %s has no monitor\n", av[1]); - } + ShowMonitor(*d); return; } } @@ -964,9 +2026,23 @@ Debugger::cmd_show() cons->Print("show: no such objects\n"); } -// モニタースクリーンを表示。 +// モニターを更新して表示。 +void +Debugger::ShowMonitor(IMonitor& monitor) +{ + // モニタを更新 + TextScreen ts; + + auto size = monitor.GetMonitorSize(); + ts.Init(size.width, size.height); + + monitor.MonitorUpdate(ts); + ShowTextScreen(ts); +} + +// テキストスクリーンを表示。 void -Debugger::ShowMonitor(TextScreen& monitor) +Debugger::ShowTextScreen(TextScreen& monitor) { char sbuf[1024]; // 適当 @@ -974,7 +2050,7 @@ Debugger::ShowMonitor(TextScreen& monito // その際属性もエスケープシーケンスで再現する。 int col = monitor.GetCol(); int row = monitor.GetRow(); - const uint16 *src = monitor.GetBuf(); + const std::vector& src = monitor.GetBuf(); for (int y = 0; y < row; y++) { int sy = y * col; @@ -1021,6 +2097,12 @@ Debugger::ShowMonitor(TextScreen& monito } *d++ = ch; } + // 行の終わりで一旦属性を戻しておく + if (attr != TA::Normal) { + *d++ = 0x1b; + *d++ = '['; + *d++ = 'm'; + } *d = '\0'; // XXX 日本語が使われてれば UTF-8 にしないといけないはず @@ -1029,13 +2111,31 @@ Debugger::ShowMonitor(TextScreen& monito } } -// ステップ実行: s[?:1] +// ステップ実行 +// s [?:1] (途中レジスタを表示しない) void Debugger::cmd_s() { - if (ac > 1) { + cmd_s_common(false); +} + +// ステップ実行 +// st [?:1] (命令ごとにレジスタを表示する) +void +Debugger::cmd_st() +{ + cmd_s_common(true); +} + +// ステップ実行共通部分 +void +Debugger::cmd_s_common(bool trace) +{ + t_enable = trace; + + if (args.size() > 1) { int count; - count = strtol(av[1], NULL, 10); + count = strtol(args[1].c_str(), NULL, 10); if (count < 1) { cons->Print(" invalid step count: %d\n", count); return; @@ -1046,37 +2146,59 @@ Debugger::cmd_s() s_count = 1; } s_enable = true; - Continue(); } -// ステップアウト +// ステップアウト (途中レジスタを表示しない) void Debugger::cmd_so() { + cmd_so_common(false); +} + +// ステップアウト (命令ごとにレジスタを表示する) +void +Debugger::cmd_sot() +{ + cmd_so_common(true); +} + +// ステップアウトの共通部分 +void +Debugger::cmd_so_common(bool trace) +{ + t_enable = trace; + so_enable = true; - so_a7 = RegA(7); - so_sr = RegSR & 0x3000; - Continue(); + md->SetStepOut(); } -// トレース実行: t[?:1] -// レジスタを表示しながら実行する +// t は st の省略形。互換のため。 void Debugger::cmd_t() { - if (ac > 1) { - int count; - count = strtol(av[1], NULL, 10); - if (count < 1) { - cons->Print(" invalid trace count: %d\n", count); - return; - } - t_count = count; - } else { - t_count = 1; + cmd_st(); +} + +// 引数で示されるプレフィックスなしの16進数値を返す。 +// 正しく変換できれば値を valp に格納して true を返す。 +// そうでなければ false を返す。 +bool +Debugger::ParseVerbHex(const char *arg, uint32 *valp) +{ + uint32 val; + char *end; + + errno = 0; + val = strtoul(arg, &end, 16); + if (arg[0] == '\0' || end[0] != '\0') { + return false; + } + if (errno == ERANGE) { + return false; } - t_enable = true; - Continue(); + + *valp = val; + return true; } // 引数で示されるアドレスを返す。 @@ -1085,52 +2207,25 @@ Debugger::cmd_t() // アドレスが正しく取得できればアドレスを addr に格納し真を返す。 // そうでなければエラーメッセージを表示して偽を返す。 bool -Debugger::ParseAddr(const char *arg, uint32_t *addrp) +Debugger::ParseAddr(const char *arg, uint32 *addrp) { - uint32_t addr; + uint32 addr; char *end; if (arg[0] == '%') { - /* '%' から始まればレジスタ */ - const char *regname = &arg[1]; - if ((*regname | 0x20) == 'a') { - addr = RegA(arg[2] - '0'); - - } else if ((*regname | 0x20) == 'd') { - addr = RegD(arg[2] - '0'); - - } else if (strcmp(regname, "sp") == 0) { - addr = RegA(7); - -#if 0 - } else if (strcmp(regname, "usp") == 0) { - addr = RegUSP; - - } else if (strcmp(regname, "isp") == 0) { - addr = RegISP; - - } else if (strcmp(regname, "msp") == 0) { - addr = RegMSP; -#endif - - } else if (strcmp(regname, "pc") == 0) { - addr = RegPC; - - } else if (strcmp(regname, "vbr") == 0) { - addr = RegVBR; - - } else if (strcmp(regname, "srp") == 0) { - addr = cpu->GetSRPl(); - - } else if (strcmp(regname, "crp") == 0) { - addr = cpu->GetCRPl(); - - } else { + // '%' から始まればレジスタ + uint64 r; + r = md->GetRegAddr(arg + 1); + if ((int64)r < 0) { +#if 0 // not yet cons->Print("valid register name are: %s%s\n", "%d0-%d7,%a0-%a7,%sp,%usp,%isp,%pc", ",%msp,%vbr,%srp,%crp"); +#endif + cons->Print("invalid register name\n"); return false; } + addr = (uint32)r; } else { /* そうでなければ番地指定 */ errno = 0; @@ -1149,408 +2244,29 @@ Debugger::ParseAddr(const char *arg, uin return true; } + // -// レジスタ表示 +// MD // -#define NORM "\x1b[0m" -#define BOLD "\x1b[1m" -#define BOLDIF(n) ((n) ? BOLD : "") - -// 基本セット -void -Debugger::ShowRegMain() -{ -/* -D0:00000070 D4:CCCCCCCC A0:00FFAA32 A4:CCCCCCCC SR=F810(S I=0 X----) -D1:0000FFFF D5:00000000 A1:00000A7A A5:00000A7A -D2:00FF0000 D6:00000000 A2:CCCCCCCC A6:CCCCCCCC -D3:CCCCCCCC D7:00000003 A3:CCCCCCCC A7:00001FD8 -*/ - bool vr[16]; - - for (int i = 0; i < 16; i++) { - vr[i] = (cpu->reg.da[i] != prev.da[i]); - } - - // 1行目 - cons->Print("%sD%d:%08x%s %sD%d:%08x%s %sA%d:%08x%s %sA%d:%08x%s ", - BOLDIF(vr[ 0]), 0, RegR( 0), NORM, - BOLDIF(vr[ 4]), 4, RegR( 4), NORM, - BOLDIF(vr[ 8]), 0, RegR( 8), NORM, - BOLDIF(vr[12]), 4, RegR(12), NORM); - - // 1行目 SR - uint16 sr = RegSR; - uint16 prevsr = prev.sr_h() - | (prev.ccr.IsX() ? M68K_CCR_X : 0) - | (prev.ccr.IsN() ? M68K_CCR_N : 0) - | (prev.ccr.IsZ() ? M68K_CCR_Z : 0) - | (prev.ccr.IsV() ? M68K_CCR_V : 0) - | (prev.ccr.IsC() ? M68K_CCR_C : 0); - // SR は上位バイトと下位バイトが変化したくらいでいいか? - cons->Print(" SR:%s%02x%s%s%02x%s", - BOLDIF((sr & 0xff00) != (prevsr & 0xff00)), - sr >> 8, - NORM, - BOLDIF((sr & 0x00ff) != (prevsr & 0x00ff)), - sr & 0xff, - NORM); - cons->Print("(%c I=%d %c%c%c%c%c)\n", - (sr & 0x2000) ? 'S' : '-', - (sr >> 8) & 7, - (sr & M68K_CCR_X) ? 'X' : '-', - (sr & M68K_CCR_N) ? 'N' : '-', - (sr & M68K_CCR_Z) ? 'Z' : '-', - (sr & M68K_CCR_V) ? 'V' : '-', - (sr & M68K_CCR_C) ? 'C' : '-'); - - // 2行目 - cons->Print("%sD%d:%08x%s %sD%d:%08x%s %sA%d:%08x%s %sA%d:%08x%s\n", - BOLDIF(vr[ 1]), 1, RegR( 1), NORM, - BOLDIF(vr[ 5]), 5, RegR( 5), NORM, - BOLDIF(vr[ 9]), 1, RegR( 9), NORM, - BOLDIF(vr[13]), 5, RegR(13), NORM); - - // 3行目 - cons->Print("%sD%d:%08x%s %sD%d:%08x%s %sA%d:%08x%s %sA%d:%08x%s\n", - BOLDIF(vr[ 2]), 2, RegR( 2), NORM, - BOLDIF(vr[ 6]), 6, RegR( 6), NORM, - BOLDIF(vr[10]), 2, RegR(10), NORM, - BOLDIF(vr[14]), 6, RegR(14), NORM); - - // 4行目 - cons->Print("%sD%d:%08x%s %sD%d:%08x%s %sA%d:%08x%s %sA%d:%08x%s\n", - BOLDIF(vr[ 3]), 3, RegR( 3), NORM, - BOLDIF(vr[ 7]), 7, RegR( 7), NORM, - BOLDIF(vr[11]), 3, RegR(11), NORM, - BOLDIF(vr[15]), 7, RegR(15), NORM); -} - -// FPU レジスタ表示 -void -Debugger::ShowRegFPU() -{ -/* -FP0:0000_12345678_12345678 (-0.1234567890123456789) FPCR:1234 -FP1: BS,SN,OP,OV,UF,DZ,I2,I1 -FP2: RP=xx RM=xx -FP3: FPSR:12345678 -FP4: N,Z,Inf,NAN Q=$xx -FP5: BS,SN,OP,OV,UF,DZ,I2,I1 -FP6: AXEC -FP7: FPIAR:12345678 -*/ - -#define PUT_FP(n) do { \ - uint32 *c_ = cpu->reg.fpframe.fpf_regs + (n) * 3; \ - uint32 *p_ = prev.fpframe.fpf_regs + (n) * 3; \ - bool d = (c_[0] ^ p_[0]) | (c_[1] ^ p_[1]) | (c_[2] ^ p_[2]); \ - cons->Print("%sFP%d:%04xxxxx_%08x_%08x (%-20s)%s ", \ - BOLDIF(d), (n), c_[0] >> 16, c_[1], c_[2], "notyet", NORM); \ -} while (0) - - uint32 fpcr, fpsr, fpiar; - uint32 ppcr, ppsr, ppiar; - static const char * const rpstr[] = { - ".EXT", - ".SGL", - ".DBL", - ".???", - }; - static const char * const rmstr[] = { - "Near", - "Zero", - "Minus", - "Plus", - }; - - fpcr = cpu->reg.fpframe.fpf_fpcr; - fpsr = cpu->reg.fpframe.fpf_fpsr; - fpiar = cpu->reg.fpframe.fpf_fpiar; - ppcr = prev.fpframe.fpf_fpcr; - ppsr = prev.fpframe.fpf_fpsr; - ppiar = prev.fpframe.fpf_fpiar; - - PUT_FP(0); - cons->Print("%sFPCR:%04x%s\n", BOLDIF(fpcr != ppcr), fpcr, NORM); - - PUT_FP(1); - cons->Print(" %s %s %s %s %s %s %s %s\n", - (fpcr & 0x8000) ? "BS" : "--", - (fpcr & 0x4000) ? "SN" : "--", - (fpcr & 0x2000) ? "OP" : "--", - (fpcr & 0x1000) ? "OV" : "--", - (fpcr & 0x0800) ? "UF" : "--", - (fpcr & 0x0400) ? "DZ" : "--", - (fpcr & 0x0200) ? "I2" : "--", - (fpcr & 0x0100) ? "I1" : "--"); - - PUT_FP(2); - cons->Print(" RP=%s RM=%s\n", - rpstr[(fpcr >> 6) & 3], - rmstr[(fpcr >> 4) & 3]); - - PUT_FP(3); - cons->Print("%sFPSR:%08x%s\n", BOLDIF(fpsr != ppsr), fpsr, NORM); - - PUT_FP(4); - uint cc = fpsr >> 24; - cons->Print(" %c %c %s %s Q=$%02x\n", - (cc & 0x08) ? 'N' : '-', - (cc & 0x04) ? 'Z' : '-', - (cc & 0x02) ? "Inf" : "---", - (cc & 0x01) ? "NAN" : "---", - (fpsr >> 16) & 0xff); - - PUT_FP(5); - cons->Print(" %s %s %s %s %s %s %s %s\n", - (fpsr & 0x8000) ? "BS" : "--", - (fpsr & 0x4000) ? "SN" : "--", - (fpsr & 0x2000) ? "OP" : "--", - (fpsr & 0x1000) ? "OV" : "--", - (fpsr & 0x0800) ? "UF" : "--", - (fpsr & 0x0400) ? "DZ" : "--", - (fpsr & 0x0200) ? "I2" : "--", - (fpsr & 0x0100) ? "I1" : "--"); - - PUT_FP(6); - cons->Print(" %s %s %s %s %s\n", - (fpsr & 0x80) ? "IOP" : "---", - (fpsr & 0x40) ? "OVFL" : "----", - (fpsr & 0x20) ? "UNFL" : "----", - (fpsr & 0x10) ? "DZ" : "--", - (fpsr & 0x08) ? "INEX" : "----"); - - PUT_FP(7); - cons->Print("%sFPIAR:%08x%s\n", BOLDIF(fpiar != ppiar), fpiar, NORM); -} - -void -Debugger::ShowRegMMU() -{ -/* -SRP:00001111_22223333 TT0:00001111 TC:00001111 (---) -CRP:00001111_22223333 TT1:00001111 MMUSR: 0011 ( -*/ - uint64 srp, osrp; - uint64 crp, ocrp; - uint32 tt0, ott0; - uint32 tt1, ott1; - uint32 tc, otc; - uint16 sr, osr; - bool p; - bool t; - bool c; - - srp = cpu->GetSRP(); - crp = cpu->GetCRP(); - tt0 = cpu->GetTT(0); - tt1 = cpu->GetTT(1); - tc = cpu->GetTC(); - sr = cpu->GetMMUSR(); - - osrp = prev.srp.q; - ocrp = prev.crp.q; - ott0 = prev.tt[0]; - ott1 = prev.tt[1]; - otc = prev.tc; - osr = prev.mmusr; - - // 1行目 - p = (srp != osrp); - t = (tt0 != ott0); - c = (tc != otc); - cons->Print("%sSRP:%08x_%08x%s %sTT0:%08x%s(%c%c%c)", - BOLDIF(p), (uint32)(srp >> 32), (uint32)srp, NORM, - BOLDIF(t), tt0, NORM, - (tt0 & m68030TT::E) ? 'E' : '-', - (tt0 & m68030TT::CI) ? 'C' : '-', - (tt0 & m68030TT::RWM) ? '-' : ((tt0 & m68030TT::RW) ? 'R' : 'W')); - cons->Print(" %sTC:%08x%s(%c%c%c)\n", - BOLDIF(c), tc, NORM, - (tc & m68030TC::TC_E) ? 'E' : '-', - (tc & m68030TC::TC_SRE) ? 'S' : '-', - (tc & m68030TC::TC_FCL) ? 'F' : '-'); - - // 2行目 - p = (crp != ocrp); - t = (tt1 != ott1); - c = (sr != osr); - cons->Print("%sCRP:%08x_%08x%s %sTT1:%08x%s(%c%c%c)", - BOLDIF(p), uint32(crp >> 32), (uint32)crp, NORM, - BOLDIF(t), tt1, NORM, - (tt1 & m68030TT::E) ? 'E' : '-', - (tt1 & m68030TT::CI) ? 'C' : '-', - (tt1 & m68030TT::RWM) ? '-' : ((tt1 & m68030TT::RW) ? 'R' : 'W')); - cons->Print(" %sMMUSR: %04x%s(%c%c%c%c%c%c%c N=%d)\n", - BOLDIF(c), sr, NORM, - (sr & m68030MMUSR::B) ? 'B' : '-', - (sr & m68030MMUSR::L) ? 'L' : '-', - (sr & m68030MMUSR::S) ? 'S' : '-', - (sr & m68030MMUSR::W) ? 'W' : '-', - (sr & m68030MMUSR::I) ? 'I' : '-', - (sr & m68030MMUSR::M) ? 'M' : '-', - (sr & m68030MMUSR::T) ? 'T' : '-', - (sr & m68030MMUSR::N)); -} - -// その他のレジスタを表示 -void -Debugger::ShowRegOther() +// デストラクタ +DebuggerMD::~DebuggerMD() { - TextScreen s(80, 2); - - // 0 1 2 3 4 5 6 - // 012345678901234567890123456789012345678901234567890123456789012345 - // CACR:01234567 VBR:01234567 USP:01234567 - // CAAR:01234567 SFC:0 DFC:0 MSP:01234567 - -#define EM(name) ((cpu->reg.name != prev.name) ? TA::Em : TA::Normal) - s.Print(0, 0, EM(cacr), "CACR:%08x", cpu->reg.cacr); - s.Print(0, 1, EM(caar), "CAAR:%08x", cpu->reg.caar); - - s.Print(15, 0, EM(vbr), "VBR:%08x", cpu->reg.vbr); - s.Print(15, 1, EM(sfc), "SFC:%d", cpu->reg.sfc); - s.Print(22, 1, EM(dfc), "DFC:%d", cpu->reg.dfc); - - // スタック欄はモードによって表示を変える。 - // ユーザ 割り込み マスタ - // 1行目 ISP USP USP - // 2行目 MSP MSP ISP - if (!cpu->reg.s) { - s.Print(29, 0, EM(isp), "ISP:%08x", cpu->reg.isp); - } else { - s.Print(29, 0, EM(usp), "USP:%08x", cpu->reg.usp); - } - if (!cpu->reg.m) { - s.Print(29, 1, EM(msp), "MSP:%08x", cpu->reg.msp); - } else { - s.Print(29, 1, EM(isp), "ISP:%08x", cpu->reg.isp); - } - - ShowMonitor(s); } -// op が 68030 の条件命令 Bcc, Scc, TRAPcc, DBcc なら true を返す。 -bool -Debugger::IsOPcc(uint16 op) -{ - uint16 cc; - - if ((op & 0xf000) == 0x6000) { - cc = (op >> 8) & 0xf; - if (cc == 0 || cc == 1) { // BRA, BSR - return false; - } else { - return true; // Bcc - } - } - - if ((op & 0xf0c0) == 0x50c0) { // Scc, TRAPcc, DBcc - return true; - } - return false; -} +// +// ブレークポイントモニター +// -// op が DBcc 命令なら true を返す。 -bool -Debugger::IsDBcc(uint16 op) +// コンストラクタ +BreakpointMonitor::BreakpointMonitor() { - if ((op & 0xf0f8) == 0x50c8) { // DBcc - return true; - } - return false; + monitor_size = nnSize(55, 9); } -// op の cccc 条件が現在の CCR で成立するなら true を返す。 -bool -Debugger::IsCond(uint16 op) +void +BreakpointMonitor::MonitorUpdate(TextScreen& monitor) { - bool N = RegIsN; - bool Z = RegIsZ; - bool V = RegIsV; - bool C = RegIsC; - - switch ((op >> 8) & 0x0f) { - case 0: // T - return true; - case 1: // F - return false; - case 2: // HI - return (!C) && (!Z); - case 3: // LS - return C || Z; - case 4: // CC - return !C; - case 5: // CS - return C; - case 6: // NE - return !Z; - case 7: // EQ - return Z; - case 8: // VC - return !V; - case 9: // VS - return V; - case 10: // PL - return !N; - case 11: // MI - return N; - case 12: // GE - return (N && V) || ((!N) && (!V)); - case 13: // LT - return (N && (!V)) || ((!N) && V); - case 14: // GT - return (N && V && (!Z)) || ((!N) && (!V) && (!Z)); - case 15: // LE - return Z || (N && (!V)) || ((!N) && V); - default: - __unreachable(); - } -} - -// addr 位置の命令が条件命令なら、成立可否などの文字列を返す。 -// Bcc, Scc, TRAPcc 命令なら、条件が成立するかどうか。 -// DBcc 命令ならブランチするかどうか。 -const char * -Debugger::OPccCondStr(uint32 addr) -{ - uint16 op; - - // XXX logical peek のような気がする - op = (((uint16)m68030_phys_peek_8(addr)) << 8) - | m68030_phys_peek_8(addr + 1); - - // DBcc 命令なら、ブランチするかどうか - // IsOPcc() は DBcc も含んでいるため、IsDBcc() の判定のほうが先。 - // DBcc は内部でカウンタレジスタを減算してから比較するため、 - // 命令実行前のカウンタが 0 の時点でループ終了となることに注意。 - if (IsDBcc(op)) { - // 成立したら何もしない - if (IsCond(op)) { - return " (will fall)"; - } - // カウンタが -1 なら何もしない - uint rr = op & 7; - if ((RegD(rr) & 0xffff) == 0) { - return " (will expire)"; - } - // それ以外はブランチ - return " (will take)"; - } - - // Bcc, Scc, TRAPcc 命令なら条件が成立するかどうか - if (IsOPcc(op)) { - if (IsCond(op)) { - return " (will take)"; - } else { - return " (will not take)"; - } - } - - // (ここで知ってる)条件命令ではない - return ""; + gDebugger->MonitorBreakpoint(monitor); } -