--- nono/debugger/debugger.cpp 2026/04/29 17:04:33 1.1.1.3 +++ nono/debugger/debugger.cpp 2026/04/29 17:04:40 1.1.1.5 @@ -15,6 +15,7 @@ #include static Debugger *gDebugger; +BreakpointMonitor *gBreakpointMonitor; CVPrompt *gCVPrompt; static void *debugger_run(void *); @@ -27,6 +28,7 @@ debugger_init() gDebugger = new Debugger(); gDebugger->Init(); + gBreakpointMonitor = new BreakpointMonitor(); gCVPrompt = new CVPrompt(); // デバッガスレッド起動 @@ -68,9 +70,28 @@ Debugger::Init() } // -b ならブレークポイント設定 - for (auto& b : gMainApp.debug_breakaddr) { + for (auto& str : gMainApp.debug_breakaddr) { + breakpoint_t bp; + md->ReqSet(CPU_REQ_TRACE); - AddBreakpoint(b, 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); } // どこでやるべか @@ -92,6 +113,11 @@ Debugger::ThreadRun() return; } + // disp_regs の初期値設定。 + // 再接続でも継続してていいような気がするのでループ外で初期化。 + disp_regs.clear(); + disp_regs.push_back("r"); + for (;;) { // 着信待ち if (Accept() == false) { @@ -120,7 +146,7 @@ Debugger::ThreadRun() m_last_addr = 0xffffffff; n_enable = false; s_enable = false; - t_enable = false; + t_enable = true; } // プロンプトが取れるのを待つ @@ -309,67 +335,249 @@ 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 == md->GetPC()) { - 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(); + } + + } 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(); + } } - // アドレス - for (auto& bp : bpoint) { - 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; + // ステップ実行系がなければブレークポイントの成否だけ + return is_break; +} + +// ブレークポイントがどれかでも成立するかを調べる。 +// 1つ以上成立してブレークするなら true を返す。 +// 1つも成立しておらずブレークしないなら false を返す。 +// 成立すれば内容をここで表示する。 +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; } - } - // ステップアウト - if (so_enable) { - if (md->IsStepOut()) { - so_enable = false; - return true; + // 条件は成立したのでカウントとスキップ + 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; + } + cons->Print("breakpoint #%d (%s) reached\n", i, desc.c_str()); } - // アドレス指定付き continue - if (bc_enable) { - if (bc_addr == md->GetPC()) { - bc_enable = false; - return true; + // 例外通知は通過ごとに常に下ろしておく + bv_vector = -1; + + // ブレークポイントが1つでも成立したかどうかを返す + return is_break; +} + +// 命令ブレークポイントが成立するか調べる。 +bool +Debugger::CheckBreakpointInst(breakpoint_t& bp) +{ + saddr_t laddr; + + // uint32 bi_inst が現在の PC 位置の命令データ (左詰め) + // bi_inst_bytes が読み込んだバイト数(bi_inst の左からの有効バイト数)。 + // bi_need_bytes が現在のブレークポイントで読み込む必要のあるバイト数。 + + laddr.addr = md->GetPC(); + laddr.super = md->IsSuper(); + laddr.logical = true; + + // 必要なバイト数に達するまで読み足す + bi_inst = 0; + while (bi_inst_bytes < bi_need_bytes) { + uint64 data = md->PeekFetch(laddr); + if ((int64)data < 0) { + return false; } + + bi_inst <<= md->inst_bytes * 8; + bi_inst |= data; + bi_inst_bytes += md->inst_bytes; + laddr.addr += 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; +} + // コマンド一覧。 // "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 }, @@ -377,6 +585,10 @@ Debugger::Check() { "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 }, @@ -384,63 +596,381 @@ Debugger::Check() { "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 (args.size() > 1) { - if (args[1] == "r") { - Help(md->GetRegisterHelp()); - return; - } + if (args.size() < 2) { + // 引数なしなら一覧表示。 + Help(HelpMsgMain); + return; } - // 引数なしか、あっても対応してないやつなら通常ヘルプを表示 - Help(HelpMsgMain); - return; + + // 引数があれば個別の詳細 + bool try_again = false; + std::string cmd = args[1]; + do { + + for (const auto& dict : HelpDetails) { + 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()); } -/*static*/ const HelpMessages -Debugger::HelpMsgMain = { - { "b", "ブレークポイント一覧表示" }, - { "b #n", "ブレークポイント n を削除" }, - { "b $", "メモリブレークポイント設定" }, - { "b ", "ブレークポイント設定" }, - { "bx", "ブレークポイント全削除" }, - { "brhist []", "ブランチ履歴表示" }, - { "c []", "実行再開(continue)" }, - { "d [ []", "逆アセンブル(論理、テーブルサーチなし)" }, - { "dt [ []", "逆アセンブル(論理、テーブルサーチあり)" }, - { "D [ []", "逆アセンブル(物理)" }, - { "h", "ヘルプ(help)" }, - { "L =", "ログレベル設定" }, - { "m [ []", "メモリダンプ(論理、テーブルサーチなし)" }, - { "mt [ []", "メモリダンプ(論理、テーブルサーチあり)" }, - { "M [ []", "メモリダンプ(物理)" }, - { "n ", "ステップ実行 (サブルーチンを飛ばす)" }, - { "q", "quit" }, - { "r*", "各種レジスタを表示 (see \"help r\")" }, - { "so", "ステップアウト" }, - { "show ", "モニター表示" }, - { "t ", "トレース実行" }, -}; +// hb : ブレークポイント系コマンドの一覧を表示 +// こいつだけ結構占めるので別階層。 +void +Debugger::cmd_hb() +{ + Help(HelpMsgBreakpoints); +} +// hr : レジスタ表示系コマンドの一覧を表示 +// CPU ごとに違うので。 void -Debugger::Help(HelpMessages msgs) +Debugger::cmd_hr() { + Help(md->GetRegisterHelp()); +} + +// ヘルプ表示の下請け。 +void +Debugger::Help(const HelpMessages& msgs) +{ + cons->Print("Type \"help \" for indivisual details.\n"); for (const auto& pair : msgs) { - cons->Print(" %-23s %s\n", pair.first.c_str(), pair.second.c_str()); + cons->Print(" %-16s %s\n", pair.first.c_str(), pair.second.c_str()); } } +// 個別ヘルプメッセージを出力用に置換。 +// 1. 行頭の連続する改行 (通常は1つのはず) を取り除く。 +// 2. 末尾の連続するタブ (通常は1つのはず) を取り除く。 +// 3. (各行頭の) タブを空白4つに置換する。 +std::string +Debugger::HelpConvert(const std::string& src) +{ + int start = 0; + int len = src.size(); + + // 末尾の連続するタブをカウント + 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); + } + } + return dst; +} + +/*static*/ const HelpMessages +Debugger::HelpMsgMain = { + { "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" }, +}; + +/*static*/ const HelpMessages +Debugger::HelpMsgBreakpoints = { + { "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" }, +}; + +// 各コマンドの詳細。"コマンド名" => "説明文" の形式。 +// 説明文は 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. You can force this by + specifier. "s" means the supervisor and "u" means the user privilege. + "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. You can force this by + specifier. "s" means the supervisor and "u" means the user privilege. + "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() @@ -482,6 +1012,9 @@ Debugger::ParseCmdbuf() // // ブレークポイント +// b ... 一覧表示 +// b #n ... #n を削除 +// b [] ... 設定 void Debugger::cmd_b() { @@ -495,76 +1028,314 @@ Debugger::cmd_b() // # 形式なら、指定番号のブレークポイントを削除 int i = atoi(&args[1][1]); if (i < 0 || i >= bpoint.size()) { - cons->Print("invaild break point number: #%d\n", i); + cons->Print("invaild breakpoint number: #%d\n", i); return; } auto& 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); + 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; - bool rv; - if (args[1][0] == '$') { - // $ 形式ならメモリブレークポイント - ismemory = true; - rv = ParseAddr(&args[1][1], &addr); + 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 { - // そうでなければアドレス指定 - ismemory = false; - rv = ParseAddr(&args[1][0], &addr); + cons->Print("breakpoint #%d added\n", bi); } - if (rv == false) { +} + +// 命令ブレークポイントの設定 +// 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; } - // すでにあればそのブレークポイントを削除 - for (int i = 0; i < bpoint.size(); i++) { - auto& 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) { + // マスク指定なし + 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 { + 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()); + } + // 空いてるところにセット - 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 { - auto& 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++) { - auto& bp = bpoint[i]; - if (bp.enable) { - cons->Print(" #%d %s%08x %d\n", - i, - bp.ismemory ? "memory $" : "", - bp.addr, bp.count); - found = 1; + 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_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"); } } @@ -573,56 +1344,131 @@ void Debugger::cmd_bx() { for (auto& bp : bpoint) { - bp.enable = false; + 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 < bpoint.size(); i++) { auto& bp = bpoint[i]; - if (!bp.enable) { - bp.enable = true; - bp.addr = addr; - bp.ismemory = ismemory; - bp.count = 0; + 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() { - int n = 20; + 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) { - n = atoi(args[1].c_str()); - if (n <= 0) n = 1; - if (n > 255) n = 256; - } - // コンソールでは古い順に表示。最下行が最新。 - auto& bh = (BranchHistory&)md->GetBrHist(); - uint8 t = bh.top; - - t = t - n; - for (int i = 0; i < n; i++) { - auto& e = bh.entry[++t]; - if (e.count == 0) continue; - cons->Print("%03d:%08x->%08x", n - i - 1, e.from, e.to); - if (e.count > 1) { - cons->Print(" x %u\n", e.count); - } else { - cons->Print("\n"); + 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 [] @@ -642,38 +1488,64 @@ Debugger::cmd_c() } } -// 引数などからアドレスを取得する共通ルーチン。 +// 引数などからアドレスを取得するごった煮共通ルーチン。 +// // 引数が1つ(以上)あれば args[1] をアドレスとしてパースする。 -// なければ、lastaddr が指定されていればそれを使う。 -// それもなければ現在の PC を使う。 +// 引数がなければ sa->addr には書き込まない、ただし sa->addr が 0xffffffff +// (last_addr がまだ使われていない) なら 現在の PC に置き換える。 +// アドレスに "s:" "u:" が前置してある場合はこれをパースして sa->super に +// 書き込む。指定がなければ sa->super には書き込まない。 +// つまり、呼び出し側で sa->addr に last_addr、sa->super に現在の CPU の +// IsSuper() を書き込んだ状態でこれを呼ぶと全部いろいろやってくれる。 +// sa は in/out パラメータ。 bool -Debugger::GetAddr(uint32& addr, uint32& lastaddr) +Debugger::GetAddr(saddr_t *sa) { - if (args.size() > 1) { - // 1つ目の引数があれば、開始アドレス - if (ParseAddr(args[1].c_str(), &addr) == false) { - return false; + if (args.size() < 2) { + // 引数なしなら、呼び出し側が sa->addr にセットした前回の値 + // (たぶん *_last_addr) をそのまま使う。ただしそれが 0xffffffff なら、 + // まだ前回値が一度もセットされていないので、仕方ないので PC を使う。 + if (sa->addr == 0xffffffff) { + sa->addr = md->GetPC(); } - // 偶数番地からに丸める - addr &= 0xfffffffe; - } else if (lastaddr != 0xffffffff) { - // 引数なしで前回値があれば前回の続き - addr = lastaddr; - } else { - // そうでなければ、今の PC - addr = md->GetPC(); + return true; + } + + // 引数ありならパースする。 + std::string& str = args[1]; + uint32 addr; + int addrpos = 0; + + // スーパーバイザ/ユーザ修飾子があればそれで上書き。 + // なければ何もしない (呼び出し側がセットした既定値を使う)。 + // XXX 将来 CMMU ID ("0:" … "7:") とかもあったほうがいいか + if (strncmp(str.c_str(), "s:", 2) == 0) { + sa->super = true; + addrpos = 2; + } else if (strncmp(str.c_str(), "u:", 2) == 0) { + sa->super = false; + addrpos = 2; } + + // アドレス + if (ParseAddr(str.c_str() + addrpos, &addr) == false) { + return false; + } + // 偶数番地に丸める + addr &= 0xfffffffe; + sa->addr = addr; + return true; } -// 逆アセンブル: d [ []] (論理、テーブルサーチなし) +// 逆アセンブル: d [[SU:] []] (論理、テーブルサーチなし) void Debugger::cmd_d() { cmd_d_common(MemdumpMode::Logical_atc); } -// 逆アセンブル: dt [] []] (論理、テーブルサーチあり) +// 逆アセンブル: dt [[SU:]] []] (論理、テーブルサーチあり) void Debugger::cmd_dt() { @@ -691,14 +1563,15 @@ Debugger::cmd_D() void Debugger::cmd_d_common(MemdumpMode mode) { - uint32 addr; saddr_t saddr; int cnt; cnt = 10; // 開始アドレス - if (GetAddr(addr, d_last_addr) == false) { + saddr.addr = d_last_addr; + saddr.super = md->IsSuper(); + if (GetAddr(&saddr) == false) { return; } // 2つ目の引数があれば行数 @@ -706,9 +1579,6 @@ Debugger::cmd_d_common(MemdumpMode mode) cnt = strtol(args[2].c_str(), NULL, 10); } - saddr.addr = addr; - saddr.super = md->IsSuper(); - // XXX そのうちなんとかする if (mode == MemdumpMode::Logical_search) { saddr.logical = true; @@ -740,6 +1610,36 @@ Debugger::cmd_d_common(MemdumpMode mode) d_last_addr = saddr.addr; } +// 表示レジスタ選択 +// 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)); + } + + // XXX チェック +} + // ログレベル設定: L void Debugger::cmd_L() @@ -804,14 +1704,15 @@ Debugger::cmd_M() void Debugger::cmd_m_common(MemdumpMode mode) { - uint32 addr; saddr_t saddr; int cnt; cnt = 10; // 開始アドレス - if (GetAddr(addr, m_last_addr) == false) { + saddr.addr = m_last_addr; + saddr.super = md->IsSuper(); + if (GetAddr(&saddr) == false) { return; } // 2つ目の引数があれば行数 @@ -819,8 +1720,6 @@ Debugger::cmd_m_common(MemdumpMode mode) cnt = strtol(args[2].c_str(), NULL, 10); } - saddr.addr = addr; - saddr.super = md->IsSuper(); // XXX if (mode == MemdumpMode::Logical_search) { saddr.logical = true; @@ -882,9 +1781,15 @@ Debugger::cmd_m_common(MemdumpMode mode) void Debugger::cmd_minus() { - // 通常レジスタを表示 - std::vector tmpargs {{ "r" }}; - md->ShowRegister(cons, tmpargs); + // 指定のレジスタ群を表示 + // 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); + } // 論理アクセス時、ATC ミスが分かったほうが便利だと思う saddr_t laddr; @@ -905,15 +1810,30 @@ Debugger::cmd_minus() mnembuf = md->FormatDisasmLive(mnemonic, ir); } cons->Print("%-18s %s\n", addrbuf.c_str(), mnembuf.c_str()); - - // XXX m88k? - nextpc = pc + ir.size(); } -// ステップ実行: n[?=1] (サブルーチンを飛ばす) +// サブルーチンとループを飛ばしながら count 命令ステップ実行 +// n [?:1] (途中レジスタを表示しない) void Debugger::cmd_n() { + 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(args[1].c_str(), NULL, 10); @@ -930,55 +1850,41 @@ Debugger::cmd_n() SetNBreakpoint(); } -// 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)vm_phys_peek_8(pc)) << 8) - | vm_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; + saddr_t laddr; + uint32 op; + + // 毎回他と干渉なく現在位置の命令語を取り出すほうが安全か + laddr.addr = md->GetPC(); + laddr.logical = true; + laddr.super = md->IsSuper(); + op = md->PeekFetch(laddr); + + // 命令がサブルーチンやトラップなどステップインできる命令か + if (md->IsOpStepIn(op)) { + // この次の命令にブレークをかける + if (md->inst_bytes_fixed != 0) { + // 固定長命令 + n_breakaddr = md->GetPC() + md->inst_bytes_fixed; + } else { + // 可変長なら逆アセンブルしてみるしか + std::string mnemonic; + std::vector v; + // XXX 失敗したらどうすべ + md->Disassemble(laddr, mnemonic, v); + if (v.size() > 0) { + n_breakaddr = md->GetPC() + v.size(); + } + } } else { - n_breakenable = true; - n_breakaddr = nextpc; - return true; + // そうでなければ1命令進める。 + // 0xffffffff は常にアドレスとして不正なのでこれをフラグに使う。 + n_breakaddr = 0xffffffff; } } @@ -989,6 +1895,13 @@ Debugger::cmd_q() cons->Print("quit debugger console.\n"); } +// VM リセット +void +Debugger::cmd_reset() +{ + gScheduler->RequestResetHard(); +} + // モニター表示 void Debugger::cmd_show() @@ -1016,14 +1929,16 @@ Debugger::cmd_show() // モニターを更新して表示。 void -Debugger::ShowMonitor(Object& obj) +Debugger::ShowMonitor(IMonitor& monitor) { // モニタを更新 - if (obj.MonitorUpdate() == false) { - cons->Print("show: no monitor\n"); - return; - } - ShowTextScreen(obj.monitor); + TextScreen ts; + + auto size = monitor.GetMonitorSize(); + ts.Init(size.width, size.height); + + monitor.MonitorUpdate(ts); + ShowTextScreen(ts); } // テキストスクリーンを表示。 @@ -1097,10 +2012,28 @@ Debugger::ShowTextScreen(TextScreen& mon } } -// ステップ実行: s[?:1] +// ステップ実行 +// s [?:1] (途中レジスタを表示しない) void Debugger::cmd_s() { + 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(args[1].c_str(), NULL, 10); @@ -1116,31 +2049,57 @@ Debugger::cmd_s() s_enable = true; } -// ステップアウト +// ステップアウト (途中レジスタを表示しない) 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; md->SetStepOut(); } -// トレース実行: t[?:1] -// レジスタを表示しながら実行する +// t は st の省略形。互換のため。 void Debugger::cmd_t() { - if (args.size() > 1) { - int count; - count = strtol(args[1].c_str(), 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; } - t_enable = true; + if (errno == ERANGE) { + return false; + } + + *valp = val; + return true; } // 引数で示されるアドレスを返す。 @@ -1149,9 +2108,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] == '%') { @@ -1221,3 +2180,53 @@ Debugger::FormatDumpAddr(saddr_t laddr, return rv; } + +// +// MD +// + +// 指定のアドレスから1命令語を読み出し、下(右)詰めにして返す。 +// 読み込めなければ (uint64)-1 を返す。 +uint64 +DebuggerMD::PeekFetch(saddr_t laddr) +{ + uint64 paddr; + uint32 data; + + // サーチあり、命令空間は固定 + laddr.search = true; + laddr.data = false; + + if (laddr.IsLogical() && MMUEnabled()) { + paddr = TranslateAddr(laddr); + if ((int64)paddr < 0) + return (uint64)-1; + } else { + paddr = laddr.addr; + } + + // 物理空間から1語読み込む + data = 0; + for (int i = 0; i < inst_bytes; i++) { + data <<= 8; + data |= vm_phys_peek_8(paddr++); + } + return data; +} + + +// +// ブレークポイントモニター +// + +// コンストラクタ +BreakpointMonitor::BreakpointMonitor() +{ + monitor_size = nnSize(55, 9); +} + +void +BreakpointMonitor::MonitorUpdate(TextScreen& monitor) +{ + gDebugger->MonitorBreakpoint(monitor); +}