--- nono/debugger/debugger.cpp 2026/04/29 17:04:51 1.1.1.8 +++ 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 "mythread.h" +#include "mystring.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,210 +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; - } - - // 実時間を停止 - gRealtime.Stop(); +#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); +} - // 実時間を再開 - gRealtime.Start(); +// funopen の read コールバックの本体 +int +Debugger::ReadFunc(char *buf, int bufsize) +{ + char *d = buf; + char *end = buf + bufsize; - // メインループから戻ったということは Leave か Quit なので - // どちらにしても、ここでプロンプトを手放す。 - gCVPrompt->NotifyRelease(); + for (; d < end; ) { + if ((bool)hostcom == false) { + errno = EIO; + return -1; + } - // quit ならここでループを1段抜ける - if (action == CommandAction::Quit) { - break; - } + int c = hostcom->Rx(); + if (c < 0) { + break; } + *d++ = c; + } + return (d - buf); +} - // デバッガを抜けるのでトレースオフ - md->ReqClr(CPU_REQ_TRACE); +// funopen の write コールバックの本体 +int +Debugger::WriteFunc(const char *buf, int len) +{ + const char *s = buf; + const char *end = buf + len; - cons->Close(); - if (gMainApp.debug_on_console) { - 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); + } + } + while (hostcom->Tx(c) == false) { + usleep(10); } } + return (s - buf); +} - delete cons; +// ホストからの1文字受信通知 (HostCOM スレッドから呼ばれる) +void +Debugger::RxCallback() +{ + // デバッガスレッドに通知 + std::unique_lock lock(mtx); + request |= REQUEST_RXCHAR; + cv_request.notify_one(); } -// コンソールに接続されるのを待つ。 -// うーん、なんだこれ。 -// -// +--- 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() +// ホストからの着信通知 (HostCOM スレッドから呼ばれる) +void +Debugger::AcceptCallback() { - if (gMainApp.debug_on_console == false) { - // TCP なら accept が成功するのを待つ - cons->Accept(); + // デバッガスレッドに通知 + std::unique_lock lock(mtx); + request |= REQUEST_ACCEPT; + cv_request.notify_one(); +} + +// ホストからの1文字入力 +void +Debugger::Input(int c) +{ + // '^@' は捨てる + // (意図的にも入力できるが nc が TELNET オプション扱えなくて送ってくる) + if (c == '\0') { + return; + } + + // 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 { - if (gMainApp.debug_on_start == false) { - // stdio、起動時にプロンプトで止まらない。 - // この場合キー入力かブレークポイントなどで止まった時点で - // プロンプトを出したいのでちょっと面倒なことに。 - for (;;) { - bool is_prompt = gCVPrompt->WaitAcquire(200); - if (is_prompt) { - break; - } + // プロンプト中なら行入力 - int r = cons->Poll(200); - if (r < 0) { - cmd_q(); - return false; - } - if (r > 0) { - break; - } - } + // 先に自力エコーバック?? + fputc(c, cons); + fflush(cons); + + if (c != '\n') { + cmdbuf.push_back(c); } else { - // stdio、起動時にプロンプトで止まる。 - // この場合は無条件に成功でよい。 + // 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); + } } } +} - return true; +// コマンドモード(プロンプト)に入る +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(); } -// プロンプトが取れるのを待つ -// 失敗すれば false を返す -bool -Debugger::AcquirePrompt() +// コマンドモード(プロンプト)から出る +void +Debugger::LeavePrompt() { - md->ReqSet(CPU_REQ_PROMPT); + // MPU のトレース状態を変更 + gScheduler->SendMessage(MessageID::MPU_TRACE, IsTrace()); - for (;;) { - bool is_prompt = gCVPrompt->WaitAcquire(200); - if (is_prompt) - break; + is_prompt = false; - // コンソールを定期観測して、入力があればブレーク要求 - if (cons->Poll()) { - // この入力は drop してみる - if (cons->Gets(cmdbuf) == false) { - // EOF or Error - cmd_q(); - return false; - } - md->ReqSet(CPU_REQ_PROMPT); - } + // 最後に VM スレッドで待機している Exec() を起こす + { + std::unique_lock lock(mtx); + prompt_released = true; + cv_prompt.notify_one(); } - return true; } -// コマンド処理のメインループ。 -// コマンドが Stay ならこの中で処理を継続。 -// コマンドが Leave か Quit ならその値を持って戻る (呼び出し側で処理する)。 -Debugger::CommandAction -Debugger::MainLoop() +// プロンプトを表示 +void +Debugger::PrintPrompt() { - for (;;) { - // ブレークポイント到達メッセージがあればここで表示 - if (bpointmsg.empty() == false) { - cons->Print("%s", bpointmsg.c_str()); - bpointmsg.clear(); - } + fprintf(cons, "> "); + fflush(cons); +} - cons->Prompt(); - if (cons->Gets(cmdbuf) == false) { - // EOF or Error - return CommandAction::Quit; +// コマンド実行 +Debugger::CmdAct +Debugger::Command() +{ + string_rtrim(cmdbuf); + + if (cmdbuf.empty()) { + // 空行なら、直前のコマンドをもう一度。 + // 前行がなければ何もせずもう一度プロンプトを表示するかね。 + if (last_cmdbuf.empty()) { + return CmdAct::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; + + // 見付かれば実行 + return (this->*(cmd.func))(); + } + + // (コマンドテーブルになくて) "r" から始まっていればレジスタ表示系 + if (args[0][0] == 'r') { + // ShowRegister() は処理したら true を返す。 + // "r" 系コマンドはすべて Stay。 + if (md->ShowRegister(cons, args)) { + return CmdAct::Stay; } + } - // 行を args に分解 - ParseCmdbuf(); + // 知らないコマンドも 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(); - // コマンドをテーブルから探す - 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 || ct_enable || s_enable || so_enable || n_enable || + is_pause; } -// デバッガ実行中なら命令開始前にメインルーチンから呼ばれる。 +// デバッガ実行中なら命令開始前に VM スレッドから呼ばれる。 // プロンプトに降りるなら true を返す。 bool Debugger::Check() @@ -360,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 残りは排他動作のはず @@ -370,34 +608,24 @@ Debugger::Check() // キャンセルする。この場合もブレークポイント側が true なので true を // 返せばよい。 - // t (トレース表示) が enable なら、終了条件にマッチしなくてもここで - // レジスタを表示。終了条件にマッチする場合に表示するとプロンプトで - // もう一回表示されて二重になってしまうので注意。 + // トレース表示に関しては Exec 側でやってある。 - if (s_enable) { // ステップ実行が.. + if (s_enable) { // ステップ実行が成立するか s_count--; - if (s_count == 0) { // 成立 + if (s_count == 0 || is_break) { s_enable = false; - return true; - } else if (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()) { // 成立 - so_enable = false; - return true; - } else if (is_break) { // 非成立だがブレークが成立 + } else if (so_enable) { // ステップアウトが成立するか + if (md->IsStepOut() || 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()) { @@ -407,33 +635,33 @@ Debugger::Check() // 1命令実行 n_count--; } - if (n_count == 0) { // 成立 + if (n_count == 0 || is_break) { n_enable = false; - return true; - } else if (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 を返す。 -// このルーチン内では Console があることを期待してはいけない (プロンプトが -// ない時でも呼ばれるので)。 +// このルーチンから cons への出力は使わないこと。(プロンプトにいない時でも +// 呼ばれるので) bool Debugger::CheckAllBreakpoints() { @@ -507,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; @@ -547,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; @@ -589,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; } // 引数があれば個別の詳細 @@ -658,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; } // 個別ヘルプメッセージを出力用に置換。 @@ -730,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" }, @@ -853,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 [[:]
] [] @@ -917,7 +1157,7 @@ Debugger::HelpDetails = { //----- { "L", R"**( - Command: L + Command: L [=][,=[]]... Set loglevel. XXX To be written... )**" }, @@ -1053,7 +1293,7 @@ Debugger::ParseCmdbuf() // b ... 一覧表示 // b #n ... #n を削除 // b [] ... 設定 -void +Debugger::CmdAct Debugger::cmd_b() { // 引数なしなら一覧表示 @@ -1064,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); @@ -1087,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) { @@ -1123,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; @@ -1141,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; } // 引数をまず分離 @@ -1162,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バイト未満なら左詰め。 @@ -1199,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 { @@ -1234,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; } // 大小が逆なら入れ替える? @@ -1271,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); } // すでに来ている例外をクリア。 @@ -1288,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 @@ -1378,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; } // ブレークポイントを設定。 @@ -1454,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 が新しいほうを下とする方向。 @@ -1494,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 [