Skip to content

MMIO tracing backend, insanely powerful

Alex requested to merge branch/io_trace into branch/dm-spy-experiments

Based on g3gg0's mem_prot. Uses the last memory protection region (unused in Canon code) to trap all MMIO accesses (reads and writes) in some given memory range mapped to various peripherals (see Register Map and eos_handlers in emulator source).

Each access in the "protected" range will result in a data abort exception, calling our handler, which logs the context (calling task, PC, source register for the trapped instruction), re-executes the trapped instruction and logs the result (the value read/written from/to the MMIO register).

Update: able to log the ENTIRE MMIO range - REGION(0xC0000000, 0x10000000) - without crashing!!!

Sample log: mmio-capture.log


  • define protected_region in io_trace.c (see comments/examples)
  • make clean; make install
  • pray so it won't lock up (hopefully not worse)

Examples (5D3 1.1.3):

1021D>    Startup:000afd90:00:00: *** register_interrupt("BLTDMA", 0x74, 0xc6d8, 0x43230), from c7ac
10424>    >>> INT-74h DMA2 0000C6D8(00043230)
10466>    <<< INT-74h done
; note: slightly out of order here; the MMIO messages are printed at next DebugMsg; use timestamps to figure it out
10275>    Startup:FF0C79A8:MMIO:  [0xC0201010] <- 0x00000074 ; enabled interrupt 74h
10281>    Startup:FF0C79AC:MMIO:  [0xC0201200] -> 0x00000001 ; acknowledged
10420> **INT-74h*:00000510:MMIO:  [0xC0201004] -> 0x000001D0 ; this one is right before calling the DMA interrupt handler; it's the global handler figuring out what interrupt was triggered (shifted by 2)
10476> **INT-74h*:00000594:MMIO:  [0xC0201010] <- 0x00000074 ; this is where int 74h returned (the interrupt handler has re-enabled this interrupt)

B3C46>    >>> INT-A3h Jp57IntrHandler FF28DAD0(00000000)
B3C5D> **INT-A3h*:FF28C95C:MMIO:  [0xC0E20044] -> 0x00000400
B3C6B> **INT-A3h*:FF28C95C:MMIO:  [0xC0E20024] -> 0x01A39113
B3C95> **INT-A3h*:000afd90:16:03: [TTL][167,86328,0] JpCoreCompleteCBR( 0x1a39113 )


  • log post-processing

    • disassemble the instruction (done in camera for the most common cases)
    • find exact register address (same as above)
    • describe the registers, similar to QEMU logs (maybe by reusing the routines somehow)
  • replay MMIO activity in QEMU (sort of what we already do with MPU logs)

  • cleanup and optimize the code

  • figure out why it locks up with certain scenarios / memory ranges (for some reason it didn't lock up with latest commit; to be tested)

Alternatives (what we used before figuring this out):

  • mmio_log entries (one register at a time, lots of manual fiddling)
  • manually configured logging hooks (still useful)
  • guesswork until the code could be emulated (with varying levels of correctness).

This backend probably works for tracing regular memory accesses as well.

Happy reversing :)

P.S. If you see unformatted text on the above links, try refreshing the page (Bitbucket issue?)

Merge request reports