--- nono/debugger/debugger.cpp 2026/04/29 17:04:43 1.1.1.6 +++ nono/debugger/debugger.cpp 2026/04/29 17:05:13 1.1.1.12 @@ -4,77 +4,158 @@ // 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 +#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); + + // メモリダンプモニター + 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); +} - // デバッガスレッド起動 - pthread_t th; - pthread_create(&th, NULL, debugger_run, NULL); +// デストラクタ +Debugger::~Debugger() +{ + // fclose(fflush) にあたり hostcom へのアクセスが発生するので、 + // hostcom より先に片付けておかなければならない。 + Close(); + + if ((bool)hostcom) { + hostcom->SetCallbackDevice(NULL); + } + + TerminateThread(); + gDebugger = NULL; } -// デバッガスレッドのエントリポイント -static void * -debugger_run(void *dummy) +// ログレベル設定 +void +Debugger::SetLogLevel(int loglevel_) { - PTHREAD_SETNAME("Debugger"); - pthread_detach(pthread_self()); + inherited::SetLogLevel(loglevel_); - gDebugger->ThreadRun(); - return NULL; + // ホストドライバを従属させる + if ((bool)hostcom) { + hostcom->SetLogLevel(loglevel_); + } } -// コンストラクタ -Debugger::Debugger() +bool +Debugger::Create() { - if (gMPU680x0) { - md = new DebuggerMD_m680x0(this, gMPU680x0->GetCPU()); - } else if (gMPU88xx0) { - md = new DebuggerMD_m88xx0(this, gMPU88xx0->GetCPU()); - } else { - throw "unknown mpu"; + // ホストドライバを作成 + try { + hostcom.reset(new HostCOMDevice("Debugger")); + } catch (...) { + return false; } + + hostcom->SetRxCallback(ToDeviceCallback(&Debugger::RxCallback)); + hostcom->SetAcceptCallback(ToDeviceCallback(&Debugger::AcceptCallback)); + hostcom->SetCallbackDevice(this); + + return true; } -// コマンドライン引数によって動作を決めるところ。 -// 名前がこれでいいのかはあるけど。 -void +// 初期化 +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,48 +168,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(); + } + } - // デバッガプロンプトにいる間はトレースオン - md->ReqSet(CPU_REQ_TRACE); + LeavePrompt(); + Close(); +} +// コンソールをクローズする +void +Debugger::Close() +{ + if (cons) { + fclose(cons); + cons = NULL; + } +} + +#if 0 bool first = true; for (;;) { // 接続後の1回目だけ実行するもの。 @@ -138,198 +253,323 @@ Debugger::ThreadRun() // greeting はプロンプトが取れる前にもう表示したい。 // 何らかの事故でプロンプトが取れなくても、ここまでは接続 // できてることが分かるように。 - cons->Print("This is debugger console\n"); + fprintf(cons, "This is debugger console\n"); // 接続ごとに初期化する値 n_enable = false; s_enable = false; t_enable = true; } + } + } - // プロンプトが取れるのを待つ - if (AcquirePrompt() == false) { - break; - } +#endif - // d/m をいきなり引数なしで実行した時のため、現在地にしておく。 - pc = md->GetPC(); - m_last_addr.Set(pc, md->IsSuper(), true); - d_last_addr.Set(pc, md->IsSuper(), false); - // プロンプトに来た時に表示するいつものやつ - 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 CmdAct::Stay: + // プロンプトに留まるならここで、次行のプロンプト? + PrintPrompt(); + break; + case CmdAct::Leave: + // プロンプトを抜ける + LeavePrompt(); + break; + case CmdAct::Quit: + // アプリケーション自体を終了 + LeavePrompt(); + UIMessage::Post(UIMessage::APPEXIT); } - md->ReqSet(CPU_REQ_PROMPT); } } - return true; } -// コマンド処理のメインループ。 -// コマンドが Stay ならこの中で処理を継続。 -// コマンドが Leave か Quit ならその値を持って戻る (呼び出し側で処理する)。 -Debugger::CommandAction -Debugger::MainLoop() +// コマンドモード(プロンプト)に入る +void +Debugger::EnterPrompt() { - for (;;) { - cons->Prompt(); - if (cons->Gets(cmdbuf) == false) { - // EOF or Error - return CommandAction::Quit; - } + is_prompt = true; - string_rtrim(cmdbuf); + // d/m をいきなり引数なしで実行した時のため、現在地にしておく。 + pc = md->GetPC(); + m_last_addr.Set(pc, md->IsSuper(), true); + d_last_addr.Set(pc, md->IsSuper(), false); - if (!cmdbuf.empty()) { - // コマンドが入力されれば次回のために保存 - last_cmdbuf = cmdbuf; - } else { - // 空行が入力されれば直前のコマンドをもう一度 - // 前行がなければ何もせずもう一度プロンプトを表示するかね - if (last_cmdbuf.empty()) - continue; - cmdbuf = last_cmdbuf; + // ブレークポイント到達メッセージがあればここで表示 + 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::CmdAct +Debugger::Command() +{ + string_rtrim(cmdbuf); + + if (cmdbuf.empty()) { + // 空行なら、直前のコマンドをもう一度。 + // 前行がなければ何もせずもう一度プロンプトを表示するかね。 + if (last_cmdbuf.empty()) { + return CmdAct::Stay; } + cmdbuf = last_cmdbuf; + } else { + // 入力された行を次回のために保存 + last_cmdbuf = cmdbuf; + } - // 行を args に分解 - ParseCmdbuf(); + // 行を args に分解 + ParseCmdbuf(); + cmdbuf.clear(); - // 前回の値が有効なのはコマンドが連続した時だけ + // 前回の値が有効なのはコマンドが連続した時だけ - // コマンドをテーブルから探す - 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; + // コマンドをテーブルから探す + 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 (this->*(cmd.func))(); + } - // Stay なら引き続きコマンド処理 - if (cmd.action == CommandAction::Stay) { - continue; - } - // それ以外ならメインループ終了 - return cmd.action; + // (コマンドテーブルになくて) "r" から始まっていればレジスタ表示系 + if (args[0][0] == 'r') { + // ShowRegister() は処理したら true を返す。 + // "r" 系コマンドはすべて Stay。 + if (md->ShowRegister(cons, args)) { + return CmdAct::Stay; } + } - // (コマンドテーブルになくて) "r" から始まっていればレジスタ表示系 - if (args[0][0] == 'r') { - // ShowRegister() は処理したら true を返す。 - // "r" 系コマンドはすべて Stay。 - if (md->ShowRegister(cons, args)) { - continue; - } + // 知らないコマンドも Stay 相当。 + fprintf(cons, "%s: unknown command\n", args[0].c_str()); + // この行は次回空エンターで再発行しないでいい。 + last_cmdbuf.clear(); + return CmdAct::Stay; +} + +// ブレークポイントとかを調べる。1命令ごとに呼び出される。 +// (VM スレッドから呼ばれる) +void +Debugger::Exec() +{ + if (Check()) { + // 実時間を停止 + gSync->StopRealTime(); + + // 待機 + { + std::unique_lock lock(mtx); + + // notify 前にクリア + prompt_released = false; + + // MPU が一時停止したことをデバッガスレッドへ通知する + request |= REQUEST_PROMPT; + cv_request.notify_one(); + + // デバッガスレッドがプロンプト解放するまで待機 + 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 || ct_enable || s_enable || so_enable || n_enable || + is_pause; } -// デバッガ実行中なら命令開始前にメインルーチンから呼ばれる。 +// デバッガ実行中なら命令開始前に VM スレッドから呼ばれる。 // プロンプトに降りるなら true を返す。 bool Debugger::Check() @@ -348,6 +588,16 @@ Debugger::Check() is_break = true; } } + if (ct_enable) { + if (gScheduler->GetVirtTime() >= ct_end_time) { + ct_enable = false; + is_break = true; + + // ブレークポイントメッセージに便乗して表示。 + bpointmsg += string_format("%s has passed.\n", + TimeToStr(ct_timespan).c_str()); + } + } // XXX 残りは排他動作のはず @@ -358,34 +608,24 @@ Debugger::Check() // キャンセルする。この場合もブレークポイント側が true なので true を // 返せばよい。 - // t (トレース表示) が enable なら、終了条件にマッチしなくてもここで - // レジスタを表示。終了条件にマッチする場合に表示するとプロンプトで - // もう一回表示されて二重になってしまうので注意。 + // トレース表示に関しては Exec 側でやってある。 - if (s_enable) { // ステップ実行が.. + if (s_enable) { // ステップ実行が成立するか s_count--; - if (s_count == 0) { // 成立 - s_enable = false; - return true; - } else if (is_break) { // 非成立だがブレークが成立 + if (s_count == 0 || is_break) { s_enable = false; - return true; - } else if (t_enable) { // どちらも非成立で trace on - cmd_minus(); + t_enable = false; + is_break = true; } - } else if (so_enable) { // ステップアウトが.. - if (md->IsStepOut()) { // 成立 + } else if (so_enable) { // ステップアウトが成立するか + if (md->IsStepOut() || is_break) { so_enable = false; - return true; - } else if (is_break) { // 非成立だがブレークが成立 - so_enable = false; - return true; - } else if (t_enable) { // どちらも非成立で trace on - cmd_minus(); + t_enable = false; + is_break = true; } - } else if (n_enable) { // 次命令まで実行が.. + } else if (n_enable) { // 指定命令数実行が完了するか if (n_breakaddr != 0xffffffff) { // ステップインをスキップ中 if (n_breakaddr == md->GetPC()) { @@ -395,32 +635,33 @@ Debugger::Check() // 1命令実行 n_count--; } - if (n_count == 0) { // 成立 - n_enable = false; - return true; - } else if (is_break) { // 非成立だがブレークが成立 + if (n_count == 0 || is_break) { n_enable = false; - return true; - } - if (t_enable) { // どちらも非成立で trace on - cmd_minus(); + t_enable = false; + is_break = true; + } else { + // スキップ中でなければ、ステップインが起きるか都度調べる。 + // すでにスキップ中なら到達するまでは何もしない。 + if (n_breakaddr == 0xffffffff) { + SetNBreakpoint(); + } } + } - // スキップ中でなければ、ステップインが起きるか都度調べる。 - // すでにスキップ中なら到達するまでは何もしない。 - if (n_breakaddr == 0xffffffff) { - SetNBreakpoint(); - } + // デバッガから MPU の一時停止が要求されているか + if (is_pause) { + is_pause = false; + is_break = true; } - // ステップ実行系がなければブレークポイントの成否だけ return is_break; } // ブレークポイントがどれかでも成立するかを調べる。 // 1つ以上成立してブレークするなら true を返す。 // 1つも成立しておらずブレークしないなら false を返す。 -// 成立すれば内容をここで表示する。 +// このルーチンから cons への出力は使わないこと。(プロンプトにいない時でも +// 呼ばれるので) bool Debugger::CheckAllBreakpoints() { @@ -494,8 +735,8 @@ Debugger::CheckAllBreakpoints() case BreakpointType::Exception: { desc = string_format("excp $%02x", bv_vector); - const char *name = md->GetExceptionName(bv_vector); - if (name != NULL && name[0] != '\0') { + const char *name = gVectorTable->GetExceptionName(bv_vector); + if (name) { desc += string_format(" \"%s\"", name); } break; @@ -509,7 +750,13 @@ Debugger::CheckAllBreakpoints() assert(false); break; } - cons->Print("breakpoint #%d (%s) reached\n", i, desc.c_str()); + + // 到達メッセージを作成。 + // この時点ではまだコンソールを取得していない可能性があるので + // (-D なしで起動した場合とか)、表示せず用意するだけ。 + // コンソールが取得できたところで表示する。 + bpointmsg += string_format("breakpoint #%d (%s) reached\n", + i, desc.c_str()); } // 例外通知は通過ごとに常に下ろしておく @@ -528,7 +775,7 @@ Debugger::CheckBreakpointInst(breakpoint // bi_need_bytes が現在のブレークポイントで読み込む必要のあるバイト数。 saddr_t laddr(md->GetPC(), md->IsSuper()); - DebuggerMemoryStream mem(md, laddr); + DebuggerMemoryStream mem(md.get(), laddr); // 必要なバイト数に達するまで読み足す bi_inst = 0; @@ -570,50 +817,51 @@ Debugger::NotifyException(int 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 }, - { "c", &Debugger::cmd_c, Debugger::CommandAction::Leave }, - { "d", &Debugger::cmd_d, Debugger::CommandAction::Stay }, - { "dt", &Debugger::cmd_dt, Debugger::CommandAction::Stay }, - { "D", &Debugger::cmd_D, Debugger::CommandAction::Stay }, - { "disp", &Debugger::cmd_disp, Debugger::CommandAction::Stay }, - { "exhist", &Debugger::cmd_exhist, Debugger::CommandAction::Stay }, - { "hb", &Debugger::cmd_hb, Debugger::CommandAction::Stay }, - { "hr", &Debugger::cmd_hr, Debugger::CommandAction::Stay }, - { "h", &Debugger::cmd_h, Debugger::CommandAction::Stay }, - { "help", &Debugger::cmd_h, Debugger::CommandAction::Stay }, - { "L", &Debugger::cmd_L, Debugger::CommandAction::Stay }, - { "m", &Debugger::cmd_m, Debugger::CommandAction::Stay }, - { "mt", &Debugger::cmd_mt, Debugger::CommandAction::Stay }, - { "M", &Debugger::cmd_M, Debugger::CommandAction::Stay }, - { "n", &Debugger::cmd_n, Debugger::CommandAction::Leave }, - { "nt", &Debugger::cmd_nt, Debugger::CommandAction::Leave }, - { "q", &Debugger::cmd_q, Debugger::CommandAction::Quit }, - { "quit", &Debugger::cmd_q, Debugger::CommandAction::Quit }, - { "reset", &Debugger::cmd_reset, Debugger::CommandAction::Leave }, - { "s", &Debugger::cmd_s, Debugger::CommandAction::Leave }, - { "st", &Debugger::cmd_st, Debugger::CommandAction::Leave }, - { "so", &Debugger::cmd_so, Debugger::CommandAction::Leave }, - { "sot", &Debugger::cmd_sot, Debugger::CommandAction::Leave }, - { "show", &Debugger::cmd_show, Debugger::CommandAction::Stay }, - { "t", &Debugger::cmd_t, Debugger::CommandAction::Leave }, - { "-", &Debugger::cmd_minus, Debugger::CommandAction::Stay }, + { "bi", &Debugger::cmd_bi, }, + { "bm", &Debugger::cmd_bm, }, + { "bv", &Debugger::cmd_bv, }, + { "bx", &Debugger::cmd_bx, }, + { "b", &Debugger::cmd_b, }, + { "brhist", &Debugger::cmd_brhist, }, + { "c", &Debugger::cmd_c, }, + { "ct", &Debugger::cmd_ct, }, + { "d", &Debugger::cmd_d, }, + { "dt", &Debugger::cmd_dt, }, + { "D", &Debugger::cmd_D, }, + { "disp", &Debugger::cmd_disp, }, + { "exhist", &Debugger::cmd_exhist, }, + { "hb", &Debugger::cmd_hb, }, + { "hr", &Debugger::cmd_hr, }, + { "h", &Debugger::cmd_h, }, + { "help", &Debugger::cmd_h, }, + { "L", &Debugger::cmd_L, }, + { "m", &Debugger::cmd_m, }, + { "mt", &Debugger::cmd_mt, }, + { "M", &Debugger::cmd_M, }, + { "n", &Debugger::cmd_n, }, + { "nt", &Debugger::cmd_nt, }, + { "q", &Debugger::cmd_q, }, + { "quit", &Debugger::cmd_q, }, + { "reset", &Debugger::cmd_reset, }, + { "s", &Debugger::cmd_s, }, + { "st", &Debugger::cmd_st, }, + { "so", &Debugger::cmd_so, }, + { "sot", &Debugger::cmd_sot, }, + { "show", &Debugger::cmd_show, }, + { "t", &Debugger::cmd_t, }, + { "-", &Debugger::cmd_minus, }, }; // ヘルプ // h : 一覧を表示 // h : 単独コマンドの詳細を表示 -void +Debugger::CmdAct Debugger::cmd_h() { if (args.size() < 2) { // 引数なしなら一覧表示。 ShowHelpList(HelpListMain); - return; + return CmdAct::Stay; } // 引数があれば個別の詳細 @@ -639,38 +887,40 @@ Debugger::cmd_h() } // そうでなければこれを表示して終了 std::string disp = HelpConvert(dict.second); - cons->Print("%s", disp.c_str()); - return; + fprintf(cons, "%s", disp.c_str()); + return CmdAct::Stay; } } } while (try_again); - cons->Print("invalid command name: %s\n", args[1].c_str()); + fprintf(cons, "invalid command name: %s\n", args[1].c_str()); + return CmdAct::Stay; } // hb : ブレークポイント系コマンドの一覧を表示 // こいつだけ結構占めるので別階層。 -void +Debugger::CmdAct Debugger::cmd_hb() { - ShowHelpList(HelpListBreakpoints); + return ShowHelpList(HelpListBreakpoints); } // hr : レジスタ表示系コマンドの一覧を表示 // CPU ごとに違うので。 -void +Debugger::CmdAct Debugger::cmd_hr() { - ShowHelpList(md->GetHelpListReg()); + return ShowHelpList(md->GetHelpListReg()); } // ヘルプ一覧を表示。 -void +Debugger::CmdAct Debugger::ShowHelpList(const HelpMessages& msgs) { - cons->Print("Type \"help \" for indivisual details.\n"); + fprintf(cons, "Type \"help \" for indivisual details.\n"); for (const auto& pair : msgs) { - cons->Print(" %-16s %s\n", pair.first.c_str(), pair.second.c_str()); + fprintf(cons, " %-16s %s\n", pair.first.c_str(), pair.second.c_str()); } + return CmdAct::Stay; } // 個別ヘルプメッセージを出力用に置換。 @@ -711,7 +961,7 @@ Debugger::HelpConvert(const std::string& Debugger::HelpListMain = { { "b*", "Set/show Breakpoints (Type \"hb\" for details)" }, { "brhist", "Show branch history" }, - { "c", "Continue" }, + { "c/ct", "Continue" }, { "d/dt/D", "Disassemble" }, { "disp", "Set register group to show" }, { "exhist", "Show exception history" }, @@ -834,6 +1084,15 @@ Debugger::HelpDetails = { )**" }, //----- + { "ct", R"**( + Command: ct [] + + Continue until specified has elapsed. If the is + ommited, the last value is used again. The unit of is + seconds in default. You can use "msec", "usec", or "nsec" suffix. + )**" }, + + //----- { "d", R"**( Command: D
] [] Command: d [[:]
] [] @@ -842,7 +1101,7 @@ Debugger::HelpDetails = { 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 + 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 @@ -898,7 +1157,7 @@ Debugger::HelpDetails = { //----- { "L", R"**( - Command: L + Command: L [=][,=[]]... Set loglevel. XXX To be written... )**" }, @@ -1034,7 +1293,7 @@ Debugger::ParseCmdbuf() // b ... 一覧表示 // b #n ... #n を削除 // b [] ... 設定 -void +Debugger::CmdAct Debugger::cmd_b() { // 引数なしなら一覧表示 @@ -1045,22 +1304,8 @@ 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(); - return; + cmd_b_delete(); + return CmdAct::Stay; } return cmd_b_set(BreakpointType::Address); @@ -1068,31 +1313,31 @@ Debugger::cmd_b() // メモリブレークポイントの設定 // bm [] -void +Debugger::CmdAct Debugger::cmd_bm() { // XXX m68k では未サポート - if (md->arch == Arch::M680x0) { - cons->Print("bm not supported yet on m68k\n"); - return; + if (md->arch == DebuggerMD::Arch::M680x0) { + fprintf(cons, "bm not supported yet on m68k\n"); + return CmdAct::Stay; } - cmd_b_set(BreakpointType::Memory); + return cmd_b_set(BreakpointType::Memory); } // type が違うだけの各種ブレークポイント設定の共通部分。 -void +Debugger::CmdAct Debugger::cmd_b_set(BreakpointType type) { breakpoint_t bp; if (args.size() < 2) { - cons->Print("usage: %s []\n", args[0].c_str()); - return; + fprintf(cons, "usage: %s []\n", args[0].c_str()); + return CmdAct::Stay; } // アドレス if (!ParseAddr(args[1].c_str(), &bp.addr)) { - return; + return CmdAct::Stay; } // あればスキップカウント if (args.size() > 2) { @@ -1104,15 +1349,41 @@ 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); } + return CmdAct::Stay; +} + +// ブレークポイント個別削除 +// (引数の先頭が '#' なことを判定したところで呼ばれる) +// b # +Debugger::CmdAct +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 CmdAct::Stay; + } + + auto& bp = bpoint[n]; + if (bp.type == BreakpointType::Unused) { + fprintf(cons, "invalid breakpoint number: #%d\n", n); + return CmdAct::Stay; + } + + fprintf(cons, "breakpoint #%d removed\n", n); + bp.type = BreakpointType::Unused; + // 今登録されている命令ブレークの必要命令長を再計算 + RecalcInstMask(); + return CmdAct::Stay; } // 命令ブレークポイントの設定 // bi [:] [] -void +Debugger::CmdAct Debugger::cmd_bi() { breakpoint_t bp; @@ -1122,8 +1393,8 @@ Debugger::cmd_bi() int masklen; if (args.size() < 2) { - cons->Print("usage: bi [:] []\n"); - return; + fprintf(cons, "usage: bi [:] []\n"); + return CmdAct::Stay; } // 引数をまず分離 @@ -1143,25 +1414,25 @@ Debugger::cmd_bi() // 命令部チェック if (ParseVerbHex(inststr.c_str(), &bp.inst) == false) { - cons->Print("%s: invalid instruction value\n", args[1].c_str()); - return; + fprintf(cons, "%s: invalid instruction value\n", args[1].c_str()); + return CmdAct::Stay; } if (instlen % (md->inst_bytes * 2) != 0) { - cons->Print("%s: invalid instruction length\n", args[1].c_str()); - return; + fprintf(cons, "%s: invalid instruction length\n", args[1].c_str()); + return CmdAct::Stay; } // マスク部チェック 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; + fprintf(cons, "%s: invalid mask value\n", args[1].c_str()); + return CmdAct::Stay; } 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; + return CmdAct::Stay; } } // 8バイト未満なら左詰め。 @@ -1180,33 +1451,34 @@ 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); } // 今登録されている命令ブレークの必要命令長を再計算 RecalcInstMask(); + return CmdAct::Stay; } // 例外ブレークポイントの設定 // bv [-] [] -void +Debugger::CmdAct Debugger::cmd_bv() { breakpoint_t bp; if (args.size() < 2) { - cons->Print("usage: be [-] []\n"); - return; + fprintf(cons, "usage: be [-] []\n"); + return CmdAct::Stay; } 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; + fprintf(cons, "%s: invalid vector number\n", args[1].c_str()); + return CmdAct::Stay; } bp.vec2 = bp.vec1; } else { @@ -1215,23 +1487,23 @@ 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()); - return; + fprintf(cons, "%s: invalid first vector number\n", args[1].c_str()); + return CmdAct::Stay; } if (ParseVerbHex(str2.c_str(), (uint32 *)&bp.vec2) == false) { - cons->Print("%s: invalid last vector number\n", args[1].c_str()); - return; + fprintf(cons, "%s: invalid last vector number\n", args[1].c_str()); + return CmdAct::Stay; } } // 範囲チェック - 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 < 0 || bp.vec1 >= gVectorTable->Size()) { + fprintf(cons, "$%x: invalid vector number\n", bp.vec1); + return CmdAct::Stay; + } + if (bp.vec2 < 0 || bp.vec2 >= gVectorTable->Size()) { + fprintf(cons, "$%x: invalid last vector number\n", bp.vec2); + return CmdAct::Stay; } // 大小が逆なら入れ替える? @@ -1252,9 +1524,9 @@ 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); } // すでに来ている例外をクリア。 @@ -1269,18 +1541,21 @@ Debugger::cmd_bv() // 設定したのだから、それ以前の事象には反応すべきでない、という意味では // ここでもいいか? bv_vector = -1; + + return CmdAct::Stay; } // ブレークポイント一覧表示 -void +Debugger::CmdAct Debugger::cmd_b_list() { - ShowMonitor(*gBreakpointMonitor); + ShowMonitor(bpoint_monitor); + return CmdAct::Stay; } // ブレークポイント一覧 (モニタ) void -Debugger::MonitorBreakpoint(TextScreen& monitor) +Debugger::MonitorUpdateBpoint(Monitor *, TextScreen& monitor) { // 0 1 2 3 4 5 6 // 0123456789012345678901234567890123456789012345678901234567890 @@ -1359,16 +1634,18 @@ Debugger::MonitorBreakpoint(TextScreen& } // ブレークポイント全削除 -void +Debugger::CmdAct Debugger::cmd_bx() { for (auto& bp : bpoint) { bp.type = BreakpointType::Unused; } - cons->Print(" All breakpoints disabled\n"); + fprintf(cons, " All breakpoints disabled\n"); // 今登録されている命令ブレークの必要命令長を再計算 RecalcInstMask(); + + return CmdAct::Stay; } // ブレークポイントを設定。 @@ -1435,20 +1712,20 @@ Debugger::RecalcInstMask() // コンソールではデフォルトで下を新しいの順とする。 を負数にすると // (行数は絶対値して) 並び順を逆にしてモニタウィンドウと同じ上を新しいの順に // する。 -void +Debugger::CmdAct Debugger::cmd_brhist() { - cmd_hist_common(md->GetBrHist(), 0); + return cmd_hist_common(md->GetBrHist()); } -void +Debugger::CmdAct Debugger::cmd_exhist() { - cmd_hist_common(md->GetExHist(), BranchHistory::ExHist); + return cmd_hist_common(md->GetExHist()); } // ブランチ履歴、例外履歴表示の共通部分。 -void -Debugger::cmd_hist_common(BranchHistory& hist, uint64 flag) +Debugger::CmdAct +Debugger::cmd_hist_common(BranchHistory& hist) { // 表示最大行数(と向き) // 向きは bottom_to_top = true が新しいほうを下とする方向。 @@ -1475,36 +1752,60 @@ 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); + + return CmdAct::Stay; } // 実行再開(continue): c [] // 指定があれば まで実行。 -void +Debugger::CmdAct Debugger::cmd_c() { if (args.size() > 1) { // 引数があれば if (!ParseAddr(args[1].c_str(), &bc_addr)) { - return; + return CmdAct::Stay; } // 偶数番地に丸める bc_addr &= 0xfffffffe; bc_enable = true; } + return CmdAct::Leave; +} + +// 指定仮想時間実行: ct [