--- nono/debugger/memdump.cpp 2026/04/29 17:04:28 1.1.1.1 +++ nono/debugger/memdump.cpp 2026/04/29 17:05:24 1.1.1.3 @@ -1,198 +1,676 @@ // // nono -// Copyright (C) 2019 isaki@NetBSD.org +// Copyright (C) 2022 nono project +// Licensed under nono-license.txt // -// メモリダンプと逆アセンブル + +// +// メモリダンプ/逆アセンブル // -#include "header.h" -#include "mystring.h" #include "memdump.h" -#include "m68030core.h" -#include "bus.h" +#include "debugger_memory.h" +#include "debugger_private.h" +#include "hd64180disasm.h" +#include "m68030disasm.h" +#include "m88100disasm.h" +#include "mainbus.h" // -// 逆アセンブル +// メモリダンプ (基本クラス) // -DisasmLine::DisasmLine(m68kcpu *arg, uint32 addr, MemdumpMode mode_) + +// コンストラクタ +Memdump::Memdump(int objid_) + : inherited(objid_) +{ + // ログは不要 + ClearAlias(); + + // 初期値 + saddr = busaddr::Read | busaddr::S | busaddr::Physical; + SetFormat(Word); +} + +// コンストラクタ (メモリダンプ用にデバッガから呼ばれるほう) +Memdump::Memdump(DebuggerMD *md_) + : Memdump(OBJ_NONE) // 移譲 +{ + InitMD(md_); +} + +// コンストラクタ (逆アセンブル用にデバッガから呼ばれるほう) +Memdump::Memdump(DebuggerMD *md_, CPUArch asm_arch) + : Memdump(OBJ_NONE) // 移譲 +{ + InitMD(md_); + + // asm_arch を Format に変換してセット + Format fmt_; + switch (asm_arch) { + case CPUArch::M680x0: + fmt_ = M68030Disasm; + break; + case CPUArch::M88xx0: + fmt_ = M88100Disasm; + break; + case CPUArch::HD64180: + fmt_ = HD64180Disasm; + break; + default: + PANIC("corrupted asm_arch=%d", (int)asm_arch); + } + SetFormat(fmt_); +} + +// デストラクタ +Memdump::~Memdump() +{ +} + +// md を初期化(代入)する。 +void +Memdump::InitMD(DebuggerMD *md_) +{ + md = md_; + bus = md->GetBus(); +} + +// busaddr を設定する。 +void +Memdump::SetAddr(busaddr saddr_) +{ + saddr = saddr_; + MaskAddr(); +} + +// アドレスを設定する。 +void +Memdump::SetAddr(uint32 laddr_) +{ + saddr.SetAddr(laddr_); + MaskAddr(); +} + +// 今の表示形式での境界に揃える。 +void +Memdump::MaskAddr() +{ + saddr &= ~(GetStride() - 1); +} + +// アドレスをバイト単位で加算(減算)する。 +void +Memdump::Offset(int bytes) { - cpu = arg; - // 開始アドレス。必ず偶数アドレスから - laddr = addr & 0xfffffffe; - mode = mode_; - // XXX TODO: FC どうするか - fc2 = cpu->reg.s ? FC_SUPER : FC_USER; + saddr += (uint32)bytes; +} - // 論理アドレス指定でも MMU オフなら物理アドレス扱いでいいだろう - is_logical = (mode != Physical && cpu->mmu_enable); +// 表示フォーマットを設定する。 +void +Memdump::SetFormat(Memdump::Format fmt_) +{ + fmt = fmt_; + + line.reset(); + fixed = 0; + inst_bytes = 0; + + switch (fmt) { + case Byte: + case Word: + case Long: + fixed = 16; + break; + case M68030PageShort: + case M88200Page: + fixed = 4; + break; + case M68030PageLong: + fixed = 8; + break; + + case M68030Disasm: + line.reset(new m680x0disasm()); + inst_bytes = 2; + break; + case M88100Disasm: + line.reset(new m88100disasm()); + inst_bytes = 4; + fixed = 4; // 固定長フォーマットでもある + break; + case HD64180Disasm: + line.reset(new hd64180disasm()); + inst_bytes = 1; + break; + } - // 奇数にしておくことで初回に必ずアドレス変換を起こさせる。 - last_lpage = 0xffffffff; - last_fc2 = 0xffffffff; + // 表示形式を切り替えたことによってアドレスに端数が出るのを防ぐ + MaskAddr(); } -// 1行出力 +// 物理メモリ paddr 以降の内容を越権的に読み出す。bytes は 1, 2, 4 を想定。 +// バスエラーなら負数を返す。 +uint64 +Memdump::Peek(uint32 paddr, int bytes) const +{ + uint32 data = 0; + + while (--bytes >= 0) { + busdata bd = bus->Peek8(paddr++); + if (__predict_false(bd.IsBusErr())) { + return bd; + } + data = (data << 8) | bd.Data(); + } + return data; +} + +// 物理メモリ paddr 以降の内容を越権的に書き換える。 +// bytes は 1, 2, 4 を想定しており、paddr はこれに整列しているはず。 +// バスエラーが起きたらその場で処理を中止して false を返す。 +bool +Memdump::Poke(uint32 paddr, uint32 data, int bytes) +{ + while (--bytes >= 0) { + uint32 d = data >> (bytes * 8); + if (bus->Poke8(paddr++, d & 0xff) == false) { + return false; + } + } + + return true; +} + +// この paddr が Poke() 可能なら true を返す。 +bool +Memdump::CanPoke(uint32 paddr) const +{ + return bus->Poke8(paddr, -1); +} + +// コンソールに出力。 +// 出力後 saddr は更新されている。 +void +Memdump::Print(FILE *cons, int row) +{ + DebuggerMemoryStream mem(md, saddr); + + for (int y = 0; y < row; y++) { + std::string addrbuf; + bool accessible; + + accessible = mem.FormatAddr(addrbuf); + if (accessible == false) { + fprintf(cons, "%s\n", addrbuf.c_str()); + mem.Offset(GetStride()); + continue; + } + // addrbuf は "VA:" もしくは "VA:PA:" の形式で返ってくる。 + // 本来は、どちらの形式であってもダンプは同じ桁から始めたいが、 + // バイト表示では 80桁に収まらない。 + // VA!=PA の場合はどうやっても 80桁に収まらないので諦めてそのまま + // 出力するが、VA==PA の時は PA 用の空き地を削れば 80桁に収められる + // ので、その努力はしてみる。 + if (fmt == Byte) { + fprintf(cons, "%s ", addrbuf.c_str()); + } else { + fprintf(cons, "%-18s ", addrbuf.c_str()); + } + + switch (fmt) { + case Byte: + case Word: + case Long: + { + std::vector vec = Read(mem, fixed); + std::string dumpbuf = DumpHex(vec); + std::string charbuf = DumpChar(vec); + // 左詰めでいいか + fprintf(cons, "%s %s\n", dumpbuf.c_str(), charbuf.c_str()); + break; + } + + case M68030PageShort: + { + std::vector vec = Read(mem, fixed); + std::string dumpbuf = Dump68030PageShort(vec); + // 文字ダンプどうすべ + fprintf(cons, "%s\n", dumpbuf.c_str()); + break; + } + case M88200Page: + { + std::vector vec = Read(mem, fixed); + std::string dumpbuf = Dump88200Page(vec); + // 文字ダンプどうすべ + fprintf(cons, "%s\n", dumpbuf.c_str()); + break; + } + + case M68030Disasm: + case M88100Disasm: + case HD64180Disasm: + line->Exec(&mem); + fprintf(cons, "%s%s\n", line->dump.c_str(), line->text.c_str()); + break; + + default: + fprintf(cons, "fmt=%d not supported\n", (int)fmt); + mem.Offset(GetStride()); + break; + } + } + + // アドレスを更新 + saddr = mem.laddr; +} + +// メモリストリームから bytes バイト分読み込んでベクタにして返す。 +// 各要素はバイトデータで、バスエラーは -1 になる。 +std::vector +Memdump::Read(DebuggerMemoryStream& mem, int bytes) +{ + std::vector vec(bytes); + + for (int i = 0; i < bytes; i++) { + vec[i] = mem.Read8(); + } + + return vec; +} + +// vec[] の文字ダンプ文字列を返す。 std::string -DisasmLine::Output() +Memdump::DumpChar(const std::vector& vec) { - std::string addrstr; - std::string dumpstr; + std::string charbuf; - // アドレス - if (is_logical) { - // 論理アドレスなら、ページ境界(と初回)でアドレス変換 - uint32 lpage = laddr & cpu->mmu_tc.lmask; - if (lpage != last_lpage || fc2 != last_fc2) { - last_lpage = lpage; - last_fc2 = fc2; - bool do_search = (mode == Logical_search); - paddr = m68030_mmu_translate_peek(cpu, laddr, fc2 | FC_PROG, - do_search); + for (auto c : vec) { + if (c < 0) { + charbuf += ' '; + } else { + charbuf += (0x20 <= c && c <= 0x7e) ? c : '.'; } - } else { - // 物理アドレス - paddr = laddr; } - // アドレス表示 - addrstr = string_format("%08x", laddr); - if (is_logical == false) { - // 物理アクセス - addrstr += ":"; - } else if ((int64)paddr < 0) { - // MMU 時点でバスエラー - addrstr += ":BusErr"; - } else if (paddr == laddr) { - // PA==VA - addrstr += "=PA"; - } else { - // PA!=VA - addrstr += string_format(":%08x:", (uint32)paddr); + return charbuf; +} + +// vec[] の HEX ダンプ文字列を返す。 +// CLI, GUI 共通。 +std::string +Memdump::DumpHex(const std::vector& vec) +{ + std::string dumpbuf; + int span; + + switch (fmt) { + case Byte: span = 1; break; + case Word: span = 2; break; + case Long: span = 4; break; + default: + PANIC("corrupted fmt=%d", (int)fmt); } - // 逆アセンブル - if ((int64)paddr >= 0) { - dumpstr = disasm.DisassembleWithDump(laddr, paddr); - uint32 len = disasm.GetLength(); - paddr += len; - laddr += len; - } else { - // 出来なかったらここでアドレスを増やさず打ち切り + int s = span; + for (auto c : vec) { + if (c < 0) { + dumpbuf += "--"; + } else { + dumpbuf += strhex(c, 2); + } + + if (--s == 0) { + dumpbuf += ' '; + s = span; + } + } + + return dumpbuf; +} + +// 68030 のショートフォーマットディスクリプタのダンプ文字列を返す。 +std::string +Memdump::Dump68030PageShort(const std::vector& vec) +{ + std::string dumpbuf; + + // 2 3 4 5 6 7 + // 9012345678901234567890123456789012345678901234567890123 + // 00000000 DT=0(Invalid) + // ffffff00 DT=1(Page) $00000000 CI M U WP + // fffffff0 DT=2(Next Short) $00000000 U WP + + uint32 data = 0; + for (auto b : vec) { + if (b < 0) { + return "BusErr"; + } + data = (data << 8) | (uint8)b; + } + + uint32 dt = (data & 0x03); + dumpbuf = string_format("%08x DT=%d%s", data, dt, dtname[dt]); + + if (dt != 0) { + if (dt == 1) { + dumpbuf += string_format(" $%08x %s %c", + (data & 0xffff'ff00), + (data & 0x40) ? "CI" : "- ", + (data & 0x10) ? 'M' : '-'); + } else { + dumpbuf += string_format(" $%08x ", data & 0xffff'fff0); + } + dumpbuf += string_format(" %c %s", + (data & 0x08) ? 'U' : '-', + (data & 0x04) ? "WP" : "- "); } - std::string rv = string_format("%-18s %s", - addrstr.c_str(), dumpstr.c_str()); - return rv; + return dumpbuf; } +// 88200 のセグメントディスクリプタとページディスクリプタ +std::string +Memdump::Dump88200Page(const std::vector& vec) +{ + std::string dumpbuf; + + // 2 3 4 5 6 7 + // 9012345678901234567890123456789012345678901234567890123 + // 00000000 $00000'000 WT SP G CI M U WP V + + uint32 data = 0; + for (auto b : vec) { + if (b < 0) { + return "BusErr"; + } + data = (data << 8) | (uint8)b; + } + + dumpbuf = string_format("%08x $%05x'000 %s %s %c %s %c %c %s %c", + data, + (data >> 12), + (data & 0x200) ? "WT" : "- ", + (data & 0x100) ? "SP" : "- ", + (data & 0x080) ? 'G' : '-', + (data & 0x040) ? "CI" : "- ", + (data & 0x010) ? 'M' : '-', + (data & 0x008) ? 'U' : '-', + (data & 0x004) ? "WP" : "- ", + (data & 0x001) ? 'V' : '-'); + + return dumpbuf; +} + +/*static*/ const char * const +Memdump::dtname[4] = { + "(Invalid) ", + "(Page) ", + "(Next Short)", + "(Next Long) ", +}; + // -// メモリダンプ +// メモリダンプモニタ // // コンストラクタ -MemdumpLine::MemdumpLine(m68kcpu *arg, uint32 addr, MemdumpMode mode_) +MemdumpMonitor::MemdumpMonitor(int objid_, int monid_) + : inherited(objid_) +{ + monitor.func = ToMonitorCallback(&MemdumpMonitor::MonitorUpdate); + monitor.SetSize(84, 16); + monitor.Regist(monid_); +} + +// デストラクタ +MemdumpMonitor::~MemdumpMonitor() +{ +} + +// モニタ更新 +void +MemdumpMonitor::MonitorUpdate(Monitor *, TextScreen& screen) { - cpu = arg; - // 開始アドレス。必ず偶数アドレスから - laddr = addr & 0xfffffffe; - mode = mode_; - // XXX TODO: FC どうするか - fc2 = cpu->reg.s ? FC_SUPER : FC_USER; + DebuggerMemoryStream mem(md, saddr); - // 論理アドレス指定でも MMU オフなら物理アドレス扱いでいいだろう - is_logical = (mode != Physical && cpu->mmu_enable); + screen.Clear(); - // 奇数にしておくことで初回に必ずアドレス変換を起こさせる。 - last_lpage = 0xffffffff; - last_fc2 = 0xffffffff; + switch (fmt) { + case Byte: + case Word: + case Long: + case M68030PageShort: + case M88200Page: + MonitorUpdateMemdump(screen, mem); + break; + + case M68030PageLong: + screen.Puts(0, 0, "68030PageLong not supported"); + break; + + case M68030Disasm: + case M88100Disasm: + case HD64180Disasm: + MonitorUpdateDisasm(screen, mem); + break; + } } -// 1行出力 -std::string -MemdumpLine::Output() +// モニタ更新 (メモリダンプ、ディスクリプタの場合) +void +MemdumpMonitor::MonitorUpdateMemdump(TextScreen& screen, + DebuggerMemoryStream& mem) +{ + // モニタでは 80桁を超えて必要サイズ確保してあるので + // "VA:" も "VA:PA:" も同じ幅を占有し、 + // ダンプフィールドは常に 19桁目から開始する。(see Print()) + int dx = 19; + + // 文字列ダンプは桁数によらず右詰め + int cx = screen.GetCol() - GetStride(); + + int row = screen.GetRow(); + for (int y = 0; y < row; y++) { + std::string addrbuf; + bool accessible; + + // アドレス欄 + accessible = mem.FormatAddr(addrbuf); + screen.Puts(0, y, addrbuf.c_str()); + if (accessible == false) { + mem.Offset(GetStride()); + continue; + } + + // 文字ダンプ + std::vector vec = Read(mem, GetStride()); + std::string charbuf = DumpChar(vec); + screen.Puts(cx, y, charbuf.c_str()); + + // ダンプ本体 + switch (fmt) { + case Byte: + case Word: + case Long: + { + std::string dumpbuf = DumpHex(vec); + screen.Puts(dx, y, dumpbuf.c_str()); + break; + } + case M68030PageShort: + Update68030PageShort(screen, dx, y, vec); + break; + case M88200Page: + Update88200Page(screen, dx, y, vec); + break; + default: + PANIC("corrupted fmt=%d", (int)fmt); + } + } +} + +// (x, y) に 68030 ページディスクリプタのダンプを出力する。 +void +MemdumpMonitor::Update68030PageShort(TextScreen& screen, int x, int y, + const std::vector& vec) { - std::string addrstr; - std::string hexstr; - std::string chrstr; - - for (int i = 0; i < 8; i++) { - if (is_logical) { - // 論理アドレスなら、ページ境界(と初回)でアドレス変換 - uint32 lpage = laddr & cpu->mmu_tc.lmask; - if (lpage != last_lpage || fc2 != last_fc2) { - last_lpage = lpage; - last_fc2 = fc2; - bool do_search = (mode == Logical_search); - // XXX FC どうする? - // XXX データATCを引くかプログラムATC を引くかもある - paddr = m68030_mmu_translate_peek(cpu, laddr, fc2 | FC_DATA, - do_search); - } - } else { - // 物理アドレス - paddr = laddr; - } - - // 行頭でアドレス表示 - // XXX 開始アドレスが16バイトに整列していない場合で、対応する - // 物理ページがないアドレスから対応する物理ページがあるアドレスに - // 切り替わるところは、次の行になるまで対応する物理アドレスが - // 表示できないけどどうするか。 - if (i == 0) { - addrstr = string_format("%08x", laddr); - if (is_logical == false) { - // 物理アクセス - addrstr += ":"; - } else if ((int64)paddr < 0) { - // MMU 時点でバスエラー - addrstr += ":BusErr"; - } else if (paddr == laddr) { - // PA==VA - addrstr += "=PA"; - } else { - // PA!=VA - addrstr += string_format(":%08x:", (uint32)paddr); - } - } - - // ダンプ - if ((int64)paddr < 0) { - // 何も表示しない - // ここで paddr は無効なので進めてはいけない - laddr += 2; - } else { - uint64 b1 = m68030_phys_peek_8(paddr); - uint64 b2 = m68030_phys_peek_8(paddr + 1); - - // 偶数バイト目 - if ((int64)b1 < 0) { - hexstr += "--"; - chrstr += " "; - } else { - uint32 c1 = (uint32)b1; - hexstr += string_format("%02x", c1); - chrstr += string_format("%c", - (0x20 <= c1 && c1 <= 0x7e) ? c1 : '.'); - } - // 奇数バイト目 - if ((int64)b2 < 0) { - hexstr += "--"; - chrstr += " "; - } else { - uint32 c2 = (uint32)b2; - hexstr += string_format("%02x", c2); - chrstr += string_format("%c", - (0x20 <= c2 && c2 <= 0x7e) ? c2 : '.'); - } - hexstr += " "; - - laddr += 2; - paddr += 2; - } - } - - std::string rv = string_format("%-18s %-40s %s", - addrstr.c_str(), hexstr.c_str(), chrstr.c_str()); - return rv; + // 2 3 4 5 6 7 + // 9012345678901234567890123456789012345678901234567890123 + // 00000000 $00000000 CI M U WP DT=1(Page) + // 00000000 $00000000 U WP DT=2(Next Short) + + uint32 data = 0; + for (auto b : vec) { + if (b < 0) { + screen.Puts(x, y, "BusErr"); + return; + } + data = (data << 8) | (uint8)b; + } + + screen.Puts(x, y, strhex(data).c_str()); + + uint32 dt = (data & 0x03); + TA attr = (dt != 0) ? TA::Normal : TA::Disable; + + if (dt == 1) { + screen.Putc(x + 10, y, attr, '$'); + screen.Puts(x + 11, y, attr, strhex(data & 0xffff'ff00).c_str()); + screen.Puts(x + 20, y, attr, (data & 0x40) ? "CI" : "-"); + screen.Putc(x + 23, y, attr, (data & 0x10) ? 'M' : '-'); + } else { + screen.Putc(x + 10, y, attr, '$'); + screen.Puts(x + 11, y, attr, strhex(data & 0xffff'fff0).c_str()); + } + screen.Putc(x + 25, y, attr, (data & 0x08) ? 'U' : '-'); + screen.Puts(x + 27, y, attr, (data & 0x04) ? "WP" : "-"); + screen.Print(x + 30, y, attr, "DT=%d%s", dt, dtname[dt]); +} + +// (x, y) に 88200 ページディスクリプタのダンプを出力する。 +void +MemdumpMonitor::Update88200Page(TextScreen& screen, int x, int y, + const std::vector& vec) +{ + // 2 3 4 5 6 7 + // 9012345678901234567890123456789012345678901234567890123 + // 00000000 $00000'000 WT SP G CI M U WP V + + uint32 data = 0; + for (auto b : vec) { + if (b < 0) { + screen.Puts(x, y, "BusErr"); + return; + } + data = (data << 8) | (uint8)b; + } + + screen.Puts(x, y, strhex(data).c_str()); + + TA attr = (data & 0x001) ? TA::Normal : TA::Disable; + screen.Print(x + 10, y, attr, "$%05x'000 %s %s %c %s %c %c %s %c", + (data >> 12), + (data & 0x200) ? "WT" : "- ", + (data & 0x100) ? "SP" : "- ", + (data & 0x080) ? 'G' : '-', + (data & 0x040) ? "CI" : "- ", + (data & 0x010) ? 'M' : '-', + (data & 0x008) ? 'U' : '-', + (data & 0x004) ? "WP" : "- ", + (data & 0x001) ? 'V' : '-'); +} + +// モニタ更新 (逆アセンブルの場合) +void +MemdumpMonitor::MonitorUpdateDisasm(TextScreen& screen, + DebuggerMemoryStream& mem) +{ + // モニタでは 80桁を超えて必要サイズ確保してあるので + // "VA:" も "VA:PA:" も同じ幅を占有し、 + // ダンプフィールドは常に 19桁目から開始する。(see Print()) + int dx = 19; + + int row = screen.GetRow(); + + // 逆アセンブラ用の次行アドレスバッファを必要ならリサイズ + if ((row + 1) != nextaddr.size()) { + nextaddr.resize(row + 1); + } + + nextaddr[0] = mem.laddr.Addr(); + for (int y = 0; y < row; y++) { + std::string addrbuf; + bool accessible; + TA attr; + + // PC のいる行をボールドに + if (__predict_false((uint32)mem.laddr == md->GetPC())) { + attr = TA::Em; + } else { + attr = TA::Normal; + } + + // アドレス欄 + accessible = mem.FormatAddr(addrbuf); + screen.Puts(0, y, attr, addrbuf.c_str()); + if (accessible == false) { + mem.Offset(GetStride()); + continue; + } + + line->Exec(&mem); + screen.Print(dx, y, attr, "%s%s", + line->dump.c_str(), line->text.c_str()); + + // 次行の開始アドレスを覚えておく。 + // 逆アセンブラで n 行下へ移動する時のため。 + nextaddr[y + 1] = mem.laddr.Addr(); + } +} + +// n 行下の行までのバイトオフセットを返す。 +int +MemdumpMonitor::GetLineOffset(int n) const +{ + if (fixed) { + // 固定長フォーマットなら fixed 倍するだけで求まる。 + return n * fixed; + } else { + // 可変長フォーマット + if (0 <= n && n < nextaddr.size()) { + return nextaddr[n] - nextaddr[0]; + } else { + // なければ仕方ないので適当に返す + return n * inst_bytes; + } + } +} + +// 前ページ位置(-1)、次ページ位置(1) までのバイトオフセットを返す。 +int +MemdumpMonitor::GetPageOffset(int n) const +{ + // 前ページ/次ページ開始位置は、メモリダンプと逆アセンブルで動作を変える。 + // メモリダンプならきっちり次のページを表示してほしい。例えば 1画面が + // 16行(256バイト) で、現在 $100 にいて次ページに飛ぶ時は $200 になって + // ほしい。 + // 逆アセンブルでは実際にこの動作をすると連続性が失われて読みにくいので + // 1行だぶらせることにする。 + + int row = monitor.GetSize().height; + if (inst_bytes) { + // 逆アセンブルの場合 + if (n < 0) { + // 戻る時は、(最短)命令長分ずつ戻るしかない。 + return -inst_bytes * (row - 1); + } else { + return GetLineOffset(row - 1); + } + } else { + // メモリダンプ系の場合は単純に row 分前か後ろというだけ + return GetLineOffset(row * n); + } }