|
|
nono 1.7.0
//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//
//
// デバッガ
//
//
// 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 "power.h"
#include "scheduler.h"
#include "syncer.h"
#include "uimessage.h"
#include "vectortable.h"
#include <algorithm>
#include <cmath>
#include <map>
#if defined(HAVE_BSD_STDIO_H)
#include <bsd/stdio.h>
#endif
static int readfunc(void *, char *, int);
static int writefunc(void *, const char *, int);
// コンストラクタ
Debugger::Debugger()
: inherited(OBJ_DEBUGGER)
{
// ベクタテーブル
pVectorTable.reset(new VectorTable(gMainApp.GetVMType()));
// ブレークポイントモニタ
bpoint_monitor = gMonitorManager->Regist(ID_MONITOR_BREAKPOINT, this);
bpoint_monitor->SetCallback(&Debugger::MonitorScreenBpoint);
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));
}
}
}
// デストラクタ
Debugger::~Debugger()
{
// fclose(fflush) にあたり hostcom へのアクセスが発生するので、
// hostcom より先に片付けておかなければならない。
Close();
if ((bool)hostcom) {
hostcom->ResetRxCallback();
hostcom->SetAcceptCallback(NULL);
}
TerminateThread();
}
bool
Debugger::Create()
{
// ホストドライバを作成
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) {
is_pause = true;
// この時点ではまだ MPU にメッセージを送ることはできない
}
// -b [<cpu>,]<addr>[,<skip>] ならブレークポイント設定
for (const auto& str : gMainApp.debug_breakaddr) {
if (ParseOptBreakAddr(str) == false) {
return false;
}
}
// --bi [<cpu>,]<inst>[:<mask>][,<skip>] で命令ブレークポイント設定。
for (auto& str : gMainApp.debug_breakinst) {
if (ParseOptBreakInst(str) == false) {
return false;
}
}
// --bv [<cpu>,]<vec1>[-<vec2>][,<skip>] でベクタブレークポイント設定。
for (auto& str : gMainApp.debug_breakvec) {
if (ParseOptBreakVector(str) == false) {
return false;
}
}
// --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);
}
// 入出力を FILE で扱う
cons = funopen(this, readfunc, writefunc, NULL, NULL);
if (cons == NULL) {
warnx("funopen2 failed");
return false;
}
return true;
}
// デバッガスレッド
void
Debugger::ThreadRun()
{
SetThreadAffinityHint(AffinityClass::Light);
// disp_regs の初期値設定。
// 再接続でも継続してていいような気がするのでループ外で初期化。
disp_regs.clear();
disp_regs.push_back("r");
// MPU のトレース状態の初期化は vm/mpu* 側のリセット例外で行っている。
for (;;) {
// 何か起きるまで待つ
uint32 req;
{
std::unique_lock<std::mutex> lock(mtx);
cv_request.wait(lock, [&] { return request != 0; });
req = request;
request = 0;
}
if ((req & REQUEST_EXIT)) {
break;
}
if ((req & REQUEST_RXCHAR)) {
// コンソールからの文字入力
int c;
while ((c = hostcom->Rx()) >= 0) {
Input(c);
}
}
if ((req & REQUEST_ACCEPT)) {
// TCP 待ち受けに着信があればすぐにプロンプトを出したい
Input('\n');
}
if ((req & REQUEST_PROMPT)) {
// VM 停止(したのでプロンプトモードへ)
EnterPrompt();
}
}
QuitPrompt();
Close();
}
// コンソールをクローズする
void
Debugger::Close()
{
if (cons) {
fclose(cons);
cons = NULL;
}
}
#if 0
bool first = true;
for (;;) {
// 接続後の1回目だけ実行するもの。
if (first) {
first = false;
// greeting はプロンプトが取れる前にもう表示したい。
// 何らかの事故でプロンプトが取れなくても、ここまでは接続
// できてることが分かるように。
fprintf(cons, "This is debugger console\n");
// 接続ごとに初期化する値
n_enable = false;
s_enable = false;
t_enable = true;
}
}
}
#endif
// スレッドに終了指示
void
Debugger::Terminate()
{
std::lock_guard<std::mutex> lock(mtx);
request |= REQUEST_EXIT;
cv_request.notify_one();
}
// funopen の read コールバック
static int
readfunc(void *cookie, char *buf, int bufsize)
{
auto debugger = reinterpret_cast<Debugger *>(cookie);
return debugger->ReadFunc(buf, bufsize);
}
// funopen の write コールバック
static int
writefunc(void *cookie, const char *buf, int len)
{
auto debugger = reinterpret_cast<Debugger *>(cookie);
return debugger->WriteFunc(buf, len);
}
// funopen の read コールバックの本体
int
Debugger::ReadFunc(char *buf, int bufsize)
{
char *d = buf;
char *end = buf + bufsize;
for (; d < end; ) {
if ((bool)hostcom == false) {
errno = EIO;
return -1;
}
int c = hostcom->Rx();
if (c < 0) {
break;
}
*d++ = c;
}
return (d - buf);
}
// funopen の write コールバックの本体
int
Debugger::WriteFunc(const char *buf, int len)
{
const char *s = buf;
const char *end = buf + len;
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);
}
// ホストからの1文字受信通知 (HostCOM スレッドから呼ばれる)
void
Debugger::RxCallback(uint32 arg)
{
// デバッガスレッドに通知
std::lock_guard<std::mutex> lock(mtx);
request |= REQUEST_RXCHAR;
cv_request.notify_one();
}
// ホストからの着信通知 (HostCOM スレッドから呼ばれる)
void
Debugger::AcceptCallback(uint32 dummy)
{
// デバッガスレッドに通知
std::lock_guard<std::mutex> lock(mtx);
request |= REQUEST_ACCEPT;
cv_request.notify_one();
}
// ホストからの1文字入力
void
Debugger::Input(int c)
{
// '^@' は捨てる
// (意図的にも入力できるが nc が TELNET オプション扱えなくて送ってくる)
if (c == '\0') {
return;
}
// HostCOM からは改行で CR が来るようなので LF にしておく
if (c == '\r') {
c = '\n';
}
if (is_prompt == false) {
// プロンプトでない時は、Enter か ^C でプロンプトを出す
if (c == '\n' || c == '\x03') {
// MPU に一時停止を要求
is_pause = true;
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);
}
} else if (c < ' ' || c == 0x07f) {
// 他のコントロールコードはとりあえず無視
} else {
// 通常文字なら、エコーバックして追加
fputc(c, cons);
fflush(cons);
cmdbuf.push_back(c);
}
}
}
// コマンドモード(プロンプト)に入る
void
Debugger::EnterPrompt()
{
is_prompt = true;
// ブレークポイント到達メッセージがあればここで表示
if (bpointmsg.empty() == false) {
fprintf(cons, "%s", bpointmsg.c_str());
bpointmsg.clear();
}
// 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<std::mutex> 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;
}
// 行を cmdargs に分解
ParseCmdbuf();
cmdbuf.clear();
// 前回の値が有効なのはコマンドが連続した時だけ
// コマンドをテーブルから探す
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;
}
// ブレークポイントとかを調べる。1命令ごとに呼び出される。
// (VM スレッドから呼ばれる)
void
Debugger::Exec(DebuggerMD *md)
{
if (Check(md)) {
// この CPU でブレークしたので、ターゲット CPU をこっちに変更。
ChangeMD(md);
// 実時間を停止
syncer->StopRealTime();
// 待機
{
std::unique_lock<std::mutex> lock(mtx);
// notify 前にクリア
prompt_released = false;
// MPU が一時停止したことをデバッガスレッドへ通知する
request |= REQUEST_PROMPT;
cv_request.notify_one();
// デバッガスレッドがプロンプト解放するまで待機
cv_prompt.wait(lock, [&] { return prompt_released; });
}
// 実時間を再開
syncer->StartRealTime();
} else {
// t (トレース表示) が有効な場合は終了条件にマッチしなかったここで
// レジスタを表示。終了条件にマッチした時は EnterPrompt() で表示する。
if (t_enable == md) {
assert(md == curmd);
pc = curmd->GetPC();
cmd_minus();
// 次回との差分のため今のレジスタセットをバックアップ
curmd->BackupRegs();
}
}
}
// MPU をトレース状態にすべきなら true を返す。
bool
Debugger::IsTrace() const
{
// 有効なブレークポイントがあれば 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(DebuggerMD *md)
{
bool is_break = false;
// ブレークポイントは他のチェックとは併用になるので先に調べる。
if (CheckAllBreakpoints(md)) {
is_break = true;
// この次に調べる各種終了条件が来ないうちに先にブレークポイントに
// 到達した場合でも、ブレークポイントによりプロンプトに降りる
// わけなので、実行中のステップ実行をキャンセルする。
// この場合もブレークポイント側が 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;
case StepType::Count: // 命令数指定
step_count--;
putlog(1, "Check s: --step_count=%d", step_count);
if (step_count == 0) {
hit = true;
}
break;
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;
case StepType::StepOut: // ステップアウト
putlog(1, "Check so");
if (step_md->IsStepOut()) {
hit = true;
}
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 (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;
}
// md で指定された CPU のブレークポイントがどれかでも成立するかを調べる。
// 1つ以上成立してブレークするなら、true を返す。
// 1つも成立しておらずブレークしないなら false を返す。
// このルーチンから cons への出力は使わないこと。(プロンプトにいない時でも
// 呼ばれるので)
bool
Debugger::CheckAllBreakpoints(DebuggerMD *md)
{
bool is_break = false;
int vector;
uint32 inst = 0;
int instbytes = 0;
// 例外はここでローカルにコピーしてから、クリアする。
vector = md->GetAndResetLastVector();
// 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 == bp.md->GetPC() &&
bp.md->GetCPUState() == CPUState::Normal)
{
break;
}
continue;
case BreakpointType::Memory:
if (bp.md->CheckLEA(bp.addr)) {
break;
}
continue;
case BreakpointType::Exception:
if (vector >= 0) {
if (bp.vec1 <= vector && vector <= bp.vec2) {
break;
}
}
continue;
case BreakpointType::Instruction:
if (instbytes < bp.instbytes) {
uint64 data = ReadBreakpointInst(bp);
if ((int64)data < 0) {
continue;
}
inst = data;
instbytes = bp.instbytes;
}
if ((inst & bp.mask) == bp.inst) {
break;
}
continue;
default:
continue;
}
// 条件は成立したのでカウントとスキップ
bp.matched++;
// skip == -1 は常にブレークしない (カウントするだけ)
if (bp.skip < 0) {
continue;
}
// skip が 1 以上なら、remain を減算
if (bp.skip > 0 && --bp.skipremain > 0) {
continue;
}
// ブレーク成立
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);
break;
case BreakpointType::Memory:
desc += string_format("mem=$%08x", bp.addr);
break;
case BreakpointType::Exception:
{
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",
instbytes * 2, inst >> ((4 - instbytes) * 8));
break;
default:
assert(false);
break;
}
// 到達メッセージを作成。
// この時点ではまだコンソールを取得していない可能性があるので
// (-D なしで起動した場合とか)、表示せず用意するだけ。
// コンソールが取得できたところで表示する。
bpointmsg += string_format("Breakpoint #%d (%s) reached.\n",
i, desc.c_str());
}
// ブレークポイントが1つでも成立したかどうかを返す
return is_break;
}
// 命令ブレークポイントの位置の命令を読み込む。
uint64
Debugger::ReadBreakpointInst(breakpoint_t& bp)
{
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;
}
// 左詰めにする
inst <<= (4 - bp.instbytes) * 8;
return (uint32)inst;
}
// コマンド一覧。
// "r" から始まるレジスタ表示系コマンドはコード中で別処理してある。
/*static*/ std::vector<Debugger::cmddef_t> Debugger::cmdtable = {
{ "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 <cmd> : 単独コマンドの詳細を表示
Debugger::CmdAct
Debugger::cmd_h()
{
if (cmdargs.size() < 2) {
// 引数なしなら一覧表示。
ShowHelpList(HelpListMain);
return CmdAct::Stay;
}
// 引数があれば個別の詳細
const std::string cmd1 = cmdargs[1];
// curmd の MD 分も併せて検索するため、ローカルに map を作成する。
// value のほうは実体コピーは不要なのでポインタ。
std::map<const std::string, const std::string *> 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 {
// メッセージ本文が "=<cmd>" の形式なら、<cmd> を探し直す。
// 別名で同じヘルプを指すシンボリックリンクみたいなもの。
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 : ブレークポイント系コマンドの一覧を表示
// こいつだけ結構占めるので別階層。
Debugger::CmdAct
Debugger::cmd_hb()
{
return ShowHelpList(HelpListBreakpoints);
}
// hr : レジスタ表示系コマンドの一覧を表示
// CPU ごとに違うので。
Debugger::CmdAct
Debugger::cmd_hr()
{
return ShowHelpList(curmd->GetHelpListReg());
}
// ヘルプ一覧を表示。
Debugger::CmdAct
Debugger::ShowHelpList(const HelpMessages& msgs)
{
fprintf(cons, "Type \"help <cmd>\" for indivisual details.\n");
for (const auto& pair : msgs) {
fprintf(cons, " %-16s %s\n", pair.first.c_str(), pair.second.c_str());
}
return CmdAct::Stay;
}
// 個別ヘルプメッセージを出力用に置換。
// 1. 行頭の連続する改行 (通常は1つのはず) を取り除く。
// 2. 末尾の連続するタブ (通常は1つのはず) を取り除く。
// 3. (各行頭の) タブを空白4つに置換する。
std::string
Debugger::HelpConvert(const std::string& src)
{
int start = 0;
int len = src.size();
// 末尾の連続するタブをカウント
while (src[len - 1] == '\t') {
len--;
}
// 先頭の連続する改行をカウント
while (src[start] == '\n') {
start++;
len--;
}
std::string stripped = src.substr(start, len);
// 面倒なので行頭に関わらず全タブを置換
const char *c_stripped = stripped.c_str();
std::string dst;
for (const char *s = c_stripped; *s; s++) {
if (*s == '\t') {
dst.append(" ");
} else {
dst.append(1, *s);
}
}
return dst;
}
/*static*/ const HelpMessages
Debugger::HelpListMain = {
{ "b*", "Set/show Breakpoints (Type \"hb\" for details)" },
{ "brhist", "Show branch history" },
{ "c/ct", "Continue" },
{ "d/D", "Disassemble" },
{ "disp", "Set register group to show" },
{ "exhist", "Show exception history" },
{ "help", "Show this message" },
{ "L", "Set log level" },
{ "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" },
{ "s/st", "Step an instruction (Step in)" },
{ "so", "Step out" },
{ "show", "Show monitor" },
};
/*static*/ const HelpMessages
Debugger::HelpListBreakpoints = {
{ "b", "Show all breakpoints" },
{ "b arg..","Set/Delete breakpoint" },
{ "bm", "Set memory breakpoint" },
{ "bi", "Set instruction breakpoint" },
{ "bv", "Set exception breakpoint" },
{ "bx", "Delete all breakpoints" },
};
// 各コマンドの詳細。"コマンド名" => "説明文" の形式。
// 説明文は C+11 の生文字リテラル機能を使って R"**( )**" で囲む。
// 1つのエントリは
// { "cmdname", R"**(
// <tab>cmdname [argument...]
//
// <tab>Description...
// <tab>)**" },
// のようになる。説明文本文はインデント1つ(TAB 幅 4) で字下げした状態で
// 80桁以内に収めること。出力の際にタブを空白4つに置き換えることでソース上
// での見た目と実際の出力とを揃えてある。
// またソースコード上の見栄えの問題として、文字列の開始記号、終了記号を本文
// とは別の行に書いているため、オブジェクトとしての文字列には先頭に改行、
// 末尾にタブが余計に入っているが、これは表示の際にコードで取り除く。
//
// 説明文の書き方は、まずコマンド書式を1行ずつ書く。
// コマンド書式と本文との間は1行あける。
/*static*/ const HelpMessages
Debugger::HelpDetails = {
//-----
{ "b", R"**(
Command: b
Command: b [<cpu>,]<address> [<skipcount>]
Command: b #n
The first form (with no arguments) shows all breakpoints.
The second form sets a new breakpoint at <address> on <cpu>.
If <cpu> is omitted, the current cpu is used.
XXX skipcount
If <skipcount> is -1, it will never match. It's useful to count the
number of times you have passed this address.
The third form deletes a breakpoint specified by number.
)**" },
//-----
{ "bm", R"**(
Command: bm [<cpu>,]<address> [<skipcount>]
Sets a memory breakpoint at <address> on <cpu>.
If <cpu> is omitted, the current cpu is used.
XXX skipcount
If <skipcount> is -1, it will never match. It's useful to count the
number of times you have passed this address.
)**" },
//-----
{ "bi", R"**(
Command: bi [<cpu>,]<inst>[:<mask>] [<skipcount>]
Sets an instruction breakpoint on <cpu>. <inst> must be 16 bits or 32
bits on m68k and must be 32 bits on m88k. <mask> can specify the mask.
Its length must be the same as <inst>. If the <mask> is omitted, all
bits of <inst> 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 <cpu> is omitted, the current cpu is used.
XXX skipcount
If <skipcount> is -1, it will never match. It's useful to count the
number of times you have passed this address.
)**" },
//-----
{ "bv", R"**(
Command: bv [<cpu>,]<vector>[-<vector2>] [<skipcount>]
Sets an exception breakpoint on <cpu>. <vector> accepts hexadecimal
number if prefixed with "$" or "0x", or decimal number if no prefix.
<vector> ranges from 0 to 255 on m68k, and 0 to 511 on m88k.
If <vector2> is specified, it matches the range from <vector> to <vector2>
(including themselves). For example, "bv 1-$1ff" on m88k means all
exceptions but reset.
If <cpu> is omitted, the current cpu is used.
XXX skipcount
If <skipcount> is -1, it will never match. It's useful to count the
number of times you have passed this address.
)**" },
//-----
{ "bx", R"**(
Command: bx
Deletes all breakpoints.
)**" },
//-----
{ "brhist", R"**(
Command: brhist [<maxlines>]
Show branch history. <maxlines> specifies the maximum number of lines
to display. If <maxlines> is negative value, sort in reverse order.
)**" },
//-----
{ "c", R"**(
Command: c [<address>]
Continue (until <address> if specified).
)**" },
//-----
{ "ct", R"**(
Command: ct [<timespan>]
Continue until specified <timespan> has elapsed. If the <timespan> is
omitted, the last value is used again. The unit of <timespan> is
seconds in default. You can use "msec", "usec", or "nsec" suffix.
)**" },
//-----
{ "d", R"**(
Command: D <address>] [<lines>]
Command: d [[<space>:]<address>] [<lines>]
Shows disassemble.
The first form ("D") interprets <address> as a physical address.
The second form ("d") interprets <address> as a logical address
if the address translation is enabled. Normally, <address> is
interpreted in the current privilege and current address space. You can
change it by <space> modifier.
On m68k, <space> 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, <space> can be specified by using combination of privilege
modifier ('s' or 'u', same as above) and following CMMU identifiers:
'd' .. Data CMMU 'i' or 'p' .. Instruction CMMU
Or CMMU can also be identified by CMMU number (like '6' or '7').
)**" },
{ "D", "=d" },
//-----
{ "disp", R"**(
Command: disp <register...>
Sets the registers list to be shown in the trace.
<registers...> is comma-separated list and each of which is a command
name that shows registers. See "hr".
The default is "r" and thus it only shows general registers in the trace.
For example, if you set "disp r,rf", it will show general registers and
FPU registers in every trace.
)**" },
//-----
{ "exhist", R"**(
Command: exhist [<maxlines>]
Show exception history. <maxlines> specifies the maximum number of lines
to display. If <maxlines> is negative value, sort in reverse order.
)**" },
//-----
{ "help", R"**(
Command: help [<command>]
Command: hb
Command: hr
The "help" command without arguments shows the list of commands.
The "help" command with an argument shows <command>'s detailed help.
"h" is a synonym of "help".
The "hb" command shows the list of breakpoint-related commands.
The "hr" command shows the list of register-related commands.
)**" },
{ "h", "=help" },
{ "hb", "=help" },
{ "hr", "=help" },
//-----
{ "L", R"**(
Command: L <name1>[=<level1>][,<name2>=[<level2>]]...
Set loglevel. XXX To be written...
)**" },
//-----
{ "m", R"**(
Command: M <address>] [<lines>]
Command: m [[<space>:]<address>] [<lines>]
Shows memory dump.
The first form ("M") interprets <address> as a physical address.
The second form ("m") interprets <address> as a logical address
if the address translation is enabled. Normally, <address> is
interpreted in the current privilege and current address space. You can
change it by <space> modifier.
On m68k, <space> 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, <space> can be specified by using combination of privilege
modifier ('s' or 'u', same as above) and following CMMU identifiers:
'd' .. Data CMMU 'i' or 'p' .. Instruction CMMU
Or CMMU can also be identified by CMMU number (like '6' or '7').
)**" },
{ "M", "=m" },
//-----
{ "mpu", R"**(
Command: mpu
Change the target CPU to "mpu"(M68030/M88100).
)**" },
//-----
{ "n", R"**(
Command: n [<count>]
Command: nt [<count>]
Step one (or <count>) instructions. Unlike "s" command, "n" skips
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 [<address>]
Print an exception frame starting with <address>. If the <address> is
omitted, %sp is used.
)**" },
//-----
{ "pf", R"**(
Command: pf <address>
Print a floating-point state frame starting with <address>.
)**" },
//-----
{ "quit", R"**(
Command: quit (or q)
Quit the debugger console. This continues the execution, as same as "c".
)**" },
{ "q", "=quit" },
//-----
{ "reset", R"**(
Command: reset
Reset the VM. Currently this does the hardware reset.
)**" },
//-----
{ "s", R"**(
Command: s [<count>]
Command: st [<count>]
Step one (or <count>) instructions. Unlike "n" command, "s" steps in
subroutine.
If "t" is suffixed, it shows a trace for each instruction.
"t" command is a synonym of "st" for backward compatibility.
)**" },
{ "st", "=s" },
//-----
{ "so", R"**(
Command: so
Command: sot
Step out this sub routine.
If "t" is suffixed, it shows a trace for each instruction.
)**" },
//-----
{ "show", R"**(
Command: show <monitor>
Shows the specified monitor.
)**" },
//-----
{ "t", "=s" },
//-----
{ "xp", R"**(
Command: xp
Change the target CPU to "xp"(HD647180).
)**" },
//-----
{ "z", R"**(
Command: z
Continue until the next address.
)**" },
};
// cmdbuf を cmdargs... に分解する。
void
Debugger::ParseCmdbuf()
{
int pos;
int start;
pos = 0;
cmdargs.clear();
// 引数を空白で分解
for (;;) {
// 先頭の空白はスキップ
for (; cmdbuf[pos] != '\0'; pos++) {
if (!is_ascii_space(cmdbuf[pos]))
break;
}
if (cmdbuf[pos] == '\0')
break;
// 終わりを探す
start = pos;
for (; cmdbuf[pos] != '\0'; pos++) {
if (is_ascii_space(cmdbuf[pos]))
break;
}
cmdargs.push_back(cmdbuf.substr(start, pos - start));
}
// デバッグ表示
if (0) {
for (int i = 0 ; i < cmdargs.size(); i++) {
printf("cmdargs[%d]=|%s|\n", i, cmdargs[i].c_str());
}
}
}
//
// デバッガコマンド
//
// ブレークポイント
// b ... 一覧表示
// b #n ... #n を削除
// b [<cpu>,]<addr> [<skip>] ... 設定
Debugger::CmdAct
Debugger::cmd_b()
{
// 引数なしなら一覧表示
if (cmdargs.size() < 2) {
return cmd_b_list();
}
// 引数取得
if (cmdargs[1][0] == '#') {
// #<n> 形式なら、指定番号のブレークポイントを削除
cmd_b_delete();
return CmdAct::Stay;
}
return cmd_b_set(BreakpointType::Address);
}
// メモリブレークポイントの設定
// bm [<cpu>,]<addr> [<skip>]
Debugger::CmdAct
Debugger::cmd_bm()
{
// XXX m88k のみ未サポート
if (curmd->arch != CPUArch::M88xx0) {
fprintf(cons, "bm: Not supported yet on m68k.\n");
return CmdAct::Stay;
}
return cmd_b_set(BreakpointType::Memory);
}
// type が違うだけの各種ブレークポイント設定の共通部分。
// すぐ下の ParseOptBreakAddr() と限りなく似ている。
Debugger::CmdAct
Debugger::cmd_b_set(BreakpointType type)
{
breakpoint_t bp;
std::string cpustr;
std::string addrstr;
if (cmdargs.size() < 2) {
fprintf(cons, "Usage: %s [<cpu>,]<addr> [<skipcount>]\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(addrstr.c_str(), &bp.addr)) {
return CmdAct::Stay;
}
// あればスキップカウント
if (cmdargs.size() > 2) {
bp.skip = atoi(cmdargs[2].c_str());
}
// 空いてるところにセット
// (よく似たエントリがあっても干渉しない)
bp.type = type;
int bi = AddBreakpoint(bp);
if (bi == -1) {
fprintf(cons, "Breakpoints full.\n");
} else {
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;
}
// <cpu> を取り出す。
bp.md = ParseCPU(cpustr);
if (bp.md == NULL) {
warnx("--b \"%s\": Invalid CPU Name.", str.c_str());
return false;
}
// <addr> を取り出す。
if (ParseVerbHex(addrstr.c_str(), &bp.addr) == false) {
warnx("--b \"%s\": Invalid address.", str.c_str());
return false;
}
// <skip> を取り出す。
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 #<n>
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 [<cpu>,]<insn>[:<mask>] [<skip>]
Debugger::CmdAct
Debugger::cmd_bi()
{
std::string cpustr;
std::string inststr;
std::string skipstr;
std::string errstr;
if (cmdargs.size() < 2) {
fprintf(cons, "Usage: bi <inst>[:<mask>] [<skipcount>]\n");
return CmdAct::Stay;
}
// 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];
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 {
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) {
*errstr = string_format("Invalid instruction: %s", argstr.c_str());
return -1;
}
if (instlen % (md->inst_bytes * 2) != 0) {
*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) {
*errstr = string_format("Invalid mask value: %s", argstr.c_str());
return -1;
}
if (masklen != instlen) {
*errstr = std::string("<inst>:<mask> must be the same length");
return -1;
}
}
// 8バイト未満なら左詰め。
if (md->inst_bytes < 4 && instlen < 8) {
bp.inst <<= 32 - instlen * 4;
bp.mask <<= 32 - instlen * 4;
}
bp.instbytes = instlen / 2;
// <skip> を取り出す。
if (skipstr.empty() == false) {
bp.skip = atoi(skipstr.c_str());
}
// 空いてるところにセット
bp.type = BreakpointType::Instruction;
int bi = AddBreakpoint(bp);
if (bi < 0) {
*errstr = std::string("Breakpoints full.");
return bi;
}
return bi;
}
// 例外ブレークポイントの設定。
// bv [<cpu>,]<vector_number>[-<end_number>] [<skip>]
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 [<cpu>,]<vec#>[-<end#>] [<skipcount>]\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;
// 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 = vecstr.find('-');
if (pos == std::string::npos) {
// ベクタ番号が1つなら vec1, vec2 を同値にしておく。
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 vec1 = vecstr.substr(0, pos);
std::string vec2 = vecstr.substr(pos + 1);
// "-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 >= 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;
}
// 大小が逆なら入れ替える?
if (bp.vec1 > bp.vec2) {
int tmp;
tmp = bp.vec1;
bp.vec1 = bp.vec2;
bp.vec2 = tmp;
}
// <skip> を取り出す。
if (skipstr.empty() == false) {
bp.skip = atoi(skipstr.c_str());
}
// 空いてるところにセット
bp.type = BreakpointType::Exception;
int bi = AddBreakpoint(bp);
if (bi < 0) {
*errstr = std::string("Breakpoints full.");
}
return bi;
}
// ベクタ名かベクタ番号をパースする。
// ベクタ番号は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<const char *> 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;
}
// ブレークポイント一覧表示
Debugger::CmdAct
Debugger::cmd_b_list()
{
ShowMonitor(bpoint_monitor);
return CmdAct::Stay;
}
// ブレークポイント一覧 (モニタ)
void
Debugger::MonitorScreenBpoint(Monitor *, TextScreen& monitor)
{
// 0 1 2 3 4 5 6
// 0123456789012345678901234567890123456789012345678901234567890
// 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 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];
int y = i + 1;
monitor.Print(0, y, "#%d", i);
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(7, y, "addr $%08x", bp.addr);
break;
case BreakpointType::Memory:
monitor.Print(7, y, "mem $%08x", bp.addr);
break;
case BreakpointType::Exception:
{
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(13 + vd, y, "-$%0*x", vd, bp.vec2);
}
break;
}
case BreakpointType::Instruction:
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(7, y, "type=%d", (int)bp.type);
continue;
}
// マッチ回数
monitor.Print(30, y, "%d", bp.matched);
// スキップ
if (bp.skip < 0) {
monitor.Print(41, y, "forever");
} else if (bp.skip > 0) {
monitor.Print(41, y, "%d / %d", (bp.skip - bp.skipremain), bp.skip);
}
}
}
// ブレークポイント全削除
Debugger::CmdAct
Debugger::cmd_bx()
{
for (auto& bp : bpoint) {
bp.type = BreakpointType::Unused;
}
fprintf(cons, "All breakpoints are removed.\n");
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)
{
for (int i = 0; i < bpoint.size(); i++) {
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;
} else {
bp.skipremain = 0;
}
return i;
}
}
return -1;
}
// ブランチ履歴、例外履歴表示
// brhist [<maxlines>]
// exhist [<maxlines>]
// <maxlines> は表示する最大エントリ数。
// コンソールではデフォルトで下を新しいの順とする。<maxlines> を負数にすると
// (行数は絶対値して) 並び順を逆にしてモニタウィンドウと同じ上を新しいの順に
// する。
Debugger::CmdAct
Debugger::cmd_brhist()
{
auto brhist = gMainApp.GetObject<BranchHistory>(OBJ_MPU_BRHIST);
return cmd_hist_common(*brhist);
}
Debugger::CmdAct
Debugger::cmd_exhist()
{
auto exhist = gMainApp.GetObject<BranchHistory>(OBJ_MPU_EXHIST);
return cmd_hist_common(*exhist);
}
// ブランチ履歴、例外履歴表示の共通部分。
Debugger::CmdAct
Debugger::cmd_hist_common(BranchHistory& hist)
{
// 表示最大行数(と向き)
// 向きは bottom_to_top = true が新しいほうを下とする方向。
int maxlines = 20;
bool bottom_to_top = true;
if (cmdargs.size() > 1) {
maxlines = atoi(cmdargs[1].c_str());
if (maxlines < 0) {
bottom_to_top = false;
maxlines = -maxlines;
}
if (maxlines < 1)
maxlines = 1;
if (maxlines > 256)
maxlines = 256;
}
// コンソールでは空エントリの行は表示したくないので、
// 先にエントリ数を調べる。
int used = hist.GetUsed();
// 表示行数 + 1行はヘッダ分
int lines = std::min(used, maxlines) + 1;
// MonitorScreen() は TextScreen 高さに合わせて出力してくれる。
auto *histmon = hist.monitor;
TextScreen screen;
screen.Init(histmon->GetCol(), lines);
// コンソールでは下が新しいの順のほうがいい
if (bottom_to_top) {
screen.userdata |= BranchHistory::BottomToTop;
}
// 表示
histmon->UpdateScreen(screen);
ShowTextScreen(screen);
return CmdAct::Stay;
}
// 実行再開(continue): c [<addr>]
// <addr> 指定があれば <addr> まで実行。
Debugger::CmdAct
Debugger::cmd_c()
{
if (cmdargs.size() > 1) {
// 引数があれば
uint32 addr;
if (!ParseAddr(cmdargs[1].c_str(), &addr)) {
return CmdAct::Stay;
}
step_type = StepType::Addr;
step_md = curmd;
// ネイティブ命令長に丸める
step_addr = addr & ~(curmd->inst_bytes - 1);
}
return CmdAct::Leave;
}
// 指定仮想時間実行: ct [<time>]
// <time> が省略されたら前回指定した時間間隔で再実行。
Debugger::CmdAct
Debugger::cmd_ct()
{
if (cmdargs.size() > 1) {
// 引数があれば
if (!ParseTime(cmdargs[1].c_str(), &ct_timespan)) {
return CmdAct::Stay;
}
}
if (ct_timespan == 0) {
fprintf(cons, "Time must be specified.\n");
return CmdAct::Stay;
}
step_type = StepType::Time;
step_md = curmd;
step_time = scheduler->GetVirtTime() + ct_timespan;
return CmdAct::Leave;
}
// 引数などからアドレスを取得するごった煮共通ルーチン。
//
// 引数がなければ sa->addr には書き込まずに true で帰る (ので、呼び出し側が
// 事前に適切なデフォルト値をセットしておくと、それがそのまま使われる)。
// 引数が1つ(以上)あれば cmdargs[1] をアドレスとしてパースする。
// アドレスには空間修飾子を前置可能、省略の場合はいずれも現在値を使う。
// エラーならメッセージを表示して false を返す。
// sa は in/out パラメータ。
bool
Debugger::GetAddr(busaddr *sa, bool default_data)
{
if (cmdargs.size() < 2) {
// 引数なしなら、呼び出し側が sa にセットした前回値をそのまま使う。
return true;
}
// 引数ありならパースする。アドレスが明示的に指定されたので、
// ここから先の省略箇所は前回値ではなくデフォルト値で補完する。
std::string& str = cmdargs[1];
uint32 addr;
// ':' があればその前はアドレス空間修飾子
int mod_super = -1; // 'u'/'s'
int mod_prog = -1; // 'd'/'p' or 'd'/'i'
int mod_num = -1; // '0'..'7'
auto addrpos = str.find(':');
if (addrpos == std::string::npos) {
// なければ先頭からアドレス
addrpos = 0;
} else {
// ':' が途中にあれば、その前が修飾子。
// ':' が先頭なら修飾子が空文字列という扱いでいいか。
std::string mod = str.substr(0, addrpos);
addrpos++;
for (auto ch : mod) {
if (ch == 'u') {
if (mod_super == 1)
goto error;
mod_super = 0;
} else if (ch == 's') {
if (mod_super == 0)
goto error;
mod_super = 1;
} else if (ch == 'd') {
if (mod_prog == 1)
goto error;
mod_prog = 0;
} else if (ch == 'p' || ch == 'i') {
// m68k では 'P'rogram space、m88k では 'I'nstruction CMMU…
if (mod_prog == 0)
goto error;
mod_prog = 1;
} else if ('0' <= ch && ch <= '7') {
int num = ch - '0';
if (curmd->arch == CPUArch::M680x0) {
// m68k では FC 指定は Super/User 指定を含んでおり、
// 値指定は他の修飾子とは同時に指定できない。
if (mod_super >= 0 || mod_prog >= 0 ||
(mod_num >= 0 && mod_num != num)) {
goto error;
}
// 有効な値は 1,2,5,6 のみ
if (num == 0 || num == 3 || num == 4 || num == 7) {
fprintf(cons, "Invalid function code %d.\n", num);
return false;
}
mod_num = num;
mod_super = mod_num >> 2; // FC2
mod_prog = (mod_num & 3) - 1; // FC1,FC0
} else if (curmd->arch == CPUArch::M88xx0) {
// m88k では CMMU 指定と、Super/User 指定は独立。
if (mod_prog >= 0 ||
(mod_num >= 0 && mod_num != num)) {
goto error;
}
mod_num = num;
mod_prog = (mod_num & 1); // CMMU7 が Inst
} else {
fprintf(cons, "not yet\n");
}
} else {
fprintf(cons, "Unknown address space modifier: %c\n", ch);
return false;
}
continue;
error:
fprintf(cons, "Address space modifiers conflict: \"%s\"\n",
mod.c_str());
return false;
}
// 数値指定を機種ごとに読み替える
if (mod_num >= 0) {
if (curmd->arch == CPUArch::M680x0) {
// m68k なら値指定で全部確定する
// "1:" -> User/Data
// "2:" -> User/Program
// "5:" -> Super/Data
// "6:" -> Super/Program
mod_super = (mod_num & 4) ? true : false;
mod_prog = (mod_num & 3) - 1;
} else if (curmd->arch == CPUArch::M88xx0) {
// m88k では mod_num を CMMU ID とする(?)。
// XXX まだ CPU は1つしかないので雑に処理。
// "6" -> Data (CPU#0)
// "7" -> Program (CPU#0)
if (mod_num < 6) {
fprintf(cons, "CMMU %d not installed.\n", mod_num);
return false;
}
mod_prog = mod_num - 6;
}
}
}
// アドレス空間修飾子のうち省略箇所はデフォルト状態で補完
// - 's'/'u' いずれもなければ現在値。
// - 'd'/'p'/'i' いずれもなければ、d/m コマンドごとに自然なほう。
if (mod_super < 0) {
mod_super = curmd->IsSuper();
}
if (mod_prog < 0) {
mod_prog = !default_data;
}
sa->ChangeSuper(mod_super);
sa->ChangeData(!mod_prog);
// アドレス
if (ParseAddr(str.c_str() + addrpos, &addr) == false) {
return false;
}
// 命令境界に丸める
sa->ChangeAddr(addr & ~(curmd->inst_bytes - 1));
return true;
}
// ターゲット CPU を "mpu" (M680x0/M88xx0) に変更。
Debugger::CmdAct
Debugger::cmd_mpu()
{
if (curmd != md_mpu.get()) {
ChangeMD(md_mpu.get());
ResetStickyAddr();
cmd_minus();
}
return CmdAct::Stay;
}
// ターゲット CPU を "xp" (HD647180) に変更。
Debugger::CmdAct
Debugger::cmd_xp()
{
if (curmd != md_xp.get()) {
ChangeMD(md_xp.get());
ResetStickyAddr();
cmd_minus();
}
return CmdAct::Stay;
}
// この命令の1つ次のアドレスまでスキップする。
// dbra とかのループを飛ばす用。
Debugger::CmdAct
Debugger::cmd_z()
{
// 次のアドレスを求める。
step_addr = curmd->GetNextPC();
step_type = StepType::Addr;
step_md = curmd;
return CmdAct::Leave;
}
void
Debugger::ChangeMD(DebuggerMD *newmd)
{
if (newmd != curmd) {
curmd = newmd;
fprintf(cons, "Target cpu is switched to '%s'.\n",
curmd->GetName().c_str());
}
}
// pc と d/m の初期値をリセット。
void
Debugger::ResetStickyAddr()
{
pc = curmd->GetPC();
m_last_addr = busaddr(pc) | BusAddr::Read | busaddr::SU(curmd->IsSuper());
d_last_addr = busaddr(pc) | BusAddr::Fetch | busaddr::SU(curmd->IsSuper());
}
// 逆アセンブル: d [[SU:]<addr> [<cnt>]] (論理)
Debugger::CmdAct
Debugger::cmd_d()
{
return cmd_d_common(BusAddr::Logical);
}
// 逆アセンブル: D [<addr> [<cnt>]] (物理)
Debugger::CmdAct
Debugger::cmd_D()
{
return cmd_d_common(BusAddr::Physical);
}
// 逆アセンブル共通
Debugger::CmdAct
Debugger::cmd_d_common(busaddr access_mode)
{
busaddr saddr;
int row;
row = 10;
// 開始アドレス。S/U は GetAddr() がセットする。
saddr = d_last_addr;
if (GetAddr(&saddr, false) == false) {
return CmdAct::Stay;
}
// 論理アドレス/物理アドレスはコマンドによって決まる。
saddr |= access_mode;
// 2つ目の引数があれば行数
if (cmdargs.size() > 2) {
row = strtol(cmdargs[2].c_str(), NULL, 10);
}
// アドレス幅を制限 (XP用) (ここ?)
uint64 mask;
if (saddr.IsPhysical()) {
mask = (1ULL << curmd->GetPASize()) - 1;
} else {
mask = (1ULL << curmd->GetLASize()) - 1;
}
saddr &= (uint32)mask;
Memdump disasm(curmd, curmd->arch);
disasm.SetAddr(saddr);
disasm.Print(cons, row);
// アドレスを次回継続用に保存
d_last_addr = disasm.GetAddr();
return CmdAct::Stay;
}
// 表示レジスタ選択
// disp 現在の内容を表示
// disp <regs>... 設定
Debugger::CmdAct
Debugger::cmd_disp()
{
if (cmdargs.size() < 2) {
// 表示
bool first = true;
for (const auto& r : disp_regs) {
fprintf(cons, "%s%s", (first ? "" : ","), r.c_str());
first = false;
}
fprintf(cons, "\n");
return CmdAct::Stay;
}
// 設定 (引数を分解する)
char buf[256];
char *p;
char *last;
strlcpy(buf, cmdargs[1].c_str(), sizeof(buf));
disp_regs.clear();
for (p = strtok_r(buf, ",", &last); p; p = strtok_r(NULL, ",", &last)) {
disp_regs.push_back(std::string(p));
}
// XXX チェック
return CmdAct::Stay;
}
// ログレベル設定: L <name>[=<level>][...]
Debugger::CmdAct
Debugger::cmd_L()
{
if (cmdargs.size() < 2) {
fprintf(cons, "Usage: L <name1>[=<level1>][,<name2>[=<level2>]]...\n");
return CmdAct::Stay;
}
// 一旦全部つなげる。
// help が混ざる場合の "L foo=1,help" と "L foo=1 help" を同じ動作に
// するため。
std::string str;
for (int i = 1, ac = cmdargs.size(); i < ac; i++) {
if (str.empty() == false) {
str += ',';
}
str += cmdargs[i];
}
// で、再分解
std::vector<std::string> items = string_split(str, ',');
// "help" があれば一覧を表示
for (const auto& item : items) {
if (item == "help") {
std::vector<std::string> list = MainApp::GetLogNames();
for (const auto& name : list) {
fprintf(cons, " %s\n", name.c_str());
}
return CmdAct::Stay;
}
}
// ログレベルを設定
std::string errmsg;
if (MainApp::SetLogopt(items, &errmsg) == false) {
fprintf(cons, "%s\n", errmsg.c_str());
return CmdAct::Stay;
}
return CmdAct::Stay;
}
// メモリダンプ: m [<addr> [<cnt>]] (論理)
Debugger::CmdAct
Debugger::cmd_m()
{
return cmd_m_common(BusAddr::Logical);
}
// メモリダンプ: M [<addr> [<cnt>]] (物理)
Debugger::CmdAct
Debugger::cmd_M()
{
return cmd_m_common(BusAddr::Physical);
}
// メモリダンプ共通
Debugger::CmdAct
Debugger::cmd_m_common(busaddr access_mode)
{
busaddr saddr;
int row;
row = 8;
// 開始アドレス。S/U は GetAddr() がセットする。
saddr = m_last_addr;
if (GetAddr(&saddr, true) == false) {
return CmdAct::Stay;
}
// 論理アドレス/物理アドレスはコマンドによって決まる。
saddr |= access_mode;
// 2つ目の引数があれば行数
if (cmdargs.size() > 2) {
row = strtol(cmdargs[2].c_str(), NULL, 10);
}
// アドレス幅を制限 (XP用) (ここ?)
uint64 mask;
if (saddr.IsPhysical()) {
mask = (1ULL << curmd->GetPASize()) - 1;
} else {
mask = (1ULL << curmd->GetLASize()) - 1;
}
saddr &= (uint32)mask;
Memdump memdump(curmd);
memdump.SetAddr(saddr);
memdump.SetFormat(Memdump::Word);
memdump.Print(cons, row);
// アドレスを次回継続用に保存
m_last_addr = memdump.GetAddr();
return CmdAct::Stay;
}
// プロンプトに来た最初に表示するいつものやつを再表示する
Debugger::CmdAct
Debugger::cmd_minus()
{
// 指定のレジスタ群を表示
// disp_regs はレジスタ群のリスト { "r", "rf" } のような感じ。
// 一方 ShowRegisters() の第2引数は1つのコマンドラインを空白で区切った
// 文字列リスト { arg[0], arg[1], .. }。型が同じで意味が違うので注意。
for (const auto& reg : disp_regs) {
std::vector<std::string> tmpargs;
tmpargs.push_back(reg);
curmd->ShowRegister(cons, tmpargs);
}
// 現在の PC の位置を逆アセンブル。
std::string addrbuf;
std::string textbuf;
busaddr laddr = busaddr(pc)
| BusAddr::Fetch
| busaddr::SU(curmd->IsSuper());
DebuggerMemoryStream mem(curmd, laddr);
// アドレス
if (mem.FormatAddr(addrbuf) == false) {
fprintf(cons, "%s\n", addrbuf.c_str());
return CmdAct::Stay;
}
// オンライン用に逆アセンブル
textbuf = curmd->Disassemble(mem);
fprintf(cons, "%-18s %s\n", addrbuf.c_str(), textbuf.c_str());
return CmdAct::Stay;
}
// サブルーチンとループを飛ばしながら count 命令ステップ実行
// n [<count>?:1] (途中レジスタを表示しない)
Debugger::CmdAct
Debugger::cmd_n()
{
return cmd_n_common(false);
}
// サブルーチンとループを飛ばしながら count 命令ステップ実行
// nt [<count>?:1] (1命令ずつレジスタを表示する)
Debugger::CmdAct
Debugger::cmd_nt()
{
return cmd_n_common(true);
}
// n と nt の共通部分
Debugger::CmdAct
Debugger::cmd_n_common(bool trace)
{
t_enable = trace ? curmd : NULL;
if (cmdargs.size() > 1) {
int count;
count = strtol(cmdargs[1].c_str(), NULL, 10);
if (count < 1) {
fprintf(cons, "Invalid step count: %d\n", count);
return CmdAct::Stay;
}
step_count = count;
} else {
step_count = 1;
}
step_type = StepType::CountSkipSub;
step_md = curmd;
SetNBreakpoint();
return CmdAct::Leave;
}
// n コマンド用のブレークポイントを設定する。
// この命令語によって仕掛けるブレークポイントが変わるので、都度都度
// 呼び出すこと。
void
Debugger::SetNBreakpoint()
{
busaddr laddr = busaddr(curmd->GetPC())
| BusAddr::Fetch
| busaddr::SU(curmd->IsSuper());
DebuggerMemoryStream mem(curmd, laddr);
// 命令がサブルーチンやトラップなどステップインできる命令か。
if (curmd->IsOpStepIn(mem)) {
// この次の命令にブレークをかける
step_addr = curmd->GetNextPC();
} else {
// そうでなければ1命令進める。
step_addr = (int64)-1;
}
}
// 例外フレームを表示する。
Debugger::CmdAct
Debugger::cmd_pe()
{
busaddr saddr;
saddr = busaddr(curmd->GetRegAddr("sp"));
if (cmdargs.size() > 1) {
if (GetAddr(&saddr, true) == false) {
return CmdAct::Stay;
}
}
// 例外フレームなのでスーパーバイザ、データ空間読み込み固定。
saddr |= BusAddr::SRead;
// 今のところ論理アドレスのみ(?)
saddr |= BusAddr::Logical;
curmd->PrintExceptionFrame(cons, saddr);
return CmdAct::Stay;
}
// FSAVE フレームを表示する。
Debugger::CmdAct
Debugger::cmd_pf()
{
busaddr saddr;
if (cmdargs.size() < 2) {
fprintf(cons, "Usage: pf <address>\n");
return CmdAct::Stay;
}
if (GetAddr(&saddr, true) == false) {
return CmdAct::Stay;
}
// スーパーバイザ、データ空間固定でいいはず。
saddr |= BusAddr::SRead;
// 今のところ論理アドレスのみ(?)
saddr |= BusAddr::Logical;
// m68k 専用。
auto mdm68k = dynamic_cast<DebuggerMD_m680x0 *>(curmd);
if (mdm68k) {
mdm68k->PrintFPStateFrame(cons, saddr);
} else {
fprintf(cons, "Not supported.\n");
}
return CmdAct::Stay;
}
// デバッガ終了コマンド
Debugger::CmdAct
Debugger::cmd_q()
{
fprintf(cons, "Quit debugger console.\n");
return CmdAct::Quit;
}
// VM リセット
Debugger::CmdAct
Debugger::cmd_reset()
{
GetPowerDevice()->MakeResetHard();
return CmdAct::Leave;
}
// モニタ表示
Debugger::CmdAct
Debugger::cmd_show()
{
if (cmdargs.size() != 2) {
std::vector<uint> list;
// 登録されているモニタだけを列挙
// (登録されていてもサブウィンドウの ID を持っているものは除外)
for (const auto mon : gMonitorManager->GetList()) {
uint id = mon->GetId();
if (id <= ID_MONITOR_END) {
list.push_back(id);
}
}
// 一覧を表示
fprintf(cons, "Usage: show <monitor>\n");
std::vector<std::string> names = gMonitorManager->MakeListString(list);
MainApp::ShowHelpList(cons, names);
return CmdAct::Stay;
}
std::vector<Monitor *> candidates;
std::vector<std::string> cand_names;
std::string name = cmdargs[1];
for (auto mon : gMonitorManager->GetList()) {
uint id = mon->GetId();
const auto& aliases = gMonitorManager->GetAliases(id);
bool matched = false;
for (const auto& alias : aliases) {
// 部分一致したら名前はすべて覚えておく
if (starts_with_ignorecase(alias, name)) {
cand_names.push_back(alias);
matched = true;
}
}
// 同じモニタで別名が複数回部分一致しても、
// モニタのほうは1回として数える
if (matched) {
candidates.push_back(mon);
}
}
if (candidates.empty()) {
// 一致しない
fprintf(cons, "Unknown monitor name: \"%s\"\n", name.c_str());
} else if (candidates.size() == 1) {
// 確定した
Monitor *mon = candidates[0];
ShowMonitor(mon);
} else {
// 候補が複数あった
std::string candstr;
for (const auto& cname : cand_names) {
candstr += ' ';
candstr += cname;
}
fprintf(cons, "Ambiguous monitor name \"%s\". Candidates are:%s\n",
name.c_str(), candstr.c_str());
}
return CmdAct::Stay;
}
// モニタを更新して表示。
void
Debugger::ShowMonitor(Monitor *monitor)
{
// モニタを更新
TextScreen ts;
auto size = monitor->GetSize();
ts.Init(size.width, size.height);
// テキストだけで表示
monitor->UpdateScreen(ts);
ShowTextScreen(ts);
}
// テキストスクリーンを表示。
void
Debugger::ShowTextScreen(TextScreen& monitor)
{
char sbuf[1024]; // 適当
// monitor 内部バッファから ShiftJIS バッファを作成。
// その際属性もエスケープシーケンスで再現する。
int col = monitor.GetCol();
int row = monitor.GetRow();
const std::vector<uint16>& src = monitor.GetBuf();
for (int y = 0; y < row; y++) {
int sy = y * col;
int sx;
uint attr;
char *d;
memset(sbuf, 0, sizeof(sbuf));
d = sbuf;
attr = TA::Normal;
for (sx = 0; sx < col; sx++) {
uint a = src[sy + sx] & 0xff00;
uint ch = src[sy + sx] & 0x00ff;
// 属性の変わり目
if (attr != a) {
switch (a) {
case TA::Normal:
case TA::Off:
*d++ = 0x1b;
*d++ = '[';
*d++ = 'm';
break;
case TA::Reverse:
case TA::ReverseRed:
case TA::ReverseGreen:
case TA::ReverseOrange:
*d++ = 0x1b;
*d++ = '[';
*d++ = '7';
*d++ = 'm';
break;
case TA::Disable:
*d++ = 0x1b;
*d++ = '[';
*d++ = '2';
*d++ = 'm';
break;
case TA::Em:
*d++ = 0x1b;
*d++ = '[';
*d++ = '1';
*d++ = 'm';
break;
}
attr = a;
}
*d++ = ch;
}
// 行の終わりで一旦属性を戻しておく
if (attr != TA::Normal) {
*d++ = 0x1b;
*d++ = '[';
*d++ = 'm';
}
*d = '\0';
// XXX 日本語が使われてれば UTF-8 にしないといけないはず
fprintf(cons, "%s\n", sbuf);
}
}
// ステップ実行
// s [<count>?:1] (途中レジスタを表示しない)
Debugger::CmdAct
Debugger::cmd_s()
{
return cmd_s_common(false);
}
// ステップ実行
// st [<count>?:1] (命令ごとにレジスタを表示する)
Debugger::CmdAct
Debugger::cmd_st()
{
return cmd_s_common(true);
}
// ステップ実行共通部分
Debugger::CmdAct
Debugger::cmd_s_common(bool trace)
{
t_enable = trace ? curmd : NULL;
if (cmdargs.size() > 1) {
int count;
count = strtol(cmdargs[1].c_str(), NULL, 10);
if (count < 1) {
fprintf(cons, "Invalid step count: %d\n", count);
return CmdAct::Stay;
}
step_count = count;
} else {
step_count = 1;
}
step_type = StepType::Count;
step_md = curmd;
return CmdAct::Leave;
}
// ステップアウト (途中レジスタを表示しない)
Debugger::CmdAct
Debugger::cmd_so()
{
return cmd_so_common(false);
}
// ステップアウト (命令ごとにレジスタを表示する)
Debugger::CmdAct
Debugger::cmd_sot()
{
return cmd_so_common(true);
}
// ステップアウトの共通部分
Debugger::CmdAct
Debugger::cmd_so_common(bool trace)
{
t_enable = trace ? curmd : NULL;
step_type = StepType::StepOut;
step_md = curmd;
step_md->SetStepOut();
return CmdAct::Leave;
}
// t は st の省略形。互換のため。
Debugger::CmdAct
Debugger::cmd_t()
{
return cmd_st();
}
// 引数で示されるプレフィックスなしの16進数値を返す。
// 正しく変換できれば値を valp に格納して true を返す。
// そうでなければ false を返す。
bool
Debugger::ParseVerbHex(const char *arg, uint32 *valp)
{
uint32 val;
char *end;
errno = 0;
val = strtoul(arg, &end, 16);
if (arg[0] == '\0' || end[0] != '\0') {
return false;
}
if (errno == ERANGE) {
return false;
}
*valp = val;
return true;
}
// 引数で示されるアドレスを返す。
// '%' で始まればレジスタ。
// そうでなければ16進数値。
// アドレスが正しく取得できればアドレスを addr に格納し真を返す。
// そうでなければエラーメッセージを表示して偽を返す。
bool
Debugger::ParseAddr(const char *arg, uint32 *addrp)
{
uint64 addr;
char *end;
if (arg[0] == '%') {
// '%' から始まればレジスタ
addr = curmd->GetRegAddr(arg + 1);
if ((int64)addr < 0) {
fprintf(cons, "Invalid register name.\n");
return false;
}
} else {
/* そうでなければ番地指定 */
errno = 0;
addr = strtoul(arg, &end, 16);
if (end == arg || *end != '\0') {
fprintf(cons, "Invalid address: '%s'\n", arg);
return false;
}
if (addr > 0xffffffff) {
fprintf(cons, "Out of range: %s\n", arg);
return false;
}
}
*addrp = (uint32)addr;
return true;
}
// 引数で示される時間間隔を返す。
bool
Debugger::ParseTime(const char *arg, uint64 *timep)
{
double f;
uint64 t;
char *end;
errno = 0;
f = strtod(arg, &end);
if (end == arg) {
fprintf(cons, "Invalid time: %s\n", arg);
return false;
}
if (errno == ERANGE || f < 0 || std::isinf(f) || std::isnan(f)) {
fprintf(cons, "Out of range: %s\n", arg);
return false;
}
// 単位省略は nsec とする。
// 接尾語は "nsec" とかの他 C++ 書式で使ってる "_nsec" も受け付けておく。
if (*end == '\0' || strcmp(end, "nsec") == 0 || strcmp(end, "_nsec") == 0) {
t = f * 1_nsec;
} else if (strcmp(end, "usec") == 0 || strcmp(end, "_usec") == 0) {
t = f * 1_usec;
} else if (strcmp(end, "msec") == 0 || strcmp(end, "_msec") == 0) {
t = f * 1_msec;
} else if (strcmp(end, "sec") == 0 || strcmp(end, "_sec") == 0) {
t = f * 1_sec;
} else {
fprintf(cons, "Unknown time suffix: %s\n", end);
return false;
}
*timep = t;
return true;
}
//
// MD
//
// コンストラクタ
DebuggerMD::DebuggerMD(Debugger *parent_, CPUArch arch_)
{
parent = parent_;
arch = arch_;
}
// デストラクタ
DebuggerMD::~DebuggerMD()
{
}
// 現在の命令の直後の命令アドレスを返す。
// 命令が可変長のアーキテクチャなら逆アセンブルしてみるしかない。
// 命令が固定長のアーキテクチャはオーバーライドして解決のこと。
uint32
DebuggerMD::GetNextPC()
{
busaddr laddr = busaddr(GetPC())
| BusAddr::Fetch
| busaddr::SU(IsSuper());
DebuggerMemoryStream mem(this, laddr);
// XXX 失敗したらどうすべ
Disassemble(mem);
return mem.laddr.Addr();
}
void
DebuggerMD::PrintExceptionFrame(FILE *out, busaddr)
{
fprintf(out, "Not supported\n");
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.