--- nono/debugger/debugger.cpp 2026/04/29 17:04:30 1.1.1.2 +++ nono/debugger/debugger.cpp 2026/04/29 17:04:37 1.1.1.4 @@ -1,6 +1,7 @@ // // nono -// Copyright (C) 2019 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // #include "console.h" @@ -8,11 +9,13 @@ #include "debugger_m680x0.h" #include "debugger_m88xx0.h" #include "mainapp.h" -#include "mpu.h" #include "mystring.h" +#include "mythread.h" +#include #include -Debugger *gDebugger; +static Debugger *gDebugger; +BreakpointMonitor *gBreakpointMonitor; CVPrompt *gCVPrompt; static void *debugger_run(void *); @@ -25,6 +28,7 @@ debugger_init() gDebugger = new Debugger(); gDebugger->Init(); + gBreakpointMonitor = new BreakpointMonitor(); gCVPrompt = new CVPrompt(); // デバッガスレッド起動 @@ -36,7 +40,7 @@ debugger_init() static void * debugger_run(void *dummy) { - pthread_setname_np("Debugger"); + PTHREAD_SETNAME("Debugger"); pthread_detach(pthread_self()); gDebugger->ThreadRun(); @@ -66,9 +70,28 @@ Debugger::Init() } // -b ならブレークポイント設定 - if (gMainApp.debug_breakaddr != 0xffffffff) { + for (auto& str : gMainApp.debug_breakaddr) { + breakpoint_t bp; + md->ReqSet(CPU_REQ_TRACE); - AddBreakpoint(gMainApp.debug_breakaddr, false); + + // , で分離できたら を取り出す + 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); } // どこでやるべか @@ -86,47 +109,135 @@ Debugger::ThreadRun() cons = new ConsoleTCP(); } if (cons->Init() == false) { + delete cons; return; } for (;;) { - cons->Open(); + // 着信待ち + if (Accept() == false) { + break; + } + cons->InitEditLine("> "); - cons->Print("This is debugger console\n"); - // 接続ごとに初期化する値 - // XXX もうちょっときれいにしたい - d_last_addr = 0xffffffff; - m_last_addr = 0xffffffff; - n_enable = false; - s_enable = false; - t_enable = false; - is_continued = true; - - // コンソールが繋がっている間がトレースオン - // コンソールが繋がるとプロンプトになる - md->ReqSet(CPU_REQ_TRACE | CPU_REQ_PROMPT); - while (MainLoop() == true) - ; - // コンソールを抜ける時はCPUの停止を解除。 + // デバッガプロンプトにいる間はトレースオン + md->ReqSet(CPU_REQ_TRACE); + + bool first = true; + for (;;) { + // 接続後の1回目だけ実行するもの。 + if (first) { + first = false; + + // greeting はプロンプトが取れる前にもう表示したい。 + // 何らかの事故でプロンプトが取れなくても、ここまでは接続 + // できてることが分かるように。 + cons->Print("This is debugger console\n"); + + // 接続ごとに初期化する値 + // XXX もうちょっときれいにしたい + d_last_addr = 0xffffffff; + m_last_addr = 0xffffffff; + n_enable = false; + s_enable = false; + t_enable = false; + } + + // プロンプトが取れるのを待つ + if (AcquirePrompt() == false) { + break; + } + + // プロンプトに来た時に表示するいつものやつ + pc = md->GetPC(); + d_last_addr = pc; + cmd_minus(); + + // コマンド処理のメインループ + auto action = MainLoop(); + + // 次回との差分のため、今のレジスタセットをバックアップ + md->BackupRegs(); + + // メインループから戻ったということは Leave か Quit なので + // どちらにしても、ここでプロンプトを手放す。 + gCVPrompt->NotifyRelease(); + + // quit ならここでループを1段抜ける + if (action == CommandAction::Quit) { + break; + } + } + + // デバッガを抜けるのでトレースオフ md->ReqClr(CPU_REQ_TRACE); - gCVPrompt->NotifyRelease(); 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::Accept() +{ + 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; + } + + int r = cons->Poll(200); + if (r < 0) { + cmd_q(); + return false; + } + if (r > 0) { + break; + } + } + } else { + // stdio、起動時にプロンプトで止まる。 + // この場合は無条件に成功でよい。 + } + } + + return true; +} + +// プロンプトが取れるのを待つ +// 失敗すれば false を返す bool -Debugger::MainLoop() +Debugger::AcquirePrompt() { - // プロンプトが取れるのを待つ + md->ReqSet(CPU_REQ_PROMPT); + for (;;) { bool is_prompt = gCVPrompt->WaitAcquire(200); - if (is_prompt) break; + if (is_prompt) + break; // コンソールを定期観測して、入力があればブレーク要求 if (cons->Poll()) { @@ -139,47 +250,71 @@ Debugger::MainLoop() md->ReqSet(CPU_REQ_PROMPT); } } + return true; +} - // VM 実行からプロンプトに来たとき - if (is_continued) { - is_continued = false; - - pc = md->GetPC(); - d_last_addr = pc; - // プロンプトに来た時に表示するいつものやつ - cmd_minus(); - } - - cons->Prompt(); - if (cons->Gets(cmdbuf) == false) { - // EOF or Error - cmd_q(); - return false; - } - - string_rtrim(cmdbuf); - - if (!cmdbuf.empty()) { - // コマンドが入力されれば次回のために保存 - last_cmdbuf = cmdbuf; - } else { - // 空行が入力されれば直前のコマンドをもう一度 - // 前行がなければ何もせずもう一度プロンプトを表示するかね - if (last_cmdbuf.empty()) - return true; - cmdbuf = last_cmdbuf; - } +// コマンド処理のメインループ。 +// コマンドが Stay ならこの中で処理を継続。 +// コマンドが Leave か Quit ならその値を持って戻る (呼び出し側で処理する)。 +Debugger::CommandAction +Debugger::MainLoop() +{ + for (;;) { + cons->Prompt(); + if (cons->Gets(cmdbuf) == false) { + // EOF or Error + return CommandAction::Quit; + } - // 行を cmd, ac, av に分解 - cmddef_t *cmd = ParseCmd(); + string_rtrim(cmdbuf); - // 前回の値が有効なのはコマンドが連続した時だけ + 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; + } - // コマンド実行 - (this->*(cmd->func))(); + // (コマンドテーブルになくて) "r" から始まっていればレジスタ表示系 + if (args[0][0] == 'r') { + // ShowRegister() は処理したら true を返す。 + // "r" 系コマンドはすべて Stay。 + if (md->ShowRegister(cons, args)) { + continue; + } + } - // 終了コマンドによる終了処理 - return (cmd->func != &Debugger::cmd_q); + // 知らないコマンドも Stay 相当。 + cons->Print("%s: unknown command\n", args[0].c_str()); + continue; + } + __unreachable(); } // ブレークポイントとかを調べる。 @@ -217,22 +352,9 @@ Debugger::Check() return true; } - // アドレス - for (int i = 0; i < countof(bpoint); i++) { - breakpoint_t *bp = &bpoint[i]; - if (bp->enable) { - if (bp->ismemory) { - if (md->CheckLEA(bp->addr)) { - bp->count++; - return true; - } - } else { - if (bp->addr == md->GetPC()) { - bp->count++; - return true; - } - } - } + // ブレークポイント + if (CheckAllBreakpoints()) { + return true; } // ステップアウト @@ -254,149 +376,258 @@ Debugger::Check() return false; } +// ブレークポイントがどれかでも成立するかを調べる。 +// 1つ以上成立してブレークするなら true を返す。 +// 1つも成立しておらずブレークしないなら false を返す。 +bool +Debugger::CheckAllBreakpoints() +{ + bool is_break = false; + + // 命令ごとにクリアする + bi_inst = 0; + bi_inst_bytes = 0; + + // 1つの条件でマッチしても(そこでブレークすること自体は確定するのだが) + // 残りの他の条件も成立すればカウントを進める必要があるため、 + // 全部処理した上でどれか一つでもブレークしたかで判断する必要がある。 + for (auto& bp : bpoint) { + 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; + } + + // 例外通知は通過ごとに常に下ろしておく + bv_vector = -1; + + // ブレークポイントが1つでも成立したかどうかを返す + return is_break; +} + +// 命令ブレークポイントが成立するか調べる。 +bool +Debugger::CheckBreakpointInst(breakpoint_t& bp) +{ + saddr_t laddr; + uint64 paddr; + + // uint32 bi_inst が現在の PC 位置の命令データ (左詰め) + // bi_inst_bytes が読み込んだバイト数(bi_inst の左からの有効バイト数)。 + // bi_need_bytes が現在のブレークポイントで読み込む必要のあるバイト数。 + + laddr.addr = md->GetPC(); + laddr.super = md->IsSuper(); + laddr.logical = true; + laddr.search = true; + // 必要なバイト数に達するまで読み足す + while (bi_inst_bytes < bi_need_bytes) { + // アドレスを求める。 + // 論理アクセスで ATC になければテーブルサーチも試みる。 + if (md->MMUEnabled()) { + paddr = md->TranslateAddr(laddr); + if ((int64)paddr < 0) { + // MMU 時点でバスエラー + return false; + } + } else { + paddr = laddr.addr; + } + + // 命令長ずつ読み込む。 + for (int i = 0; i < md->inst_bits / 8; i++) { + uint64 data = vm_phys_peek_8(paddr); + if ((int64)data < 0) { + return false; + } + bi_inst = (bi_inst << 8) | data; + bi_inst_bytes++; + laddr.addr++; + paddr++; + } + } + // 左詰めにする + bi_inst <<= (4 - bi_inst_bytes) * 8; + + // 必要なバイト数取得できたので比較 + if ((bi_inst & bp.mask) == bp.inst) { + return true; + } + return false; +} + +// 例外通知 (MPU からの連絡用) +void +debugger_notifyexception(int vector) +{ + gDebugger->NotifyException(vector); +} + +// 例外通知 (本体) +void +Debugger::NotifyException(int vector) +{ + bv_vector = vector; +} + // コマンド一覧。 -// quit コマンドによるメインループ終了は MainLoop() 側で処理してある。 -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, }, - { "q", &Debugger::cmd_q, }, - { "quit", &Debugger::cmd_q, }, - { "r", &Debugger::cmd_r, }, - { "ra", &Debugger::cmd_ra, }, - { "rc", &Debugger::cmd_rc, }, - { "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 }, + { "exhist", &Debugger::cmd_exhist, 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 }, + { "q", &Debugger::cmd_q, Debugger::CommandAction::Quit }, + { "quit", &Debugger::cmd_q, Debugger::CommandAction::Quit }, + { "s", &Debugger::cmd_s, Debugger::CommandAction::Leave }, + { "so", &Debugger::cmd_so, Debugger::CommandAction::Leave }, + { "show", &Debugger::cmd_show, Debugger::CommandAction::Stay }, + { "t", &Debugger::cmd_t, Debugger::CommandAction::Leave }, + { "-", &Debugger::cmd_minus, Debugger::CommandAction::Stay }, }; // ヘルプ void Debugger::cmd_h() { - if (ac == 1) { - // 引数なし - HelpMain(); - return; - } else { - // 引数あり(今の所対応していない) - HelpMain(); - return; + if (args.size() > 1) { + if (args[1] == "r") { + Help(md->GetRegisterHelp()); + return; + } + } + // 引数なしか、あっても対応してないやつなら通常ヘルプを表示 + Help(HelpMsgMain); + return; +} + +/*static*/ const HelpMessages +Debugger::HelpMsgMain = { + { "b", "ブレークポイント一覧表示" }, + { "b #n", "ブレークポイント n を削除" }, + { "b []", "ブレークポイント設定" }, + { "bm []", "メモリブレークポイント設定" }, + { "bi [:] []", "命令ブレークポイント設定" }, + { "bv [-] []", "例外ブレークポイント設定" }, + { "bx", "ブレークポイント全削除" }, + { "brhist []", "ブランチ履歴表示" }, + { "c []", "実行再開(continue)" }, + { "d [ []", "逆アセンブル(論理、テーブルサーチなし)" }, + { "dt [ []", "逆アセンブル(論理、テーブルサーチあり)" }, + { "D [ []", "逆アセンブル(物理)" }, + { "exhist []", "例外履歴表示" }, + { "h", "ヘルプ(help)" }, + { "L =", "ログレベル設定" }, + { "m [ []", "メモリダンプ(論理、テーブルサーチなし)" }, + { "mt [ []", "メモリダンプ(論理、テーブルサーチあり)" }, + { "M [ []", "メモリダンプ(物理)" }, + { "n ", "ステップ実行 (サブルーチンを飛ばす)" }, + { "q", "quit" }, + { "r*", "各種レジスタを表示 (see \"help r\")" }, + { "s []", "ステップ実行 (途中レジスタを表示しない)" }, + { "so", "ステップアウト" }, + { "show ", "モニター表示" }, + { "t []", "トレース実行 (命令ごとにレジスタを表示)" }, +}; + +void +Debugger::Help(HelpMessages msgs) +{ + for (const auto& pair : msgs) { + cons->Print(" %-23s %s\n", pair.first.c_str(), pair.second.c_str()); } } +// cmdbuf を args... に分解する。 void -Debugger::HelpMain() +Debugger::ParseCmdbuf() { - 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 通常レジスタを表示\n"); - cons->Print(" ra ATC を表示\n"); - cons->Print(" rc 制御レジスタを表示 (m88xx0)\n"); - cons->Print(" rf FPU レジスタを表示\n"); - cons->Print(" rm MMU レジスタを表示\n"); - cons->Print(" ro その他のレジスタを表示\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 に分解する。 -// コマンド(の1ワード目)が一致しなければ cmd_unknown を返し、 -// 終了コマンドと一致したら NULL を返す。 -Debugger::cmddef_t * -Debugger::ParseCmd() -{ - char buf[cmdbuf.length() + 1]; - char *p; - cmddef_t *cmd; - - strcpy(buf, cmdbuf.c_str()); - p = buf; - ac = 0; - memset(&av, 0, sizeof(av)); + int pos; + int start; + + pos = 0; + args.clear(); // 引数を空白で分解 - for (; ac < countof(av); ) { - // 先頭の空白は取り除く - while (isspace((int)*p)) - p++; - if (*p == '\0') + for (;;) { + // 先頭の空白はスキップ + for (; cmdbuf[pos] != '\0'; pos++) { + if (!isspace((unsigned int)cmdbuf[pos])) + break; + } + if (cmdbuf[pos] == '\0') break; - av[ac++] = p; - // 次の空白まで進める - for (; *p != '\0'; p++) { - if (isspace((int)*p)) + // 終わりを探す + start = pos; + for (; cmdbuf[pos] != '\0'; pos++) { + if (isspace((unsigned int)cmdbuf[pos])) break; } - if (*p != '\0') - *p++ = '\0'; - } - // buf はスコープを抜けると無効なので、 - // コピーを取って挿げ替え - for (int i = 0; i < ac; i++) { - strav[i] = av[i]; - av[i] = strav[i].c_str(); + args.push_back(cmdbuf.substr(start, pos - start)); } // デバッグ表示 if (0) { - for (int i = 0; i < ac; i++) { - printf("av[%d]=|%s|\n", i, av[i]); + for (int i = 0 ; i < args.size(); i++) { + printf("args[%d]=|%s|\n", i, args[i].c_str()); } } - - // コマンドを探す - for (int i = 0; i < countof(cmdtable); i++) { - cmd = &cmdtable[i]; - - if (strcmp(av[0], cmd->name) == 0) - return cmd; - } - - // 見つからなければ cmd_unknown コマンドを実行。 - return &cmdtable_unknown; } // @@ -404,64 +635,230 @@ 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 (dynamic_cast(md)) { + 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 { - // そうでなければアドレス指定 - ismemory = false; - addr = strtoul(av[1], NULL, 16); + // マスク指定あり + 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_bits / 4) != 0) { + cons->Print("%s: invalid instruction length\n", args[1].c_str()); + 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); + // マスク部チェック + 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_bits < 32 && 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()); + } // 空いてるところにセット - int bi = AddBreakpoint(addr, ismemory); + bp.type = BreakpointType::Instruction; + 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); + } + + // 今登録されている命令ブレークの必要命令長を再計算 + RecalcInstMask(); +} + +// 例外ブレークポイントの設定 +// bv [-] [] +void +Debugger::cmd_bv() +{ + breakpoint_t bp; + + if (args.size() < 2) { + cons->Print("usage: be [-] []\n"); + return; + } + + 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()); + } + + // 空いてるところにセット + bp.type = BreakpointType::Exception; + int bi = AddBreakpoint(bp); + if (bi == -1) { + cons->Print("no free breakpoints\n"); + } else { + cons->Print("breakpoint #%d added\n", bi); } } @@ -469,20 +866,86 @@ Debugger::cmd_b() 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 < 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; + 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; + + 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_bits == 32) { + 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"); } } @@ -490,38 +953,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_bits); + // バイト数に変換 + 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 [] @@ -529,9 +1086,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; } // 偶数番地に丸める @@ -539,31 +1096,18 @@ Debugger::cmd_c() bc_enable = true; } - Continue(); -} - -// 実行再開 -void -Debugger::Continue() -{ - is_continued = true; - - // 今のレジスタセットを次回との差分のためにバックアップしておく - md->BackupRegs(); - - gCVPrompt->NotifyRelease(); } // 引数などからアドレスを取得する共通ルーチン。 -// 引数が1つ(以上)あれば av[1] をアドレスとしてパースする。 +// 引数が1つ(以上)あれば args[1] をアドレスとしてパースする。 // なければ、lastaddr が指定されていればそれを使う。 // それもなければ現在の PC を使う。 bool Debugger::GetAddr(uint32& addr, uint32& lastaddr) { - if (ac > 1) { + if (args.size() > 1) { // 1つ目の引数があれば、開始アドレス - if (ParseAddr(av[1], &addr) == false) { + if (ParseAddr(args[1].c_str(), &addr) == false) { return false; } // 偶数番地からに丸める @@ -614,8 +1158,8 @@ Debugger::cmd_d_common(MemdumpMode mode) return; } // 2つ目の引数があれば行数 - if (ac > 2) { - cnt = strtol(av[2], NULL, 10); + if (args.size() > 2) { + cnt = strtol(args[2].c_str(), NULL, 10); } saddr.addr = addr; @@ -659,13 +1203,13 @@ 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 に同じものがあるのでどうにかしたほうがいい @@ -674,6 +1218,7 @@ Debugger::cmd_L() if (strcmp(name, "scc") == 0) name = "sio"; + // 複数のオブジェクトが同じ logname を持ってもよい (CMMUとか)。 // all はどうするか std::string sname = name; bool found = false; @@ -682,7 +1227,6 @@ Debugger::cmd_L() obj->loglevel = level; cons->Print("%s=%d\n", name, obj->loglevel); found = true; - break; } } // 見付からない場合はエラー @@ -727,8 +1271,8 @@ Debugger::cmd_m_common(MemdumpMode mode) return; } // 2つ目の引数があれば行数 - if (ac > 2) { - cnt = strtol(av[2], NULL, 10); + if (args.size() > 2) { + cnt = strtol(args[2].c_str(), NULL, 10); } saddr.addr = addr; @@ -794,7 +1338,9 @@ Debugger::cmd_m_common(MemdumpMode mode) void Debugger::cmd_minus() { - cmd_r(); + // 通常レジスタを表示 + std::vector tmpargs {{ "r" }}; + md->ShowRegister(cons, tmpargs); // 論理アクセス時、ATC ミスが分かったほうが便利だと思う saddr_t laddr; @@ -824,9 +1370,9 @@ Debugger::cmd_minus() void Debugger::cmd_n() { - if (ac > 1) { + 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; @@ -838,7 +1384,6 @@ Debugger::cmd_n() } n_enable = true; SetNBreakpoint(); - Continue(); } // n コマンド用のサブルーチンを飛ばすブレークポイントを設定。 @@ -898,76 +1443,13 @@ void Debugger::cmd_q() { cons->Print("quit debugger console.\n"); - - // XXX: このフラグでいいのかとか exit でいいのかとか - if (gMainApp.debug_on_console) { - // コンソールモードのときはプログラムも終了 - exit(0); - } -} - -// レジスタ表示 -// 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 'c': - md->ShowRegCtrl(cons); - return; - case 'f': - md->ShowRegFPU(cons); - return; - case 'm': - md->ShowRegMMU(cons); - return; - case 'o': - md->ShowRegOther(cons); - return; - } - } - md->ShowRegMain(cons); -} - -// ショートカット -void -Debugger::cmd_ra() -{ - md->ShowRegATC(cons); -} -void -Debugger::cmd_rc() -{ - md->ShowRegCtrl(cons); -} -void -Debugger::cmd_rf() -{ - md->ShowRegFPU(cons); -} -void -Debugger::cmd_rm() -{ - md->ShowRegMMU(cons); -} -void -Debugger::cmd_ro() -{ - md->ShowRegOther(cons); } // モニター表示 void Debugger::cmd_show() { - if (ac != 2) { + if (args.size() != 2) { cons->Print("usage: show \n"); for (auto& d : gObjects) { if (d->logname != "?") { @@ -977,17 +1459,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; } } @@ -995,9 +1470,23 @@ Debugger::cmd_show() cons->Print("show: no such objects\n"); } -// モニタースクリーンを表示。 +// モニターを更新して表示。 void -Debugger::ShowMonitor(TextScreen& monitor) +Debugger::ShowMonitor(IMonitor& monitor) +{ + // モニタを更新 + TextScreen ts; + + auto size = monitor.GetMonitorSize(); + ts.Init(size.width, size.height); + + monitor.MonitorUpdate(ts); + ShowTextScreen(ts); +} + +// テキストスクリーンを表示。 +void +Debugger::ShowTextScreen(TextScreen& monitor) { char sbuf[1024]; // 適当 @@ -1052,6 +1541,12 @@ Debugger::ShowMonitor(TextScreen& monito } *d++ = ch; } + // 行の終わりで一旦属性を戻しておく + if (attr != TA::Normal) { + *d++ = 0x1b; + *d++ = '['; + *d++ = 'm'; + } *d = '\0'; // XXX 日本語が使われてれば UTF-8 にしないといけないはず @@ -1061,12 +1556,13 @@ Debugger::ShowMonitor(TextScreen& monito } // ステップ実行: s[?:1] +// 途中レジスタを表示せずに 命令実行する void Debugger::cmd_s() { - if (ac > 1) { + 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; @@ -1077,7 +1573,6 @@ Debugger::cmd_s() s_count = 1; } s_enable = true; - Continue(); } // ステップアウト @@ -1086,17 +1581,16 @@ Debugger::cmd_so() { so_enable = true; md->SetStepOut(); - Continue(); } // トレース実行: t[?:1] -// レジスタを表示しながら実行する +// レジスタを表示しながら 命令実行する void Debugger::cmd_t() { - if (ac > 1) { + 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 trace count: %d\n", count); return; @@ -1106,7 +1600,28 @@ Debugger::cmd_t() t_count = 1; } t_enable = true; - Continue(); +} + +// 引数で示されるプレフィックスなしの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; + } + + *valp = val; + return true; } // 引数で示されるアドレスを返す。 @@ -1115,9 +1630,9 @@ 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] == '%') { @@ -1187,3 +1702,19 @@ Debugger::FormatDumpAddr(saddr_t laddr, return rv; } + +// +// ブレークポイントモニター +// + +// コンストラクタ +BreakpointMonitor::BreakpointMonitor() +{ + monitor_size = nnSize(55, 9); +} + +void +BreakpointMonitor::MonitorUpdate(TextScreen& monitor) +{ + gDebugger->MonitorBreakpoint(monitor); +}