debugger: memdump through the MMU (68030/040/060)

`m [s|u] [b|w|l] <address>` reads logical addresses through the MMU as
supervisor (s) or user (u) data instead of physical memory, so a
breakpoint script can inspect a kernel stack or a process's memory. The
address space is independent of the item size; items the MMU can't
translate print as "--"; `m` without arguments continues in the same
address space; and s/u fail unless an MMU is translating. Works with the
68030, 68040 and 68060 MMUs.

The translation uses the core's debugger functions
(debug_mmu030_translate / debug_mmu_translate), which don't write the
descriptors' used/modified bits but do flush the ATC, and whose faults
stay out of the trace.

Tested with a hand-built page table on 68030/040/060 (`m u` returns the
mapped physical pages, across a page boundary; an unmapped page prints
"--"), in user processes under Atari System V (030) and m68k Linux
(040/060), and without an MMU.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
SlaapliedjeandClaude Fable 5.1 authored and Eero Tamminen committed 2026-10-19 17:08:00 +03:00
1 parent db4faac5c1
commit 31cdcca2aa
5 files changed
+178 -36

No files matched your search

+6 -2
View File
@@ -366,10 +366,14 @@ the DSP, use "dm" and "dd" commands.
<pre>
&gt; help memdump
'memdump' or 'm' - dump memory
Usage: m [b|w|l] [start address-[end address| count]]
Usage: m [s|u] [b|w|l] [start address-[end address| count]]
dump memory at address or continue dump from previous address.
By default memory output is done as bytes, with 'w' or 'l'
option, it will be done as words/longs instead. Output amount
option, it will be done as words/longs instead. With 's' or
'u' the address is a logical one, read through the MMU as
supervisor or user data, and '--' marks what the MMU can't
translate. Without arguments, the dump continues from the
previous address in the same address space. Output amount
can be given either as a count or an address range.
</pre>
<pre>
+41
View File
@@ -365,6 +365,47 @@ static int debug_out (const TCHAR *format, ...)
#endif /* ! WINUAE_FOR_HATARI */
#ifdef WINUAE_FOR_HATARI
/* Whether an emulated MMU is translating addresses at the moment
* (not before the OS enables it, e.g. from a --parse file at startup) */
bool debug_mmu_translating(void)
{
if (currprefs.mmu_model == 68030)
return (tc_030 & 0x80000000) != 0; /* TC enable */
if (currprefs.mmu_model >= 68040)
return regs.mmu_enabled;
return false;
}
/* Translate a logical data address for Hatari's debugger, as supervisor
* or user data, into *phys. Returns false when the MMU can't map it.
* The debug variants of the translation set mmu_debugger: the table walk
* leaves the descriptors' used/modified bits alone and a fault is not
* traced. */
bool debug_mmu_translate_data(uaecptr addr, bool super, uaecptr *phys)
{
struct mmu_debug_data *mdd;
flagtype olds = regs.s;
bool ok = true;
regs.s = super; /* FC_DATA follows it */
TRY(p) {
if (currprefs.mmu_model == 68030)
*phys = debug_mmu030_translate(addr, FC_DATA, false, &mdd);
else /* (addr, val, super, data, write, size, mdd) */
*phys = debug_mmu_translate(addr, 0, super, true, false, sz_byte, &mdd);
} CATCH(p) {
ok = false;
} ENDTRY
if (currprefs.mmu_model == 68030)
debug_mmu030_translate_end();
else
debug_mmu_translate_end();
regs.s = olds;
return ok;
}
#endif
uae_u32 get_byte_debug (uaecptr addr)
{
uae_u32 v = 0xff;
+5
View File
@@ -361,4 +361,9 @@ extern void mmu_do_hit (void);
#endif /* DEBUGGER */
#ifdef WINUAE_FOR_HATARI
extern bool debug_mmu_translating(void);
extern bool debug_mmu_translate_data(uaecptr addr, bool super, uaecptr *phys);
#endif
#endif /* UAE_DEBUG_H */
+114 -34
View File
@@ -33,6 +33,7 @@ const char DebugCpu_fileid[] = "Hatari debugcpu.c";
#include "symbols.h"
#include "stacktrace.h"
#include "68kDisass.h"
#include "debug.h"
#include "console.h"
#include "options.h"
#include "tos.h"
@@ -44,6 +45,10 @@ const char DebugCpu_fileid[] = "Hatari debugcpu.c";
static uint32_t disasm_addr; /* disasm address */
static uint32_t memdump_addr; /* memdump address */
/* memdump address space: physical memory, or a logical address read
* through the MMU as supervisor / user data */
enum { MEMDUMP_PHYSICAL, MEMDUMP_SUPER, MEMDUMP_USER };
static int memdump_space; /* address space of the last memdump */
static uint32_t fake_regs[8]; /* virtual debugger "registers" */
static bool bFakeRegsUsed; /* whether to show virtual regs */
@@ -468,33 +473,76 @@ static unsigned get_type_width(char mode)
}
/**
* Helper: read one memory item.
* @param addr the item's address
* @param size the size of the item in bytes
* @param space MEMDUMP_PHYSICAL to read physical memory, or MEMDUMP_SUPER
* / MEMDUMP_USER to read a logical address through the MMU
* as supervisor / user data
* @param value the item is stored here
* @return false if the MMU can't translate (part of) the item
*/
static bool read_mem_value(uint32_t addr, int size, int space, uint32_t *value)
{
uint32_t phys;
if (space == MEMDUMP_PHYSICAL)
{
switch (size)
{
case 4:
*value = STMemory_ReadLong(addr);
break;
case 2:
*value = STMemory_ReadWord(addr);
break;
case 1:
default:
*value = STMemory_ReadByte(addr);
break;
}
return true;
}
/* byte by byte, as an item can straddle two pages */
*value = 0;
for (int i = 0; i < size; i++)
{
if (!debug_mmu_translate_data(addr + i, space == MEMDUMP_SUPER, &phys))
return false;
*value = (*value << 8) | STMemory_ReadByte(phys);
}
return true;
}
/**
* Helper: print `count` `size` sized memory items from `addr` in `base`.
* @param addr the start address
* @param count the amount of items that should be printed
* @param size the size of one item
* @param base the number base
* @param space the address space, see read_mem_value()
*/
static void print_mem_values(uint32_t addr, int count, int size, int base)
static void print_mem_values(uint32_t addr, int count, int size, int base, int space)
{
const char *separator = "";
for (int i = 0; i < count; i++)
{
uint32_t value;
switch (size)
if (!read_mem_value(addr, size, space, &value))
{
case 4:
value = STMemory_ReadLong(addr);
break;
case 2:
value = STMemory_ReadWord(addr);
break;
case 1:
default:
value = STMemory_ReadByte(addr);
break;
/* the MMU can't translate it */
int width;
switch (base)
{
case 1: width = 8*size; break;
case 8: width = 3*size; break;
default: width = 2*size; break;
}
fprintf(debugOutput, "%s%.*s", separator, width,
"--------------------------------");
}
switch (base)
else switch (base)
{
case 1:
fputs(separator, debugOutput);
@@ -521,43 +569,71 @@ static void print_mem_values(uint32_t addr, int count, int size, int base)
* user-configured conversion for host character conversion.
* @param addr The ST RAM start address
* @param count the amount of bytes that should get printed
* @param space the address space, see read_mem_value()
*/
static void print_mem_chars(uint32_t addr, uint8_t count)
static void print_mem_chars(uint32_t addr, uint8_t count, int space)
{
for (int i = 0; i < count; i++)
{
Str_PrintMemChar(debugOutput, STMemory_ReadByte(addr + i));
uint32_t value;
if (read_mem_value(addr + i, 1, space, &value))
Str_PrintMemChar(debugOutput, value);
else
fputc('-', debugOutput);
}
}
/**
* Do a memory dump, args = starting address.
*/
/**
* Helper: the lower-cased mode letter if `arg` is a single non-digit
* character, else 0 (an address, a count or a symbol)
*/
static char mode_letter(const char *arg)
{
if (arg[0] && !arg[1] && !isdigit((unsigned char)arg[0]))
return tolower((unsigned char)arg[0]);
return 0;
}
int DebugCpu_MemDump(int nArgc, char *psArgs[])
{
int arg = 1;
unsigned size;
unsigned size = 1;
char mode = 0;
int space = memdump_space;
uint32_t memdump_upper = 0;
if (nArgc > 1)
mode = tolower((unsigned char)psArgs[arg][0]);
if (!mode || isdigit((unsigned char)psArgs[arg][0]) || psArgs[arg][1])
/* without arguments, continue in the address space used last */
if (nArgc > arg)
{
/* no args, single digit or multiple chars -> default mode */
mode = 'b';
size = 1;
space = MEMDUMP_PHYSICAL;
mode = mode_letter(psArgs[arg]);
}
else if ((size = get_type_width(mode)))
if (mode == 's' || mode == 'u')
{
/* logical address, read through the MMU as supervisor/user data */
space = (mode == 's') ? MEMDUMP_SUPER : MEMDUMP_USER;
arg += 1;
mode = nArgc > arg ? mode_letter(psArgs[arg]) : 0;
}
if (mode)
{
if (!(size = get_type_width(mode)))
{
fprintf(stderr, "Invalid mode (not [s|u] [b|w|l])!\n");
return DEBUGGER_CMDDONE;
}
arg += 1;
}
else
if (space != MEMDUMP_PHYSICAL && !debug_mmu_translating())
{
fprintf(stderr, "Invalid width mode (not b|w|l)!\n");
fprintf(stderr, "'s' and 'u' need an MMU that translates addresses!\n");
memdump_space = MEMDUMP_PHYSICAL;
return DEBUGGER_CMDDONE;
}
memdump_space = space;
if (nArgc > arg)
{
@@ -607,12 +683,12 @@ int DebugCpu_MemDump(int nArgc, char *psArgs[])
/* print addr: HEX */
fprintf(debugOutput, "%08X: ", memdump_addr);
print_mem_values(memdump_addr, cols, size, 16);
print_mem_values(memdump_addr, cols, size, 16, memdump_space);
/* print character data */
align = (all-cols)*(2*size+1);
fprintf(debugOutput, "%*c", align + 2, ' ');
print_mem_chars(memdump_line, cols*size);
print_mem_chars(memdump_line, cols*size, memdump_space);
fprintf(debugOutput, "\n");
memdump_addr += cols*size;
@@ -805,9 +881,9 @@ static int DebugCpu_Struct(int nArgc, char *psArgs[])
if (split)
fprintf(debugOutput, " ");
if (type == 'c')
print_mem_chars(addr, cols);
print_mem_chars(addr, cols, MEMDUMP_PHYSICAL);
else
print_mem_values(addr, cols, size, base);
print_mem_values(addr, cols, size, base, MEMDUMP_PHYSICAL);
fprintf(debugOutput, "\n");
addr += cols * size;
@@ -1153,9 +1229,9 @@ static int DebugCpu_MemFind(int nArgc, char *psArgs[])
/* print <addr>: <hex> <chars> */
fprintf(debugOutput, "%08X: ", find_addr);
print_mem_values(find_addr, count, size, 16);
print_mem_values(find_addr, count, size, 16, MEMDUMP_PHYSICAL);
fprintf(debugOutput, " ");
print_mem_chars(find_addr, count*size);
print_mem_chars(find_addr, count*size, MEMDUMP_PHYSICAL);
fprintf(debugOutput, "\n");
matches++;
@@ -1486,10 +1562,14 @@ static const dbgcommand_t cpucommands[] =
{ DebugCpu_MemDump, Symbols_MatchCpuDataAddress,
"memdump", "m",
"dump memory",
"[b|w|l] [<start address>[-<end address>| <count>]]\n"
"[s|u] [b|w|l] [<start address>[-<end address>| <count>]]\n"
"\tdump memory at address or continue dump from previous address.\n"
"\tBy default memory output is done as bytes, with 'w' or 'l'\n"
"\toption, it will be done as words/longs instead. Output amount\n"
"\toption, it will be done as words/longs instead. With 's' or\n"
"\t'u' the address is a logical one, read through the MMU as\n"
"\tsupervisor or user data, and '--' marks what the MMU can't\n"
"\ttranslate. Without arguments, the dump continues from the\n"
"\tprevious address in the same address space. Output amount\n"
"\tcan be given either as a count or an address range.",
false },
{ DebugCpu_Struct, Symbols_MatchCpuDataAddress,
+12
View File
@@ -221,3 +221,15 @@ void Disasm (FILE *f, uaecptr addr, uaecptr *nextpc, int count) {}
void Disasm_GetColumns(int *columns) {}
void Disasm_SetColumns(int *columns) {}
void Disasm_DisableColumn(int column, const int *oldcols, int *newcols) {}
/* fake MMU translation for the debugger (debug/debugcpu.c memdump s/u) */
bool debug_mmu_translating(void);
bool debug_mmu_translating(void)
{
return false;
}
bool debug_mmu_translate_data(uint32_t addr, bool super, uint32_t *phys);
bool debug_mmu_translate_data(uint32_t addr, bool super, uint32_t *phys)
{
return false;
}