--- nono/debugger/debugger.cpp 2026/04/29 17:04:40 1.1.1.5 +++ nono/debugger/debugger.cpp 2026/04/29 17:05:58 1.1.1.22 @@ -4,131 +4,291 @@ // 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 "ascii_ctype.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; -BreakpointMonitor *gBreakpointMonitor; -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); -// 初期化。 -// この時点で VM が初期化されていること。 -void -debugger_init() +// コンストラクタ +Debugger::Debugger() + : inherited(OBJ_DEBUGGER) { - gDebugger = new Debugger(); - gDebugger->Init(); + // ベクタテーブル + pVectorTable.reset(new VectorTable(gMainApp.GetVMType())); + + // ブレークポイントモニタ + bpoint_monitor = gMonitorManager->Regist(ID_MONITOR_BREAKPOINT, this); + bpoint_monitor->SetCallback(&Debugger::MonitorScreenBpoint); + bpoint_monitor->SetSize(59, 9); - gBreakpointMonitor = new BreakpointMonitor(); - gCVPrompt = new CVPrompt(); + // メモリダンプモニタ + 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)); + } - // デバッガスレッド起動 - pthread_t th; - pthread_create(&th, NULL, debugger_run, NULL); + 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)); + } + } } -// デバッガスレッドのエントリポイント -static void * -debugger_run(void *dummy) +// デストラクタ +Debugger::~Debugger() { - PTHREAD_SETNAME("Debugger"); - pthread_detach(pthread_self()); + // fclose(fflush) にあたり hostcom へのアクセスが発生するので、 + // hostcom より先に片付けておかなければならない。 + Close(); - gDebugger->ThreadRun(); - return NULL; + if ((bool)hostcom) { + hostcom->ResetRxCallback(); + hostcom->SetAcceptCallback(NULL); + } + + TerminateThread(); } -// コンストラクタ -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(this, -1, "Debugger")); + } catch (...) { } + if ((bool)hostcom == false) { + warnx("Failed to initialize HostCOMDevice at %s", __method__); + return false; } + + hostcom->SetRxCallback(ToDeviceCallback(&Debugger::RxCallback), 0); + hostcom->SetAcceptCallback(ToDeviceCallback(&Debugger::AcceptCallback)); + + return true; } -// コマンドライン引数によって動作を決めるところ。 -// 名前がこれでいいのかはあるけど。 +// ログレベル設定 void +Debugger::SetLogLevel(int loglevel_) +{ + inherited::SetLogLevel(loglevel_); + + // ホストドライバを従属させる + if ((bool)hostcom) { + hostcom->SetLogLevel(loglevel_); + } +} + +// 初期化 +bool Debugger::Init() { + 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; + } + } + + // とりあえずメインプロセッサに固定 + 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 *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()); + } + } + // -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); } - // どこでやるべか - 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(); + } + } + + QuitPrompt(); + Close(); +} + +// コンソールをクローズする +void +Debugger::Close() +{ + if (cons) { + fclose(cons); + cons = NULL; + } +} + +#if 0 bool first = true; for (;;) { // 接続後の1回目だけ実行するもの。 @@ -138,298 +298,479 @@ Debugger::ThreadRun() // greeting はプロンプトが取れる前にもう表示したい。 // 何らかの事故でプロンプトが取れなくても、ここまでは接続 // できてることが分かるように。 - cons->Print("This is debugger console\n"); + fprintf(cons, "This is debugger console\n"); // 接続ごとに初期化する値 - // XXX もうちょっときれいにしたい - d_last_addr = 0xffffffff; - m_last_addr = 0xffffffff; n_enable = false; s_enable = false; t_enable = true; } + } + } - // プロンプトが取れるのを待つ - if (AcquirePrompt() == false) { - break; - } +#endif - // プロンプトに来た時に表示するいつものやつ - pc = md->GetPC(); - d_last_addr = pc; - 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); +} - // メインループから戻ったということは Leave か Quit なので - // どちらにしても、ここでプロンプトを手放す。 - gCVPrompt->NotifyRelease(); +// funopen の read コールバックの本体 +int +Debugger::ReadFunc(char *buf, int bufsize) +{ + char *d = buf; + char *end = buf + bufsize; - // quit ならここでループを1段抜ける - if (action == CommandAction::Quit) { - break; - } + for (; d < end; ) { + if ((bool)hostcom == false) { + errno = EIO; + return -1; } - // デバッガを抜けるのでトレースオフ - md->ReqClr(CPU_REQ_TRACE); - - cons->Close(); - if (gMainApp.debug_on_console) { + int c = hostcom->Rx(); + if (c < 0) { break; } + *d++ = c; } - - delete cons; + return (d - buf); } -// コンソールに接続されるのを待つ。 -// うーん、なんだこれ。 -// -// +--- debug_on_console (-D オプション) -// | +- debug_on_start (-d オプション) -// | | 動作 -// 0 0 TCP、Accept で待機。 -// 0 1 TCP、Accpet で待機。 -// 1 0 stdio、起動時にプロンプトで止まらない -> キー入力待ち。 -// 1 1 stdio、起動時にプロンプトで止まる。 -bool -Debugger::Accept() +// funopen の write コールバックの本体 +int +Debugger::WriteFunc(const char *buf, int len) { - if (gMainApp.debug_on_console == false) { - // TCP なら accept が成功するのを待つ - cons->Accept(); - } else { - if (gMainApp.debug_on_start == false) { - // stdio、起動時にプロンプトで止まらない。 - // この場合キー入力かブレークポイントなどで止まった時点で - // プロンプトを出したいのでちょっと面倒なことに。 - for (;;) { - bool is_prompt = gCVPrompt->WaitAcquire(200); - if (is_prompt) { - break; - } + const char *s = buf; + const char *end = buf + len; - int r = cons->Poll(200); - if (r < 0) { - cmd_q(); - return false; - } - if (r > 0) { - break; - } + for (; s < end; ) { + if ((bool)hostcom == false) { + errno = EIO; + return -1; + } + + // ホストスレッドのキューが満杯なら空くまで待つ。うーん…。 + // XXX 無期限で大丈夫だろうか + int c = *s++; + if (c == '\n') { + // ここで LF を CRLF にする? + // (TELNET に出力するのに必要) + while (hostcom->Tx('\r') == false) { + usleep(10); } - } else { - // stdio、起動時にプロンプトで止まる。 - // この場合は無条件に成功でよい。 + } + while (hostcom->Tx(c) == false) { + usleep(10); } } + return (s - buf); +} - return true; +// ホストからの1文字受信通知 (HostCOM スレッドから呼ばれる) +void +Debugger::RxCallback(uint32 arg) +{ + // デバッガスレッドに通知 + std::lock_guard lock(mtx); + request |= REQUEST_RXCHAR; + cv_request.notify_one(); } -// プロンプトが取れるのを待つ -// 失敗すれば false を返す -bool -Debugger::AcquirePrompt() +// ホストからの着信通知 (HostCOM スレッドから呼ばれる) +void +Debugger::AcceptCallback(uint32 dummy) { - md->ReqSet(CPU_REQ_PROMPT); + // デバッガスレッドに通知 + std::lock_guard lock(mtx); + request |= REQUEST_ACCEPT; + cv_request.notify_one(); +} - for (;;) { - bool is_prompt = gCVPrompt->WaitAcquire(200); - if (is_prompt) - break; +// ホストからの1文字入力 +void +Debugger::Input(int c) +{ + // '^@' は捨てる + // (意図的にも入力できるが nc が TELNET オプション扱えなくて送ってくる) + if (c == '\0') { + return; + } - // コンソールを定期観測して、入力があればブレーク要求 - if (cons->Poll()) { - // この入力は drop してみる - if (cons->Gets(cmdbuf) == false) { - // EOF or Error - cmd_q(); - return false; + // HostCOM からは改行で CR が来るようなので LF にしておく + if (c == '\r') { + c = '\n'; + } + + if (is_prompt == false) { + // プロンプトでない時は、Enter か ^C でプロンプトを出す + if (c == '\n' || c == '\x03') { + // MPU に一時停止を要求 + is_pause = true; + 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: + // アプリケーション自体を終了 + QuitPrompt(); + gMainApp.GetUIMessage()->Post(UIMessage::APPEXIT); } - md->ReqSet(CPU_REQ_PROMPT); + } else if (c < ' ' || c == 0x07f) { + // 他のコントロールコードはとりあえず無視 + } else { + // 通常文字なら、エコーバックして追加 + fputc(c, cons); + fflush(cons); + + cmdbuf.push_back(c); } } - return true; } -// コマンド処理のメインループ。 -// コマンドが Stay ならこの中で処理を継続。 -// コマンドが Leave か Quit ならその値を持って戻る (呼び出し側で処理する)。 -Debugger::CommandAction -Debugger::MainLoop() +// コマンドモード(プロンプト)に入る +void +Debugger::EnterPrompt() { - for (;;) { - cons->Prompt(); - if (cons->Gets(cmdbuf) == false) { - // EOF or Error - return CommandAction::Quit; - } + is_prompt = true; - string_rtrim(cmdbuf); + // ブレークポイント到達メッセージがあればここで表示 + if (bpointmsg.empty() == false) { + fprintf(cons, "%s", bpointmsg.c_str()); + bpointmsg.clear(); + } - if (!cmdbuf.empty()) { - // コマンドが入力されれば次回のために保存 - last_cmdbuf = cmdbuf; - } else { - // 空行が入力されれば直前のコマンドをもう一度 - // 前行がなければ何もせずもう一度プロンプトを表示するかね - if (last_cmdbuf.empty()) - continue; - cmdbuf = last_cmdbuf; + // d/m をいきなり引数なしで実行した時のため、現在地にしておく。 + ResetStickyAddr(); + + // プロンプトのたびに表示するやつ + cmd_minus(); + + PrintPrompt(); +} + +// コマンドモード(プロンプト)から出る。終了用。 +void +Debugger::QuitPrompt() +{ + // 終了までの間 VM の実行が進んでしまわないよう停止指示を出しておく。 + scheduler->Terminate(); + + LeavePrompt(false); +} + +// コマンドモード(プロンプト)から出る。 +// trace はプロンプトから抜ける際のトレース状態の指示。 +// スレッド終了時は false を指定すること。 +void +Debugger::LeavePrompt(bool trace) +{ + // MPU のトレース状態を変更 + scheduler->SendMessage(MessageID::MPU_TRACE_ALL, trace); + + is_prompt = false; + + // 最後に VM スレッドで待機している Exec() を起こす + { + std::lock_guard lock(mtx); + prompt_released = true; + cv_prompt.notify_one(); + } +} + +// プロンプトを表示 +void +Debugger::PrintPrompt() +{ + fprintf(cons, "%s> ", curmd->GetName().c_str()); + fflush(cons); +} + +// コマンド実行 +Debugger::CmdAct +Debugger::Command() +{ + string_rtrim(cmdbuf); + + if (cmdbuf.empty()) { + // 空行なら、直前のコマンドをもう一度。 + // 前行がなければ何もせずもう一度プロンプトを表示するかね。 + if (last_cmdbuf.empty()) { + return CmdAct::Stay; } + cmdbuf = last_cmdbuf; + } else { + // 入力された行を次回のために保存 + last_cmdbuf = cmdbuf; + } - // 行を args に分解 - ParseCmdbuf(); + // 行を cmdargs に分解 + ParseCmdbuf(); + cmdbuf.clear(); - // 前回の値が有効なのはコマンドが連続した時だけ + // 前回の値が有効なのはコマンドが連続した時だけ - // コマンドをテーブルから探す - auto it = std::find_if(cmdtable.begin(), cmdtable.end(), - [=](auto x) { return strcmp(args[0].c_str(), x.name) == 0; }); - if (it != cmdtable.end()) { - auto cmd = *it; + // コマンドをテーブルから探す + auto it = std::find_if(cmdtable.begin(), cmdtable.end(), + [=](auto x) { return strcmp(cmdargs[0].c_str(), x.name) == 0; }); + if (it != cmdtable.end()) { + auto cmd = *it; - // 見付かれば実行 - (this->*(cmd.func))(); + // 見付かれば実行 + return (this->*(cmd.func))(); + } - // Stay なら引き続きコマンド処理 - if (cmd.action == CommandAction::Stay) { - continue; - } - // それ以外ならメインループ終了 - return cmd.action; + // (コマンドテーブルになくて) "r" から始まっていればレジスタ表示系 + if (cmdargs[0][0] == 'r') { + // ShowRegister() は処理したら true を返す。 + // "r" 系コマンドはすべて Stay。 + if (curmd->ShowRegister(cons, cmdargs)) { + return CmdAct::Stay; } + } - // (コマンドテーブルになくて) "r" から始まっていればレジスタ表示系 - if (args[0][0] == 'r') { - // ShowRegister() は処理したら true を返す。 - // "r" 系コマンドはすべて Stay。 - if (md->ShowRegister(cons, args)) { - continue; - } + // 知らないコマンドも Stay 相当。 + fprintf(cons, "%s: Unknown command.\n", cmdargs[0].c_str()); + // この行は次回空エンターで再発行しないでいい。 + last_cmdbuf.clear(); + return CmdAct::Stay; +} + +// ブレークポイントとかを調べる。1命令ごとに呼び出される。 +// (VM スレッドから呼ばれる) +void +Debugger::Exec(DebuggerMD *md) +{ + if (Check(md)) { + // この CPU でブレークしたので、ターゲット CPU をこっちに変更。 + ChangeMD(md); + + // 実時間を停止 + syncer->StopRealTime(); + + // 待機 + { + std::unique_lock lock(mtx); + + // notify 前にクリア + prompt_released = false; + + // MPU が一時停止したことをデバッガスレッドへ通知する + request |= REQUEST_PROMPT; + cv_request.notify_one(); + + // デバッガスレッドがプロンプト解放するまで待機 + cv_prompt.wait(lock, [&] { return prompt_released; }); } - // 知らないコマンドも Stay 相当。 - cons->Print("%s: unknown command\n", args[0].c_str()); - continue; + // 実時間を再開 + 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 を返す。 -// 成立すれば内容をここで表示する。 +// このルーチンから cons への出力は使わないこと。(プロンプトにいない時でも +// 呼ばれるので) bool -Debugger::CheckAllBreakpoints() +Debugger::CheckAllBreakpoints(DebuggerMD *md) { bool is_break = false; + int vector; + uint32 inst = 0; + int instbytes = 0; - // 命令ごとにクリアする - bi_inst = 0; - bi_inst_bytes = 0; + // 例外はここでローカルにコピーしてから、クリアする。 + vector = md->GetAndResetLastVector(); // 1つの条件でマッチしても(そこでブレークすること自体は確定するのだが) // 残りの他の条件も成立すればカウントを進める必要があるため、 @@ -437,32 +778,47 @@ Debugger::CheckAllBreakpoints() 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 (instbytes < bp.instbytes) { + uint64 data = ReadBreakpointInst(bp); + if ((int64)data < 0) { + continue; + } + inst = data; + instbytes = bp.instbytes; } - break; + if ((inst & bp.mask) == bp.inst) { + break; + } + continue; default: continue; @@ -485,191 +841,191 @@ 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", - bi_inst_bytes * 2, - bi_inst >> ((4 - bi_inst_bytes) * 8)); + desc += string_format("inst=%0*x", + instbytes * 2, inst >> ((4 - instbytes) * 8)); break; default: assert(false); break; } - cons->Print("breakpoint #%d (%s) reached\n", i, desc.c_str()); - } - // 例外通知は通過ごとに常に下ろしておく - bv_vector = -1; + // 到達メッセージを作成。 + // この時点ではまだコンソールを取得していない可能性があるので + // (-D なしで起動した場合とか)、表示せず用意するだけ。 + // コンソールが取得できたところで表示する。 + bpointmsg += string_format("Breakpoint #%d (%s) reached.\n", + i, desc.c_str()); + } // ブレークポイントが1つでも成立したかどうかを返す return is_break; } -// 命令ブレークポイントが成立するか調べる。 -bool -Debugger::CheckBreakpointInst(breakpoint_t& bp) +// 命令ブレークポイントの位置の命令を読み込む。 +uint64 +Debugger::ReadBreakpointInst(breakpoint_t& bp) { - saddr_t laddr; - - // uint32 bi_inst が現在の PC 位置の命令データ (左詰め) - // bi_inst_bytes が読み込んだバイト数(bi_inst の左からの有効バイト数)。 - // bi_need_bytes が現在のブレークポイントで読み込む必要のあるバイト数。 - - laddr.addr = md->GetPC(); - laddr.super = md->IsSuper(); - laddr.logical = true; - - // 必要なバイト数に達するまで読み足す - bi_inst = 0; - while (bi_inst_bytes < bi_need_bytes) { - uint64 data = md->PeekFetch(laddr); - if ((int64)data < 0) { - return false; - } - - bi_inst <<= md->inst_bytes * 8; - bi_inst |= data; - bi_inst_bytes += md->inst_bytes; - laddr.addr += md->inst_bytes; + busaddr laddr = busaddr(bp.md->GetPC()) + | BusAddr::Fetch + | busaddr::SU(bp.md->IsSuper()); + DebuggerMemoryStream mem(bp.md, laddr); + uint64 inst = mem.Read(bp.instbytes); + if ((int64)inst < 0) { + return inst; } - // 左詰めにする - bi_inst <<= (4 - bi_inst_bytes) * 8; - - // 必要なバイト数取得できたので比較 - if ((bi_inst & bp.mask) == bp.inst) { - return true; - } - return false; -} - -// 例外通知 (MPU からの連絡用) -void -debugger_notify_exception(int vector) -{ - gDebugger->NotifyException(vector); -} - -// 例外通知 (本体) -void -Debugger::NotifyException(int vector) -{ - bv_vector = vector; + inst <<= (4 - bp.instbytes) * 8; + return (uint32)inst; } // コマンド一覧。 // "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, }, + { "pe", &Debugger::cmd_pe, }, + { "pf", &Debugger::cmd_pf, }, + { "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) { // 引数なしなら一覧表示。 - Help(HelpMsgMain); - return; + ShowHelpList(HelpListMain); + return CmdAct::Stay; } // 引数があれば個別の詳細 - bool try_again = false; - std::string cmd = args[1]; - do { - - for (const auto& dict : HelpDetails) { - if (cmd == dict.first) { - const auto& desc = dict.second; - if (desc[0] == '=') { - // "=" 形式なら を探しなおす。 - // 別名で同じヘルプを指すシンボリックリンクみたいなもの。 - cmd = desc.substr(1); - try_again = true; - break; - } - // そうでなければこれを表示して終了 - std::string disp = HelpConvert(dict.second); - cons->Print("%s", disp.c_str()); - return; - } - } - } while (try_again); - cons->Print("invalid command name: %s\n", args[1].c_str()); + 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() { - Help(HelpMsgBreakpoints); + return ShowHelpList(HelpListBreakpoints); } // hr : レジスタ表示系コマンドの一覧を表示 // CPU ごとに違うので。 -void +Debugger::CmdAct Debugger::cmd_hr() { - Help(md->GetRegisterHelp()); + return ShowHelpList(curmd->GetHelpListReg()); } -// ヘルプ表示の下請け。 -void -Debugger::Help(const HelpMessages& msgs) +// ヘルプ一覧を表示。 +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; } // 個別ヘルプメッセージを出力用に置換。 @@ -707,17 +1063,18 @@ Debugger::HelpConvert(const std::string& } /*static*/ const HelpMessages -Debugger::HelpMsgMain = { +Debugger::HelpListMain = { { "b*", "Set/show Breakpoints (Type \"hb\" for details)" }, { "brhist", "Show branch history" }, - { "c", "Continue" }, - { "d/dt/D", "Disassemble" }, + { "c/ct", "Continue" }, + { "d/D", "Disassemble" }, { "disp", "Set register group to show" }, { "exhist", "Show exception history" }, { "help", "Show this message" }, { "L", "Set log level" }, - { "m/mt/M", "Memory dump" }, + { "m/M", "Memory dump" }, { "n/nt", "Step until next instruction (Skip subroutines)" }, + { "pe/pf", "Print exception frame / floating-point state frame" }, { "quit", "Quit" }, { "r*", "Show registers (Type \"hr\" for details)" }, { "reset", "Reset the VM" }, @@ -727,7 +1084,7 @@ Debugger::HelpMsgMain = { }; /*static*/ const HelpMessages -Debugger::HelpMsgBreakpoints = { +Debugger::HelpListBreakpoints = { { "b", "Show all breakpoints" }, { "b arg..","Set/Delete breakpoint" }, { "bm", "Set memory breakpoint" }, @@ -758,11 +1115,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. @@ -771,9 +1129,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. @@ -781,16 +1140,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. @@ -798,13 +1158,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. @@ -833,21 +1196,34 @@ 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 [[:]
] [] + Command: d [[:]
] [] Shows disassemble. The first form ("D") interprets
as a physical address. - The second and third form ("d", "dt") interprets
as a logical - address if the address translation is enabled. Normally,
is - interpreted in the current privilege. You can force this by - specifier. "s" means the supervisor and "u" means the user privilege. - "d" only looks up in ATC (on m68k) or in BATC/PATC (on m88k). - "dt" will simulate to search the page table in addition to that. + 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 + ('1', '2', '5', and '6) or by one or more following modifiers: + 's' .. Supervisor mode 'u' .. User mode + 'd' .. Data space 'p' or 'i' .. Program space + On m88k, can be specified by using combination of privilege + modifier ('s' or 'u', same as above) and following CMMU identifiers: + 'd' .. Data CMMU 'i' or 'p' .. Instruction CMMU + Or CMMU can also be identified by CMMU number (like '6' or '7'). )**" }, - { "dt", "=d" }, { "D", "=d" }, //----- @@ -888,7 +1264,7 @@ Debugger::HelpDetails = { //----- { "L", R"**( - Command: L + Command: L [=][,=[]]... Set loglevel. XXX To be written... )**" }, @@ -896,34 +1272,60 @@ Debugger::HelpDetails = { //----- { "m", R"**( Command: M
] [] - Command: m [[:]
] [] - Command: mt [[:]
] [] + Command: m [[:]
] [] Shows memory dump. The first form ("M") interprets
as a physical address. - The second and third form ("m", "mt") interprets
as a logical - address if the address translation is enabled. Normally,
is - interpreted in the current privilege. You can force this by - specifier. "s" means the supervisor and "u" means the user privilege. - "m" only looks up in ATC (on m68k) or in BATC/PATC (on m88k). - "mt" will simulate to search the page table in addition to that. + 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 + ('1', '2', '5', and '6) or by one or more following modifiers: + 's' .. Supervisor mode 'u' .. User mode + 'd' .. Data space 'p' or 'i' .. Program space + On m88k, can be specified by using combination of privilege + modifier ('s' or 'u', same as above) and following CMMU identifiers: + 'd' .. Data CMMU 'i' or 'p' .. Instruction CMMU + Or CMMU can also be identified by CMMU number (like '6' or '7'). )**" }, - { "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). )**" }, { "nt", "=n" }, //----- + { "pe", R"**( + Command: pe [
] + + Print an exception frame starting with
. If the
is + omitted, %sp is used. + )**" }, + + //----- + { "pf", R"**( + Command: pf
+ + Print a floating-point state frame starting with
. + )**" }, + + //----- { "quit", R"**( Command: quit (or q) @@ -969,9 +1371,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() { @@ -979,13 +1395,13 @@ Debugger::ParseCmdbuf() int start; pos = 0; - args.clear(); + cmdargs.clear(); // 引数を空白で分解 for (;;) { // 先頭の空白はスキップ for (; cmdbuf[pos] != '\0'; pos++) { - if (!isspace((unsigned int)cmdbuf[pos])) + if (!is_ascii_space(cmdbuf[pos])) break; } if (cmdbuf[pos] == '\0') @@ -994,15 +1410,15 @@ Debugger::ParseCmdbuf() // 終わりを探す start = pos; for (; cmdbuf[pos] != '\0'; pos++) { - if (isspace((unsigned int)cmdbuf[pos])) + if (is_ascii_space(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()); } } } @@ -1012,72 +1428,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] == '#') { // # 形式なら、指定番号のブレークポイントを削除 - 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); } // メモリブレークポイントの設定 -// bm [] -void +// bm [,] [] +Debugger::CmdAct Debugger::cmd_bm() { - // XXX m68k では未サポート - if (dynamic_cast(md)) { - 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()); } // 空いてるところにセット @@ -1085,64 +1508,246 @@ 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 # +Debugger::CmdAct +Debugger::cmd_b_delete() +{ + int n = atoi(&cmdargs[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, "Breakpoint #%d already removed.\n", n); + return CmdAct::Stay; + } + + fprintf(cons, "Breakpoint #%d removed.\n", n); + bp.type = BreakpointType::Unused; + 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 hexstr; + 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; + + // 命令部。 + // ニーモニックが指定できたら便利。 + hexstr = md->ParseInst(argstr); + if (hexstr.empty()) { masklen = -1; } else { - // マスク指定あり - inststr = args[1].substr(0, pos); - instlen = inststr.size(); - maskstr = args[1].substr(pos + 1); - masklen = maskstr.size(); + auto pos = hexstr.find(':'); + if (pos == std::string::npos) { + // マスク指定なし。 + inststr = hexstr; + masklen = -1; + } else { + // マスク指定あり。 + inststr = hexstr.substr(0, pos); + maskstr = hexstr.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バイト未満なら左詰め。 @@ -1150,69 +1755,194 @@ Debugger::cmd_bi() bp.inst <<= 32 - instlen * 4; bp.mask <<= 32 - instlen * 4; } + bp.instbytes = instlen / 2; - // あればスキップカウント - 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->cpu->last_vector がセットされる + // 2. 例外ブレークポイントを設定していないとこれがクリアされない + // 3. bv コマンドで例外ブレークを新たに設定すると、次の命令境界で + // 1.のベクタが反応してしまう。 + // 命令ごととかにクリアしてもいいかも知れないが、ここでブレークポイントを + // 設定したのだから、それ以前の事象には反応すべきでない、という意味では + // ここでもいいか? + + // bp.md は AddBreakpoint() で bpoint 登録時にセットされるので + // インデックスから bp をもう一度読み込む。 + auto bp = bpoint[bi]; + bp.md->GetAndResetLastVector(); + + 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); - 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; + // "-vec2" なら vec1 = 0、 + // "vec1-" なら vec2 は最大値 (機種による)、とする。 + + 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; } // 大小が逆なら入れ替える? @@ -1223,57 +1953,107 @@ 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(*gBreakpointMonitor); + ShowMonitor(bpoint_monitor); + return CmdAct::Stay; } // ブレークポイント一覧 (モニタ) void -Debugger::MonitorBreakpoint(TextScreen& monitor) +Debugger::MonitorScreenBpoint(Monitor *, TextScreen& monitor) { // 0 1 2 3 4 5 6 // 0123456789012345678901234567890123456789012345678901234567890 - // No Type Parameter Matched Skip - // #0 addr $01234567 123456789 123456789/123456789 - // #1 inst 00000000/00000000 - // #2 excp $00-$00 + // 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]; @@ -1281,80 +2061,116 @@ Debugger::MonitorBreakpoint(TextScreen& 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"); + + return CmdAct::Stay; +} - // 今登録されている命令ブレークの必要命令長を再計算 - RecalcInstMask(); +// 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) @@ -1363,6 +2179,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; @@ -1376,39 +2196,6 @@ Debugger::AddBreakpoint(const breakpoint return -1; } -// すべての命令ブレークのマスクのうち最長のものを再計算する。 -// 命令ブレークポイントの追加/削除のたびに呼び出すこと。 -void -Debugger::RecalcInstMask() -{ - uint32 mask; - bi_need_bytes = 0; - - // 登録されている命令ブレークの最長マスクを求める - mask = 0; - for (const auto& bp : bpoint) { - if (bp.type == BreakpointType::Instruction) { - mask |= bp.mask; - } - } - - // mask の Number of Trailing Zero を求める。 - // (x & -x) で x の最も下の立ってるビットだけを立てる、 - // (x & -x) -1 でそれより下の全ビットを立てる、 - // それを popcount で数えるので、$fffffff0 なら ntz = 4 になる。 - int ntz = __builtin_popcount((mask & -(int32)mask) - 1); - // 上位側から数えたマスクに必要なビット数 - int mlen = 32 - ntz; - // 命令語単位に切り上げる - mlen = roundup(mlen, md->inst_bytes * 8); - // バイト数に変換 - mlen /= 8; - - if (mlen > bi_need_bytes) { - bi_need_bytes = mlen; - } -} - // ブランチ履歴、例外履歴表示 // brhist [] // exhist [] @@ -1416,27 +2203,29 @@ Debugger::RecalcInstMask() // コンソールではデフォルトで下を新しいの順とする。 を負数にすると // (行数は絶対値して) 並び順を逆にしてモニタウィンドウと同じ上を新しいの順に // する。 -void +Debugger::CmdAct Debugger::cmd_brhist() { - cmd_hist_common(md->GetBrHist(), 0); + auto brhist = gMainApp.GetObject(OBJ_MPU_BRHIST); + return cmd_hist_common(*brhist); } -void +Debugger::CmdAct Debugger::cmd_exhist() { - cmd_hist_common(md->GetExHist(), BranchHistory::ExHist); + auto exhist = gMainApp.GetObject(OBJ_MPU_EXHIST); + return cmd_hist_common(*exhist); } // ブランチ履歴、例外履歴表示の共通部分。 -void -Debugger::cmd_hist_common(BranchHistory& hist, uint64 flag) +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; @@ -1455,330 +2244,450 @@ Debugger::cmd_hist_common(BranchHistory& // 表示行数 + 1行はヘッダ分 int lines = std::min(used, maxlines) + 1; - // MonitorUpdate() は TextScreen 高さに合わせて出力してくれる。 - auto size = hist.GetMonitorSize(); - TextScreen tscr; - tscr.Init(size.width, lines); + // MonitorScreen() は TextScreen 高さに合わせて出力してくれる。 + auto *histmon = hist.monitor; + TextScreen screen; + screen.Init(histmon->GetCol(), lines); // コンソールでは下が新しいの順のほうがいい if (bottom_to_top) { - flag |= BranchHistory::BottomToTop; + screen.userdata |= BranchHistory::BottomToTop; } - tscr.userdata = flag; // 表示 - hist.MonitorUpdate(tscr); - ShowTextScreen(tscr); + histmon->UpdateScreen(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 [