--- nono/debugger/debugger.cpp 2026/04/29 17:04:37 1.1.1.4 +++ nono/debugger/debugger.cpp 2026/04/29 17:05:09 1.1.1.11 @@ -4,77 +4,157 @@ // Licensed under nono-license.txt // -#include "console.h" +// +// デバッガ +// + +// +// VM スレッド デバッガスレッド HostCOM +// condvar +// | | |<--- 入力 +// | |<----------------------| +// | | RxCallback +// | | +// | |---------------------->| +// | | HostCOMDevice::Tx() |---> 出力 + +// |<---------------------------| +// | is_prompt = true; | デバッガスレッドからプロンプトを出したい +// | Message(MPU_TRACE); | 場合は MPU (VM) をトレースモードにする。 +// | | その際 is_prompt を立てておくことで止まる。 +// | | +// |--------------------------->| +// | condvar REQUEST_PROMPT | VM スレッドからプロンプトを出したい場合 +// | | (上述の例も含む) は条件変数で通知。 +// | | +// |<---------------------------| +// | condvar prompt_released | プロンプトを出している間 VM スレッドは +// | | 条件変数で待機しているので、これを起こす +// | | ことで実行再開。 + +#include "debugger.h" #include "debugger_private.h" #include "debugger_m680x0.h" #include "debugger_m88xx0.h" +#include "hostcom.h" #include "mainapp.h" #include "mystring.h" -#include "mythread.h" +#include "power.h" +#include "scheduler.h" +#include "sync.h" +#include "uimessage.h" +#include "vectortable.h" #include -#include +#if defined(HAVE_BSD_STDIO_H) +#include +#endif -static Debugger *gDebugger; -BreakpointMonitor *gBreakpointMonitor; -CVPrompt *gCVPrompt; +static int readfunc(void *, char *, int); +static int writefunc(void *, const char *, int); -static void *debugger_run(void *); +// グローバル参照用 +Debugger *gDebugger; -// 初期化。 -// この時点で VM が初期化されていること。 -void -debugger_init() +// メモリダンプモニタを外部から取得 +Monitor& +debugger_memdump_monitor(int n) { - gDebugger = new Debugger(); - gDebugger->Init(); + assert(n < MAX_MEMDUMP_MONITOR); + return gDebugger->memdump_monitor[n]; +} - gBreakpointMonitor = new BreakpointMonitor(); - gCVPrompt = new CVPrompt(); +// コンストラクタ +Debugger::Debugger() + : inherited("Debugger") +{ + // ベクタテーブル + pVectorTable.reset(new VectorTable(gMainApp.GetVMType())); + gVectorTable = pVectorTable.get(); + + // ブレークポイントモニター + bpoint_monitor.func = ToMonitorCallback(&Debugger::MonitorUpdateBpoint); + bpoint_monitor.SetSize(55, 9); + bpoint_monitor.Regist(ID_MONITOR_BREAKPOINT); - // デバッガスレッド起動 - pthread_t th; - pthread_create(&th, NULL, debugger_run, NULL); + // メモリダンプモニター + for (int i = 0, end = memdump_monitor.size(); i < end; i++) { + auto& mon = memdump_monitor[i]; + mon.obj = this; + mon.func = ToMonitorCallback(&Debugger::MonitorUpdateMemdump); + mon.SetSize(76, 16); + mon.Regist(ID_MONITOR_MEMDUMP(i)); + } + // m/M コマンド用。こっちは Regist 不要 + m_monitor.func = ToMonitorCallback(&Debugger::MonitorUpdateMemdump); + m_monitor.SetSize(76, 16); } -// デバッガスレッドのエントリポイント -static void * -debugger_run(void *dummy) +// デストラクタ +Debugger::~Debugger() { - PTHREAD_SETNAME("Debugger"); - pthread_detach(pthread_self()); + // fclose(fflush) にあたり hostcom へのアクセスが発生するので、 + // hostcom より先に片付けておかなければならない。 + Close(); + + if ((bool)hostcom) { + hostcom->SetCallbackDevice(NULL); + } - gDebugger->ThreadRun(); - return NULL; + TerminateThread(); + gDebugger = NULL; } -// コンストラクタ -Debugger::Debugger() +// ログレベル設定 +void +Debugger::SetLogLevel(int loglevel_) { - if (gMPU680x0) { - md = new DebuggerMD_m680x0(this, gMPU680x0->GetCPU()); - } else if (gMPU88xx0) { - md = new DebuggerMD_m88xx0(this, gMPU88xx0->GetCPU()); - } else { - throw "unknown mpu"; + inherited::SetLogLevel(loglevel_); + + // ホストドライバを従属させる + if ((bool)hostcom) { + hostcom->SetLogLevel(loglevel_); } } -// コマンドライン引数によって動作を決めるところ。 -// 名前がこれでいいのかはあるけど。 -void +bool +Debugger::Create() +{ + // ホストドライバを作成 + try { + hostcom.reset(new HostCOMDevice("Debugger")); + } catch (...) { + return false; + } + + hostcom->SetRxCallback(ToDeviceCallback(&Debugger::RxCallback)); + hostcom->SetAcceptCallback(ToDeviceCallback(&Debugger::AcceptCallback)); + hostcom->SetCallbackDevice(this); + + return true; +} + +// 初期化 +bool Debugger::Init() { + if (gMPU680x0) { + md.reset(new DebuggerMD_m680x0(this, gMPU680x0->GetCPU())); + } else if (gMPU88xx0) { + md.reset(new DebuggerMD_m88xx0(this, gMPU88xx0->GetCPU())); + } else { + assertmsg(false, "unknown mpu"); + } + // -d なら CPU 起動時点で停止してプロンプトを待つ if (gMainApp.debug_on_start) { - md->ReqSet(CPU_REQ_PROMPT); + is_pause = true; + // この時点ではまだ MPU にメッセージを送ることはできない } // -b ならブレークポイント設定 for (auto& str : gMainApp.debug_breakaddr) { breakpoint_t bp; - md->ReqSet(CPU_REQ_TRACE); - // , で分離できたら を取り出す int pos = str.find(','); if (pos != std::string::npos) { @@ -87,43 +167,82 @@ Debugger::Init() // そしてどちらにしても を取り出す if (!ParseAddr(str.c_str(), &bp.addr)) { warnx("\"%s\": Invalid breakpoint address", str.c_str()); - return; + return false; } // 登録 bp.type = BreakpointType::Address; AddBreakpoint(bp); } - // どこでやるべか - signal(SIGPIPE, SIG_IGN); + // 入出力を FILE で扱う + cons = funopen(this, readfunc, writefunc, NULL, NULL); + if (cons == NULL) { + warnx("funopen2 failed"); + return false; + } + + return true; } // デバッガスレッド void Debugger::ThreadRun() { - // XXX どこかでコンソールの選択とパラメータの取得 - 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"); + + // MPU のトレース状態の初期化は vm/mpu* 側のリセット例外で行っている。 for (;;) { - // 着信待ち - if (Accept() == false) { + // 何か起きるまで待つ + uint32 req; + { + std::unique_lock lock(mtx); + cv_request.wait(lock, [&] { return request != 0; }); + req = request; + request = 0; + } + + if ((req & REQUEST_EXIT)) { break; } - cons->InitEditLine("> "); + if ((req & REQUEST_RXCHAR)) { + // コンソールからの文字入力 + int c; + while ((c = hostcom->Rx()) >= 0) { + Input(c); + } + } + + if ((req & REQUEST_ACCEPT)) { + // TCP 待ち受けに着信があればすぐにプロンプトを出したい + Input('\n'); + } + + if ((req & REQUEST_PROMPT)) { + // VM 停止(したのでプロンプトモードへ) + EnterPrompt(); + } + } + + LeavePrompt(); + Close(); +} - // デバッガプロンプトにいる間はトレースオン - md->ReqSet(CPU_REQ_TRACE); +// コンソールをクローズする +void +Debugger::Close() +{ + if (cons) { + fclose(cons); + cons = NULL; + } +} +#if 0 bool first = true; for (;;) { // 接続後の1回目だけ実行するもの。 @@ -133,252 +252,406 @@ Debugger::ThreadRun() // greeting はプロンプトが取れる前にもう表示したい。 // 何らかの事故でプロンプトが取れなくても、ここまでは接続 // できてることが分かるように。 - cons->Print("This is debugger console\n"); + fprintf(cons, "This is debugger console\n"); // 接続ごとに初期化する値 - // XXX もうちょっときれいにしたい - d_last_addr = 0xffffffff; - m_last_addr = 0xffffffff; n_enable = false; s_enable = false; - t_enable = false; + t_enable = true; } + } + } - // プロンプトが取れるのを待つ - if (AcquirePrompt() == false) { - break; - } +#endif - // プロンプトに来た時に表示するいつものやつ - pc = md->GetPC(); - d_last_addr = pc; - cmd_minus(); +// スレッドに終了指示 +void +Debugger::Terminate() +{ + std::unique_lock lock(mtx); + request |= REQUEST_EXIT; + cv_request.notify_one(); +} - // コマンド処理のメインループ - auto action = MainLoop(); +// funopen の read コールバック +static int +readfunc(void *cookie, char *buf, int bufsize) +{ + return ((Debugger *)cookie)->ReadFunc(buf, bufsize); +} - // 次回との差分のため、今のレジスタセットをバックアップ - md->BackupRegs(); +// funopen の write コールバック +static int +writefunc(void *cookie, const char *buf, int len) +{ + return ((Debugger *)cookie)->WriteFunc(buf, len); +} - // メインループから戻ったということは Leave か Quit なので - // どちらにしても、ここでプロンプトを手放す。 - gCVPrompt->NotifyRelease(); +// funopen の read コールバックの本体 +int +Debugger::ReadFunc(char *buf, int bufsize) +{ + char *d = buf; + char *end = buf + bufsize; - // quit ならここでループを1段抜ける - if (action == CommandAction::Quit) { - break; - } + for (; d < end; ) { + if ((bool)hostcom == false) { + errno = EIO; + return -1; } - // デバッガを抜けるのでトレースオフ - md->ReqClr(CPU_REQ_TRACE); - - cons->Close(); - if (gMainApp.debug_on_console) { + int c = hostcom->Rx(); + if (c < 0) { break; } + *d++ = c; } - - delete cons; + return (d - buf); } -// コンソールに接続されるのを待つ。 -// うーん、なんだこれ。 -// -// +--- 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() +// funopen の write コールバックの本体 +int +Debugger::WriteFunc(const char *buf, int len) { - 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; - } + const char *s = buf; + const char *end = buf + len; - int r = cons->Poll(200); - if (r < 0) { - cmd_q(); - return false; - } - if (r > 0) { - break; - } + for (; s < end; ) { + if ((bool)hostcom == false) { + errno = EIO; + return -1; + } + + // ホストスレッドのキューが満杯なら空くまで待つ。うーん…。 + // XXX 無期限で大丈夫だろうか + int c = *s++; + if (c == '\n') { + // ここで LF を CRLF にする? + // (TELNET に出力するのに必要) + while (hostcom->Tx('\r') == false) { + usleep(10); } - } else { - // stdio、起動時にプロンプトで止まる。 - // この場合は無条件に成功でよい。 + } + while (hostcom->Tx(c) == false) { + usleep(10); } } + return (s - buf); +} - return true; +// ホストからの1文字受信通知 (HostCOM スレッドから呼ばれる) +void +Debugger::RxCallback() +{ + // デバッガスレッドに通知 + std::unique_lock lock(mtx); + request |= REQUEST_RXCHAR; + cv_request.notify_one(); } -// プロンプトが取れるのを待つ -// 失敗すれば false を返す -bool -Debugger::AcquirePrompt() +// ホストからの着信通知 (HostCOM スレッドから呼ばれる) +void +Debugger::AcceptCallback() { - md->ReqSet(CPU_REQ_PROMPT); + // デバッガスレッドに通知 + std::unique_lock lock(mtx); + request |= REQUEST_ACCEPT; + cv_request.notify_one(); +} - for (;;) { - bool is_prompt = gCVPrompt->WaitAcquire(200); - if (is_prompt) - break; +// ホストからの1文字入力 +void +Debugger::Input(int c) +{ + // '^@' は捨てる + // (意図的にも入力できるが nc が TELNET オプション扱えなくて送ってくる) + if (c == '\0') { + return; + } - // コンソールを定期観測して、入力があればブレーク要求 - if (cons->Poll()) { - // この入力は drop してみる - if (cons->Gets(cmdbuf) == false) { - // EOF or Error - cmd_q(); - return false; + // HostCOM からは改行で CR が来るようなので LF にしておく + if (c == '\r') { + c = '\n'; + } + + if (is_prompt == false) { + // プロンプトでない時は、Enter か ^C でプロンプトを出す + if (c == '\n' || c == '\x03') { + // MPU に一時停止を要求 + is_pause = true; + gScheduler->SendMessage(MessageID::MPU_TRACE, true); + } + } else { + // プロンプト中なら行入力 + + // 先に自力エコーバック?? + fputc(c, cons); + fflush(cons); + + if (c != '\n') { + cmdbuf.push_back(c); + } else { + // Enter ならここでコマンド実行 + + auto act = Command(); + + // 次回との差分のため、今のレジスタセットをバックアップ + md->BackupRegs(); + + switch (act) { + case CommandAction::Stay: + // プロンプトに留まるならここで、次行のプロンプト? + PrintPrompt(); + break; + case CommandAction::Leave: + // プロンプトを抜ける + LeavePrompt(); + break; + case CommandAction::Quit: + // アプリケーション自体を終了 + LeavePrompt(); + UIMessage::Post(UIMessage::APPEXIT); } - md->ReqSet(CPU_REQ_PROMPT); } } - return true; } -// コマンド処理のメインループ。 -// コマンドが Stay ならこの中で処理を継続。 -// コマンドが Leave か Quit ならその値を持って戻る (呼び出し側で処理する)。 +// コマンドモード(プロンプト)に入る +void +Debugger::EnterPrompt() +{ + is_prompt = true; + + // d/m をいきなり引数なしで実行した時のため、現在地にしておく。 + pc = md->GetPC(); + m_last_addr.Set(pc, md->IsSuper(), true); + d_last_addr.Set(pc, md->IsSuper(), false); + + // ブレークポイント到達メッセージがあればここで表示 + if (bpointmsg.empty() == false) { + fprintf(cons, "%s", bpointmsg.c_str()); + bpointmsg.clear(); + } + + // プロンプトのたびに表示するやつ + cmd_minus(); + + PrintPrompt(); +} + +// コマンドモード(プロンプト)から出る +void +Debugger::LeavePrompt() +{ + // MPU のトレース状態を変更 + gScheduler->SendMessage(MessageID::MPU_TRACE, IsTrace()); + + is_prompt = false; + + // 最後に VM スレッドで待機している Exec() を起こす + { + std::unique_lock lock(mtx); + prompt_released = true; + cv_prompt.notify_one(); + } +} + +// プロンプトを表示 +void +Debugger::PrintPrompt() +{ + fprintf(cons, "> "); + fflush(cons); +} + +// コマンド実行 Debugger::CommandAction -Debugger::MainLoop() +Debugger::Command() { - for (;;) { - cons->Prompt(); - if (cons->Gets(cmdbuf) == false) { - // EOF or Error - return CommandAction::Quit; + string_rtrim(cmdbuf); + + if (cmdbuf.empty()) { + // 空行なら、直前のコマンドをもう一度。 + // 前行がなければ何もせずもう一度プロンプトを表示するかね。 + if (last_cmdbuf.empty()) { + return CommandAction::Stay; } + cmdbuf = last_cmdbuf; + } else { + // 入力された行を次回のために保存 + last_cmdbuf = cmdbuf; + } - string_rtrim(cmdbuf); + // 行を args に分解 + ParseCmdbuf(); + cmdbuf.clear(); - if (!cmdbuf.empty()) { - // コマンドが入力されれば次回のために保存 - last_cmdbuf = cmdbuf; - } else { - // 空行が入力されれば直前のコマンドをもう一度 - // 前行がなければ何もせずもう一度プロンプトを表示するかね - if (last_cmdbuf.empty()) - continue; - cmdbuf = last_cmdbuf; + // 前回の値が有効なのはコマンドが連続した時だけ + + // コマンドをテーブルから探す + 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))(); + + return cmd.action; + } + + // (コマンドテーブルになくて) "r" から始まっていればレジスタ表示系 + if (args[0][0] == 'r') { + // ShowRegister() は処理したら true を返す。 + // "r" 系コマンドはすべて Stay。 + if (md->ShowRegister(cons, args)) { + return CommandAction::Stay; } + } - // 行を args に分解 - ParseCmdbuf(); + // 知らないコマンドも Stay 相当。 + fprintf(cons, "%s: unknown command\n", args[0].c_str()); + // この行は次回空エンターで再発行しないでいい。 + last_cmdbuf.clear(); + return CommandAction::Stay; +} - // 前回の値が有効なのはコマンドが連続した時だけ +// ブレークポイントとかを調べる。1命令ごとに呼び出される。 +// (VM スレッドから呼ばれる) +void +Debugger::Exec() +{ + if (Check()) { + // 実時間を停止 + gSync->StopRealTime(); - // コマンドをテーブルから探す - 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; + // 待機 + { + std::unique_lock lock(mtx); - // 見付かれば実行 - (this->*(cmd.func))(); + // notify 前にクリア + prompt_released = false; - // Stay なら引き続きコマンド処理 - if (cmd.action == CommandAction::Stay) { - continue; - } - // それ以外ならメインループ終了 - return cmd.action; - } + // MPU が一時停止したことをデバッガスレッドへ通知する + request |= REQUEST_PROMPT; + cv_request.notify_one(); - // (コマンドテーブルになくて) "r" から始まっていればレジスタ表示系 - if (args[0][0] == 'r') { - // ShowRegister() は処理したら true を返す。 - // "r" 系コマンドはすべて Stay。 - if (md->ShowRegister(cons, args)) { - continue; - } + // デバッガスレッドがプロンプト解放するまで待機 + cv_prompt.wait(lock, [&] { return prompt_released; }); } - // 知らないコマンドも Stay 相当。 - cons->Print("%s: unknown command\n", args[0].c_str()); - continue; + // 実時間を再開 + gSync->StartRealTime(); + } else { + // t (トレース表示) が有効な場合は終了条件にマッチしなかったここで + // レジスタを表示。終了条件にマッチした時は EnterPrompt() で表示する。 + if (t_enable) { + pc = md->GetPC(); + cmd_minus(); + // 次回との差分のため今のレジスタセットをバックアップ + md->BackupRegs(); + } } - __unreachable(); } -// ブレークポイントとかを調べる。 -// CPU_REQ_TRACE フラグが立っている間1命令ごとに呼び出される。 +// MPU をトレース状態にすべきなら true を返す。 bool -debugger_check() +Debugger::IsTrace() const { - return gDebugger->Check(); + // 有効なブレークポイントがあれば MPU をトレースにする + for (const auto& bp : bpoint) { + if (bp.type != BreakpointType::Unused) { + return true; + } + } + + // この辺のどれかがあれば MPU をトレースにする + return bc_enable || s_enable || so_enable || n_enable || is_pause; } -// デバッガ実行中なら命令開始前にメインルーチンから呼ばれる。 +// デバッガ実行中なら命令開始前に VM スレッドから呼ばれる。 // プロンプトに降りるなら true を返す。 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 を + // 返せばよい。 + + // トレース表示に関しては Exec 側でやってある。 + + if (s_enable) { // ステップ実行が成立するか s_count--; - if (s_count == 0) { + if (s_count == 0 || is_break) { s_enable = false; - return true; + t_enable = false; + is_break = true; } - } - - // n による1命令実行のブレークポイントか - if (n_enable && n_breakenable && n_breakaddr == md->GetPC()) { - n_breakenable = false; - return true; - } - - // ブレークポイント - if (CheckAllBreakpoints()) { - return true; - } - // ステップアウト - if (so_enable) { - if (md->IsStepOut()) { + } else if (so_enable) { // ステップアウトが成立するか + if (md->IsStepOut() || is_break) { so_enable = false; - return true; + t_enable = false; + is_break = true; } - } - // アドレス指定付き continue - if (bc_enable) { - if (bc_addr == md->GetPC()) { - bc_enable = false; - 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 || is_break) { + n_enable = false; + t_enable = false; + is_break = true; + } else { + // スキップ中でなければ、ステップインが起きるか都度調べる。 + // すでにスキップ中なら到達するまでは何もしない。 + if (n_breakaddr == 0xffffffff) { + SetNBreakpoint(); + } } } - return false; + // デバッガから MPU の一時停止が要求されているか + if (is_pause) { + is_pause = false; + is_break = true; + } + + return is_break; } // ブレークポイントがどれかでも成立するかを調べる。 // 1つ以上成立してブレークするなら true を返す。 // 1つも成立しておらずブレークしないなら false を返す。 +// このルーチンから cons への出力は使わないこと。(プロンプトにいない時でも +// 呼ばれるので) bool Debugger::CheckAllBreakpoints() { @@ -391,7 +664,9 @@ Debugger::CheckAllBreakpoints() // 1つの条件でマッチしても(そこでブレークすること自体は確定するのだが) // 残りの他の条件も成立すればカウントを進める必要があるため、 // 全部処理した上でどれか一つでもブレークしたかで判断する必要がある。 - for (auto& bp : bpoint) { + 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()) { @@ -438,6 +713,40 @@ Debugger::CheckAllBreakpoints() // ブレーク成立 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 = gVectorTable->GetExceptionName(bv_vector); + if (name) { + 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()); } // 例外通知は通過ごとに常に下ろしておく @@ -451,43 +760,26 @@ Debugger::CheckAllBreakpoints() 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; + saddr_t laddr(md->GetPC(), md->IsSuper()); + DebuggerMemoryStream mem(md.get(), laddr); + // 必要なバイト数に達するまで読み足す + bi_inst = 0; 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; + uint64 data = mem.FetchInst(); + if ((int64)data < 0) { + return false; } - // 命令長ずつ読み込む。 - 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 <<= md->inst_bytes * 8; + bi_inst |= data; + bi_inst_bytes += md->inst_bytes; } + // 左詰めにする bi_inst <<= (4 - bi_inst_bytes) * 8; @@ -500,7 +792,7 @@ Debugger::CheckBreakpointInst(breakpoint // 例外通知 (MPU からの連絡用) void -debugger_notifyexception(int vector) +debugger_notify_exception(int vector) { gDebugger->NotifyException(vector); } @@ -525,7 +817,10 @@ Debugger::NotifyException(int vector) { "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 }, @@ -533,67 +828,405 @@ Debugger::NotifyException(int vector) { "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) { + // 引数なしなら一覧表示。 + ShowHelpList(HelpListMain); + return; } - // 引数なしか、あっても対応してないやつなら通常ヘルプを表示 - Help(HelpMsgMain); - 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); + fprintf(cons, "%s", disp.c_str()); + return; + } + } + } while (try_again); + fprintf(cons, "invalid command name: %s\n", args[1].c_str()); } -/*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 []", "トレース実行 (命令ごとにレジスタを表示)" }, -}; +// hb : ブレークポイント系コマンドの一覧を表示 +// こいつだけ結構占めるので別階層。 +void +Debugger::cmd_hb() +{ + ShowHelpList(HelpListBreakpoints); +} +// hr : レジスタ表示系コマンドの一覧を表示 +// CPU ごとに違うので。 void -Debugger::Help(HelpMessages msgs) +Debugger::cmd_hr() { + ShowHelpList(md->GetHelpListReg()); +} + +// ヘルプ一覧を表示。 +void +Debugger::ShowHelpList(const HelpMessages& msgs) +{ + fprintf(cons, "Type \"help \" for indivisual details.\n"); for (const auto& pair : msgs) { - cons->Print(" %-23s %s\n", pair.first.c_str(), pair.second.c_str()); + fprintf(cons, " %-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::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" }, +}; + +/*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" }, +}; + +// 各コマンドの詳細。"コマンド名" => "説明文" の形式。 +// 説明文は 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() @@ -649,21 +1282,7 @@ Debugger::cmd_b() // 引数取得 if (args[1][0] == '#') { // # 形式なら、指定番号のブレークポイントを削除 - int i = atoi(&args[1][1]); - if (i < 0 || i >= bpoint.size()) { - cons->Print("invaild breakpoint number: #%d\n", i); - return; - } - 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(); + cmd_b_delete(); return; } @@ -676,8 +1295,8 @@ void Debugger::cmd_bm() { // XXX m68k では未サポート - if (dynamic_cast(md)) { - cons->Print("bm not supported yet on m68k\n"); + if (md->arch == DebuggerMD::Arch::M680x0) { + fprintf(cons, "bm not supported yet on m68k\n"); return; } cmd_b_set(BreakpointType::Memory); @@ -690,7 +1309,7 @@ Debugger::cmd_b_set(BreakpointType type) breakpoint_t bp; if (args.size() < 2) { - cons->Print("usage: %s []\n", args[0].c_str()); + fprintf(cons, "usage: %s []\n", args[0].c_str()); return; } @@ -708,12 +1327,36 @@ Debugger::cmd_b_set(BreakpointType type) bp.type = type; int bi = AddBreakpoint(bp); if (bi == -1) { - cons->Print("no free breakpoints\n"); + fprintf(cons, "no free breakpoints\n"); } else { - cons->Print("breakpoint #%d added\n", bi); + fprintf(cons, "breakpoint #%d added\n", bi); } } +// ブレークポイント個別削除 +// (引数の先頭が '#' なことを判定したところで呼ばれる) +// b # +void +Debugger::cmd_b_delete() +{ + int n = atoi(&args[1][1]); + if (n < 0 || n >= bpoint.size()) { + fprintf(cons, "invalid breakpoint number: #%d\n", n); + return; + } + + auto& bp = bpoint[n]; + if (bp.type == BreakpointType::Unused) { + fprintf(cons, "invalid breakpoint number: #%d\n", n); + return; + } + + fprintf(cons, "breakpoint #%d removed\n", n); + bp.type = BreakpointType::Unused; + // 今登録されている命令ブレークの必要命令長を再計算 + RecalcInstMask(); +} + // 命令ブレークポイントの設定 // bi [:] [] void @@ -726,7 +1369,7 @@ Debugger::cmd_bi() int masklen; if (args.size() < 2) { - cons->Print("usage: bi [:] []\n"); + fprintf(cons, "usage: bi [:] []\n"); return; } @@ -747,11 +1390,11 @@ Debugger::cmd_bi() // 命令部チェック if (ParseVerbHex(inststr.c_str(), &bp.inst) == false) { - cons->Print("%s: invalid instruction value\n", args[1].c_str()); + fprintf(cons, "%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()); + if (instlen % (md->inst_bytes * 2) != 0) { + fprintf(cons, "%s: invalid instruction length\n", args[1].c_str()); return; } @@ -759,17 +1402,17 @@ Debugger::cmd_bi() 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()); + fprintf(cons, "%s: invalid mask value\n", args[1].c_str()); return; } if (masklen != instlen) { - cons->Print("%s: inst:mask must be the same length\n", + fprintf(cons, "%s: inst:mask must be the same length\n", args[1].c_str()); return; } } // 8バイト未満なら左詰め。 - if (md->inst_bits < 32 && instlen < 8) { + if (md->inst_bytes < 4 && instlen < 8) { bp.inst <<= 32 - instlen * 4; bp.mask <<= 32 - instlen * 4; } @@ -784,9 +1427,9 @@ Debugger::cmd_bi() bp.type = BreakpointType::Instruction; int bi = AddBreakpoint(bp); if (bi == -1) { - cons->Print("no free breakpoints\n"); + fprintf(cons, "no free breakpoints\n"); } else { - cons->Print("breakpoint #%d added\n", bi); + fprintf(cons, "breakpoint #%d added\n", bi); } // 今登録されている命令ブレークの必要命令長を再計算 @@ -801,7 +1444,7 @@ Debugger::cmd_bv() breakpoint_t bp; if (args.size() < 2) { - cons->Print("usage: be [-] []\n"); + fprintf(cons, "usage: be [-] []\n"); return; } @@ -809,7 +1452,7 @@ Debugger::cmd_bv() 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()); + fprintf(cons, "%s: invalid vector number\n", args[1].c_str()); return; } bp.vec2 = bp.vec1; @@ -819,22 +1462,22 @@ Debugger::cmd_bv() 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()); + fprintf(cons, "%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()); + fprintf(cons, "%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); + if (bp.vec1 < 0 || bp.vec1 >= gVectorTable->Size()) { + fprintf(cons, "$%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); + if (bp.vec2 < 0 || bp.vec2 >= gVectorTable->Size()) { + fprintf(cons, "$%x: invalid last vector number\n", bp.vec2); return; } @@ -856,22 +1499,35 @@ Debugger::cmd_bv() bp.type = BreakpointType::Exception; int bi = AddBreakpoint(bp); if (bi == -1) { - cons->Print("no free breakpoints\n"); + fprintf(cons, "no free breakpoints\n"); } else { - cons->Print("breakpoint #%d added\n", bi); + fprintf(cons, "breakpoint #%d added\n", bi); } + + // すでに来ている例外をクリア。 + // ブレークポイント設定の有無に関わらず例外が起きたら CPU 側から常に + // 通知されている。これをクリアするのは CheckAllBreakpoints() で、これは + // 命令間(命令前)に呼ばれるやつ、なのでこうなる。 + // 1. 例外が起きると bv_vector がセットされる + // 2. 例外ブレークポイントを設定していないとこれがクリアされない + // 3. bv コマンドで例外ブレークを新たに設定すると、次の命令境界で + // 1.のベクタが反応してしまう。 + // 命令ごととかにクリアしてもいいかも知れないが、ここでブレークポイントを + // 設定したのだから、それ以前の事象には反応すべきでない、という意味では + // ここでもいいか? + bv_vector = -1; } // ブレークポイント一覧表示 void Debugger::cmd_b_list() { - ShowMonitor(*gBreakpointMonitor); + ShowMonitor(bpoint_monitor); } // ブレークポイント一覧 (モニタ) void -Debugger::MonitorBreakpoint(TextScreen& monitor) +Debugger::MonitorUpdateBpoint(Monitor *, TextScreen& monitor) { // 0 1 2 3 4 5 6 // 0123456789012345678901234567890123456789012345678901234567890 @@ -912,7 +1568,7 @@ Debugger::MonitorBreakpoint(TextScreen& case BreakpointType::Instruction: monitor.Print(3, y, "inst"); - if (md->inst_bits == 32) { + if (md->inst_bytes == 4) { if (bp.mask == 0xffffffff) { monitor.Print(8, y, "%08x", bp.inst); } else { @@ -956,7 +1612,7 @@ Debugger::cmd_bx() for (auto& bp : bpoint) { bp.type = BreakpointType::Unused; } - cons->Print(" All breakpoints disabled\n"); + fprintf(cons, " All breakpoints disabled\n"); // 今登録されている命令ブレークの必要命令長を再計算 RecalcInstMask(); @@ -1010,7 +1666,7 @@ Debugger::RecalcInstMask() // 上位側から数えたマスクに必要なビット数 int mlen = 32 - ntz; // 命令語単位に切り上げる - mlen = roundup(mlen, md->inst_bits); + mlen = roundup(mlen, md->inst_bytes * 8); // バイト数に変換 mlen /= 8; @@ -1029,17 +1685,17 @@ Debugger::RecalcInstMask() void Debugger::cmd_brhist() { - cmd_hist_common(md->GetBrHist(), 0); + cmd_hist_common(md->GetBrHist()); } void Debugger::cmd_exhist() { - cmd_hist_common(md->GetExHist(), BranchHistory::ExHist); + cmd_hist_common(md->GetExHist()); } // ブランチ履歴、例外履歴表示の共通部分。 void -Debugger::cmd_hist_common(BranchHistory& hist, uint64 flag) +Debugger::cmd_hist_common(BranchHistory& hist) { // 表示最大行数(と向き) // 向きは bottom_to_top = true が新しいほうを下とする方向。 @@ -1066,19 +1722,19 @@ Debugger::cmd_hist_common(BranchHistory& int lines = std::min(used, maxlines) + 1; // MonitorUpdate() は TextScreen 高さに合わせて出力してくれる。 - auto size = hist.GetMonitorSize(); - TextScreen tscr; - tscr.Init(size.width, lines); + auto& histmon = hist.monitor; + auto size = histmon.GetSize(); + TextScreen screen; + screen.Init(size.width, lines); // コンソールでは下が新しいの順のほうがいい if (bottom_to_top) { - flag |= BranchHistory::BottomToTop; + screen.userdata |= BranchHistory::BottomToTop; } - tscr.userdata = flag; // 表示 - hist.MonitorUpdate(tscr); - ShowTextScreen(tscr); + MONITOR_UPDATE(histmon, screen); + ShowTextScreen(screen); } // 実行再開(continue): c [] @@ -1098,63 +1754,176 @@ Debugger::cmd_c() } } -// 引数などからアドレスを取得する共通ルーチン。 +// 引数などからアドレスを取得するごった煮共通ルーチン。 +// +// 引数がなければ sa->addr には書き込まずに true で帰る (ので、呼び出し側が +// 事前に適切なデフォルト値をセットしておくと、それがそのまま使われる)。 // 引数が1つ(以上)あれば args[1] をアドレスとしてパースする。 -// なければ、lastaddr が指定されていればそれを使う。 -// それもなければ現在の PC を使う。 +// アドレスには空間修飾子を前置可能、省略の場合はいずれも現在値を使う。 +// エラーならメッセージを表示して false を返す。 +// sa は in/out パラメータ。 bool -Debugger::GetAddr(uint32& addr, uint32& lastaddr) +Debugger::GetAddr(saddr_t *sa, bool default_data) { - if (args.size() > 1) { - // 1つ目の引数があれば、開始アドレス - if (ParseAddr(args[1].c_str(), &addr) == false) { + if (args.size() < 2) { + // 引数なしなら、呼び出し側が sa にセットした前回値をそのまま使う。 + return true; + } + + // 引数ありならパースする。アドレスが明示的に指定されたので、 + // ここから先の省略箇所は前回値ではなくデフォルト値で補完する。 + + std::string& str = args[1]; + uint32 addr; + + // ':' があればその前はアドレス空間修飾子 + 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 == DebuggerMD::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) { + fprintf(cons, "%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 == DebuggerMD::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 { + fprintf(cons, "%c: unknown address space modifier\n", ch); + return false; + } + continue; + + error: + fprintf(cons, "%s: address space modifier '%c' conflicts\n", + mod.c_str(), ch); return false; } - // 偶数番地からに丸める - addr &= 0xfffffffe; - } else if (lastaddr != 0xffffffff) { - // 引数なしで前回値があれば前回の続き - addr = lastaddr; - } else { - // そうでなければ、今の PC - addr = md->GetPC(); + + // 数値指定を機種ごとに読み替える + if (mod_num >= 0) { + if (md->arch == DebuggerMD::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 == DebuggerMD::Arch::M88xx0) { + // m88k では mod_num を CMMU ID とする(?)。 + // XXX まだ CPU は1つしかないので雑に処理。 + // "6" -> Data (CPU#0) + // "7" -> Program (CPU#0) + if (mod_num < 6) { + fprintf(cons, "%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つ目の引数があれば行数 @@ -1162,16 +1931,7 @@ 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; - saddr.search = true; - } else if (mode == MemdumpMode::Logical_atc) { - saddr.logical = true; - } + DebuggerMemoryStream mem(md.get(), saddr, access_mode, lookup_mode); for (int i = 0; i < cnt; i++) { std::string addrbuf; @@ -1179,59 +1939,98 @@ Debugger::cmd_d_common(MemdumpMode mode) std::string mnembuf; std::vector local_ir; - if (FormatDumpAddr(saddr, addrbuf)) { - md->Disassemble(saddr, mnemonic, local_ir); - // オフライン用に整形 - mnembuf = md->FormatDisasm(mnemonic, local_ir); + // アドレス + if (mem.FormatAddr(addrbuf) == false) { + // アドレス変換でバスエラーならここで終了 + fprintf(cons, "%s\n", addrbuf.c_str()); + break; } - // アドレス変換でバスエラーが起きてもここは表示する - cons->Print("%-18s %s\n", addrbuf.c_str(), mnembuf.c_str()); + + // 逆アセンブル + md->Disassemble(mem, mnemonic, local_ir); + // オフライン用に整形 + mnembuf = md->FormatDisasm(mnemonic, local_ir); + + fprintf(cons, "%-18s %s\n", addrbuf.c_str(), mnembuf.c_str()); // 変換できなければここで終了 if (local_ir.size() == 0) break; - saddr.addr += local_ir.size(); } - d_last_addr = saddr.addr; + // アドレスと Super/User 状態を次回継続用に保存 + d_last_addr = mem.laddr; } -// ログレベル設定: L +// 表示レジスタ選択 +// disp 現在の内容を表示 +// disp ... 設定 void -Debugger::cmd_L() +Debugger::cmd_disp() { - const char *name; - int level; + if (args.size() < 2) { + // 表示 + bool first = true; + for (const auto& r : disp_regs) { + fprintf(cons, "%s%s", (first ? "" : ","), r.c_str()); + first = false; + } + fprintf(cons, "\n"); + return; + } - if (args.size() < 3) { - cons->Print("L \n"); + // 設定 (引数を分解する) + 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() +{ + if (args.size() < 2) { + fprintf(cons, "L [=][,[=]]...\n"); return; } - name = args[1].c_str(); - level = atoi(args[2].c_str()); - - // 短縮形とかエイリアスとか - // lib/mainapp.cpp に同じものがあるのでどうにかしたほうがいい - if (strcmp(name, "sch") == 0) - name = "scheduler"; - if (strcmp(name, "scc") == 0) - name = "sio"; - - // 複数のオブジェクトが同じ logname を持ってもよい (CMMUとか)。 - // all はどうするか - std::string sname = name; - bool found = false; - for (auto& obj : gObjects) { - if (obj->logname == sname) { - obj->loglevel = level; - cons->Print("%s=%d\n", name, obj->loglevel); - found = true; - } - } - // 見付からない場合はエラー - if (!found) { - cons->Print("%s: invalid logname\n", name); + // 一旦全部つなげる。 + // help が混ざる場合の "L foo=1,help" と "L foo=1 help" を同じ動作に + // するため。 + std::string str; + for (int i = 1, ac = args.size(); i < ac; i++) { + if (str.empty() == false) { + str += ','; + } + str += args[i]; + } + + // で、再分解 + std::vector items = string_split(str, ','); + + // "help" があれば一覧を表示 + for (const auto& item : items) { + if (item == "help") { + std::vector list = MainApp::GetLogNames(); + for (const auto& name : list) { + fprintf(cons, " %s\n", name.c_str()); + } + return; + } + } + + // ログレベルを設定 + std::string errmsg; + if (MainApp::SetLogopt(items, &errmsg) == false) { + fprintf(cons, "%s\n", errmsg.c_str()); + return; } } @@ -1239,85 +2038,119 @@ 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; + int row; - cnt = 10; + row = 8; // 開始アドレス - if (GetAddr(addr, m_last_addr) == false) { + saddr = m_last_addr; + if (GetAddr(&saddr, true) == false) { return; } // 2つ目の引数があれば行数 if (args.size() > 2) { - cnt = strtol(args[2].c_str(), NULL, 10); + row = strtol(args[2].c_str(), NULL, 10); } - saddr.addr = addr; - saddr.super = md->IsSuper(); - // XXX - if (mode == MemdumpMode::Logical_search) { - saddr.logical = true; - saddr.search = true; - } else if (mode == MemdumpMode::Logical_atc) { - saddr.logical = true; + // userdata を作成 + uint64 userdata = saddr.GetAddr(); + if (saddr.IsSuper() == false) { + userdata |= (1ULL << 32); + } + if (saddr.IsData()) { + userdata |= (1ULL << 33); } + if (access_mode == MemoryMode::Logical) { + userdata |= (1ULL << 34); + } + if (lookup_mode == MMULookupMode::True) { + userdata |= (1ULL << 35); + } + // CLI では毎回次を表示していってほしい + userdata |= (1ULL << 36); - for (int i = 0; i < cnt; i++) { + TextScreen screen; + screen.Init(m_monitor.GetSize().width, row); + screen.userdata = userdata; + + MONITOR_UPDATE(m_monitor, screen); + ShowTextScreen(screen); + + // アドレスを次回継続用に保存 (Super/Data は維持) + m_last_addr = (uint32)screen.userdata; +} + +// メモリダンプモニタ +// +// userdata の下位32bit はアドレス。上位は各種フラグ。 +// +// ..| b36 | b35 | b34 | b33 | b32 | b31 .. b0 +// userdata | A | T | L | D | U | address +// | | | | +---- 1:User 0:Supervisor space +// | | | +---------- 1:Data, 0:Instruction space +// | | +---------------- 1:Logical, 0:Physical +// | +---------------------- 1:Table Lookup, 0:ATC Only +// +---------------------------- 1:Auto increment mode +void +Debugger::MonitorUpdateMemdump(Monitor *, TextScreen& screen) +{ + uint32 laddr = (uint32)screen.userdata; + bool user = (screen.userdata >> 32) & 1; + bool data = (screen.userdata >> 33) & 1; + MemoryMode access_mode = ((screen.userdata >> 34) & 1) + ? MemoryMode::Logical : MemoryMode::Physical; + MMULookupMode lookup_mode = ((screen.userdata >> 35) & 1) + ? MMULookupMode::True : MMULookupMode::None; + bool autoinc = ((screen.userdata >> 36) & 1); + + saddr_t saddr(laddr, !user, data); + DebuggerMemoryStream mem(md.get(), saddr, access_mode, lookup_mode); + + screen.Clear(); + + int row = screen.GetRow(); + for (int y = 0; y < row; y++) { std::string addrbuf; std::string dumpbuf; std::string charbuf; - if (FormatDumpAddr(saddr, addrbuf) == false) { - // アドレス変換でバスエラーならダンプもくそもない - cons->Print("%s\n", addrbuf.c_str()); - // ここで終わる? - // XXX 16バイト先を試してみるとか? + // アドレス + bool ok = mem.FormatAddr(addrbuf); + screen.Print(0, y, "%s", addrbuf.c_str()); + if (ok == false) { + // アドレス変換でバスエラーならここで終了 break; } for (int j = 0; j < 8; j++) { - uint64 paddr; - // もう一回アドレス変換するのやや冗長だが仕方ない - if (saddr.IsLogical() && md->MMUEnabled()) { - // アドレス変換 - paddr = md->TranslateAddr(saddr); - if ((int64)paddr < 0) { - // バスエラーが起きたら改行して仕切り直し - break; - } - } else { - paddr = saddr.addr; - } - for (int k = 0; k < 2; k++) { - uint64 b = vm_phys_peek_8(paddr + k); + uint64 b = mem.Fetch8(); if ((int64)b < 0) { dumpbuf += "--"; - charbuf += " "; + charbuf += ' '; } else { uint32 c = (uint32)b; dumpbuf += string_format("%02x", c); @@ -1325,56 +2158,85 @@ Debugger::cmd_m_common(MemdumpMode mode) (0x20 <= c && c <= 0x7e) ? c : '.'); } } - dumpbuf += " "; - saddr.addr += 2; + dumpbuf += ' '; } - cons->Print("%-18s %-40s %s\n", - addrbuf.c_str(), dumpbuf.c_str(), charbuf.c_str()); + + screen.Print(19, y, "%s", dumpbuf.c_str()); + screen.Print(60, y, "%s", charbuf.c_str()); + } + + // 次回継続用に書き戻す + if (autoinc) { + union64 udata; + udata.q = screen.userdata; + udata.l = mem.laddr.GetAddr(); + screen.userdata = udata.q; } - m_last_addr = saddr.addr; } // プロンプトに来た最初に表示するいつものやつを再表示する void Debugger::cmd_minus() { - // 通常レジスタを表示 - std::vector tmpargs {{ "r" }}; - md->ShowRegister(cons, tmpargs); - - // 論理アクセス時、ATC ミスが分かったほうが便利だと思う - saddr_t laddr; - laddr.addr = pc; - laddr.super = md->IsSuper(); - laddr.logical = true; - laddr.search = false; + // 指定のレジスタ群を表示 + // 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(); - // アドレスを表示用に - if (FormatDumpAddr(laddr, addrbuf)) { - md->Disassemble(laddr, mnemonic, ir); - // ライブ表示用に整形 - mnembuf = md->FormatDisasmLive(mnemonic, ir); + + // 論理アクセス時、ATC ミスが分かったほうが便利だと思うので lookup なし + saddr_t laddr(pc, md->IsSuper()); + DebuggerMemoryStream mem(md.get(), laddr); + // アドレス + if (mem.FormatAddr(addrbuf) == false) { + fprintf(cons, "%s\n", addrbuf.c_str()); + return; } - cons->Print("%-18s %s\n", addrbuf.c_str(), mnembuf.c_str()); + // 逆アセンブル + md->Disassemble(mem, mnemonic, ir); + // ライブ表示用に整形 + mnembuf = md->FormatDisasmLive(mnemonic, ir); - // XXX m88k? - nextpc = pc + ir.size(); + fprintf(cons, "%-18s %s\n", addrbuf.c_str(), mnembuf.c_str()); } -// ステップ実行: 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); if (count < 1) { - cons->Print(" invalid step count: %d\n", count); + fprintf(cons, " invalid step count: %d\n", count); return; } @@ -1386,55 +2248,34 @@ 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(md->GetPC(), md->IsSuper()); + DebuggerMemoryStream mem(md.get(), laddr); + + // 命令がサブルーチンやトラップなどステップインできる命令か。 + 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 { - n_breakenable = true; - n_breakaddr = nextpc; - return true; + // そうでなければ1命令進める。 + // 0xffffffff は常にアドレスとして不正なのでこれをフラグに使う。 + n_breakaddr = 0xffffffff; } } @@ -1442,7 +2283,14 @@ Debugger::SetNBreakpoint() void Debugger::cmd_q() { - cons->Print("quit debugger console.\n"); + fprintf(cons, "quit debugger console.\n"); +} + +// VM リセット +void +Debugger::cmd_reset() +{ + gPower->MakeResetHard(); } // モニター表示 @@ -1450,37 +2298,78 @@ void Debugger::cmd_show() { if (args.size() != 2) { - cons->Print("usage: show \n"); - for (auto& d : gObjects) { - if (d->logname != "?") { - cons->Print(" %s\n", d->logname.c_str()); + std::vector list; + + // 登録されているモニターだけを列挙 + // (登録されていてもサブウィンドウの ID を持っているものは除外) + for (const auto mon : gMonitorManager->GetList()) { + int id = mon->GetId(); + if (id <= ID_MONITOR_END) { + list.push_back(id); } } + + // 一覧を表示 + std::string msg = MonitorManager::MakeListString(list); + fprintf(cons, "usage: show \n"); + fprintf(cons, "%s", msg.c_str()); return; } + std::vector candidates; + std::vector cand_names; std::string name = args[1]; - for (auto& d : gObjects) { - if (d->logname == name) { - ShowMonitor(*d); - return; + + for (auto mon : gMonitorManager->GetList()) { + int id = mon->GetId(); + const auto& aliases = gMonitorManager->GetAliases(id); + + bool matched = false; + for (const auto& alias : aliases) { + // 部分一致したら名前はすべて覚えておく + if (starts_with_ignorecase(alias, name)) { + cand_names.push_back(alias); + matched = true; + } + } + + // 同じモニタで別名が複数回部分一致しても、 + // モニタのほうは1回として数える + if (matched) { + candidates.push_back(mon); } } - // 一致しない - cons->Print("show: no such objects\n"); + + if (candidates.empty()) { + // 一致しない + fprintf(cons, "unknown monitor name: \"%s\"\n", name.c_str()); + } else if (candidates.size() == 1) { + // 確定した + Monitor& mon = *(candidates[0]); + ShowMonitor(mon); + } else { + // 候補が複数あった + std::string candstr; + for (const auto& cname : cand_names) { + candstr += ' '; + candstr += cname; + } + fprintf(cons, "ambiguous monitor name \"%s\": candidates are%s\n", + name.c_str(), candstr.c_str()); + } } // モニターを更新して表示。 void -Debugger::ShowMonitor(IMonitor& monitor) +Debugger::ShowMonitor(Monitor& monitor) { // モニタを更新 TextScreen ts; - auto size = monitor.GetMonitorSize(); + auto size = monitor.GetSize(); ts.Init(size.width, size.height); - monitor.MonitorUpdate(ts); + MONITOR_UPDATE(monitor, ts); ShowTextScreen(ts); } @@ -1494,7 +2383,7 @@ Debugger::ShowTextScreen(TextScreen& mon // その際属性もエスケープシーケンスで再現する。 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; @@ -1551,20 +2440,37 @@ Debugger::ShowTextScreen(TextScreen& mon // XXX 日本語が使われてれば UTF-8 にしないといけないはず - cons->Print("%s\n", sbuf); + fprintf(cons, "%s\n", sbuf); } } -// ステップ実行: 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); if (count < 1) { - cons->Print(" invalid step count: %d\n", count); + fprintf(cons, " invalid step count: %d\n", count); return; } @@ -1575,31 +2481,35 @@ 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; - } - t_enable = true; + cmd_st(); } // 引数で示されるプレフィックスなしの16進数値を返す。 @@ -1641,11 +2551,11 @@ Debugger::ParseAddr(const char *arg, uin r = md->GetRegAddr(arg + 1); if ((int64)r < 0) { #if 0 // not yet - cons->Print("valid register name are: %s%s\n", + fprintf(cons, "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"); + fprintf(cons, "invalid register name\n"); return false; } addr = (uint32)r; @@ -1654,11 +2564,11 @@ Debugger::ParseAddr(const char *arg, uin errno = 0; addr = strtoul(arg, &end, 16); if (arg[0] == '\0' || end[0] != '\0') { - cons->Print("invalid address: '%s'\n", arg); + fprintf(cons, "invalid address: '%s'\n", arg); return false; } if (errno == ERANGE) { - cons->Print("out of range: %s\n", arg); + fprintf(cons, "out of range: %s\n", arg); return false; } } @@ -1667,54 +2577,12 @@ Debugger::ParseAddr(const char *arg, uin return true; } -// (論理、物理)アドレスを表示用に整形。 -// 逆アセンブル表示、メモリダンプ表示等で共通。 -// アドレス変換でバスエラーが起きたら false を返す (この場合も str は有効)。 -// 末尾を空白パディングはしていないので必要なら "%-18s" 等して使うこと。 -// 123456789012345678 -// "00112233:44556677:" -bool -Debugger::FormatDumpAddr(saddr_t laddr, std::string& str) -{ - bool rv = true; - - // laddr は常に表示 - str = string_format("%08x", laddr.addr); - - if (laddr.IsLogical() && md->MMUEnabled()) { - // アドレス変換してみる - uint64 paddr = md->TranslateAddr(laddr); - if ((int64)paddr < 0) { - // MMU 時点でバスエラー - str += ":BusErr"; - rv = false; - } else if (paddr == laddr.addr) { - // PA==VA - str += "=PA"; - } else { - // PA!=VA - str += string_format(":%08x:", (uint32)paddr); - } - } else { - // 物理アクセス - str += ":"; - } - - return rv; -} // -// ブレークポイントモニター +// MD // -// コンストラクタ -BreakpointMonitor::BreakpointMonitor() -{ - monitor_size = nnSize(55, 9); -} - -void -BreakpointMonitor::MonitorUpdate(TextScreen& monitor) +// デストラクタ +DebuggerMD::~DebuggerMD() { - gDebugger->MonitorBreakpoint(monitor); }