Skip to content

Instantly share code, notes, and snippets.

@Pokechu22
Last active July 7, 2022 22:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Pokechu22/abed8faefa0afc6dd881a8958e2407fe to your computer and use it in GitHub Desktop.
Save Pokechu22/abed8faefa0afc6dd881a8958e2407fe to your computer and use it in GitHub Desktop.
Writeup of the Datel BS2 HLE issue https://github.com/dolphin-emu/dolphin/pull/10746

Datel titles previously crashed when booting them with Dolphin's skip GameCube menu option was enabled. What exactly caused this crash?

Well, the original bug report from 7 years ago is titled "Datel AGP requires default exception handlers" (where AGP refers to Advance Game Port, but this issue applies to all Datel products), and its full text is this:

The game doesn't install exception handlers on boot like regular games that use the Nintendo SDK.
It instead relies on the handlers that the IPL install upon boot.
It does something odd that involves hitting the exception handlers that needs to be investigated.
Our non-IPL path doesn't install default handlers, but it may end up needing to do so at some point in order to properly support this game.
Since we don't only install a couple exception handlers that only do an rfi the game doesn't continue very far in the boot process without having the actual IPL running.

One could most likely start with investigating the route the game takes through the exception handlers with the IPL and attempting to replicate that without the IPL.

That certainly gives a lead.

Throughout this article, I will be using addresses and error messages from "Ultimate Codes for use with Animal Crossing" V1.1, MD5 9641b07e6aad8010520aff52be904dfd, as that is what I already had decompiled. (And as a side note, the easiest way to decompile Datel titles is to boot them normally and then select "Dump MRAM" in Dolphin's memory widget, and then import that memory dump with Ghidra-GameCube-Loader. Datel doesn't use a normal DOL file; they just shove their code at offset 0x50000000 in the disc. But, dumping RAM like that does mean that all initialization has already happened, which is important to keep in mind.)

Also, note that the order I use here is not the exact order I investigated this issue in; I didn't keep detailed notes.

Apparent behavior in Dolphin

The way the game crashes varies based on settings, even limiting it to DSP HLE (which was a separate issue which I'm treating as solved for this analysis).

With MMU disabled and using the x86-64 JIT, the following panic alerts start appearing, looping forever in this pattern:

28:29:915 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid read from 0x00000c38, PC = 0x8002c964
28:29:915 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000c3c, PC = 0x8002fd84
28:29:915 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000c38, PC = 0x8002fd88
28:29:915 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e38, PC = 0x8002faec
28:29:915 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000df0, PC = 0x8002faf0
28:29:915 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000df2, PC = 0x8002faf4
28:29:915 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000dfc, PC = 0x8002faf8
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e04, PC = 0x8002fafc
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e08, PC = 0x8002fb00
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e0c, PC = 0x8002fb04
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e10, PC = 0x8002fb08
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000de4, PC = 0x8002fb0c
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000de8, PC = 0x8002fb10
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000dec, PC = 0x8002fb14
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000df4, PC = 0x8002fb18
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000df8, PC = 0x8002fb1c
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e00, PC = 0x8002fb20
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e90, PC = 0x8002faec
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e48, PC = 0x8002faf0
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e4a, PC = 0x8002faf4
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e54, PC = 0x8002faf8
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e5c, PC = 0x8002fafc
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e60, PC = 0x8002fb00
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e64, PC = 0x8002fb04
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e68, PC = 0x8002fb08
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e3c, PC = 0x8002fb0c
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e40, PC = 0x8002fb10
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e44, PC = 0x8002fb14
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e4c, PC = 0x8002fb18
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e50, PC = 0x8002fb1c
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e58, PC = 0x8002fb20
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ee8, PC = 0x8002faec
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ea0, PC = 0x8002faf0
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ea2, PC = 0x8002faf4
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000eac, PC = 0x8002faf8
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000eb4, PC = 0x8002fafc
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000eb8, PC = 0x8002fb00
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ebc, PC = 0x8002fb04
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ec0, PC = 0x8002fb08
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e94, PC = 0x8002fb0c
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e98, PC = 0x8002fb10
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e9c, PC = 0x8002fb14
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ea4, PC = 0x8002fb18
28:29:916 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ea8, PC = 0x8002fb1c
28:29:917 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000eb0, PC = 0x8002fb20
28:29:917 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000de0, PC = 0x8002fddc
28:29:917 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000dd8, PC = 0x8002fde0
28:29:917 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ddc, PC = 0x8002fde4

With MMU disabled and the cached interpreter, the pattern seems to be similar, but PC seems to be at the start of JIT blocks instead - this is probably a bug that I should look into at some point. (The cached interpreter re-uses JIT infrastructure for basic blocks.)

35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid read from 0x00000c38, PC = 0x8002c960
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000c3c, PC = 0x8002fd68
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000c38, PC = 0x8002fd68
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e38, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000df0, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000df2, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000dfc, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e04, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e08, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e0c, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e10, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000de4, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000de8, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000dec, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000df4, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000df8, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e00, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e90, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e48, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e4a, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e54, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e5c, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e60, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e64, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e68, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e3c, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e40, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e44, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e4c, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e50, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e58, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ee8, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ea0, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ea2, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000eac, PC = 0x8002facc
35:14:411 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000eb4, PC = 0x8002facc
35:14:412 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000eb8, PC = 0x8002facc
35:14:412 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ebc, PC = 0x8002facc
35:14:412 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ec0, PC = 0x8002facc
35:14:412 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e94, PC = 0x8002facc
35:14:412 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e98, PC = 0x8002facc
35:14:412 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e9c, PC = 0x8002facc
35:14:412 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ea4, PC = 0x8002facc
35:14:412 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ea8, PC = 0x8002facc
35:14:412 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000eb0, PC = 0x8002facc
35:14:412 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000de0, PC = 0x8002fdd4
35:14:412 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000dd8, PC = 0x8002fdd4
35:14:412 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ddc, PC = 0x8002fdd4

With MMU disabled and the pure interpreter, the result is identical to MMU disabled with the JIT:

38:12:936 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid read from 0x00000c38, PC = 0x8002c964
38:12:936 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000c3c, PC = 0x8002fd84
38:12:936 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000c38, PC = 0x8002fd88
38:12:936 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e38, PC = 0x8002faec
38:12:936 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000df0, PC = 0x8002faf0
38:12:936 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000df2, PC = 0x8002faf4
38:12:936 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000dfc, PC = 0x8002faf8
38:12:936 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e04, PC = 0x8002fafc
38:12:936 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e08, PC = 0x8002fb00
38:12:936 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e0c, PC = 0x8002fb04
38:12:936 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e10, PC = 0x8002fb08
38:12:936 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000de4, PC = 0x8002fb0c
38:12:936 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000de8, PC = 0x8002fb10
38:12:936 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000dec, PC = 0x8002fb14
38:12:936 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000df4, PC = 0x8002fb18
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000df8, PC = 0x8002fb1c
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e00, PC = 0x8002fb20
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e90, PC = 0x8002faec
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e48, PC = 0x8002faf0
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e4a, PC = 0x8002faf4
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e54, PC = 0x8002faf8
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e5c, PC = 0x8002fafc
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e60, PC = 0x8002fb00
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e64, PC = 0x8002fb04
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e68, PC = 0x8002fb08
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e3c, PC = 0x8002fb0c
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e40, PC = 0x8002fb10
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e44, PC = 0x8002fb14
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e4c, PC = 0x8002fb18
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e50, PC = 0x8002fb1c
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e58, PC = 0x8002fb20
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ee8, PC = 0x8002faec
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ea0, PC = 0x8002faf0
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ea2, PC = 0x8002faf4
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000eac, PC = 0x8002faf8
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000eb4, PC = 0x8002fafc
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000eb8, PC = 0x8002fb00
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ebc, PC = 0x8002fb04
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ec0, PC = 0x8002fb08
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e94, PC = 0x8002fb0c
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e98, PC = 0x8002fb10
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000e9c, PC = 0x8002fb14
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ea4, PC = 0x8002fb18
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ea8, PC = 0x8002fb1c
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000eb0, PC = 0x8002fb20
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000de0, PC = 0x8002fddc
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000dd8, PC = 0x8002fde0
38:12:937 Core\PowerPC\MMU.cpp:1142 E[MASTER]: Warning: Invalid write to 0x00000ddc, PC = 0x8002fde4

With MMU enabled and the x86-64 JIT, results finally differ:

39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036130 PC 308
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036134 PC 30c
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036138 PC 310
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 8003613c PC 314
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036140 PC 318
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036144 PC 31c
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036148 PC 320
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 8003614c PC 324
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036150 PC 328
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036154 PC 32c
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036158 PC 330
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 8003615c PC 334
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036160 PC 338
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036164 PC 33c
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036168 PC 340
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 8003616c PC 344
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036170 PC 348
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036174 PC 34c
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036178 PC 350
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 8003617c PC 354
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036180 PC 358
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036184 PC 35c
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036188 PC 360
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 8003618c PC 364
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036190 PC 368
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036194 PC 36c
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036198 PC 370
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 8003619c PC 374
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800361a0 PC 378
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800361a4 PC 37c
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800361a8 PC 380
39:51:601 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 8003613c PC 388
39:51:601 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036140 PC 38c
39:51:600 Core\PowerPC\MMU.cpp:246 E[MASTER]: Warning: Unable to resolve read address 800362ce PC 390
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800362ce PC 398
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800361ac PC 3a0
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800361b0 PC 3a8
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800361b4 PC 3b0
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800361b8 PC 3b8
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800362c4 PC 3c0
39:51:600 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800362c8 PC 3c8

The cached interpreter with MMU enabled is the same:


43:42:875 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036130 PC 308
43:42:875 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036134 PC 30c
43:42:875 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036138 PC 310
43:42:875 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 8003613c PC 314
43:42:875 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036140 PC 318
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036144 PC 31c
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036148 PC 320
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 8003614c PC 324
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036150 PC 328
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036154 PC 32c
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036158 PC 330
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 8003615c PC 334
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036160 PC 338
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036164 PC 33c
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036168 PC 340
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 8003616c PC 344
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036170 PC 348
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036174 PC 34c
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036178 PC 350
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 8003617c PC 354
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036180 PC 358
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036184 PC 35c
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036188 PC 360
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 8003618c PC 364
43:42:876 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036190 PC 368
43:42:877 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036194 PC 36c
43:42:877 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036198 PC 370
43:42:877 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 8003619c PC 374
43:42:877 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800361a0 PC 378
43:42:877 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800361a4 PC 37c
43:42:877 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800361a8 PC 380
43:42:877 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 8003613c PC 388
43:42:877 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 80036140 PC 38c
43:42:877 Core\PowerPC\MMU.cpp:246 E[MASTER]: Warning: Unable to resolve read address 800362ce PC 390
43:42:877 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800362ce PC 398
43:42:877 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800361ac PC 3a0
43:42:877 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800361b0 PC 3a8
43:42:877 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800361b4 PC 3b0
43:42:877 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800361b8 PC 3b8
43:42:877 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800362c4 PC 3c0
43:42:877 Core\PowerPC\MMU.cpp:400 E[MASTER]: Warning: Unable to resolve write address 800362c8 PC 3c8

Lastly, pure interpreter with MMU enabled shows this, which continues on for a while afterwards with unknown instructions at other addresses (though it repeats the same ones for a while, too).

48:34:752 Core\PowerPC\Interpreter\Interpreter.cpp:330 N[PowerPC]: Last PC = 8002c98c : mtlr	r0
48:34:752 Core\Debugger\Debugger_SymbolMap.cpp:102 N[PowerPC]: == STACK TRACE - SP = 8002c960 ==
48:34:752 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:34:752 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:34:752 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:34:752 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:34:753 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:34:753 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:34:753 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:34:753 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:34:753 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:34:753 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:34:753 Core\PowerPC\Interpreter\Interpreter.cpp:335 N[PowerPC]: 
IntCPU: Unknown instruction 000006c9 at PC = 8002c990  last_PC = 8002c98c  LR = 8002c384

48:34:753 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r0: 0x8002c384 r1: 0x8002c960 r2: 0x800c2b40 r3: 0x8002ca00
48:34:753 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r4: 0x8003612c r5: 0x8002c9f0 r6: 0x00000000 r7: 0x800ba8d0
48:34:753 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r8: 0x00000000 r9: 0x8002c9fc r10: 0x00000c00 r11: 0x800ba934
48:34:753 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r12: 0x00000000 r13: 0x800c27c0 r14: 0x00000000 r15: 0x00000000
48:34:753 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r16: 0x00000000 r17: 0x00000000 r18: 0x00000000 r19: 0x00000000
48:34:753 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r20: 0x00000000 r21: 0x00000000 r22: 0x00000000 r23: 0x00000000
48:34:753 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r24: 0x80030000 r25: 0x00000300 r26: 0x800ae26c r27: 0x00000000
48:34:753 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r28: 0x8002c9f0 r29: 0x8003612c r30: 0x8003c9e8 r31: 0x8002ca00
48:34:753 Core\PowerPC\Interpreter\Interpreter.cpp:343 E[PowerPC]: Warning: An error occurred.


IntCPU: Unknown instruction 000006c9 at PC = 8002c990  last_PC = 8002c98c  LR = 8002c384


  Condition: 0
  File: d:\Source\Core\Core\PowerPC\Interpreter\Interpreter.cpp
  Line: 343
  Function: unknown_instruction

Ignore and continue?
48:37:742 Core\PowerPC\Interpreter\Interpreter.cpp:330 N[PowerPC]: Last PC = 8002c99c : 	---
48:37:743 Core\Debugger\Debugger_SymbolMap.cpp:102 N[PowerPC]: == STACK TRACE - SP = 8002c960 ==
48:37:743 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:37:743 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:37:743 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:37:743 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:37:743 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:37:743 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:37:743 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:37:743 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:37:743 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:37:743 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:37:743 Core\PowerPC\Interpreter\Interpreter.cpp:335 N[PowerPC]: 
IntCPU: Unknown instruction 000006c9 at PC = 8002c9a0  last_PC = 8002c99c  LR = 8002c384

48:37:743 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r0: 0x52070000 r1: 0x8002c960 r2: 0x800c2b40 r3: 0x8002ca00
48:37:743 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r4: 0x8003612c r5: 0x8002c9f0 r6: 0x00000000 r7: 0x800ba8d0
48:37:743 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r8: 0x00000000 r9: 0x8002c9fc r10: 0x00000c00 r11: 0x800ba934
48:37:743 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r12: 0x00000000 r13: 0x800c27c0 r14: 0x00000000 r15: 0x00000000
48:37:743 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r16: 0x00000000 r17: 0x00000000 r18: 0x00000000 r19: 0x00000000
48:37:743 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r20: 0x00000000 r21: 0x00000000 r22: 0x00000000 r23: 0x00000000
48:37:743 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r24: 0x80030000 r25: 0x00000300 r26: 0x800ae26c r27: 0x00000000
48:37:743 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r28: 0x8002c9f0 r29: 0x8003612c r30: 0x8003c9e8 r31: 0x8002ca00
48:37:743 Core\PowerPC\Interpreter\Interpreter.cpp:343 E[PowerPC]: Warning: An error occurred.


IntCPU: Unknown instruction 000006c9 at PC = 8002c9a0  last_PC = 8002c99c  LR = 8002c384


  Condition: 0
  File: d:\Source\Core\Core\PowerPC\Interpreter\Interpreter.cpp
  Line: 343
  Function: unknown_instruction

Ignore and continue?
48:42:008 Core\PowerPC\Interpreter\Interpreter.cpp:330 N[PowerPC]: Last PC = 8002c9a0 : (ill)	000006c9
48:42:008 Core\Debugger\Debugger_SymbolMap.cpp:102 N[PowerPC]: == STACK TRACE - SP = 8002c960 ==
48:42:008 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:42:008 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:42:008 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:42:008 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:42:008 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:42:008 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:42:008 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:42:008 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:42:009 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:42:009 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:42:009 Core\PowerPC\Interpreter\Interpreter.cpp:335 N[PowerPC]: 
IntCPU: Unknown instruction 00000c00 at PC = 8002c9a4  last_PC = 8002c9a0  LR = 8002c384

48:42:009 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r0: 0x52070000 r1: 0x8002c960 r2: 0x800c2b40 r3: 0x8002ca00
48:42:009 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r4: 0x8003612c r5: 0x8002c9f0 r6: 0x00000000 r7: 0x800ba8d0
48:42:009 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r8: 0x00000000 r9: 0x8002c9fc r10: 0x00000c00 r11: 0x800ba934
48:42:009 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r12: 0x00000000 r13: 0x800c27c0 r14: 0x00000000 r15: 0x00000000
48:42:009 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r16: 0x00000000 r17: 0x00000000 r18: 0x00000000 r19: 0x00000000
48:42:009 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r20: 0x00000000 r21: 0x00000000 r22: 0x00000000 r23: 0x00000000
48:42:009 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r24: 0x80030000 r25: 0x00000300 r26: 0x800ae26c r27: 0x00000000
48:42:009 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r28: 0x8002c9f0 r29: 0x8003612c r30: 0x8003c9e8 r31: 0x8002ca00
48:42:009 Core\PowerPC\Interpreter\Interpreter.cpp:343 E[PowerPC]: Warning: An error occurred.


IntCPU: Unknown instruction 00000c00 at PC = 8002c9a4  last_PC = 8002c9a0  LR = 8002c384


  Condition: 0
  File: d:\Source\Core\Core\PowerPC\Interpreter\Interpreter.cpp
  Line: 343
  Function: unknown_instruction

Ignore and continue?
48:44:598 Core\PowerPC\Interpreter\Interpreter.cpp:330 N[PowerPC]: Last PC = 8002c9a4 : (ill)	00000c00
48:44:598 Core\Debugger\Debugger_SymbolMap.cpp:102 N[PowerPC]: == STACK TRACE - SP = 8002c960 ==
48:44:598 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:44:598 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:44:599 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:44:599 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:44:599 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:44:599 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:44:599 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:44:599 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:44:599 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:44:599 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:44:599 Core\PowerPC\Interpreter\Interpreter.cpp:335 N[PowerPC]: 
IntCPU: Unknown instruction 0000ffe4 at PC = 8002c9a8  last_PC = 8002c9a4  LR = 8002c384

48:44:599 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r0: 0x52070000 r1: 0x8002c960 r2: 0x800c2b40 r3: 0x8002ca00
48:44:599 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r4: 0x8003612c r5: 0x8002c9f0 r6: 0x00000000 r7: 0x800ba8d0
48:44:599 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r8: 0x00000000 r9: 0x8002c9fc r10: 0x00000c00 r11: 0x800ba934
48:44:599 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r12: 0x00000000 r13: 0x800c27c0 r14: 0x00000000 r15: 0x00000000
48:44:599 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r16: 0x00000000 r17: 0x00000000 r18: 0x00000000 r19: 0x00000000
48:44:599 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r20: 0x00000000 r21: 0x00000000 r22: 0x00000000 r23: 0x00000000
48:44:599 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r24: 0x80030000 r25: 0x00000300 r26: 0x800ae26c r27: 0x00000000
48:44:599 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r28: 0x8002c9f0 r29: 0x8003612c r30: 0x8003c9e8 r31: 0x8002ca00
48:44:599 Core\PowerPC\Interpreter\Interpreter.cpp:343 E[PowerPC]: Warning: An error occurred.


IntCPU: Unknown instruction 0000ffe4 at PC = 8002c9a8  last_PC = 8002c9a4  LR = 8002c384


  Condition: 0
  File: d:\Source\Core\Core\PowerPC\Interpreter\Interpreter.cpp
  Line: 343
  Function: unknown_instruction

Ignore and continue?
48:45:768 Core\PowerPC\Interpreter\Interpreter.cpp:330 N[PowerPC]: Last PC = 8002c9ec : stw	r28, 0x0250 (sp)
48:45:768 Core\Debugger\Debugger_SymbolMap.cpp:102 N[PowerPC]: == STACK TRACE - SP = 8002c700 ==
48:45:768 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80030038 ]
48:45:768 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:45:768 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:45:768 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:45:768 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:45:768 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:45:768 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:45:768 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:45:768 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:45:768 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:45:768 Core\PowerPC\Interpreter\Interpreter.cpp:335 N[PowerPC]: 
IntCPU: Unknown instruction 02000000 at PC = 8002c9f0  last_PC = 8002c9ec  LR = 8002c384

48:45:768 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r0: 0x8002c384 r1: 0x8002c700 r2: 0x800c2b40 r3: 0x8002ca00
48:45:768 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r4: 0x8003612c r5: 0x8002c9f0 r6: 0x00000000 r7: 0x800ba8d0
48:45:768 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r8: 0x00000000 r9: 0x8002c9fc r10: 0x00000c00 r11: 0x800ba934
48:45:768 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r12: 0x00000000 r13: 0x800c27c0 r14: 0x00000000 r15: 0x00000000
48:45:768 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r16: 0x00000000 r17: 0x00000000 r18: 0x00000000 r19: 0x00000000
48:45:768 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r20: 0x00000000 r21: 0x00000000 r22: 0x00000000 r23: 0x00000000
48:45:768 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r24: 0x80030000 r25: 0x00000300 r26: 0x800ae26c r27: 0x00000000
48:45:768 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r28: 0x8002c9f0 r29: 0x8003612c r30: 0x8003c9e8 r31: 0x8002ca00
48:45:768 Core\PowerPC\Interpreter\Interpreter.cpp:343 E[PowerPC]: Warning: An error occurred.


IntCPU: Unknown instruction 02000000 at PC = 8002c9f0  last_PC = 8002c9ec  LR = 8002c384


  Condition: 0
  File: d:\Source\Core\Core\PowerPC\Interpreter\Interpreter.cpp
  Line: 343
  Function: unknown_instruction

Ignore and continue?
48:46:968 Core\PowerPC\Interpreter\Interpreter.cpp:330 N[PowerPC]: Last PC = 8002c940 : lwz	r0, 0 (r3)
48:46:968 Core\Debugger\Debugger_SymbolMap.cpp:102 N[PowerPC]: == STACK TRACE - SP = 8002c5b0 ==
48:46:968 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 4182003c ]
48:46:968 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:46:968 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:46:968 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 800ae26c ]
48:46:968 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:46:968 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:46:968 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:46:968 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c384 ]
48:46:968 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 80013a8c ]
48:46:968 Core\Debugger\Debugger_SymbolMap.cpp:118 N[PowerPC]:  *  ---  [ addr = 8002c930 ]
48:46:968 Core\PowerPC\Interpreter\Interpreter.cpp:335 N[PowerPC]: 
IntCPU: Unknown instruction 00000300 at PC = 8002c944  last_PC = 8002c940  LR = 8002c384

48:46:969 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r0: 0x80500000 r1: 0x8002c5b0 r2: 0x800c2b40 r3: 0x8002c650
48:46:969 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r4: 0x8003612c r5: 0x8002c640 r6: 0x00000000 r7: 0x800ba8d0
48:46:969 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r8: 0x00000000 r9: 0x8002c64c r10: 0x00000c00 r11: 0x800ba934
48:46:969 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r12: 0x00000000 r13: 0x800c27c0 r14: 0x00000000 r15: 0x00000000
48:46:969 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r16: 0x00000000 r17: 0x00000000 r18: 0x00000000 r19: 0x00000000
48:46:969 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r20: 0x00000000 r21: 0x00000000 r22: 0x00000000 r23: 0x00000000
48:46:969 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r24: 0x80030000 r25: 0x00000300 r26: 0x800ae26c r27: 0x00000000
48:46:969 Core\PowerPC\Interpreter\Interpreter.cpp:339 N[PowerPC]: r28: 0x8003612c r29: 0x8002c640 r30: 0x8003c9dc r31: 0x8002ca00
48:46:969 Core\PowerPC\Interpreter\Interpreter.cpp:343 E[PowerPC]: Warning: An error occurred.


IntCPU: Unknown instruction 00000300 at PC = 8002c944  last_PC = 8002c940  LR = 8002c384


  Condition: 0
  File: d:\Source\Core\Core\PowerPC\Interpreter\Interpreter.cpp
  Line: 343
  Function: unknown_instruction

Ignore and continue?

There are 3 results here: With MMU disabled, there are invalid reads/writes to 0x00000c38-0x00000ec0 from 0x8002c964-0x8002fde4. With MMU enabled and the JIT or cached interpreter, there are invalid reads and writes to 0x80036130-0x800362ce from 0x00000308-0x000003c8. And with MMU enabled and the pure interpreter, there are invalid instructions at 0x8002c944-0x8002c990 which probably means code in that region got overwritten with garbage values.

Invalid reads with MMU enabled

I tend to have MMU enabled by default, and the invalid reads are of a virtual address from a physical address in what is probably an exception handler, so that was the first place I checked. According to the PowerPC 750cl manual (which is close to the GameCube/Wii CPU in that it supports paired singles and other functionality, though it isn't an exact match), in Table 4-2 Exceptions and Conditions on page 158, address 00300 is the vector offset for DSI: "For translation lookaside buffers (TLB) misses on load, store, or cache operations, a data-storage exception (DSI) exception occurs if a page fault occurs." So, it ends up at that address because of an invalid read or write, and in Dolphin with MMU enabled the exception handlers are called instead of just logging a warning.

By enabling Dolphin's debugging UI and then checking Options → "Boot to Pause", it is possible to start a game and then pause emulation as soon as the boot process finishes (but after the apploader runs, so code is already in memory). It is then possible to set a breakpoint at address 00000300, which will pause emulation when the exception is hit. Here are the values of a few registers in that case (from View → Registers): sp = 800ba950, r3 = 00000c00, r4 = 80036140, r5 = 800ba9e0, r6 = 00000000, r7 = 800ba8d0, r8 = 00000000, r9 = 800ba9ec, r10 = 00000c00, r11 = 800ba934, PC = 00000300, LR = 8002c930, CTR = 8002cd9c, MSR = 00000000, SRR0 = 8002c964, SRR1 = 00000030. SRR0 and SRR1 are particularly important, as they indicate the values of PC and MSR before the exception. In this case, SRR0 matches the address of the first invalid read with MMU disabled, so the behavior with MMU disabled is just due to the inherently inaccurate properties of having MMU disabled.

Except, nothing goes wrong initially. It's possible to step through the exception handler, and then the ending rfi (Return from Interrupt) instruction results in jumping to 80013a34 (after storing a lot of registers to memory). That function ends in an infinite loop, but if we resume execution then we end up at the 00000300 breakpoint again, this time with sp = 800ba800, r3 = 00000c00, r4 = 8003612c, r5 = 800ba890, r6 = 00000000, r7 = 800ba8d0, r8 = 00000000, r9 = 800ba89c, r10 = 00000c00, r11 = 800ba934, PC = 00000300, LR = 8002c930, CTR = 8002cd9c, MSR = 00000000, SRR0 = 8002c964, SRR1 = 00000030. Most registers here are the same; of the ones I listed, only sp, r4, r5, and r9 are different. Again, though, everything works properly and the rfi instruction jumps to 80013a34.

The breakpoint is hit a 3rd time, and only sp, r5, and r7 differ, with sp = 800ba6b0, r5 = 800ba740, and r9 = 800ba74c. This repeats for a while, with those registers decreasing by 0x150 each time.

Exception handler, part 1

It doesn't seem practical to click run repeatedly to determine what conditions change, so let's look at the actual address that ends up being written, instead. Here's what's near 80036130:

80035f20  58 00 00 00 00 00 00 00  6d 73 72 00 73 72 72 31  |X.......msr.srr1|
80035f30  00 00 00 00 73 72 72 30  00 00 00 00 6c 72 00 00  |....srr0....lr..|
80035f40  72 33 31 00 72 33 30 00  72 32 39 00 72 32 38 00  |r31.r30.r29.r28.|
80035f50  72 32 37 00 72 32 36 00  72 32 35 00 72 32 34 00  |r27.r26.r25.r24.|
80035f60  72 32 33 00 72 32 32 00  72 32 31 00 72 32 30 00  |r23.r22.r21.r20.|
80035f70  72 31 39 00 72 31 38 00  72 31 37 00 72 31 36 00  |r19.r18.r17.r16.|
80035f80  72 31 35 00 72 31 34 00  72 31 33 00 72 31 32 00  |r15.r14.r13.r12.|
80035f90  72 31 31 00 72 31 30 00  20 72 39 00 20 72 38 00  |r11.r10. r9. r8.|
80035fa0  20 72 37 00 20 72 36 00  20 72 35 00 20 72 34 00  | r7. r6. r5. r4.|
80035fb0  20 72 33 00 72 74 6f 63  00 00 00 00 20 73 70 00  | r3.rtoc.... sp.|
80035fc0  20 72 30 00 54 68 65 72  6d 61 6c 20 6d 61 6e 61  | r0.Thermal mana|
80035fd0  67 65 6d 65 6e 74 00 00  49 41 20 42 72 65 61 6b  |gement..IA Break|
80035fe0  70 6f 69 6e 74 00 00 00  4d 6f 6e 69 74 6f 72 00  |point...Monitor.|
80035ff0  54 72 61 63 65 00 00 00  53 79 73 74 65 6d 20 43  |Trace...System C|
80036000  61 6c 6c 00 44 65 63 72  65 6d 65 6e 74 65 72 00  |all.Decrementer.|
80036010  46 50 20 55 6e 61 76 61  69 6c 61 62 6c 65 00 00  |FP Unavailable..|
80036020  50 72 6f 67 72 61 6d 00  41 6c 69 67 6e 6d 65 6e  |Program.Alignmen|
80036030  74 00 00 00 45 78 74 65  72 6e 61 6c 20 49 6e 74  |t...External Int|
80036040  65 72 72 75 70 74 00 00  49 53 49 00 44 53 49 00  |errupt..ISI.DSI.|
80036050  4d 61 63 68 69 6e 65 20  43 68 65 63 6b 00 00 00  |Machine Check...|
80036060  53 79 73 74 65 6d 20 52  65 73 65 74 00 00 00 00  |System Reset....|
80036070  52 65 73 65 72 76 65 64  00 00 00 00 55 4e 48 41  |Reserved....UNHA|
80036080  4e 44 4c 45 44 20 56 45  43 54 4f 52 21 00 00 00  |NDLED VECTOR!...|
80036090  52 45 47 53 20 53 54 41  43 4b 20 43 4f 52 52 55  |REGS STACK CORRU|
800360a0  50 54 45 44 21 00 00 00  55 4e 48 41 4e 44 4c 45  |PTED!...UNHANDLE|
800360b0  44 20 56 45 43 54 4f 52  21 20 53 54 41 43 4b 20  |D VECTOR! STACK |
800360c0  43 4f 52 52 55 50 54 45  44 20 2d 20 76 65 63 74  |CORRUPTED - vect|
800360d0  6f 72 20 25 30 38 78 2c  20 73 74 61 63 6b 20 25  |or %08x, stack %|
800360e0  30 38 78 0a 00 00 00 00  55 4e 48 41 4e 44 4c 45  |08x.....UNHANDLE|
800360f0  44 20 56 45 43 54 4f 52  21 20 50 43 20 2d 20 76  |D VECTOR! PC - v|
80036100  65 63 74 6f 72 20 25 30  38 78 2c 50 43 20 25 30  |ector %08x,PC %0|
80036110  38 78 2c 20 6c 72 20 25  30 38 78 2c 20 73 74 61  |8x, lr %08x, sta|
80036120  63 6b 20 25 30 38 78 0a  00 00 00 00 45 78 63 65  |ck %08x.....Exce|
80036130  70 74 69 6f 6e 20 43 6f  75 6e 74 20 25 64 0a 00  |ption Count %d..|
80036140  45 58 43 45 50 54 49 4f  4e 21 20 72 65 67 73 20  |EXCEPTION! regs |
80036150  25 30 38 78 0a 00 00 00  66 72 6f 6d 20 25 30 38  |%08x....from %08|
80036160  78 20 28 25 73 29 0a 00  52 65 67 73 20 6c 69 73  |x (%s)..Regs lis|
80036170  74 3a 0a 00 25 73 20 25  30 38 78 0a 00 00 00 00  |t:..%s %08x.....|
80036180  6c 72 20 25 30 38 78 0a  00 00 00 00 65 70 63 20  |lr %08x.....epc |
80036190  25 30 38 78 0a 00 00 00  45 58 43 45 50 54 49 4f  |%08x....EXCEPTIO|
800361a0  4e 21 00 00 45 58 43 45  50 54 49 4f 4e 21 20 53  |N!..EXCEPTION! S|
800361b0  54 41 43 4b 20 43 4f 52  52 55 50 54 45 44 20 2d  |TACK CORRUPTED -|
800361c0  20 76 65 63 74 6f 72 20  25 30 38 78 2c 20 73 74  | vector %08x, st|
800361d0  61 63 6b 20 25 30 38 78  0a 00 00 00 50 41 4e 49  |ack %08x....PANI|
800361e0  43 21 20 55 4e 48 41 4e  44 4c 45 44 20 49 4e 54  |C! UNHANDLED INT|
800361f0  45 52 52 55 50 54 21 20  72 65 67 73 20 25 30 38  |ERRUPT! regs %08|
80036200  78 0a 00 00 63 61 75 73  65 20 25 30 38 78 0a 00  |x...cause %08x..|
80036210  6d 61 73 6b 20 25 30 38  78 0a 00 00 50 41 4e 49  |mask %08x...PANI|
80036220  43 20 55 4e 48 41 4e 44  4c 45 44 20 45 58 54 45  |C UNHANDLED EXTE|
80036230  52 4e 41 4c 20 49 4e 54  45 52 52 55 50 54 21 00  |RNAL INTERRUPT!.|
80036240  45 58 20 50 41 4e 49 43  21 20 53 54 41 43 4b 20  |EX PANIC! STACK |
80036250  43 4f 52 52 55 50 54 45  44 20 2d 20 63 61 75 73  |CORRUPTED - caus|
80036260  65 20 25 30 38 78 20 0a  09 09 09 6d 61 73 6b 20  |e %08x ....mask |
80036270  25 30 38 78 20 73 74 61  63 6b 20 25 30 38 78 0a  |%08x stack %08x.|
80036280  00 00 00 00 43 61 75 73  65 00 00 00 4d 61 73 6b  |....Cause...Mask|

Hmm. This definitely looks like unhandled exception stuff. In particular, r4 is 8003612c, which points to the string "Exception Count %d". Ghidra also shows that this string is used by the function at 80013a34, which is the jump location from the rfi instruction.

Here's what Ghidra disassembles that function as:

void FUN_80013a34(uint param_1,uint param_2)

{
  uint uVar1;
  int iVar2;
  int iVar3;
  undefined4 uVar4;
  undefined **ppuVar5;
  void *pvVar6;
  int iVar7;
  void *pvVar8;
  int iVar9;
  undefined4 *puVar10;
  
  DAT_800ba82c = DAT_800ba82c + 1;
  FUN_8002c2ac(&DAT_80500000,s_Exception_Count_%d_8003612c,DAT_800ba82c);
  pvVar6 = (void *)(param_1 | 0x80000000);
  iVar2 = FUN_8002c2ac(&DAT_80500000,s_EXCEPTION!_regs_%08x_80036140,pvVar6);
  iVar3 = FUN_8002c2ac((int)&DAT_80500000 + iVar2,s_from_%08x_(%s)_80036158,param_2,
                       *(undefined4 *)((int)&PTR_s_Reserved_800ae2fc + (param_2 >> 6 & 0x5c)));
  iVar9 = 0;
  iVar3 = (int)&DAT_80500000 + iVar2 + iVar3;
  iVar2 = FUN_8002c2ac(iVar3,s_Regs_list:_80036168);
  iVar7 = 0x1f;
  iVar3 = iVar3 + iVar2;
  do {
    iVar2 = FUN_8002c2ac(iVar3,s_%s_%08x_80036174,*(undefined4 *)((int)&PTR_s__r0_800ae26c + iVar9),
                         *(undefined4 *)(iVar9 + (int)pvVar6));
    iVar9 = iVar9 + 4;
    iVar7 = iVar7 + -1;
    iVar3 = iVar3 + iVar2;
  } while (iVar7 != 0);
  iVar2 = FUN_8002c2ac(iVar3,s_lr_%08x_80036180,*(undefined4 *)((int)pvVar6 + 0x84));
  iVar7 = FUN_8002c2ac(iVar3 + iVar2,s_epc_%08x_8003618c,*(undefined4 *)((int)pvVar6 + 0x198));
  FUN_8002c004(iVar3 + iVar2 + iVar7,0x80000000,0x100);
  FUN_80015818(&DAT_80500000,0x800);
  FUN_80015914();
  instructionSynchronize();
  sync(0);
  iVar2 = FUN_8001368c(pvVar6);
  if (iVar2 == 0) {
    FUN_80015818(pvVar6,0x200);
  }
  uVar1 = read_volatile_4(DAT_cc00201c);
  pvVar8 = (void *)(uVar1 | 0x80000000);
  FUN_80013208(5,0x7d000,pvVar8);
  FUN_80013208(1,0xa0,(int)pvVar8 + 0x7d000);
  FUN_80013090(pvVar8);
  FUN_80015e68(pvVar8,s_EXCEPTION!_80036198,0x28,0x28,4,1);
  FUN_80015e68(pvVar8,*(undefined4 *)((int)&PTR_s_Reserved_800ae2fc + (param_2 >> 6 & 0x5c)),200,
               0x30,4,1);
  FUN_80015f28(pvVar8,param_2,0x20,200,0x28,4,1);
  if (iVar2 == 0) {
    iVar3 = 0x40;
    iVar7 = 0;
    iVar2 = 0x10;
    do {
      FUN_80015e68(pvVar8,*(undefined4 *)((int)&PTR_s__r0_800ae26c + iVar7),0x10,iVar3,4,1);
      FUN_80015f28(pvVar8,*(undefined4 *)(iVar7 + (int)pvVar6),0x20,0x58,iVar3,4,1);
      iVar3 = iVar3 + 0x10;
      iVar2 = iVar2 + -1;
      iVar7 = iVar7 + 4;
    } while (iVar2 != 0);
    ppuVar5 = &PTR_s_r16_800ae2ac;
    iVar3 = 0x40;
    puVar10 = (undefined4 *)((int)pvVar6 + 0x40);
    iVar2 = 0x10;
    do {
      FUN_80015f28(pvVar8,*puVar10,0x20,400,iVar3,4,1);
      puVar10 = puVar10 + 1;
      FUN_80015e68(pvVar8,*ppuVar5,0x148,iVar3,4,1);
      ppuVar5 = ppuVar5 + 1;
      iVar2 = iVar2 + -1;
      iVar3 = iVar3 + 0x10;
    } while (iVar2 != 0);
    FUN_80015f28(pvVar8,*(undefined4 *)((int)pvVar6 + 0x84),0x20,400,0x140,4,1);
    FUN_80015e68(pvVar8,PTR_s_lr_800ae2ec,0x148,0x140,4,1);
    FUN_80015f28(pvVar8,*(undefined4 *)((int)pvVar6 + 0x198),0x20,0x58,0x150,4,1);
    FUN_80015e68(pvVar8,PTR_s_srr0_800ae2f0,0x10,0x150,4,1);
    FUN_80015f28(pvVar8,*(undefined4 *)((int)pvVar6 + 0x19c),0x20,400,0x150,4,1);
    FUN_80015e68(pvVar8,PTR_s_srr1_800ae2f4,0x148,0x150,4,1);
    uVar4 = FUN_80016c6c();
    FUN_80015f28(pvVar8,uVar4,0x20,0x58,0x160,4,1);
    FUN_80015e68(pvVar8,PTR_s_msr_800ae2f8,0x10,0x160,4,1);
  }
  else {
    FUN_80015e68(pvVar8,s_REGS_STACK_CORRUPTED!_80036090,0x28,0x50,4,1);
    FUN_80015f28(pvVar8,param_1,0x20,0x58,0x5a,4,1);
    FUN_8002c2ac(&DAT_80500000,s_EXCEPTION!_STACK_CORRUPTED_-_vec_800361a4,param_2,pvVar6);
    FUN_80015818(&DAT_80500000,0x100);
  }
  FUN_80015818(pvVar8,0x96000);
  do {
                    /* WARNING: Do nothing block with infinite loop */
  } while( true );
}

If we mark the RAM address space as read-only temporarily (by unchecking the W checkbox in Ghidra's memory map), and make the assumption that FUN_8002c2ac is sprintf and FUN_8002c004 is memcpy, then we end up with this:

void FUN_80013a34(uint param_1,uint param_2)

{
  uint uVar1;
  int iVar2;
  int iVar3;
  undefined4 uVar4;
  undefined **ppuVar5;
  void *pvVar6;
  char *pcVar7;
  void *pvVar8;
  int iVar9;
  undefined4 *puVar10;
  
  DAT_800ba82c = DAT_800ba82c + 1;
  sprintf((char *)&DAT_80500000,"Exception Count %d\n",DAT_800ba82c);
  pvVar6 = (void *)(param_1 | 0x80000000);
  iVar2 = sprintf((char *)&DAT_80500000,"EXCEPTION! regs %08x\n",pvVar6);
  iVar3 = sprintf((char *)((int)&DAT_80500000 + iVar2),"from %08x (%s)\n",param_2,
                  *(undefined4 *)((int)&PTR_s_Reserved_800ae2fc + (param_2 >> 6 & 0x5c)));
  iVar9 = 0;
  pcVar7 = (char *)((int)&DAT_80500000 + iVar2) + iVar3;
  iVar2 = sprintf(pcVar7,"Regs list:\n");
  iVar3 = 0x1f;
  pcVar7 = pcVar7 + iVar2;
  do {
    iVar2 = sprintf(pcVar7,"%s %08x\n",*(undefined4 *)((int)&PTR_s__r0_800ae26c + iVar9),
                    *(undefined4 *)(iVar9 + (int)pvVar6));
    iVar9 = iVar9 + 4;
    iVar3 = iVar3 + -1;
    pcVar7 = pcVar7 + iVar2;
  } while (iVar3 != 0);
  iVar2 = sprintf(pcVar7,"lr %08x\n",*(undefined4 *)((int)pvVar6 + 0x84));
  iVar3 = sprintf(pcVar7 + iVar2,"epc %08x\n",*(undefined4 *)((int)pvVar6 + 0x198));
  memcpy(pcVar7 + iVar2 + iVar3,0x80000000,0x100);
  FUN_80015818(&DAT_80500000,0x800);
  FUN_80015914();
  instructionSynchronize();
  sync(0);
  iVar2 = FUN_8001368c(pvVar6);
  if (iVar2 == 0) {
    FUN_80015818(pvVar6,0x200);
  }
  uVar1 = read_volatile_4(DAT_cc00201c);
  pvVar8 = (void *)(uVar1 | 0x80000000);
  FUN_80013208(5,0x7d000,pvVar8);
  FUN_80013208(1,0xa0,(int)pvVar8 + 0x7d000);
  FUN_80013090(pvVar8);
  FUN_80015e68(pvVar8,"EXCEPTION!",0x28,0x28,4,1);
  FUN_80015e68(pvVar8,*(undefined4 *)((int)&PTR_s_Reserved_800ae2fc + (param_2 >> 6 & 0x5c)),200,
               0x30,4,1);
  FUN_80015f28(pvVar8,param_2,0x20,200,0x28,4,1);
  if (iVar2 == 0) {
    iVar3 = 0x40;
    iVar9 = 0;
    iVar2 = 0x10;
    do {
      FUN_80015e68(pvVar8,*(undefined4 *)((int)&PTR_s__r0_800ae26c + iVar9),0x10,iVar3,4,1);
      FUN_80015f28(pvVar8,*(undefined4 *)(iVar9 + (int)pvVar6),0x20,0x58,iVar3,4,1);
      iVar3 = iVar3 + 0x10;
      iVar2 = iVar2 + -1;
      iVar9 = iVar9 + 4;
    } while (iVar2 != 0);
    ppuVar5 = &PTR_s_r16_800ae2ac;
    iVar3 = 0x40;
    puVar10 = (undefined4 *)((int)pvVar6 + 0x40);
    iVar2 = 0x10;
    do {
      FUN_80015f28(pvVar8,*puVar10,0x20,400,iVar3,4,1);
      puVar10 = puVar10 + 1;
      FUN_80015e68(pvVar8,*ppuVar5,0x148,iVar3,4,1);
      ppuVar5 = ppuVar5 + 1;
      iVar2 = iVar2 + -1;
      iVar3 = iVar3 + 0x10;
    } while (iVar2 != 0);
    FUN_80015f28(pvVar8,*(undefined4 *)((int)pvVar6 + 0x84),0x20,400,0x140,4,1);
    FUN_80015e68(pvVar8,"lr",0x148,0x140,4,1);
    FUN_80015f28(pvVar8,*(undefined4 *)((int)pvVar6 + 0x198),0x20,0x58,0x150,4,1);
    FUN_80015e68(pvVar8,"srr0",0x10,0x150,4,1);
    FUN_80015f28(pvVar8,*(undefined4 *)((int)pvVar6 + 0x19c),0x20,400,0x150,4,1);
    FUN_80015e68(pvVar8,"srr1",0x148,0x150,4,1);
    uVar4 = FUN_80016c6c();
    FUN_80015f28(pvVar8,uVar4,0x20,0x58,0x160,4,1);
    FUN_80015e68(pvVar8,"msr",0x10,0x160,4,1);
  }
  else {
    FUN_80015e68(pvVar8,"REGS STACK CORRUPTED!",0x28,0x50,4,1);
    FUN_80015f28(pvVar8,param_1,0x20,0x58,0x5a,4,1);
    sprintf((char *)&DAT_80500000,"EXCEPTION! STACK CORRUPTED - vector %08x, stack %08x\n",param_2,
            pvVar6);
    FUN_80015818(&DAT_80500000,0x100);
  }
  FUN_80015818(pvVar8,0x96000);
  do {
                    /* WARNING: Do nothing block with infinite loop */
  } while( true );
}

which appears to write an error message into memory at 0x80500000. Why 0x80500000? Maybe Datel had some way to dump the entirety of main memory to investigate it externally?

The function also reads cc00201c and then does a second pass at some string stuff. Yagcd says that register is "TFBL - Top Field Base Register (L) (External Framebuffer Half 1)" - so I suspect this is it attempting to draw to the XFB. I know from experience that Datel does have a crash handler that does this, as it's possible to trigger it with Max Drive Pro, and the screen doesn't appear if Immediately Present XFB is enabled. However, I have Immediately Present XFB disabled, and it's still not showing up. So, that's odd...

In any case, we can set a breakpoint at 00000300, resume once, and then we should be able to see what's in memory at 0x80500000 after the exception is hit again... or rather, at 00500000, as in the exception handler physical addresses are used. Here's what's there:

00500000  45 78 63 65 70 74 69 6f  6e 20 43 6f 75 6e 74 20  |Exception Count |
00500010  36 0a 00 40 00 00 00 00  00 00 00 01 80 0b a9 30  |6..@...........0|
00500020  80 0b a9 98 80 50 01 00  00 00 00 01 80 50 01 00  |.....P.......P..|
00500030  00 00 00 00 80 0c 27 c0  00 00 00 00 00 00 00 00  |......'.........|
00500040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00500060  80 03 00 00 00 00 0c 00  80 0a e2 6c 80 00 00 00  |...........l....|
00500070  00 00 00 00 80 50 00 00  00 00 00 00 00 00 00 7c  |.....P.........||
00500080  20 00 00 03 80 01 3b 88  00 00 00 00 00 00 00 00  | .....;.........|
00500090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
005000d0  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
005000e0  00 00 00 00 00 00 00 00  00 00 00 00 81 80 00 00  |................|
005000f0  01 80 00 00 81 7f c8 c0  09 a7 ec 80 1c f7 c5 80  |................|
00500100  30 30 30 30 30 30 30 30  0a 72 31 36 20 30 30 30  |00000000.r16 000|
00500110  30 30 30 30 30 0a 72 31  37 20 30 30 30 30 30 30  |00000.r17 000000|
00500120  30 30 0a 72 31 38 20 30  30 30 30 30 30 30 30 0a  |00.r18 00000000.|
00500130  72 31 39 20 30 30 30 30  30 30 30 30 0a 72 32 30  |r19 00000000.r20|
00500140  20 30 30 30 30 30 30 30  30 0a 72 32 31 20 30 30  | 00000000.r21 00|
00500150  30 30 30 30 30 30 0a 72  32 32 20 30 30 30 30 30  |000000.r22 00000|
00500160  30 30 30 0a 72 32 33 20  30 30 30 30 30 30 30 30  |000.r23 00000000|
00500170  0a 72 32 34 20 38 30 30  33 30 30 30 30 0a 72 32  |.r24 80030000.r2|
00500180  35 20 30 30 30 30 30 63  30 30 0a 72 32 36 20 38  |5 00000c00.r26 8|
00500190  30 30 61 65 32 36 63 0a  72 32 37 20 38 30 30 30  |00ae26c.r27 8000|
005001a0  30 30 30 30 0a 72 32 38  20 30 30 30 30 30 30 30  |0000.r28 0000000|
005001b0  30 0a 72 32 39 20 38 30  35 30 30 31 64 38 0a 72  |0.r29 805001d8.r|
005001c0  33 30 20 30 30 30 30 30  30 30 30 0a 6c 72 20 38  |30 00000000.lr 8|
005001d0  30 30 31 33 62 38 38 0a  65 70 63 20 38 30 30 31  |0013b88.epc 8001|
005001e0  35 38 34 38 0a 47 4e 48  45 80 0b ad 10 80 0c 2b  |5848.GNHE......+|
005001f0  40 80 50 08 00 00 00 00  40 00 00 00 00 00 00 00  |@.P.....@.......|
00500200  00 80 0b ab 70 80 0b a9  c8 80 50 02 e5 ff ff ff  |....p.....P.....|
00500210  ff 00 00 00 01 00 00 00  00 80 0c 27 c0 00 00 00  |...........'....|
00500220  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00500240  00 00 00 00 00 80 03 00  00 00 00 0c 00 80 0a e2  |................|
00500250  6c 80 00 00 00 00 00 00  00 80 50 01 d8 00 00 00  |l.........P.....|
00500260  00 00 00 00 7c 20 00 00  03 80 01 3b 88 00 00 00  |....| .....;....|
00500270  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
005002b0  00 00 00 00 00 01 00 00  00 00 00 00 00 00 00 00  |................|
005002c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
005002d0  00 81 80 00 00 01 80 00  00 81 7f c8 c0 09 a7 ec  |................|
005002e0  80 1c f7 c5 80 00 00 00  00 00 00 00 00 00 00 00  |................|

Hmm. "Exception Count 6"? And things look corrupted... OK, what's there when 00000300 is hit for the first time? You'd expect nothing to be there, if it's the first exception, but perhaps it's not...

00500000  45 78 63 65 70 74 69 6f  6e 20 43 6f 75 6e 74 20  |Exception Count |
00500010  36 0a 00 40 00 00 00 00  00 00 00 01 80 0b a9 30  |6..@...........0|
00500020  80 0b a9 98 80 50 01 00  00 00 00 01 80 50 01 00  |.....P.......P..|
00500030  00 00 00 00 80 0c 27 c0  00 00 00 00 00 00 00 00  |......'.........|
00500040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00500060  80 03 00 00 00 00 0c 00  80 0a e2 6c 80 00 00 00  |...........l....|
00500070  00 00 00 00 80 50 00 00  00 00 00 00 00 00 00 7c  |.....P.........||
00500080  20 00 00 03 80 01 3b 88  00 00 00 00 00 00 00 00  | .....;.........|
00500090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
005000d0  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
005000e0  00 00 00 00 00 00 00 00  00 00 00 00 81 80 00 00  |................|
005000f0  01 80 00 00 81 7f c8 c0  09 a7 ec 80 1c f7 c5 80  |................|
00500100  30 30 30 30 30 30 30 30  0a 72 31 36 20 30 30 30  |00000000.r16 000|
00500110  30 30 30 30 30 0a 72 31  37 20 30 30 30 30 30 30  |00000.r17 000000|
00500120  30 30 0a 72 31 38 20 30  30 30 30 30 30 30 30 0a  |00.r18 00000000.|
00500130  72 31 39 20 30 30 30 30  30 30 30 30 0a 72 32 30  |r19 00000000.r20|
00500140  20 30 30 30 30 30 30 30  30 0a 72 32 31 20 30 30  | 00000000.r21 00|
00500150  30 30 30 30 30 30 0a 72  32 32 20 30 30 30 30 30  |000000.r22 00000|
00500160  30 30 30 0a 72 32 33 20  30 30 30 30 30 30 30 30  |000.r23 00000000|
00500170  0a 72 32 34 20 38 30 30  33 30 30 30 30 0a 72 32  |.r24 80030000.r2|
00500180  35 20 30 30 30 30 30 63  30 30 0a 72 32 36 20 38  |5 00000c00.r26 8|
00500190  30 30 61 65 32 36 63 0a  72 32 37 20 38 30 30 30  |00ae26c.r27 8000|
005001a0  30 30 30 30 0a 72 32 38  20 30 30 30 30 30 30 30  |0000.r28 0000000|
005001b0  30 0a 72 32 39 20 38 30  35 30 30 31 64 38 0a 72  |0.r29 805001d8.r|
005001c0  33 30 20 30 30 30 30 30  30 30 30 0a 6c 72 20 38  |30 00000000.lr 8|
005001d0  30 30 31 33 62 38 38 0a  65 70 63 20 38 30 30 31  |0013b88.epc 8001|
005001e0  35 38 34 38 0a 47 4e 48  45 80 0b ad 10 80 0c 2b  |5848.GNHE......+|
005001f0  40 80 50 08 00 00 00 00  40 00 00 00 00 00 00 00  |@.P.....@.......|
00500200  00 80 0b ab 70 80 0b a9  c8 80 50 02 e5 ff ff ff  |....p.....P.....|
00500210  ff 00 00 00 01 00 00 00  00 80 0c 27 c0 00 00 00  |...........'....|
00500220  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00500240  00 00 00 00 00 80 03 00  00 00 00 0c 00 80 0a e2  |................|
00500250  6c 80 00 00 00 00 00 00  00 80 50 01 d8 00 00 00  |l.........P.....|
00500260  00 00 00 00 7c 20 00 00  03 80 01 3b 88 00 00 00  |....| .....;....|
00500270  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
005002b0  00 00 00 00 00 01 00 00  00 00 00 00 00 00 00 00  |................|
005002c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
005002d0  00 81 80 00 00 01 80 00  00 81 7f c8 c0 09 a7 ec  |................|
005002e0  80 1c f7 c5 80 00 00 00  00 00 00 00 00 00 00 00  |................|

The exact same thing is there. As one other test, I tried overwriting the memory there, and it jumps back to 300 immediately after, so presumably the exception is occurring in the first sprintf call.

Exception handler, part 2

Let's add a breakpoint at 80013a34 instead. It's worth noting that Ghidra does not show any references to 80013a34, presumably because it's accessed by an rfi instruction instead of a normal call, so at this point it's not clear what else can reach it.

When that breakpoint is first hit, 0x80500000 is all zeros, as is expected, and PC = 80013a34, LR = 8001365c, MSR = 00000030, SRR0 = 80013a34, SRR1 = 00000030. (SRR0 equals PC and SRR1 equals MSR because SRR0 and SRR1 save the values to load into PC and MSR when using rfi; see 4.3.4 Returning from an Exception Handler in the manual on page 166).

If we resume execution, we end up with this:

00500000  45 58 43 45 50 54 49 4f  4e 21 20 72 65 67 73 20  |EXCEPTION! regs |
00500010  38 30 30 30 30 30 30 30  0a 66 72 6f 6d 20 30 30  |80000000.from 00|
00500020  30 30 30 63 30 30 20 28  49 53 49 29 0a 52 65 67  |000c00 (ISI).Reg|
00500030  73 20 6c 69 73 74 3a 0a  20 72 30 20 34 37 34 65  |s list:. r0 474e|
00500040  34 38 34 35 0a 20 73 70  20 38 30 30 63 61 63 64  |4845. sp 800cacd|
00500050  30 0a 72 74 6f 63 20 38  30 30 63 32 62 34 30 0a  |0.rtoc 800c2b40.|
00500060  20 72 33 20 38 30 30 30  30 64 30 30 0a 20 72 34  | r3 80000d00. r4|
00500070  20 30 30 30 30 30 30 30  38 0a 20 72 35 20 30 30  | 00000008. r5 00|
00500080  30 30 30 30 30 30 0a 20  72 36 20 30 30 30 30 30  |000000. r6 00000|
00500090  30 30 30 0a 20 72 37 20  30 30 30 30 30 30 30 30  |000. r7 00000000|
005000a0  0a 20 72 38 20 38 31 32  30 30 63 32 34 0a 20 72  |. r8 81200c24. r|
005000b0  39 20 38 30 30 30 30 63  66 38 0a 72 31 30 20 38  |9 80000cf8.r10 8|
005000c0  30 30 61 65 32 32 63 0a  72 31 31 20 38 30 30 30  |00ae22c.r11 8000|
005000d0  30 63 66 38 0a 72 31 32  20 30 30 30 30 30 30 30  |0cf8.r12 0000000|
005000e0  30 0a 72 31 33 20 38 30  30 63 32 37 63 30 0a 72  |0.r13 800c27c0.r|
005000f0  31 34 20 30 30 30 30 30  30 30 30 0a 72 31 35 20  |14 00000000.r15 |
00500100  30 30 30 30 30 30 30 30  0a 72 31 36 20 30 30 30  |00000000.r16 000|
00500110  30 30 30 30 30 0a 72 31  37 20 30 30 30 30 30 30  |00000.r17 000000|
00500120  30 30 0a 72 31 38 20 30  30 30 30 30 30 30 30 0a  |00.r18 00000000.|
00500130  72 31 39 20 30 30 30 30  30 30 30 30 0a 72 32 30  |r19 00000000.r20|
00500140  20 30 30 30 30 30 30 30  30 0a 72 32 31 20 30 30  | 00000000.r21 00|
00500150  30 30 30 30 30 30 0a 72  32 32 20 30 30 30 30 30  |000000.r22 00000|
00500160  30 30 30 0a 72 32 33 20  30 30 30 30 30 30 30 30  |000.r23 00000000|
00500170  0a 72 32 34 20 30 30 30  30 30 30 30 30 0a 72 32  |.r24 00000000.r2|
00500180  35 20 30 30 30 30 30 30  30 30 0a 72 32 36 20 30  |5 00000000.r26 0|
00500190  30 30 30 30 30 30 30 0a  72 32 37 20 38 30 30 61  |0000000.r27 800a|
005001a0  65 32 35 30 0a 72 32 38  20 30 30 30 30 30 30 66  |e250.r28 000000f|
005001b0  32 0a 72 32 39 20 38 30  30 31 35 66 39 38 0a 72  |2.r29 80015f98.r|
005001c0  33 30 20 30 30 30 30 30  30 66 38 0a 6c 72 20 38  |30 000000f8.lr 8|
005001d0  30 30 31 33 36 35 63 0a  65 70 63 20 38 30 30 31  |001365c.epc 8001|
005001e0  35 38 34 38 0a 47 4e 48  45 80 0c ac d0 80 0c 2b  |5848.GNHE......+|
005001f0  40 80 00 0d 00 00 00 00  08 00 00 00 00 00 00 00  |@...............|
00500200  00 00 00 00 00 81 20 0c  24 80 00 0c f8 80 0a e2  |...... .$.......|
00500210  2c 80 00 0c f8 00 00 00  00 80 0c 27 c0 00 00 00  |,..........'....|
00500220  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00500250  00 80 0a e2 50 00 00 00  f2 80 01 5f 98 00 00 00  |....P......_....|
00500260  f8 80 00 0c 00 20 00 00  00 80 01 36 5c 00 00 00  |..... .....6\...|
00500270  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
005002b0  00 00 00 00 00 01 00 00  00 00 00 00 00 00 00 00  |................|
005002c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
005002d0  00 81 80 00 00 01 80 00  00 81 7f c8 c0 09 a7 ec  |................|
005002e0  80 1c f7 c5 80 00 00 00  00 00 00 00 00 00 00 00  |................|

or in non-hexdump format:

EXCEPTION! regs 80000000
from 00000c00 (ISI)
Regs list:
 r0 474e4845
 sp 800cacd0
rtoc 800c2b40
 r3 80000d00
 r4 00000008
 r5 00000000
 r6 00000000
 r7 00000000
 r8 81200c24
 r9 80000cf8
r10 800ae22c
r11 80000cf8
r12 00000000
r13 800c27c0
r14 00000000
r15 00000000
r16 00000000
r17 00000000
r18 00000000
r19 00000000
r20 00000000
r21 00000000
r22 00000000
r23 00000000
r24 00000000
r25 00000000
r26 00000000
r27 800ae250
r28 000000f2
r29 80015f98
r30 000000f8
lr 8001365c
epc 80015848

There are a few things that are odd: "regs 80000000" seems dubious, and "from 00000c00 (ISI)" is wrong. Referring again to Table 4-2 Exceptions and Conditions on page 158, c00 is... "System call"? Well, that's odd. And, in the list of exception names, "System Call" is present (at 80035ff8). This seems to be a bug, as the exception handler code uses (param_2 >> 6 & 0x5c) to create an offset into the table (which can also be read as an index of (param_2 >> 8) & 0x17, as 32-bit pointers are used). The largest exception vector address is 0x1700, so it looks like this code was intended to keep things in-bounds in the array (which contains 0x18 entries), but masking doesn't work like that, so this gives invalid values for exceptions where the vector address is 0x800 through 0xf00, system call included.

As I mentioned earlier, the function at 80013a34 also seems to draw to the XFB, but not seems to be showing up there. Using Dolphin's "step over" function, the call to memcpy (at 8002c004) succeeds, but then the call to 80015818 fails. Here's what Ghidra decompiles that function as:

uint FUN_80015818(uint param_1,int param_2)
{
  uint uVar1;
  
  if (param_2 == 0) {
    return param_1;
  }
  if ((param_1 & 0x1f) != 0) {
    param_2 = param_2 + 0x20;
  }
  uVar1 = param_2 + 0x1fU >> 5;
  do {
    dataCacheBlockStore(param_1);
    param_1 = param_1 + 0x20;
    uVar1 = uVar1 - 1;
  } while (uVar1 != 0);
  syscall();
  return param_1;
}

This function is DCStoreRange, which forces data out of the CPU's data cache and into main memory. Under the assumption that Datel was able to read main memory externally, this would be needed because they wouldn't be able to read the cache on the CPU.

Setting a breakpoint at 80013b80 (right before the call to that function) shows that execution makes it through the loop of dcbst instructions (shown as dataCacheBlockStore by Ghidra)... but then jumps to 00000c00 in a sc instruction at 80015844 (shown as syscall()).

This is odd: Datel seems to be treating the syscall instruction as an error, but it's also using it directly in their error handler. What is that supposed to do normally? The PowerPC manual says (on page 505) that "In the PowerPC UISA, the sc instruction calls the operating system to perform a service." It doesn't say anything else about what it's for, other than that it jumps to the exception handler at c00 (more or less). So, we need to check what the IPL's syscall exception handler does.

To do so, I launched the GameCube IPL and then paused execution and looked at virtual address 80000c00 in the code viewer. The syscall exception handler itself is only 7 instructions long, and consists of setting the HID0[ABE] bit to 1, running isync and sync, restoring the previous value of HID0, and then using rti to return from the interrupt. Oddly, after the rti instruction there a bunch of additional unreachable instructions that seem to match the IPL's unexpected exception handler, but since these are after a rti they probably don't matter. In any case, what this syscall handler does is force synchronisation up to that point and ensure that that's broadcast over the bus (preventing speculative execution of further instructions and forcing later instructions to be re-fetched, and ensuring that stores have also completed); the relevant manual pages are 433 (isync), 543 (sync), 116 (Table 2-57. Memory Synchronization Instructions), and 146 (Table 3-5. Bus Operations Caused by Cache Control Instructions). Dolphin currently only partially supports the instruction cache, and doesn't support the data cache at all, so synchronisation and features are completely unimplemented (Interpreter sync isync, JIT sync isync). But, the way things work means that the cache and bus should already be coherent, meaning we don't need to do anything here.

Note that the syscall instruction, and exceptions in general, results in a switch to supervisor mode (see "Table 4-7. MSR Setting Due to Exception" on page 168 of the manual, which indicates that PR is set to 0 for the syscall exception, and "Table 4-4. MSR Bit Settings" on page 163, which says that PR means privilege level and 0 is supervisor). According to "Table 2-56. SPR Encodings for 750CL-Defined Registers" on page 114, HID0 is supervisor-only. But, this doesn't matter at all, because on the GameCube and Wii everything is already in supervisor mode, and other supervisor-only things (such as the dcbi (Data Cache Block Invalidate) instruction (page 388)) are used freely. As such, I have no idea why Nintendo used the syscall exception handler for this purpose.

Wait, why are there exception handlers at all?

The original bug report mentioned that "The game doesn't install exception handlers on boot like regular games that use the Nintendo SDK." It also says "Our non-IPL path doesn't install default handlers" and "Since we don't only install a couple exception handlers that only do an rfi". That latter comment doesn't exactly make sense grammatically, and seems like it should be "we do install a couple exception handlers that only do an rfi" - this seems to be the case as of the earliest available commit (from 2008), where default handlers are provided for the DSI (misspelled as "DFI"), FPU, and syscall exceptions. A single rfi instruction does not match the behavior we're seeing of storing some registers and then jumping to 80013a34. So, clearly, Datel is installing exception handlers of some sort.

A memory breakpoint at address 80000300 shows that it is written from 8002c028 (which is part of memcpy), with a call stack of 80013648, 8000e3a4, 80012174, 800049ec, 80003110. Although the exact order doesn't match here, we can compare with Super Mario Sunshine (which has debug symbols available). 80003110 is __start, and 800049ec is a combination of OSInit and main. 80012174 calls FUN_8000e384 with 0 as a parameter - I'm not entirely sure what this is for, but I think it's used to override the TV display mode (which isn't relevant for Ultimate Codes for use with Animal Crossing" but would make sense for freeloader discs). 8000e384 does a lot of initialization, but the first thing it does is initialize interrupt handlers in a function at 80013648 (which is roughly equivalent to OSExceptionInit).

Here's a decompilation of that function:

void* exception_vectors[] = { // at 800ae22c
	(void*)0x80000100, // System reset
	(void*)0x80000200, // Machine check
	(void*)0x80000300, // DSI
	(void*)0x80000400, // ISI
	(void*)0x80000500, // External interrupt
	(void*)0x80000600, // Alignment
	(void*)0x80000700, // Program
	(void*)0x80000800, // Floating-point unavailable
	(void*)0x80000900, // Decrementer
	// 0xa00-0xbff is reserved
	(void*)0x80000c00, // System call
	(void*)0x80000d00, // Trace
	(void*)0x80000e00, // Reserved on 750CL, "Other PowerPC processors may use this vector for floating-point assist exceptions". I don't think this is actually used
	(void*)0x80000f00, // Performance monitor
	(void*)0x80001300, // Instruction address breakpoint
	// 0x1400 is System management exception, not handled
	(void*)0x80001700, // Thermal-management interrupt
	(void*)0x999, // ending marker
};

u32 default_exception_handler[] = { ... }; // code, at 80015f98

void FUN_80013648(void)
{
	void** vector = &exception_vectors[0];
	while (*vector != (void*)0x999)
	{
		memcpy(*vector, default_exception_handler, 0xf8);
		DCStoreRange(*vector, 0xf8);
	}
}

Basically, it copies a default exception handler to all of the exception vectors, including the syscall one, and after copying each one it also uses DCStoreRange, which as determined earlier includes the sc instruction, which jumps to the now-overridden syscall exception handler.

Before I explain why this somehow still works, let's compare this with OSExceptionInit in Super Mario Sunshine:

void* __OSExceptionLocations[15] = {
	(void*)0x00000100, // System reset
	(void*)0x00000200, // Machine check
	(void*)0x00000300, // DSI
	(void*)0x00000400, // ISI
	(void*)0x00000500, // External interrupt
	(void*)0x00000600, // Alignment
	(void*)0x00000700, // Program
	(void*)0x00000800, // Floating-point unavailable
	(void*)0x00000900, // Decrementer
	// 0xa00-0xbff is reserved
	(void*)0x00000c00, // System call
	(void*)0x00000d00, // Trace
	// 0xe00 is reserved too
	(void*)0x00000f00, // Performance monitor
	(void*)0x00001300, // Instruction address breakpoint
	(void*)0x00001400, // System management exception
	(void*)0x00001700, // Thermal-management interrupt
};

u32 OSExceptionVector[] = { ... }; // code, at 80342424

void OSExceptionInit(void)
{
	for (int i = 0; i < 15; i++)
	{
		// equivalent to MEM_PHYSICAL_TO_K0(__OSExceptionLocations[i]):
		// convert a virtual address to a cached physical one
		void* dest = __OSExceptionLocations[i] + 0x80000000;
		memcpy(dest, OSExceptionVector, 0x98);
		DCFlushRangeNoSync(dest, 0x98);
		sync(); // literally the sync instruction
		ICInvalidateRange(dest, 0x98);
	}
	// (This is slightly simplified, as it can also patch `OSExceptionVector` for debugging support of some sort.)
}

Although this uses a slightly different set of exceptions, it still overwrites the syscall exception handler with a default one (which still treats the exception as a crash, though it works in a different way that's not relevant here). The significant difference is that instead of using DCStoreRange, they use DCFlushRangeNoSync, a sync instruction, and then ICInvalidateRange. As its name implies, DCFlushRangeNoSync doesn't include a sync instruction in it, or rather it doesn't include a sc instruction (so it doesn't jump to the syscall handler). ICInvalidateRange invalidates the instruction cache at that address, so that the old exception handler can't be executed from icache. ICInvalidateRange also includes sync and isync instructions at the end (though it doesn't use sc to jump to the syscall handler, and doesn't set the HID0[ABE] bit to 1.

Other than that, DCStoreRange versus DCFlushRange differs by use of the dcbf versus dcbst instructions (see pages 387 and 389 of the manual), where dcbf invalidates the data cache block after storing it to main memory while dcbst stores it to main memory but also leaves a copy in the data cache. dcbf is probably faster as the copied exception vector doesn't need to be in the data cache anymore since it's code, not data, but I doubt this actually makes a significant difference, since this is only ever run once on startup.

The two key differences here are that Datel is triggering the syscall exception handler as they are installing exception handlers (which doesn't happen in Super Mario Sunshine), and that Datel is not invalidating the instruction cache after replacing the exception handler (while Super Mario Sunshine is).

Why it works with the main menu

The second difference highly implies that the instruction cache (icache) is relevant here. In fact, there's something else that shows up in the logs upon booting a Datel title with the system menu enabled:

44:04:335 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 013008fc returned stale data: CACHED: 7c1e07ec vs. RAM: 00000000
44:04:335 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 013008fc returned stale data: CACHED: 7c1e07ec vs. RAM: 00000000
44:04:335 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 01300900 returned stale data: CACHED: 7c1e00ac vs. RAM: 00000000
44:04:335 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 01300904 returned stale data: CACHED: 3bde0020 vs. RAM: 00000000
44:04:335 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 01300908 returned stale data: CACHED: 4200fff4 vs. RAM: 00000000
44:04:335 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 0130090c returned stale data: CACHED: 48000004 vs. RAM: 00000000
44:04:335 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 01300910 returned stale data: CACHED: 7fe803a6 vs. RAM: 00000000
44:04:336 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 01300914 returned stale data: CACHED: 4e800020 vs. RAM: 00000000
44:04:337 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c00 returned stale data: CACHED: 7d30faa6 vs. RAM: 7c9043a6
44:04:337 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c04 returned stale data: CACHED: 612a0008 vs. RAM: 808000c0
44:04:337 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c08 returned stale data: CACHED: 7d50fba6 vs. RAM: 90240004
44:04:337 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c0c returned stale data: CACHED: 4c00012c vs. RAM: 90440008
44:04:337 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c10 returned stale data: CACHED: 7c0004ac vs. RAM: 9064000c
44:04:337 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c14 returned stale data: CACHED: 7d30fba6 vs. RAM: 90840010
44:04:337 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c18 returned stale data: CACHED: 4c000064 vs. RAM: 90a40014

Although I'm not entirely sure what the first portion is referring to, the reads at 00000c00 are related to the syscall exception handler; specifically, it's using the old code that the IPL wrote instead of the exception handler Datel wrote (and exits on an rfi instruction encoded as 4c000064 at 00000c18).

Running with the pure interpreter gives slightly different results: the reads near 01300900 are shown a HUGE number of times, and the reads at 00000c00 are repeated 7 times:

50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c00 returned stale data: CACHED: 7d30faa6 vs. RAM: 7c9043a6
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c04 returned stale data: CACHED: 612a0008 vs. RAM: 808000c0
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c08 returned stale data: CACHED: 7d50fba6 vs. RAM: 90240004
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c0c returned stale data: CACHED: 4c00012c vs. RAM: 90440008
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c10 returned stale data: CACHED: 7c0004ac vs. RAM: 9064000c
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c14 returned stale data: CACHED: 7d30fba6 vs. RAM: 90840010
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c18 returned stale data: CACHED: 4c000064 vs. RAM: 90a40014

50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c00 returned stale data: CACHED: 7d30faa6 vs. RAM: 7c9043a6
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c04 returned stale data: CACHED: 612a0008 vs. RAM: 808000c0
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c08 returned stale data: CACHED: 7d50fba6 vs. RAM: 90240004
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c0c returned stale data: CACHED: 4c00012c vs. RAM: 90440008
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c10 returned stale data: CACHED: 7c0004ac vs. RAM: 9064000c
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c14 returned stale data: CACHED: 7d30fba6 vs. RAM: 90840010
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c18 returned stale data: CACHED: 4c000064 vs. RAM: 90a40014

50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c00 returned stale data: CACHED: 7d30faa6 vs. RAM: 7c9043a6
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c04 returned stale data: CACHED: 612a0008 vs. RAM: 808000c0
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c08 returned stale data: CACHED: 7d50fba6 vs. RAM: 90240004
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c0c returned stale data: CACHED: 4c00012c vs. RAM: 90440008
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c10 returned stale data: CACHED: 7c0004ac vs. RAM: 9064000c
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c14 returned stale data: CACHED: 7d30fba6 vs. RAM: 90840010
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c18 returned stale data: CACHED: 4c000064 vs. RAM: 90a40014

50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c00 returned stale data: CACHED: 7d30faa6 vs. RAM: 7c9043a6
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c04 returned stale data: CACHED: 612a0008 vs. RAM: 808000c0
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c08 returned stale data: CACHED: 7d50fba6 vs. RAM: 90240004
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c0c returned stale data: CACHED: 4c00012c vs. RAM: 90440008
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c10 returned stale data: CACHED: 7c0004ac vs. RAM: 9064000c
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c14 returned stale data: CACHED: 7d30fba6 vs. RAM: 90840010
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c18 returned stale data: CACHED: 4c000064 vs. RAM: 90a40014

50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c00 returned stale data: CACHED: 7d30faa6 vs. RAM: 7c9043a6
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c04 returned stale data: CACHED: 612a0008 vs. RAM: 808000c0
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c08 returned stale data: CACHED: 7d50fba6 vs. RAM: 90240004
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c0c returned stale data: CACHED: 4c00012c vs. RAM: 90440008
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c10 returned stale data: CACHED: 7c0004ac vs. RAM: 9064000c
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c14 returned stale data: CACHED: 7d30fba6 vs. RAM: 90840010
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c18 returned stale data: CACHED: 4c000064 vs. RAM: 90a40014

50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c00 returned stale data: CACHED: 7d30faa6 vs. RAM: 7c9043a6
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c04 returned stale data: CACHED: 612a0008 vs. RAM: 808000c0
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c08 returned stale data: CACHED: 7d50fba6 vs. RAM: 90240004
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c0c returned stale data: CACHED: 4c00012c vs. RAM: 90440008
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c10 returned stale data: CACHED: 7c0004ac vs. RAM: 9064000c
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c14 returned stale data: CACHED: 7d30fba6 vs. RAM: 90840010
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c18 returned stale data: CACHED: 4c000064 vs. RAM: 90a40014

50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c00 returned stale data: CACHED: 7d30faa6 vs. RAM: 7c9043a6
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c04 returned stale data: CACHED: 612a0008 vs. RAM: 808000c0
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c08 returned stale data: CACHED: 7d50fba6 vs. RAM: 90240004
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c0c returned stale data: CACHED: 4c00012c vs. RAM: 90440008
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c10 returned stale data: CACHED: 7c0004ac vs. RAM: 9064000c
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c14 returned stale data: CACHED: 7d30fba6 vs. RAM: 90840010
50:30:914 Core\PowerPC\PPCCache.cpp:199 I[PowerPC]: ICache read at 00000c18 returned stale data: CACHED: 4c000064 vs. RAM: 90a40014

6 of these are from installing the exception handlers at 80000c00 (System call), 80000d00 (Trace), 80000e00 (Reserved), 80000f00 (Performance monitor), 80001300 (Instruction address breakpoint), and 80001700 (Thermal-management interrupt). The 7th is from other code that I'll get to in a bit.

What's odd here is that these same icache messages don't show up with skip main menu enabled. Here is the code that generates that message... and right at the start of InstructionCache::ReadInstruction is a check for if icache is enabled, which checks the HID0.ICE bit (and also a separate bypass that disables the icache emulation when it causes problems with the JIT). Per page 62 of the manual, HID0.ICE is "Instruction cache enable", and also "ICE is zero at power-up", and the ICE bit corresponds to 0x00008000.

Starting a Datel title via BS2 EMU and then looking in Dolphin's registers widget shows that HID0 is defaulting to 00000000, which includes the instruction cache being disabled. But setting a breakpoint at Datel's entry point (80003100) and then launching with the main menu enabled shows that the main menu normally sets HID0 to 0011c464, which has the instruction cache enabled.

And... that's it. If the ICE bit is enabled by default, then everything boots successfully (although textures are missing (see also regular action replay) due to a separate issue). Perhaps on real hardware other bits would matter, but for Dolphin, enabling the instruction cache is enough to get it to boot.

That said, there are some lingering questions...

Doesn't the JIT not support the instruction cache?

Dolphin's JIT and cached interpreter don't accurately handle the instruction cache; they only call out to it when they need to compile a new JIT block from a set of instructions, but don't re-check it when running those instructions later. This sometimes leads to issues where instructions stay in the instruction cache far longer than they actually should, resulting in games freezing.

As such, even with the instruction cache disabled (with HID0.ICE set to 0), it would be expected that if the game doesn't use an icbi instruction, Dolphin wouldn't notice that things have changed and wouldn't bother recompiling the cache block, effectively behaving the same as if the instruction cache were enabled.

The explanation is that, as a heuristic, Dolphin also invalidates the JIT cache on data cache instructions (even on the cached interpreter, which re-uses the same JIT block functionality). This means that Datel's DCStoreRange is invalidating the JIT cache, even though they aren't calling ICInvalidateRange afterwards.

(Additionally, Dolphin previously didn't invalidate the JIT cache on icbi if the instruction cache was disabled. I opened a PR to fix this, though it shouldn't matter in practice.)

Why did 80000000 get corrupted?

Normally, virtual address 80000000 contains various bits of global state, including the game's ID (see WiiBrew for more info). For most Datel titles, the game ID is GNHE5d, which is officially used by NHL Hitz 2002. This is the only known official game ID to have a lowercase character, but it's unclear if that's actually significant.

Datel copies the contents of 80000000 into their crash info at 80500000, but it appears to be corrupted there; only "GNHE" is visible, not GNHE5d.

What happened here is that address 800000c0 (not to be confused with the sycall handler at 80000c00) is a pointer named __osCurrentCtxPhysical, which is the location where registers are supposed to be saved when a context change occurs (I think this is used for threads normally?). Importantly, the exception handler saves various registers to that location.

Dolphin doesn't initialize that address on startup to anything other than 0. However, a physical address of 0 corresponds to a virtual address of 80000000, meaning that some of these global variables get corrupted. (Datel does have code at address 800133dc that sets these values (to 800b89c0), but that hasn't been called yet. This code also calls DCStoreRange((void*)0x80000000,0x100), which is the 7th sc instruction that produces ICache messages in pure interpreter mode.)

Although the OSContext structure that is being written to is 0x2b8 bytes long, offset 0-0x80 is used by the integer general purpose registers, while 0x90-0x190 is used by the floating-point registers. The exception handler doesn't save r0, so the 4-byte game ID isn't changed (leaving GNHE) along, but it does save the other GPRs, meaning everything else is modified (including the developer ID "5d"). The exception handler doesn't save the floating-point registers, so the process of saving registers doesn't corrupt 800000c0, and future exceptions will still be saved at 80000000. Thus, this corruption doesn't really result in any new problems. Datel does still log the address where registers were saved, which is where "regs 80000000" comes from.

Where do the DSI exceptions and illegal instructions come from?

So far, it seems like this should just be an infinite loop of a sc instruction being triggered, the unhandled exception handler (placed at the syscall exception vector at 80000c00) jumping to a function at 80013a34 to write info about the exception at 80500000, and then that function trying to use DCStoreRange and triggering another sc instruction. This should just be a simple infinite loop, but we get writes to invalid addresses (both from sprintf writing to an invalid address and the DSI exception handler that's called on writes to an invalid address) and eventually illegal instructions. Where are these coming from?

Well, I mentioned earlier that a breakpoint at 00000300 (the DSI exception vector) shows the stack pointer (sp or r1) decreasing by 0x150 each time. That's already after things have broken, but a stack overflow would be a good starting point to check.

At 80003118, Datel initializes the stack pointer to 800cad40. If we set a breakpoint at 80013a34, the stack pointer is 800cacd0 the first time it's reached. The next time, it's 800caca0, then 800cac70, on and on, decreasing by 0x30 each time. This makes sense; 80013a34 starts with a stwu r1, -0x30(r1) instruction, which (per page 534 of the manual) stores r1 at MEM[r1 - 0x30] and then sets r1 to r1 - 0x30 (creating a new stack frame).sprintf creates its own stack frame (of 0x100 bytes, and then 0x20 more bytes for logic shared with other printf-like functions, and then even more bytes later on), though memcpy and DCStoreRange do not, and since sprintf is returning normally at this point, its stack frames have been popped and when sc is used by DCStoreRange, the total stack change is still 0x30 bytes.

Since we're in an infinite loop where the stack is decreasing by 0x30 bytes each time, eventually it will run out of stack space. The GameCube/Wii doesn't use any kind of guard page to detect stack overflows (although theoretically it could with the MMU), so eventually this starts corrupting data earlier in memory. After 0x55a iterations, some of the printf code writes address 800ba830 (this was checked using a memory breakpoint; this address is significant because Datel tracks the number of times an exception occurred at 800ba82c, so once this happens it's more annoying to keep track of the number of iterations and I just stopped doing so).

After a bit of experimentation, I've determined that the crash happens after the stack overflow overwrites address 800ba6ec. Setting a memory breakpoint there (with both read and write enabled) and then letting it get 6 times (3 reads and 3 writes from within printf code), and then setting a breakpoint at the start of sprintf, results in a crash the next time around. I'm not entirely sure what causes the crash (and trying to reverse-engineer exactly how printf works isn't worth it right now, but it seems like printf has some kind of global data at around 800ba934 and on, which the stack clobbered, and then it tries to dereference an invalid pointer in some of the shared printf code at 8002c964. This is a good enough explanation for me, though it's not entirely complete.

Just because we're now writing to invalid pointer doesn't mean Datel's Wild Ride of Exception Handler Fun™ is over. Instead, it's now accelerated, as we're now generating an exception in the printf code, where the stack has changed by 0x150 since execution started. It eventually will overwrite all of memory. In particular, though, it'll eventually overwrite the printf function itself, an once it clobbers something earlier than address 8002c964, that instruction will be hit instead.

However, Dolphin's JIT cache means that writes to code won't get detected unless the game uses a data cache or instruction cache related instruction, and that doesn't happen for writes to the stack. Only on the pure interpreter does this get detected, and at that point Dolphin starts complaining, but it just treats the unknown instructions as NOPs. Even still, Datel's code doesn't necessarily write to all of the registers in a stack frame, so some instructions survive this, enough for more exceptions to be raised. Eventually 80013a34 gets clobbered, but some of the sprintf calls survive - though sprintf is broken enough that it won't do write anything anymore. It just so happens that the last thing it successfully writes is "Exception Count 6", but that's mostly a coincidence.

Execution continues further, and eventually the stack pointer reaches 80000cc0, and then 80000340, clobbering the exception handlers (for instance, addresses 80000cd4 and 800003e0 through 800003e8 get clobbered). Although the mtspr SRR0, r5 and rti instructions survive, the clobbering of 800003e0-800003e8 means that r5 no longer points to 80013a34, but instead to 00000030, which is not valid virtual memory, and thus an attempt to execute code there results in an ISI exception, jumping to the handler at physical address 00000400. This too has been corrupted, but some code still survives that sets r5 to 8002cd9c, which is at least a valid address (near the printf code, though I'm not entirely sure what it's for), but it too has been trashed. At 8002cdf4 a lbz r9, 0 (r27) instruction is executed, but r27 is 0, resulting in another invalid read and DSI a exception.

This isn't an infinite loop, because there still are external interrupts, which jump to address 500. That exception handler still manages to jump to 80013a34, which calls sprintf and decreases the stack too. Eventually, the stack itself is no longer pointing at valid memory, and exceptions start occurring in the exception handlers, and things just die completely.

Where is the actual syscall handler set?

This whole issue revolves around Datel registering the unhandled exception handler for crashes as the syscall handler. But, if you look above, Super Mario Sunshine's OSExceptionInit also registers the unhandled exception handler as the syscall handler. What's going on here?

In Super Mario Sunshine, OSExceptionInit is called by OSInit. The very next function OSInit calls is __OSInitSystemCall, which looks like this:

void __OSInitSystemCall(void)
{
  memcpy(&DAT_80000c00,SystemCallVector,0x1c);
  DCFlushRangeNoSync(&DAT_80000c00,0x100);
  sync(0);
  ICInvalidateRange(&DAT_80000c00,0x100);
  return;
}

This registers the expected syscall handler that modifies HID0[ABE], runs isync and sync, and then restores HID0. So, what about Datel's code?

Well, the function at 800135e0 registers the default exception handlers. The function that calls that (at 8000e384) then calls 80013420. Here's what that function looks like:

void FUN_800133dc(OSContext *param_1)
{
  __osCurrentCtxPhysical = (OSContext *)((uint)param_1 & 0xfffffff);
  __osCurrentCtx = param_1;
  DCStoreRange(&__bootInfo,0x100);
  return;
}

void FUN_80013354(void *param_1,undefined2 param_2)
{
  memcpy(param_1,&LAB_80016090,0x80);
  *(undefined2 *)((int)param_1 + 0x6a) = param_2;
  DCStoreRange(param_1,0x100);
  return;
}


undefined * FUN_800132a4(int param_1,undefined *param_2)
{
  undefined *puVar1;
  
  puVar1 = (&PTR_FUN_80003000)[param_1];
  (&PTR_FUN_80003000)[param_1] = param_2;
  DCStoreRange(&PTR_FUN_80003000 + param_1,0x20);
  return puVar1;
}

void FUN_80013420(void)
{
  int iVar1;
  
  OSDisableInterrupts();
  FUN_800133dc(&DAT_800b89c0);
  memcpy(&LAB_80000c00,&SyscallHandler,0x1c);
  DCFlushRangeNoSync(&LAB_80000c00,0x100);
  sync();
  ICInvalidateRange(&LAB_80000c00,0x100);
  memset(&DAT_80003040,0,0x80);
  iVar1 = 0;
  __osCurrInterruptMask = 0;
  write_volatile_4(DAT_cc003004,0);
  __osPrevInterruptMask = 0xffe0;
  FUN_80013354(&LAB_80000500,4);
  FUN_80013354(&LAB_80000900,8);
  do {
    FUN_800132a4(iVar1,FUN_800136c8);
    iVar1 = iVar1 + 1;
  } while (iVar1 < 0xf);
  FUN_800132a4(4,&LAB_80016110);
  FUN_800132a4(8,&LAB_8001616c);
  FUN_80014338();
  OSEnableInterrupts();
  FUN_800164f4();
  return;
}

This is the function that sets up the OS context, and also that registers the syscall handler and sets a new batch of exception handlers (including an alternative unhandled exception handler). Interestingly, this function correctly uses DCFlushRangeNoSync, sync, and ICInvalidateRange, so after this point the sc instruction will call the expected syscall handler.

Speculation as to why Datel's code is like this

Datel's code here mostly seems like a reimplementation of Nintendo's code. So, why is it missing the ICInvalidateRange call when setting exception handlers initially? How did this bug even get introduced?

I can only speculate, but my guess is that Datel originally wrote something like this:

void SetInterruptHandlers(void)
{
	void** vector = &exception_vectors[0];
	while (*vector != (void*)0x999)
	{
		memcpy(*vector, default_exception_handler, 0xf8);
		DCFlushRange(*vector, 0xf8);  // Oops!
		sync();
		ICInvalidateRange(*vector, 0xf8);
	}
}

If they were working without debug symbols (which seems likely to me), then most likely they didn't spot the difference between DCFlushRangeNoSync and DCFlushRange, and chose the wrong one. This would work the same way as the Dolphin issue for the first few iterations, with DCFlushRange calling the system menu's syscall handler. But then, when they register the syscall handler, they invalidate icache for it. That iteration still succeeds because the DCFlushRange call and the corresponding sc instruction happen before icache is invalidated, but the next iteration would jump to the new syscall exception handler, and then their unhandled exception handler function at 80013a34, and then eventually call DCFlushRange and enter the same loop that we just dealt with.

Imagine trying to debug this on real hardware without any kind of breakpoint support, and with only the ability to dump the console's memory (and only what's been flushed to main memory, not the contents of the data cache or instruction cache). You'd need to call DCFlushRange if you wrote anything, but any call to that in the exception handler just introduces a new infinite loop (although it does successfully flush the relevant data too). You might want to check if an infinite loop is happening, and add some kind of logging with the number of times an exception has occurred (even if you only expect it to go once), say "Exception Count %d\n".

Furthermore, that "Exception Count %d\n" message only shows up if nothing after the first sprintf call runs, as the following calls also restart writing at address 80500000. I don't entirely know what to make of this, but perhaps this is a remnant of an earlier version of the exception handler where that was the only message (though it being the only message that survives being printed by sprintf in the end is interesting).

Now, imagine also that the information you see is this:

EXCEPTION! regs 80000000
from 00000c00 (ISI)

Because of the incorrect masking bug, 0xc00 & 0x1700 becomes 400, which is treated as ISI. So, you think that an ISI exception is occurring, instead of a syscall exception, and can't tell exactly why it's happening. The actual location is there, but perhaps they missed it (or only added it later on?).

The obvious solution is to try to experiment; for instance, by replacing DCFlushRange with DCStoreRange, or removing the ICInvalidateRange call. And, surprisingly, removing the ICInvalidateRange call seems to fix the issue: on real hardware, things boot. So, you declare the code cursed, and stop investigating it further to work on actually useful things.

One thing that slightly goes against this hypothesis is that Datel does use DCFlushRangeNoSync when registering the syscall handler alter on. That said, there is something funky with that: Datel doesn't have all of the cache functions that Nintendo does. Specifically, they have DCStoreRange (80015818), DCFlushRangeNoSync (8001584c), ICInvalidateRange (8001587c), DCInvalidateRange (800158b4), and then a second copy of DCFlushRangeNoSync (800158e4). In comparison, Super Mario Sunshine has DCInvalidateRange (8034365c), DCFlushRange (8034368c), DCStoreRange (803436c0), DCFlushRangeNoSync (803436f4), DCZeroRange (80343724), and ICInvalidateRange (80343754), along with functions for enabling and disabling the caches and for working with the L2 cache. To me, it looks like they may have tried adding these functions as they needed them, and thus ended up with duplicates because they all look fairly similar.

There isn't really any way to be sure, but it does seem like this is something that was arrived at through experimentation that happened to work on hardware (after an initial mistake broke it).

Summary

Datel's code to register exception handlers uses DCStoreRange instead of DCFlushRangeNoSync. DCStoreRange includes a sc instruction, which triggers a syscall exception. The code that registers exception handlers overwrites the syscall exception handler with one that treats it as a crash. The code that attempts to report a crash also uses DCStoreRange, which again triggers a syscall exception, resulting in an infinite loop. This infinite loop eventually results in a stack overflow clobbering data used by sprintf, and then code, resulting in more crashyness.

The infinite loop is averted when using the system menu because icache gets enabled, and since the code to register exception handlers doesn't use ICInvalidateRange, the original syscall handler remains in the instruction cache. The problem in Dolphin was that system menu/BS2 HLE didn't enable icache, and Datel never enabled it either.

Hopefully all of this makes sense (and is correct). I haven't done any proofreading, and these notes are mostly so that if this somehow breaks in the future, there's an explanation of how it ended up working.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment