Memory Dump

A memory dump displays the raw contents of computer memory in hexadecimal, providing direct visibility into program state for debugging and system examination.

Why This Matters

When a program misbehaves and you have no high-level debugger, the memory dump is your window into what the computer actually contains. A variable that should hold 42 but contains 255. A string that should say β€œHELLO” but contains garbage. A return address on the stack that points nowhere useful. All of these show up in a memory dump before any analysis or guesswork.

For rebuilders working on primitive hardware with no operating system, no symbolic debugger, and no error messages, the memory dump is often the only diagnostic tool available beyond LED blinkers. Knowing how to read one, write one, and interpret one is an essential survival skill for low-level programming.

The memory dump also serves a more constructive purpose: understanding exactly how your data is laid out in memory, confirming that your assembler or compiler produced the binary you expected, and inspecting the state of a running system at any point in time.

Reading a Memory Dump

A standard memory dump displays memory in 16-byte rows, each row showing:

  • The starting address of the row (in hexadecimal)
  • The 16 bytes of content (in hexadecimal, space-separated or grouped in pairs)
  • The same 16 bytes interpreted as ASCII characters (printable characters shown, others replaced with a period)

Example:

1000: 48 65 6C 6C 6F 2C 20 57  6F 72 6C 64 0D 0A 00 00  Hello, World....
1010: 01 00 03 FF 12 34 56 78  9A BC DE F0 00 00 00 00  .....4Vx........
1020: 4C 44 20 41 2C 20 42 0D  43 41 4C 4C 20 49 4E 49  LD A, B.CALL INI

Reading this dump:

  • Address 0x1000: The bytes 48 65 6C 6C 6F are ASCII β€˜H’,β€˜e’,β€˜l’,β€˜l’,β€˜o’ β€” a string β€œHello, World” followed by CR (0x0D), LF (0x0A), and two zero bytes.
  • Address 0x1010: Binary data β€” 01 00 is the little-endian 16-bit value 1, 03 might be a count, FF might be a sentinel, 12 34 56 78 9A BC DE F0 is patterned test data.
  • Address 0x1020: 4C 44 20 41 2C 20 42 0D β€” ASCII text β€œLD A, B\r” β€” possibly assembly source text in memory.

To interpret a dump, you must know what should be there. From your program’s design or memory map, you know: address 0x1000 holds the message string, address 0x1010 holds the configuration block, address 0x1020 holds the source buffer. Without that context, the bytes are meaningless.

Writing a Memory Dump Routine

A memory dump subroutine needs to:

  1. Print the starting address of each row in hexadecimal
  2. Print each of the 16 bytes on the row in hexadecimal
  3. Print the ASCII interpretation
  4. Advance to the next 16-byte row and repeat

Subcomponents needed:

  • PRINT_HEX_BYTE: Converts an 8-bit value to two hexadecimal digits and outputs them
  • PRINT_HEX_WORD: Converts a 16-bit address to four hexadecimal digits
  • PRINT_ASCII_BYTE: Outputs a byte as its ASCII character if printable (0x20-0x7E), or ’.’ otherwise
  • PRINT_CHAR: The lowest-level output β€” writes one character to the serial port or display

Implementing PRINT_HEX_BYTE:

PRINT_HEX_BYTE:
  ; A = byte to print
  PUSH AF
  ; print high nibble
  RRCA
  RRCA
  RRCA
  RRCA
  AND 0x0F
  CALL NIBBLE_TO_CHAR
  CALL PRINT_CHAR
  ; print low nibble
  POP AF
  AND 0x0F
  CALL NIBBLE_TO_CHAR
  CALL PRINT_CHAR
  RET

NIBBLE_TO_CHAR:
  ; A = nibble (0-15), returns ASCII char in A
  CP 10
  JP C, DIGIT        ; less than 10 β†’ digit '0'-'9'
  ADD 'A' - 10       ; >= 10 β†’ letter 'A'-'F'
  RET
DIGIT:
  ADD '0'
  RET

The full dump loop:

DUMP_MEMORY:
  ; HL = start address, BC = byte count
DUMP_ROW:
  ; print address
  CALL PRINT_HEX_WORD   ; HL = address
  LD A, ':'
  CALL PRINT_CHAR
  LD A, ' '
  CALL PRINT_CHAR

  ; print 16 hex bytes
  PUSH HL
  LD DE, 16            ; 16 bytes per row
DUMP_HEX:
  LD A, (HL)
  CALL PRINT_HEX_BYTE
  LD A, ' '
  CALL PRINT_CHAR
  INC HL
  DEC DE
  ; insert extra space at midpoint for readability
  JP NZ, DUMP_HEX

  ; print ASCII column
  POP HL
  LD DE, 16
DUMP_ASCII:
  LD A, (HL)
  CALL PRINT_ASCII_BYTE
  INC HL
  DEC DE
  JP NZ, DUMP_ASCII

  ; newline
  LD A, 0x0D
  CALL PRINT_CHAR
  LD A, 0x0A
  CALL PRINT_CHAR

  ; advance and check if done
  DEC BC
  ; ... check BC != 0, loop for next row
  RET

Stack Dumps

The stack is particularly useful to dump when debugging crashes. If a program crashes (jumps to an invalid address or halts unexpectedly), dumping the memory around the stack pointer often reveals:

  • Return addresses pushed by CALL instructions β€” these tell you which functions were active
  • Local variables pushed by the program
  • Saved register values
  • The last few values computed before the crash

Read stack entries from top (current SP) upward. On Z80, each PUSH stores 2 bytes; each CALL pushes 2 bytes (the return address). So dumping 32 bytes above SP shows the last 8 return addresses or saved register values.

Interpret return addresses by looking them up in your assembly listing. An address that points into the middle of a subroutine tells you where in that subroutine execution will continue when the function returns.

Checksum Verification

When loading a program from paper tape, serial line, or ROM, errors can corrupt individual bytes. A checksum can verify the loaded data matches the expected content.

After computing the expected checksum of your program before transmission, dump the loaded memory, sum the bytes manually (or with a checksum subroutine), and compare. A mismatch means at least one byte loaded incorrectly.

A simple XOR checksum subroutine:

CHECKSUM:
  ; HL = start, BC = count, returns checksum in A
  XOR A              ; A = 0
CHECKSUM_LOOP:
  XOR (HL)           ; XOR current byte into running total
  INC HL
  DEC BC
  LD D, B
  OR E
  JP NZ, CHECKSUM_LOOP
  RET

Practical Notes for Rebuilders

Implement a memory dump routine as one of your first utility programs. Before you can debug anything else, you need the ability to see what is in memory. Keep the routine small (under 100 bytes of code is achievable) so it can be loaded even on severely memory-constrained systems.

Memorize the ASCII table for the range 0x20-0x7F. When you see a block of bytes in a dump that looks like it might be text, you can read the hex values directly without consulting a chart. The ranges are: 0x30-0x39 = β€˜0’-β€˜9’, 0x41-0x5A = β€˜A’-β€˜Z’, 0x61-0x7A = β€˜a’-β€˜z’, 0x20 = space. These are enough to recognize strings in a dump at a glance.

When examining a dump after a crash, pay attention to any pattern breaks. If a buffer that should contain your program’s data suddenly contains a run of identical bytes or a known pattern from a different part of memory, you have found a buffer overrun or misdirected copy operation. The pattern itself is the clue.