--- nono/debugger/debugger.cpp 2026/04/29 17:04:56 1.1.1.10 +++ nono/debugger/debugger.cpp 2026/04/29 17:05:49 1.1.1.20 @@ -4,161 +4,293 @@ // Licensed under nono-license.txt // -#include "console.h" -#include "debugger_private.h" +// +// デバッガ +// + +// +// VM スレッド デバッガスレッド HostCOM +// condvar +// | | |<--- 入力 +// | |<----------------------| +// | | RxCallback +// | | +// | |---------------------->| +// | | HostCOMDevice::Tx() |---> 出力 + +// |<---------------------------| +// | is_prompt = true; | デバッガスレッドからプロンプトを出したい +// | Message(MPU_TRACE_ALL); | 場合は MPU (VM) をトレースモードにする。 +// | | その際 is_prompt を立てておくことで止まる。 +// | | +// |--------------------------->| +// | condvar REQUEST_PROMPT | VM スレッドからプロンプトを出したい場合 +// | | (上述の例も含む) は条件変数で通知。 +// | | +// |<---------------------------| +// | condvar prompt_released | プロンプトを出している間 VM スレッドは +// | | 条件変数で待機しているので、これを起こす +// | | ことで実行再開。 + +#include "debugger.h" +#include "debugger_hd64180.h" #include "debugger_m680x0.h" #include "debugger_m88xx0.h" +#include "event.h" +#include "hostcom.h" #include "mainapp.h" +#include "memdump.h" #include "mystring.h" -#include "mythread.h" +#include "power.h" #include "scheduler.h" +#include "syncer.h" +#include "uimessage.h" +#include "vectortable.h" #include -#include - -static Debugger *gDebugger; -CVPrompt *gCVPrompt; +#include +#include +#if defined(HAVE_BSD_STDIO_H) +#include +#endif -static void *debugger_run(void *); +static int readfunc(void *, char *, int); +static int writefunc(void *, const char *, int); -// 作成 (MainApp::Init1 から呼ばれる) -void -debugger_create() +// コンストラクタ +Debugger::Debugger() + : inherited(OBJ_DEBUGGER) { - gDebugger = new Debugger(); - gCVPrompt = new CVPrompt(); + // ベクタテーブル + pVectorTable.reset(new VectorTable(gMainApp.GetVMType())); + + // ブレークポイントモニタ + bpoint_monitor = gMonitorManager->Regist(ID_MONITOR_BREAKPOINT, this); + bpoint_monitor->func = ToMonitorCallback(&Debugger::MonitorUpdateBpoint); + bpoint_monitor->SetSize(59, 9); + + // メモリダンプモニタ + for (int i = 0, end = memdump_monitor.size(); i < end; i++) { + uint objid = OBJ_MPU_MEMDUMP(i); + int monid = ID_MONITOR_MEMDUMP(i); + memdump_monitor[i].reset(new MemdumpMonitor(objid, monid)); + } + + if (gMainApp.Has(VMCap::LUNA)) { + // XP 空間のメモリダンプモニタ + for (int i = 0, end = xpmemdump_monitor.size(); i < end; i++) { + uint objid = OBJ_XP_MEMDUMP(i); + int monid = ID_MONITOR_XPMEMDUMP(i); + xpmemdump_monitor[i].reset(new MemdumpMonitor(objid, monid)); + } + } } -// 初期化 (MainApp::Init2 から呼ばれる) -// この時点で VM が初期化されていること。 -void -debugger_init() +// デストラクタ +Debugger::~Debugger() { - gDebugger->Init(); + // fclose(fflush) にあたり hostcom へのアクセスが発生するので、 + // hostcom より先に片付けておかなければならない。 + Close(); - // デバッガスレッド起動 - pthread_t th; - pthread_create(&th, NULL, debugger_run, NULL); + if ((bool)hostcom) { + hostcom->ResetRxCallback(); + hostcom->SetAcceptCallback(NULL); + } + + TerminateThread(); } -// デバッガスレッドのエントリポイント -static void * -debugger_run(void *dummy) +bool +Debugger::Create() { - PTHREAD_SETNAME("Debugger"); - pthread_detach(pthread_self()); + // ホストドライバを作成 + try { + hostcom.reset(new HostCOMDevice(this, -1, "Debugger")); + } catch (...) { } + if ((bool)hostcom == false) { + warnx("Failed to initialize HostCOMDevice at %s", __method__); + return false; + } - gDebugger->ThreadRun(); - return NULL; + hostcom->SetRxCallback(ToDeviceCallback(&Debugger::RxCallback), 0); + hostcom->SetAcceptCallback(ToDeviceCallback(&Debugger::AcceptCallback)); + + return true; } -// メモリダンプモニタを外部から取得 -Monitor& -debugger_memdump_monitor(int n) +// ログレベル設定 +void +Debugger::SetLogLevel(int loglevel_) { - assert(n < MAX_MEMDUMP_MONITOR); - return gDebugger->memdump_monitor[n]; + inherited::SetLogLevel(loglevel_); + + // ホストドライバを従属させる + if ((bool)hostcom) { + hostcom->SetLogLevel(loglevel_); + } } -// コンストラクタ -Debugger::Debugger() - : inherited("Debugger") +// 初期化 +bool +Debugger::Init() { - if (gMPU680x0) { - md = new DebuggerMD_m680x0(this, gMPU680x0->GetCPU()); - } else if (gMPU88xx0) { - md = new DebuggerMD_m88xx0(this, gMPU88xx0->GetCPU()); - } else { - throw "unknown mpu"; + if (inherited::Init() == false) { + return false; + } + + syncer = GetSyncer(); + + try { + if (gMainApp.Has(VMCap::M88K)) { + md_mpu.reset(new DebuggerMD_m88xx0(this)); + } else { + md_mpu.reset(new DebuggerMD_m680x0(this)); + } + } catch (...) { } + if ((bool)md_mpu == false) { + warnx("Failed to initialize md_mpu at %s", __method__); + return false; + } + + if (gMainApp.Has(VMCap::LUNA)) { + try { + md_xp.reset(new DebuggerMD_hd64180(this)); + } catch (...) { } + if ((bool)md_xp == false) { + warnx("Failed to initialize md_xp at %s", __method__); + return false; + } } - // ブレークポイントモニター - bpoint_monitor.func = (MonitorCallback_t)&Debugger::MonitorUpdateBpoint; - bpoint_monitor.SetSize(55, 9); - bpoint_monitor.Regist(ID_MONITOR_BREAKPOINT); + // とりあえずメインプロセッサに固定 + curmd = md_mpu.get(); - // メモリダンプモニター + // md_* が用意できたので MemdumpMonitor にセットする。 + // これらは Monitor なので Init() より前に用意してなければならないが、 + // 一方で md は諸々が落ち着いた後のここでないと用意できない。うーん。 + // ここはバス別のモニタなので curmd ではなく md_{mpu,xp} を参照する。 for (int i = 0, end = memdump_monitor.size(); i < end; i++) { - auto& mon = memdump_monitor[i]; - mon.obj = this; - mon.func = (MonitorCallback_t)&Debugger::MonitorUpdateMemdump; - mon.SetSize(76, 16); - mon.Regist(ID_MONITOR_MEMDUMP(i)); - } - // m/M コマンド用。こっちは Regist 不要 - m_monitor.func = (MonitorCallback_t)&Debugger::MonitorUpdateMemdump; - m_monitor.SetSize(76, 16); -} + auto *mem = memdump_monitor[i].get(); + mem->InitMD(md_mpu.get()); + } + if (gMainApp.Has(VMCap::LUNA)) { + for (int i = 0, end = xpmemdump_monitor.size(); i < end; i++) { + auto *mem = xpmemdump_monitor[i].get(); + mem->InitMD(md_xp.get()); + } + } -// コマンドライン引数によって動作を決めるところ。 -// 名前がこれでいいのかはあるけど。 -void -Debugger::Init() -{ // -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; + // -b [,][,] ならブレークポイント設定 + for (const auto& str : gMainApp.debug_breakaddr) { + if (ParseOptBreakAddr(str) == false) { + return false; + } + } - md->ReqSet(CPU_REQ_TRACE); + // --bi [,][:][,] で命令ブレークポイント設定。 + for (auto& str : gMainApp.debug_breakinst) { + if (ParseOptBreakInst(str) == false) { + return false; + } + } - // , で分離できたら を取り出す - int pos = str.find(','); - if (pos != std::string::npos) { - std::string skipstr = str.substr(pos + 1); - bp.skip = atoi(skipstr.c_str()); - - // XXX これ出来るんだっけ? - str[pos] = '\0'; - } - // そしてどちらにしても を取り出す - if (!ParseAddr(str.c_str(), &bp.addr)) { - warnx("\"%s\": Invalid breakpoint address", str.c_str()); - return; + // --bv [,][-][,] でベクタブレークポイント設定。 + for (auto& str : gMainApp.debug_breakvec) { + if (ParseOptBreakVector(str) == false) { + return false; } - // 登録 - bp.type = BreakpointType::Address; + } + + // --bi-exg なら命令ブレークポイントを設定 + // (CPU のチェックはしていない) + if (gMainApp.debug_breakinst_exg) { + breakpoint_t bp; + bp.type = BreakpointType::Instruction; + bp.inst = 0xcf4f0000; + bp.mask = 0xffff0000; + bp.md = curmd; AddBreakpoint(bp); + // 今登録されている命令ブレークの必要命令長を再計算 + RecalcInstMask(); } - // どこでやるべか - 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; - } + SetThreadAffinityHint(AffinityClass::Light); // 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); + } + } - // デバッガプロンプトにいる間はトレースオン - md->ReqSet(CPU_REQ_TRACE); + if ((req & REQUEST_ACCEPT)) { + // TCP 待ち受けに着信があればすぐにプロンプトを出したい + Input('\n'); + } + if ((req & REQUEST_PROMPT)) { + // VM 停止(したのでプロンプトモードへ) + EnterPrompt(); + } + } + + LeavePrompt(false); + Close(); +} + +// コンソールをクローズする +void +Debugger::Close() +{ + if (cons) { + fclose(cons); + cons = NULL; + } +} + +#if 0 bool first = true; for (;;) { // 接続後の1回目だけ実行するもの。 @@ -168,343 +300,513 @@ 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::lock_guard lock(mtx); + request |= REQUEST_EXIT; + cv_request.notify_one(); +} - // コマンド処理のメインループ - auto action = MainLoop(); +// funopen の read コールバック +static int +readfunc(void *cookie, char *buf, int bufsize) +{ + auto debugger = reinterpret_cast(cookie); + return debugger->ReadFunc(buf, bufsize); +} - // 次回との差分のため、今のレジスタセットをバックアップ - md->BackupRegs(); +// funopen の write コールバック +static int +writefunc(void *cookie, const char *buf, int len) +{ + auto debugger = reinterpret_cast(cookie); + return debugger->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(uint32 arg) +{ + // デバッガスレッドに通知 + std::lock_guard 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(uint32 dummy) { - 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; - } + // デバッガスレッドに通知 + std::lock_guard lock(mtx); + request |= REQUEST_ACCEPT; + cv_request.notify_one(); +} - int r = cons->Poll(200); - if (r < 0) { - cmd_q(); - return false; - } - if (r > 0) { - break; - } +// ホストからの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; + scheduler->SendMessage(MessageID::MPU_TRACE_ALL, true); + } + } else { + // プロンプト中なら行入力 + if (c == '\b') { + if (cmdbuf.empty() == false) { + // 簡易バックスペースを出力。 + // XXX 実際どうするんだこれ + fputc('\b', cons); + fputc(' ', cons); + fputc('\b', cons); + fflush(cons); + + cmdbuf.pop_back(); + } + } else if (c == '\n') { + // Enter ならここでコマンド実行 + fputc(c, cons); + fflush(cons); + + auto act = Command(); + + // 次回との差分のため、今のレジスタセットをバックアップ + curmd->BackupRegs(); + + switch (act) { + case CmdAct::Stay: + // プロンプトに留まるならここで、次行のプロンプト? + PrintPrompt(); + break; + case CmdAct::Leave: + // プロンプトを抜ける + LeavePrompt(IsTrace()); + break; + case CmdAct::Quit: + // アプリケーション自体を終了 + LeavePrompt(false); + gMainApp.GetUIMessage()->Post(UIMessage::APPEXIT); } + } else if (c < ' ' || c == 0x07f) { + // 他のコントロールコードはとりあえず無視 } else { - // stdio、起動時にプロンプトで止まる。 - // この場合は無条件に成功でよい。 + // 通常文字なら、エコーバックして追加 + fputc(c, cons); + fflush(cons); + + cmdbuf.push_back(c); } } +} - return true; +// コマンドモード(プロンプト)に入る +void +Debugger::EnterPrompt() +{ + is_prompt = true; + + // ブレークポイント到達メッセージがあればここで表示 + if (bpointmsg.empty() == false) { + fprintf(cons, "%s", bpointmsg.c_str()); + bpointmsg.clear(); + } + + // d/m をいきなり引数なしで実行した時のため、現在地にしておく。 + ResetStickyAddr(); + + // プロンプトのたびに表示するやつ + cmd_minus(); + + PrintPrompt(); } -// プロンプトが取れるのを待つ -// 失敗すれば false を返す -bool -Debugger::AcquirePrompt() +// コマンドモード(プロンプト)から出る。 +// trace はプロンプトから抜ける際のトレース状態の指示。 +// スレッド終了時は false を指定すること。 +void +Debugger::LeavePrompt(bool trace) { - md->ReqSet(CPU_REQ_PROMPT); + // MPU のトレース状態を変更 + scheduler->SendMessage(MessageID::MPU_TRACE_ALL, trace); - 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::lock_guard lock(mtx); + prompt_released = true; + cv_prompt.notify_one(); } - return true; } -// コマンド処理のメインループ。 -// コマンドが Stay ならこの中で処理を継続。 -// コマンドが Leave か Quit ならその値を持って戻る (呼び出し側で処理する)。 -Debugger::CommandAction -Debugger::MainLoop() +// プロンプトを表示 +void +Debugger::PrintPrompt() +{ + fprintf(cons, "%s> ", curmd->GetName().c_str()); + fflush(cons); +} + +// コマンド実行 +Debugger::CmdAct +Debugger::Command() { - for (;;) { - // ブレークポイント到達メッセージがあればここで表示 - if (bpointmsg.empty() == false) { - cons->Print("%s", bpointmsg.c_str()); - bpointmsg.clear(); - } + string_rtrim(cmdbuf); - cons->Prompt(); - if (cons->Gets(cmdbuf) == false) { - // EOF or Error - return CommandAction::Quit; + if (cmdbuf.empty()) { + // 空行なら、直前のコマンドをもう一度。 + // 前行がなければ何もせずもう一度プロンプトを表示するかね。 + if (last_cmdbuf.empty()) { + return CmdAct::Stay; } + cmdbuf = last_cmdbuf; + } else { + // 入力された行を次回のために保存 + last_cmdbuf = cmdbuf; + } - string_rtrim(cmdbuf); + // 行を cmdargs に分解 + 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(cmdargs[0].c_str(), x.name) == 0; }); + if (it != cmdtable.end()) { + auto cmd = *it; + + // 見付かれば実行 + return (this->*(cmd.func))(); + } + + // (コマンドテーブルになくて) "r" から始まっていればレジスタ表示系 + if (cmdargs[0][0] == 'r') { + // ShowRegister() は処理したら true を返す。 + // "r" 系コマンドはすべて Stay。 + if (curmd->ShowRegister(cons, cmdargs)) { + return CmdAct::Stay; } + } + + // 知らないコマンドも Stay 相当。 + fprintf(cons, "%s: Unknown command.\n", cmdargs[0].c_str()); + // この行は次回空エンターで再発行しないでいい。 + last_cmdbuf.clear(); + return CmdAct::Stay; +} - // 行を args に分解 - ParseCmdbuf(); +// ブレークポイントとかを調べる。1命令ごとに呼び出される。 +// (VM スレッドから呼ばれる) +void +Debugger::Exec(DebuggerMD *md) +{ + if (Check(md)) { + // この CPU でブレークしたので、ターゲット CPU をこっちに変更。 + ChangeMD(md); - // 前回の値が有効なのはコマンドが連続した時だけ + // 実時間を停止 + syncer->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; + // 実時間を再開 + syncer->StartRealTime(); + } else { + // t (トレース表示) が有効な場合は終了条件にマッチしなかったここで + // レジスタを表示。終了条件にマッチした時は EnterPrompt() で表示する。 + if (t_enable == md) { + assert(md == curmd); + pc = curmd->GetPC(); + cmd_minus(); + // 次回との差分のため今のレジスタセットをバックアップ + curmd->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 is_pause || (step_type != StepType::None); } -// デバッガ実行中なら命令開始前にメインルーチンから呼ばれる。 +// デバッガ実行中なら命令開始前に VM スレッドから呼ばれる。 // プロンプトに降りるなら true を返す。 bool -Debugger::Check() +Debugger::Check(DebuggerMD *md) { bool is_break = false; // ブレークポイントは他のチェックとは併用になるので先に調べる。 - if (CheckAllBreakpoints()) { + if (CheckAllBreakpoints(md)) { is_break = true; - } - // アドレス指定付き continue もブレークポイントと似た動作なのでこっち。 - // XXX ブレークポイントとして実装するかどうか - if (bc_enable) { - if (bc_addr == md->GetPC()) { - bc_enable = false; - is_break = true; - } - } - // XXX 残りは排他動作のはず + // この次に調べる各種終了条件が来ないうちに先にブレークポイントに + // 到達した場合でも、ブレークポイントによりプロンプトに降りる + // わけなので、実行中のステップ実行をキャンセルする。 + // この場合もブレークポイント側が true なので true を返せばよい。 + step_type = StepType::None; + step_md = NULL; + } + + // ステップ実行系は、ターゲット CPU 側でだけ判定を行い、 + // いずれの場合も終了条件にマッチしたらブレークポイントの成否に関わらず + // true を返せばいい。 + // なお、トレース表示に関してはここではなく Exec() 側でやってある。 + + if (step_md == md) { + bool hit = false; + switch (step_type) { + case StepType::None: + break; - // いずれの場合も、ステップ実行が終了条件にマッチしたら、ブレークポイント - // の成否に関わらず true を返せばいい。 - // 終了条件が来ないうちに先にブレークポイントに到達した場合でも、ブレーク - // ポイントによりプロンプトに降りるわけなので、実行中のステップ実行を - // キャンセルする。この場合もブレークポイント側が true なので true を - // 返せばよい。 - - // t (トレース表示) が enable なら、終了条件にマッチしなくてもここで - // レジスタを表示。終了条件にマッチする場合に表示するとプロンプトで - // もう一回表示されて二重になってしまうので注意。 - - if (s_enable) { // ステップ実行が.. - s_count--; - if (s_count == 0) { // 成立 - s_enable = false; - return true; - } else if (is_break) { // 非成立だがブレークが成立 - s_enable = false; - return true; - } else if (t_enable) { // どちらも非成立で trace on - cmd_minus(); - } + case StepType::Count: // 命令数指定 + step_count--; + putlog(1, "Check s: --step_count=%d", step_count); + if (step_count == 0) { + hit = true; + } + break; - } else if (so_enable) { // ステップアウトが.. - if (md->IsStepOut()) { // 成立 - so_enable = false; - return true; - } else if (is_break) { // 非成立だがブレークが成立 - so_enable = false; - return true; - } else if (t_enable) { // どちらも非成立で trace on - cmd_minus(); - } + case StepType::CountSkipSub: // 命令数指定 (サブルーチンをスキップ) + if ((int64)step_addr >= 0) { + // アドレスが指定されていれば、ステップインをスキップ中 + if (step_addr == step_md->GetPC()) { + step_count--; + step_addr = (uint64)-1; + } + } else { + step_count--; + } + if (loglevel >= 1) { + if ((int64)step_addr < 0) { + putlogn("Check n: --step_count=%d", step_count); + } else { + putlogn("Check n: step_addr=$%08x", (uint32)step_addr); + } + } + if (step_count == 0 || is_break/*?*/) { + hit = true; + } else { + // スキップ中でなければ、ステップインが起きるか都度調べる。 + // すでにスキップ中なら到達するまでは何もしない。 + if ((int64)step_addr < 0) { + SetNBreakpoint(); + } + } + break; - } else if (n_enable) { // 次命令まで実行が.. - if (n_breakaddr != 0xffffffff) { - // ステップインをスキップ中 - if (n_breakaddr == md->GetPC()) { - n_count--; + case StepType::StepOut: // ステップアウト + putlog(1, "Check so"); + if (step_md->IsStepOut()) { + hit = true; } - } else { - // 1命令実行 - n_count--; - } - if (n_count == 0) { // 成立 - n_enable = false; - return true; - } else if (is_break) { // 非成立だがブレークが成立 - n_enable = false; - return true; - } - if (t_enable) { // どちらも非成立で trace on - cmd_minus(); + break; + + case StepType::Addr: // アドレス指定 + putlog(1, "Check c: step_addr=$%08x", (uint32)step_addr); + if (step_addr == step_md->GetPC()) { + hit = true; + } + break; + + case StepType::Time: // 時間指定 + putlog(1, "Check ct"); + if (scheduler->GetVirtTime() >= step_time) { + hit = true; + + // 時間到達は他のと比べて分かりづらいので、 + // ブレークポイントメッセージに便乗して表示(?) + bpointmsg += string_format("%s has passed.\n", + TimeToStr(ct_timespan).c_str()); + } + break; + + default: + PANIC("StepType %d not supported\n", (int)step_type); } - // スキップ中でなければ、ステップインが起きるか都度調べる。 - // すでにスキップ中なら到達するまでは何もしない。 - if (n_breakaddr == 0xffffffff) { - SetNBreakpoint(); + if (hit) { + is_break = true; + step_type = StepType::None; + step_md = NULL; + t_enable = NULL; } } - // ステップ実行系がなければブレークポイントの成否だけ + // デバッガから MPU の一時停止が要求されているか + if (is_pause) { + is_pause = false; + is_break = true; + } + return is_break; } -// ブレークポイントがどれかでも成立するかを調べる。 -// 1つ以上成立してブレークするなら true を返す。 +// md で指定された CPU のブレークポイントがどれかでも成立するかを調べる。 +// 1つ以上成立してブレークするなら、true を返す。 // 1つも成立しておらずブレークしないなら false を返す。 -// このルーチン内では Console があることを期待してはいけない (プロンプトが -// ない時でも呼ばれるので)。 +// このルーチンから cons への出力は使わないこと。(プロンプトにいない時でも +// 呼ばれるので) bool -Debugger::CheckAllBreakpoints() +Debugger::CheckAllBreakpoints(DebuggerMD *md) { bool is_break = false; + int vector; // 命令ごとにクリアする bi_inst = 0; bi_inst_bytes = 0; + // 例外はここでローカルにコピーしてから、クリアする。 + // 厳密には atomic exchange すべきのような。 + vector = md->bv_vector; + md->bv_vector = -1; + // 1つの条件でマッチしても(そこでブレークすること自体は確定するのだが) // 残りの他の条件も成立すればカウントを進める必要があるため、 // 全部処理した上でどれか一つでもブレークしたかで判断する必要がある。 for (int i = 0, end = bpoint.size(); i < end; i++) { auto& bp = bpoint[i]; + if (bp.md != md) { + continue; + } + switch (bp.type) { case BreakpointType::Address: - if (bp.addr != md->GetPC()) { - continue; + // 通常動作中でアドレスが一致すればマッチ + if (bp.addr == bp.md->GetPC() && + bp.md->GetCPUState() == CPUState::Normal) + { + break; } - break; + continue; case BreakpointType::Memory: - if (!md->CheckLEA(bp.addr)) { - continue; + if (bp.md->CheckLEA(bp.addr)) { + break; } - break; + continue; case BreakpointType::Exception: - if (bv_vector >= 0) { - if (bp.vec1 <= bv_vector && bv_vector <= bp.vec2) { + if (vector >= 0) { + if (bp.vec1 <= vector && vector <= bp.vec2) { break; } } continue; case BreakpointType::Instruction: - if (CheckBreakpointInst(bp) == false) { - continue; + if (CheckBreakpointInst(bp)) { + break; } - break; + continue; default: continue; @@ -527,24 +829,31 @@ Debugger::CheckAllBreakpoints() is_break = true; std::string desc; + desc = string_format("cpu=%s ", bp.md->GetName().c_str()); switch (bp.type) { case BreakpointType::Address: - desc = string_format("addr $%08x", bp.addr); + desc += string_format("addr=$%08x", bp.addr); break; case BreakpointType::Memory: - desc = string_format("mem $%08x", bp.addr); + desc += string_format("mem=$%08x", bp.addr); break; case BreakpointType::Exception: { - desc = string_format("excp $%02x", bv_vector); - const char *name = md->GetExceptionName(bv_vector); - if (name != NULL && name[0] != '\0') { + desc += string_format("excp=$%02x", vector); + const char *name; + if (bp.md->arch == CPUArch::HD64180) { + name = MPU64180Device::InterruptName[vector]; + } else { + auto vectortable = pVectorTable.get(); + name = vectortable->GetExceptionName(vector); + } + if (name) { desc += string_format(" \"%s\"", name); } break; } case BreakpointType::Instruction: - desc = string_format("inst %0*x", + desc += string_format("inst=%0*x", bi_inst_bytes * 2, bi_inst >> ((4 - bi_inst_bytes) * 8)); break; @@ -557,13 +866,10 @@ Debugger::CheckAllBreakpoints() // この時点ではまだコンソールを取得していない可能性があるので // (-D なしで起動した場合とか)、表示せず用意するだけ。 // コンソールが取得できたところで表示する。 - bpointmsg += string_format("breakpoint #%d (%s) reached\n", + bpointmsg += string_format("Breakpoint #%d (%s) reached.\n", i, desc.c_str()); } - // 例外通知は通過ごとに常に下ろしておく - bv_vector = -1; - // ブレークポイントが1つでも成立したかどうかを返す return is_break; } @@ -576,20 +882,22 @@ Debugger::CheckBreakpointInst(breakpoint // bi_inst_bytes が読み込んだバイト数(bi_inst の左からの有効バイト数)。 // bi_need_bytes が現在のブレークポイントで読み込む必要のあるバイト数。 - saddr_t laddr(md->GetPC(), md->IsSuper()); - DebuggerMemoryStream mem(md, laddr); + busaddr laddr = busaddr(bp.md->GetPC()) + | BusAddr::Fetch + | busaddr::SU(bp.md->IsSuper()); + DebuggerMemoryStream mem(bp.md, laddr); // 必要なバイト数に達するまで読み足す bi_inst = 0; while (bi_inst_bytes < bi_need_bytes) { - uint64 data = mem.FetchInst(); + uint64 data = mem.Read(bp.md->inst_bytes); if ((int64)data < 0) { return false; } - bi_inst <<= md->inst_bytes * 8; + bi_inst <<= bp.md->inst_bytes * 8; bi_inst |= data; - bi_inst_bytes += md->inst_bytes; + bi_inst_bytes += bp.md->inst_bytes; } // 左詰めにする @@ -602,124 +910,145 @@ Debugger::CheckBreakpointInst(breakpoint return false; } -// 例外通知 (MPU からの連絡用) +// 例外通知 (メイン CPU) void -debugger_notify_exception(int vector) +Debugger::NotifyExceptionMain(int vector) { - gDebugger->NotifyException(vector); + auto md = md_mpu.get(); + md->bv_vector = vector; } -// 例外通知 (本体) +// 例外通知 (XP)。 +// ベクタとして割り込み優先順位 (Intmap*) を使う。 void -Debugger::NotifyException(int vector) +Debugger::NotifyExceptionXP(int vector) { - bv_vector = vector; + auto md = md_xp.get(); + md->bv_vector = vector; } // コマンド一覧。 // "r" から始まるレジスタ表示系コマンドはコード中で別処理してある。 /*static*/ std::vector Debugger::cmdtable = { - { "bi", &Debugger::cmd_bi, Debugger::CommandAction::Stay }, - { "bm", &Debugger::cmd_bm, Debugger::CommandAction::Stay }, - { "bv", &Debugger::cmd_bv, Debugger::CommandAction::Stay }, - { "bx", &Debugger::cmd_bx, Debugger::CommandAction::Stay }, - { "b", &Debugger::cmd_b, Debugger::CommandAction::Stay }, - { "brhist", &Debugger::cmd_brhist, Debugger::CommandAction::Stay }, - { "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, }, + { "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, }, + { "M", &Debugger::cmd_M, }, + { "mpu", &Debugger::cmd_mpu, }, + { "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, }, + { "xp", &Debugger::cmd_xp, }, + { "z", &Debugger::cmd_z, }, + { "-", &Debugger::cmd_minus, }, }; // ヘルプ // h : 一覧を表示 // h : 単独コマンドの詳細を表示 -void +Debugger::CmdAct Debugger::cmd_h() { - if (args.size() < 2) { + if (cmdargs.size() < 2) { // 引数なしなら一覧表示。 ShowHelpList(HelpListMain); - return; + return CmdAct::Stay; } // 引数があれば個別の詳細 - 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); - cons->Print("%s", disp.c_str()); - return; - } - } - } while (try_again); - cons->Print("invalid command name: %s\n", args[1].c_str()); + const std::string cmd1 = cmdargs[1]; + + // curmd の MD 分も併せて検索するため、ローカルに map を作成する。 + // value のほうは実体コピーは不要なのでポインタ。 + std::map helpmap; + for (const auto& pair : HelpDetails) { + helpmap.insert(std::make_pair(pair.first, &pair.second)); + } + for (const auto& pair : curmd->GetHelpReg()) { + helpmap.insert(std::make_pair(pair.first, &pair.second)); + } + + // 検索 + const auto it1 = helpmap.find(cmd1); + if (it1 == helpmap.end()) { + fprintf(cons, "Invalid command name: %s\n", cmd1.c_str()); + return CmdAct::Stay; + } + const auto& msg1 = *(it1->second); + + const std::string *msg; + if (msg1[0] != '=') { + // メッセージ本文が "=" から始まってなければこれを採用。 + msg = &msg1; + } else { + // メッセージ本文が "=" の形式なら、 を探し直す。 + // 別名で同じヘルプを指すシンボリックリンクみたいなもの。 + std::string cmd2 = msg1.substr(1); + + // 再検索 (こっちは見付からないはずはない) + const auto it2 = helpmap.find(cmd2); + if (it2 == helpmap.end()) { + fprintf(cons, "Warning: '%s' in '%s' not found.\n", + msg1.c_str(), cmd1.c_str()); + return CmdAct::Stay; + } + const auto& msg2 = *(it2->second); + msg = &msg2; + } + + std::string disp = HelpConvert(*msg); + fprintf(cons, "%s", disp.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(curmd->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; } // 個別ヘルプメッセージを出力用に置換。 @@ -760,7 +1089,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" }, @@ -808,11 +1137,12 @@ Debugger::HelpDetails = { //----- { "b", R"**( Command: b - Command: b
[] + Command: b [,]
[] Command: b #n The first form (with no arguments) shows all breakpoints. - The second form sets a new breakpoint on
. + The second form sets a new breakpoint at
on . + If is omitted, the current cpu is used. XXX skipcount If is -1, it will never match. It's useful to count the number of times you have passed this address. @@ -821,9 +1151,10 @@ Debugger::HelpDetails = { //----- { "bm", R"**( - Command: bm
[] + Command: bm [,]
[] - Sets a memory breakpoint on
. + Sets a memory breakpoint at
on . + If is omitted, the current cpu is used. XXX skipcount If is -1, it will never match. It's useful to count the number of times you have passed this address. @@ -831,16 +1162,17 @@ Debugger::HelpDetails = { //----- { "bi", R"**( - Command: bi [:] [] + 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. + Sets an instruction breakpoint on . 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 omitted, 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'. + If is omitted, the current cpu is used. XXX skipcount If is -1, it will never match. It's useful to count the number of times you have passed this address. @@ -848,13 +1180,16 @@ Debugger::HelpDetails = { //----- { "bv", R"**( - Command: bv [-] [] + 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. + Sets an exception breakpoint on . accepts hexadecimal + number if prefixed with "$" or "0x", or decimal number if no prefix. + ranges from 0 to 255 on m68k, and 0 to 511 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. + + If is omitted, the current cpu is used. XXX skipcount If is -1, it will never match. It's useful to count the number of times you have passed this address. @@ -883,15 +1218,23 @@ Debugger::HelpDetails = { )**" }, //----- + { "ct", R"**( + Command: ct [] + + Continue until specified has elapsed. If the is + omitted, 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 [[:]
] [] - 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 + The second form ("d") 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 @@ -902,11 +1245,7 @@ Debugger::HelpDetails = { 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" }, //----- @@ -956,12 +1295,11 @@ Debugger::HelpDetails = { { "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 + The second form ("m") 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 @@ -972,20 +1310,23 @@ Debugger::HelpDetails = { 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" }, //----- + { "mpu", R"**( + Command: mpu + + Change the target CPU to "mpu"(M68030/M88100). + )**" }, + + //----- { "n", R"**( Command: n [] Command: nt [] Step one (or ) instructions. Unlike "s" command, "n" skips - subroutine. + subroutine (and repeat instruction like as LDIR in HD64*180). If "t" is suffixed, it shows a trace for each instruction (including while skipping). )**" }, @@ -1037,9 +1378,23 @@ Debugger::HelpDetails = { //----- { "t", "=s" }, + + //----- + { "xp", R"**( + Command: xp + + Change the target CPU to "xp"(HD647180). + )**" }, + + //----- + { "z", R"**( + Command: z + + Continue until the next address. + )**" }, }; -// cmdbuf を args... に分解する。 +// cmdbuf を cmdargs... に分解する。 void Debugger::ParseCmdbuf() { @@ -1047,7 +1402,7 @@ Debugger::ParseCmdbuf() int start; pos = 0; - args.clear(); + cmdargs.clear(); // 引数を空白で分解 for (;;) { @@ -1065,12 +1420,12 @@ Debugger::ParseCmdbuf() if (isspace((unsigned int)cmdbuf[pos])) break; } - args.push_back(cmdbuf.substr(start, pos - start)); + cmdargs.push_back(cmdbuf.substr(start, pos - start)); } // デバッグ表示 if (0) { - for (int i = 0 ; i < args.size(); i++) { - printf("args[%d]=|%s|\n", i, args[i].c_str()); + for (int i = 0 ; i < cmdargs.size(); i++) { + printf("cmdargs[%d]=|%s|\n", i, cmdargs[i].c_str()); } } } @@ -1080,58 +1435,79 @@ Debugger::ParseCmdbuf() // // ブレークポイント -// b ... 一覧表示 -// b #n ... #n を削除 -// b [] ... 設定 -void +// b ... 一覧表示 +// b #n ... #n を削除 +// b [,] [] ... 設定 +Debugger::CmdAct Debugger::cmd_b() { // 引数なしなら一覧表示 - if (args.size() < 2) { + if (cmdargs.size() < 2) { return cmd_b_list(); } // 引数取得 - if (args[1][0] == '#') { + if (cmdargs[1][0] == '#') { // # 形式なら、指定番号のブレークポイントを削除 cmd_b_delete(); - return; + return CmdAct::Stay; } return cmd_b_set(BreakpointType::Address); } // メモリブレークポイントの設定 -// bm [] -void +// bm [,] [] +Debugger::CmdAct Debugger::cmd_bm() { - // XXX m68k では未サポート - if (md->arch == Arch::M680x0) { - cons->Print("bm not supported yet on m68k\n"); - return; + // XXX m88k のみ未サポート + if (curmd->arch != CPUArch::M88xx0) { + 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 +// すぐ下の ParseOptBreakAddr() と限りなく似ている。 +Debugger::CmdAct Debugger::cmd_b_set(BreakpointType type) { breakpoint_t bp; + std::string cpustr; + std::string addrstr; - if (args.size() < 2) { - cons->Print("usage: %s []\n", args[0].c_str()); - return; + if (cmdargs.size() < 2) { + fprintf(cons, "Usage: %s [,] []\n", + cmdargs[0].c_str()); + return CmdAct::Stay; + } + + // まず CPU を分離 + auto pos = cmdargs[1].find(','); + if (pos == std::string::npos) { + // CPU 指定なし + addrstr = cmdargs[1]; + } else { + // CPU 指定あり + cpustr = cmdargs[1].substr(0, pos); + addrstr = cmdargs[1].substr(pos + 1); } + // CPU + bp.md = ParseCPU(cpustr); + if (bp.md == NULL) { + fprintf(cons, "Invalid CPU Name.\n"); + return CmdAct::Stay; + } // アドレス - if (!ParseAddr(args[1].c_str(), &bp.addr)) { - return; + if (!ParseAddr(addrstr.c_str(), &bp.addr)) { + return CmdAct::Stay; } // あればスキップカウント - if (args.size() > 2) { - bp.skip = atoi(args[2].c_str()); + if (cmdargs.size() > 2) { + bp.skip = atoi(cmdargs[2].c_str()); } // 空いてるところにセット @@ -1139,88 +1515,247 @@ Debugger::cmd_b_set(BreakpointType type) bp.type = type; int bi = AddBreakpoint(bp); if (bi == -1) { - cons->Print("no free breakpoints\n"); + fprintf(cons, "Breakpoints full.\n"); } else { - cons->Print("breakpoint #%d added\n", bi); + fprintf(cons, "Breakpoint #%d added.\n", bi); } + return CmdAct::Stay; +} + +// コマンドラインオプション -b(--b) の引数 str からのブレークポイント(アドレス) +// を登録する。 +// 成功すれば true を返す。失敗すればエラーメッセージを表示して false を返す。 +// すぐ上の cmd_b_set() と限りなく似ている。 +bool +Debugger::ParseOptBreakAddr(const std::string& str) +{ + breakpoint_t bp; + std::string cpustr; + std::string addrstr; + std::string skipstr; + + // "," で分離。 + auto args = string_split(str, ','); + if (args.size() == 1) { + // 1個ならアドレスのみ。 + addrstr = args[0]; + } else if (args.size() == 2) { + // 2個なら cpu か skip のどちらかが省略されている…。 + // 先頭が CPU 文字列かどうかで判定する。 + if (IsCPUStr(args[0])) { + cpustr = args[0]; + addrstr = args[1]; + } else { + addrstr = args[0]; + skipstr = args[1]; + } + } else if (args.size() == 3) { + cpustr = args[0]; + addrstr = args[1]; + skipstr = args[2]; + } else { + warnx("--b \"%s\": Syntax error.", str.c_str()); + return false; + } + + // を取り出す。 + bp.md = ParseCPU(cpustr); + if (bp.md == NULL) { + warnx("--b \"%s\": Invalid CPU Name.", str.c_str()); + return false; + } + + // を取り出す。 + if (ParseVerbHex(addrstr.c_str(), &bp.addr) == false) { + warnx("--b \"%s\": Invalid address.", str.c_str()); + return false; + } + + // を取り出す。 + if (skipstr.empty() == false) { + bp.skip = atoi(skipstr.c_str()); + } + + // 登録。 + bp.type = BreakpointType::Address; + if (AddBreakpoint(bp) < 0) { + warnx("--b \"%s\": Breakpoints full.", str.c_str()); + return false; + } + return true; } // ブレークポイント個別削除 // (引数の先頭が '#' なことを判定したところで呼ばれる) // b # -void +Debugger::CmdAct Debugger::cmd_b_delete() { - int n = atoi(&args[1][1]); + int n = atoi(&cmdargs[1][1]); if (n < 0 || n >= bpoint.size()) { - cons->Print("invalid breakpoint number: #%d\n", n); - return; + fprintf(cons, "Invalid breakpoint number: %d\n", n); + return CmdAct::Stay; } auto& bp = bpoint[n]; if (bp.type == BreakpointType::Unused) { - cons->Print("invalid breakpoint number: #%d\n", n); - return; + fprintf(cons, "Breakpoint #%d already removed.\n", n); + return CmdAct::Stay; } - cons->Print("breakpoint #%d removed\n", n); + fprintf(cons, "Breakpoint #%d removed.\n", n); bp.type = BreakpointType::Unused; // 今登録されている命令ブレークの必要命令長を再計算 RecalcInstMask(); + return CmdAct::Stay; } // 命令ブレークポイントの設定 -// bi [:] [] -void +// bi [,][:] [] +Debugger::CmdAct Debugger::cmd_bi() { - breakpoint_t bp; + std::string cpustr; std::string inststr; - std::string maskstr; - int instlen; - int masklen; + std::string skipstr; + std::string errstr; - if (args.size() < 2) { - cons->Print("usage: bi [:] []\n"); - return; + if (cmdargs.size() < 2) { + fprintf(cons, "Usage: bi [:] []\n"); + return CmdAct::Stay; } - // 引数をまず分離 - auto pos = args[1].find(':'); + // CPU をまず分離 + auto pos = cmdargs[1].find(','); if (pos == std::string::npos) { - // マスク指定なし + // CPU 指定なし + inststr = cmdargs[1]; + } else { + // CPU 指定あり + cpustr = cmdargs[1].substr(0, pos); + inststr = cmdargs[1].substr(pos + 1); + } + + // あればスキップカウント + if (cmdargs.size() > 2) { + skipstr = cmdargs[2]; + } + + // 命令ブレークポイントを登録。 + int bi = AddBreakInst(cpustr, inststr, skipstr, &errstr); + if (errstr.empty() == false) { + fprintf(cons, "%s\n", errstr.c_str()); + return CmdAct::Stay; + } + fprintf(cons, "Breakpoint #%d added.\n", bi); + return CmdAct::Stay; +} + +// --bi オプションの引数 str から命令ブレークポイントを登録する。 +// 成功すれば true を返す。失敗すればエラーメッセージを表示して false を返す。 +bool +Debugger::ParseOptBreakInst(const std::string& str) +{ + std::string cpustr; + std::string inststr; + std::string skipstr; + std::string errstr; + + // "," で分離。 + auto args = string_split(str, ','); + if (args.size() == 1) { + // 1個なら命令のみ。 + inststr = args[0]; + } else if (args.size() == 2) { + // 2個なら cpu か skip のどちらかが省略されている…。 + // 先頭が CPU 文字列かどうかで判定する。 + if (IsCPUStr(args[0])) { + cpustr = args[0]; + inststr = args[1]; + } else { + inststr = args[0]; + skipstr = args[1]; + } + } else if (args.size() == 3) { + cpustr = args[0]; inststr = args[1]; - instlen = inststr.size(); + skipstr = args[2]; + } else { + warnx("--bi \"%s\": Syntax error.", str.c_str()); + return false; + } + + // 命令ブレークポイントを登録。 + AddBreakInst(cpustr, inststr, skipstr, &errstr); + if (errstr.empty() == false) { + warnx("--bi \"%s\": %s", str.c_str(), errstr.c_str()); + return false; + } + return true; +} + +// 命令ブレークポイントを登録する。cmd_bi と --bi の後半の共通部分。 +// 成功すれば *errstr は empty でブレークポイント番号(>=0)を返す。 +// 失敗すれば *errstr をセットして -1 を返す。 +int +Debugger::AddBreakInst(const std::string& cpustr, const std::string& argstr, + const std::string& skipstr, std::string *errstr) +{ + DebuggerMD *md; + breakpoint_t bp; + std::string inststr; + std::string maskstr; + int instlen; + int masklen; + + // CPU 部チェック + md = ParseCPU(cpustr); + if (md == NULL) { + *errstr = std::string("Invalid CPU Name."); + return CmdAct::Stay; + } + bp.md = md; + + // 命令部。 + // ニーモニックが指定できたら便利。 + inststr = md->ParseInst(argstr); + if (inststr.empty() == false) { masklen = -1; } else { - // マスク指定あり - inststr = args[1].substr(0, pos); - instlen = inststr.size(); - maskstr = args[1].substr(pos + 1); - masklen = maskstr.size(); + auto pos = argstr.find(':'); + if (pos == std::string::npos) { + // マスク指定なし。 + inststr = argstr; + masklen = -1; + } else { + // マスク指定あり。 + inststr = argstr.substr(0, pos); + maskstr = argstr.substr(pos + 1); + masklen = maskstr.size(); + } } + instlen = inststr.size(); - // 命令部チェック if (ParseVerbHex(inststr.c_str(), &bp.inst) == false) { - cons->Print("%s: invalid instruction value\n", args[1].c_str()); - return; + *errstr = string_format("Invalid instruction: %s", argstr.c_str()); + return -1; } if (instlen % (md->inst_bytes * 2) != 0) { - cons->Print("%s: invalid instruction length\n", args[1].c_str()); - return; + *errstr = string_format("Invalid instruction length: %s", + argstr.c_str()); + return -1; } // マスク部チェック 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; + *errstr = string_format("Invalid mask value: %s", argstr.c_str()); + return -1; } if (masklen != instlen) { - cons->Print("%s: inst:mask must be the same length\n", - args[1].c_str()); - return; + *errstr = std::string(": must be the same length"); + return -1; } } // 8バイト未満なら左詰め。 @@ -1229,68 +1764,195 @@ Debugger::cmd_bi() bp.mask <<= 32 - instlen * 4; } - // あればスキップカウント - bp.skip = 0; - if (args.size() > 2) { - bp.skip = atoi(args[2].c_str()); + // を取り出す。 + if (skipstr.empty() == false) { + bp.skip = atoi(skipstr.c_str()); } // 空いてるところにセット bp.type = BreakpointType::Instruction; int bi = AddBreakpoint(bp); - if (bi == -1) { - cons->Print("no free breakpoints\n"); - } else { - cons->Print("breakpoint #%d added\n", bi); + if (bi < 0) { + *errstr = std::string("Breakpoints full."); + return bi; } - // 今登録されている命令ブレークの必要命令長を再計算 + // 今登録されている命令ブレークの必要命令長を再計算。 RecalcInstMask(); + + return bi; } -// 例外ブレークポイントの設定 -// bv [-] [] -void +// 例外ブレークポイントの設定。 +// bv [,][-] [] +Debugger::CmdAct Debugger::cmd_bv() { + std::string cpustr; + std::string vecstr; + std::string skipstr; + std::string errstr; + + if (cmdargs.size() < 2) { + fprintf(cons, "Usage: bv [,][-] []\n"); + return CmdAct::Stay; + } + + // CPU をまず分離 + auto pos = cmdargs[1].find(','); + if (pos == std::string::npos) { + // CPU 指定なし + vecstr = cmdargs[1]; + } else { + // CPU 指定あり + cpustr = cmdargs[1].substr(0, pos); + vecstr = cmdargs[1].substr(pos + 1); + } + + if (cmdargs.size() > 2) { + skipstr = cmdargs[2]; + } + + // ベクタブレークポイントを登録。 + int bi = AddBreakVector(cpustr, vecstr, skipstr, &errstr); + if (errstr.empty() == false) { + fprintf(cons, "%s\n", errstr.c_str()); + return CmdAct::Stay; + } + fprintf(cons, "Breakpoint #%d added.\n", bi); + + // この CPU 側に、すでに来ている例外をクリア。 + // ブレークポイント設定の有無に関わらず例外が起きたら CPU 側から常に + // 通知されている。これをクリアするのは CheckAllBreakpoints() で、これは + // 命令間(命令前)に呼ばれるやつ、なのでこうなる。 + // 1. 例外が起きると md->bv_vector がセットされる + // 2. 例外ブレークポイントを設定していないとこれがクリアされない + // 3. bv コマンドで例外ブレークを新たに設定すると、次の命令境界で + // 1.のベクタが反応してしまう。 + // 命令ごととかにクリアしてもいいかも知れないが、ここでブレークポイントを + // 設定したのだから、それ以前の事象には反応すべきでない、という意味では + // ここでもいいか? + + // bp.md は AddBreakpoint() で bpoint 登録時にセットされるので + // インデックスから bp をもう一度読み込む。 + auto bp = bpoint[bi]; + bp.md->bv_vector = -1; + + return CmdAct::Stay; +} + +// --bv オプションの引数 str からベクタブレークポイントを登録する。 +// 成功すれば true を返す。失敗すればエラーメッセージを表示して false を返す。 +bool +Debugger::ParseOptBreakVector(const std::string& str) +{ + std::string cpustr; + std::string vecstr; + std::string skipstr; + std::string errstr; + + // "," で分離。 + auto args = string_split(str, ','); + if (args.size() == 1) { + // 1個ならベクタのみ。 + vecstr = args[0]; + } else if (args.size() == 2) { + // 2個なら cpu か skip のどちらかが省略されている…。 + // 先頭が CPU 文字列かどうかで判定する。 + if (IsCPUStr(args[0])) { + cpustr = args[0]; + vecstr = args[1]; + } else { + vecstr = args[0]; + skipstr = args[1]; + } + } else if (args.size() == 3) { + cpustr = args[0]; + vecstr = args[1]; + skipstr = args[2]; + } else { + warnx("--bv \"%s\": Syntax error.", str.c_str()); + return false; + } + + // ベクタブレークポイントを登録。 + AddBreakVector(cpustr, vecstr, skipstr, &errstr); + if (errstr.empty() == false) { + warnx("--bv \"%s\": %s", str.c_str(), errstr.c_str()); + return false; + } + return true; +} + +// ベクタブレークポイントを登録する。cmd_bv と --bv の後半の共通部分。 +// 成功すれば *errstr は empty でブレークポイント番号(>=0)を返す。 +// 失敗すれば *errstr をセットして -1 を返す。 +int +Debugger::AddBreakVector(const std::string& cpustr, const std::string& vecstr, + const std::string& skipstr, std::string *errstr) +{ + uint32 maxvector; breakpoint_t bp; + DebuggerMD *md; - if (args.size() < 2) { - cons->Print("usage: be [-] []\n"); - return; + // CPU 部。ベクタ解釈にも使う。 + md = ParseCPU(cpustr); + if (md == NULL) { + *errstr = std::string("Invalid CPU Name."); + return CmdAct::Stay; + } + bp.md = md; + + // ベクタ数 (最大値) を取得。 + if (md->arch == CPUArch::HD64180) { + // XXX あってる? + maxvector = 256; + } else { + maxvector = pVectorTable->Size(); } - auto pos = args[1].find('-'); + // ベクタを取り出す。 + auto pos = vecstr.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; + if (ParseVector(md, vecstr.c_str(), (uint32 *)&bp.vec1) == false) { + *errstr = string_format("Invalid vector number: %s", + vecstr.c_str()); + return -1; } bp.vec2 = bp.vec1; } else { // ベクタ番号(範囲指定 [vec1, vec2]) - std::string str1 = args[1].substr(0, pos); - std::string str2 = args[1].substr(pos + 1); + std::string vec1 = vecstr.substr(0, pos); + std::string vec2 = vecstr.substr(pos + 1); + + // "-vec2" なら vec1 = 0、 + // "vec1-" なら vec2 は最大値 (機種による)、とする。 - if (ParseVerbHex(str1.c_str(), (uint32 *)&bp.vec1) == false) { - cons->Print("%s: invalid first vector number\n", args[1].c_str()); - return; - } - if (ParseVerbHex(str2.c_str(), (uint32 *)&bp.vec2) == false) { - cons->Print("%s: invalid last vector number\n", args[1].c_str()); - return; + if (vec1.empty()) { + bp.vec1 = 0; + } else if (ParseVector(md, vec1.c_str(), (uint32 *)&bp.vec1) == false) { + *errstr = string_format("Invalid first vector number: %s", + vecstr.c_str()); + return -1; + } + if (vec2.empty()) { + bp.vec2 = maxvector - 1; + } else if (ParseVector(md, vec2.c_str(), (uint32 *)&bp.vec2) == false) { + *errstr = string_format("Invalid last vector number: %s", + vecstr.c_str()); + return -1; } } // 範囲チェック - 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 >= maxvector) { + *errstr = string_format("Out of range: $%x", bp.vec1); + return -1; + } + if (bp.vec2 < 0 || bp.vec2 >= maxvector) { + *errstr = string_format("Out of range: $%x", bp.vec2); + return -1; } // 大小が逆なら入れ替える? @@ -1301,40 +1963,90 @@ Debugger::cmd_bv() bp.vec2 = tmp; } - // あればスキップカウント - bp.skip = 0; - if (args.size() > 2) { - bp.skip = atoi(args[2].c_str()); + // を取り出す。 + if (skipstr.empty() == false) { + bp.skip = atoi(skipstr.c_str()); } // 空いてるところにセット bp.type = BreakpointType::Exception; int bi = AddBreakpoint(bp); - if (bi == -1) { - cons->Print("no free breakpoints\n"); - } else { - cons->Print("breakpoint #%d added\n", bi); + if (bi < 0) { + *errstr = std::string("Breakpoints full."); } + return bi; +} - // すでに来ている例外をクリア。 - // ブレークポイント設定の有無に関わらず例外が起きたら CPU 側から常に - // 通知されている。これをクリアするのは CheckAllBreakpoints() で、これは - // 命令間(命令前)に呼ばれるやつ、なのでこうなる。 - // 1. 例外が起きると bv_vector がセットされる - // 2. 例外ブレークポイントを設定していないとこれがクリアされない - // 3. bv コマンドで例外ブレークを新たに設定すると、次の命令境界で - // 1.のベクタが反応してしまう。 - // 命令ごととかにクリアしてもいいかも知れないが、ここでブレークポイントを - // 設定したのだから、それ以前の事象には反応すべきでない、という意味では - // ここでもいいか? - bv_vector = -1; +// ベクタ名かベクタ番号をパースする。 +// ベクタ番号は10進数で表記されることが多いので、10進/16進どちらも対応する。 +// - "$" か "0x" から始まれば16進数。 +// - それ以外で [0-9] から始まれば10進数。"0" プレフィックスを8進数にはしない。 +// - それ以外ならベクタ名とみなす。 +bool +Debugger::ParseVector(DebuggerMD *md, const char *arg, uint32 *valp) +{ + assert(arg); + + if (arg[0] == '$') { + // 1文字目以降が16進数。 + return ParseVerbHex(arg + 1, valp); + } + if (strncmp(arg, "0x", 2) == 0) { + // 2文字目以降が16進数。 + return ParseVerbHex(arg + 2, valp); + } + if ('0' <= arg[0] && arg[0] <= '9') { + // 先頭から10進数。 + uint32 val; + char *end; + errno = 0; + val = strtoul(arg, &end, 10); + if (*end != '\0') { + return false; + } + if (errno == ERANGE) { + return false; + } + *valp = val; + return true; + } + + // 数値に出来なければ、機種ごとにベクタ名と比較する。 + if (md->arch == CPUArch::HD64180) { + static std::vector table = { + "trap", + "nmi", + "int0", + "int1", + "int2", + "inpcap", + "outcmp", + "timeov", + "timer0", + "timer1", + "dma0", + "dma1", + "csio", + "asci0", + "asci1", + }; + for (int i = 0, end = table.size(); i < end; i++) { + if (strcasecmp(arg, table[i]) == 0) { + *valp = i; + return true; + } + } + } + + return false; } // ブレークポイント一覧表示 -void +Debugger::CmdAct Debugger::cmd_b_list() { ShowMonitor(bpoint_monitor); + return CmdAct::Stay; } // ブレークポイント一覧 (モニタ) @@ -1343,15 +2055,15 @@ Debugger::MonitorUpdateBpoint(Monitor *, { // 0 1 2 3 4 5 6 // 0123456789012345678901234567890123456789012345678901234567890 - // No Type Parameter Matched Skip - // #0 addr $01234567 123456789 123456789/123456789 - // #1 inst 00000000/00000000 - // #2 excp $00-$00 + // No CPU Type Parameter Matched Skip + // #0 xp addr $01234567 123456789 123456789/123456789 + // #1 mpu inst 00000000:00000000 + // #2 mpu excp $00-$00 monitor.Clear(); - monitor.Print(0, 0, "No Type Parameter"); - monitor.Print(26, 0, "Matched"); - monitor.Print(37, 0, "Skip"); + monitor.Print(0, 0, "No CPU Type Parameter"); + monitor.Print(30, 0, "Matched"); + monitor.Print(41, 0, "Skip"); for (int i = 0; i < bpoint.size(); i++) { const auto& bp = bpoint[i]; @@ -1359,80 +2071,119 @@ Debugger::MonitorUpdateBpoint(Monitor *, monitor.Print(0, y, "#%d", i); - // 種別ごとの表示 - switch (bp.type) { - case BreakpointType::Unused: + if (bp.type == BreakpointType::Unused) { continue; + } + monitor.Print(3, y, "%s", bp.md ? bp.md->GetName().c_str() : "?"); + // 種別ごとの表示 + switch (bp.type) { case BreakpointType::Address: - monitor.Print(3, y, "addr $%08x", bp.addr); + monitor.Print(7, y, "addr $%08x", bp.addr); break; + case BreakpointType::Memory: - monitor.Print(3, y, "mem $%08x", bp.addr); + monitor.Print(7, y, "mem $%08x", bp.addr); break; case BreakpointType::Exception: - monitor.Print(3, y, "excp $%02x", bp.vec1); + { + uint vd; // ベクタの桁数 + if (bp.md->arch == CPUArch::M88xx0) { + vd = 3; + } else { + vd = 2; + } + monitor.Print(7, y, "excp $%0*x", vd, bp.vec1); if (bp.vec2 != bp.vec1) { - monitor.Print(11, y, "-$%02x", bp.vec2); + monitor.Print(13 + vd, y, "-$%0*x", vd, bp.vec2); } break; + } case BreakpointType::Instruction: - monitor.Print(3, y, "inst"); - if (md->inst_bytes == 4) { - if (bp.mask == 0xffffffff) { - monitor.Print(8, y, "%08x", bp.inst); - } else { - monitor.Print(8, y, "%08x:%08x", bp.inst, bp.mask); - } - } else { - if (bp.mask == 0xffffffff) { - monitor.Print(8, y, "%08x", bp.inst); - } else if (((bp.inst | bp.mask) & 0x0000ffff) != 0) { - monitor.Print(8, y, "%08x:%08x", bp.inst, bp.mask); - } else if (bp.mask == 0xffff0000) { - monitor.Print(8, y, "%04x", bp.inst >> 16); - } else { - monitor.Print(8, y, "%04x:%04x", - bp.inst >> 16, bp.mask >> 16); + monitor.Puts(7, y, "inst"); + for (int n = bp.md->inst_bytes; n <= 4; n += bp.md->inst_bytes) { + // 左詰め n バイト分のマスク。 + int bits = (4 - n) * 8; + uint imask = 0xffffffffU << bits; + // n が 4 だと ~imask の式は使えないが無条件で入る。 + if (n == 4 || ((bp.inst | bp.mask) & ~imask) == 0) { + if (bp.mask == imask) { + monitor.Print(12, y, "%0*x", n * 2, (bp.inst >> bits)); + } else { + monitor.Print(12, y, "%0*x:%0*x", + n * 2, (bp.inst >> bits), + n * 2, (bp.mask >> bits)); + } + break; } } break; default: - monitor.Print(3, y, "type=%d", (int)bp.type); + monitor.Print(7, y, "type=%d", (int)bp.type); continue; } // マッチ回数 - monitor.Print(26, y, "%d", bp.matched); + monitor.Print(30, y, "%d", bp.matched); // スキップ if (bp.skip < 0) { - monitor.Print(37, y, "forever"); + monitor.Print(41, y, "forever"); } else if (bp.skip > 0) { - monitor.Print(37, y, "%d / %d", (bp.skip - bp.skipremain), bp.skip); + monitor.Print(41, y, "%d / %d", (bp.skip - bp.skipremain), bp.skip); } } } // ブレークポイント全削除 -void +Debugger::CmdAct Debugger::cmd_bx() { for (auto& bp : bpoint) { bp.type = BreakpointType::Unused; } - cons->Print(" All breakpoints disabled\n"); + fprintf(cons, "All breakpoints are removed.\n"); // 今登録されている命令ブレークの必要命令長を再計算 RecalcInstMask(); + + return CmdAct::Stay; +} + +// CPU 文字列なら true を返す。 +bool +Debugger::IsCPUStr(const std::string& cpustr) const +{ + if (cpustr.empty()) { + return false; + } + if (ParseCPU(cpustr) == NULL) { + return false; + } + return true; +} + +// CPU 文字列から対応する MD を返す。 +// 一致しなければ NULL を返す。 +DebuggerMD * +Debugger::ParseCPU(const std::string& cpustr) const +{ + if (cpustr.empty()) { + return curmd; + } else if (cpustr == "xp") { + return md_xp.get(); + } else if (cpustr == "mpu") { + return md_mpu.get(); + } + return NULL; } // ブレークポイントを設定。 // new_bp のうち matched, skipremain はこちらで初期化する。 -// それ以外を埋めてから呼ぶこと。 +// それ以外の type、md と type ごとに必要なパラメータを埋めてから呼ぶこと。 // 設定できればその番号、できなければ -1 を返す。 int Debugger::AddBreakpoint(const breakpoint_t& new_bp) @@ -1441,6 +2192,10 @@ Debugger::AddBreakpoint(const breakpoint auto& bp = bpoint[i]; if (bp.type == BreakpointType::Unused) { bp = new_bp; + // 本当は assert レベルだが、とは言えここで死なれるとつらいので。 + if (bp.md == NULL) { + bp.md = curmd; + } bp.matched = 0; if (bp.skip > 0) { bp.skipremain = bp.skip; @@ -1478,7 +2233,7 @@ Debugger::RecalcInstMask() // 上位側から数えたマスクに必要なビット数 int mlen = 32 - ntz; // 命令語単位に切り上げる - mlen = roundup(mlen, md->inst_bytes * 8); + mlen = roundup(mlen, curmd->inst_bytes * 8); // バイト数に変換 mlen /= 8; @@ -1494,27 +2249,29 @@ Debugger::RecalcInstMask() // コンソールではデフォルトで下を新しいの順とする。 を負数にすると // (行数は絶対値して) 並び順を逆にしてモニタウィンドウと同じ上を新しいの順に // する。 -void +Debugger::CmdAct Debugger::cmd_brhist() { - cmd_hist_common(md->GetBrHist()); + auto brhist = gMainApp.GetObject(OBJ_MPU_BRHIST); + return cmd_hist_common(*brhist); } -void +Debugger::CmdAct Debugger::cmd_exhist() { - cmd_hist_common(md->GetExHist()); + auto exhist = gMainApp.GetObject(OBJ_MPU_EXHIST); + return cmd_hist_common(*exhist); } // ブランチ履歴、例外履歴表示の共通部分。 -void +Debugger::CmdAct Debugger::cmd_hist_common(BranchHistory& hist) { // 表示最大行数(と向き) // 向きは bottom_to_top = true が新しいほうを下とする方向。 int maxlines = 20; bool bottom_to_top = true; - if (args.size() > 1) { - maxlines = atoi(args[1].c_str()); + if (cmdargs.size() > 1) { + maxlines = atoi(cmdargs[1].c_str()); if (maxlines < 0) { bottom_to_top = false; maxlines = -maxlines; @@ -1534,10 +2291,9 @@ Debugger::cmd_hist_common(BranchHistory& int lines = std::min(used, maxlines) + 1; // MonitorUpdate() は TextScreen 高さに合わせて出力してくれる。 - auto& histmon = hist.monitor; - auto size = histmon.GetSize(); + auto *histmon = hist.monitor; TextScreen screen; - screen.Init(size.width, lines); + screen.Init(histmon->GetCol(), lines); // コンソールでは下が新しいの順のほうがいい if (bottom_to_top) { @@ -1547,37 +2303,63 @@ Debugger::cmd_hist_common(BranchHistory& // 表示 MONITOR_UPDATE(histmon, screen); ShowTextScreen(screen); + + return CmdAct::Stay; } // 実行再開(continue): c [] // 指定があれば まで実行。 -void +Debugger::CmdAct Debugger::cmd_c() { - if (args.size() > 1) { + if (cmdargs.size() > 1) { // 引数があれば - if (!ParseAddr(args[1].c_str(), &bc_addr)) { - return; + uint32 addr; + if (!ParseAddr(cmdargs[1].c_str(), &addr)) { + return CmdAct::Stay; } - // 偶数番地に丸める - bc_addr &= 0xfffffffe; + step_type = StepType::Addr; + step_md = curmd; + // ネイティブ命令長に丸める + step_addr = addr & ~(curmd->inst_bytes - 1); + } + return CmdAct::Leave; +} - bc_enable = true; +// 指定仮想時間実行: ct [