There are some mechanisms built into the DeviceEmulator's JIT which can make debugging and tracing both the JIT and guest code fairly straightfoward.
1) Define LOGGING_ENABLED to 1 in include\emulator.h, to compile in debug logging in the JIT. Several variables control logging:
- LogInstructionStart - begin logging once 'n' guest instructions have been executed
- the LogIf() macro specifies the conditions under which logging happens. The expression "(Cpu.CPSR.Bits.Mode == UserModeValue && InstructionCount >= LogInstructionStart)" logs only instructions executed in usermode, and only after LogInstructionStart instructions have executed. This is a handy trigger, as it filters out interrupt handlers. JIT logging will dump the disassembled ARM opcodes and register state, showing you a single-step trace of your code as it runs.
2) In cpus\arm\armcpu.cpp, edit g_fOptimizeCode to be 0/false instead of 1/true. This disables cross-instruction optimizations such as the code that recognizes ARMFlushICacheLines and converts it to a no-op. It also inserts a call to SingleStepHelper() between guest instructions, to poll for single-step requests from the hardware debugger interface.
3) Write your own low-level debugger - create an instance of IDeviceEmulatorDebugger from another process, and you can read/write memory, read/write registers, break in, single-step, set breakpoints, etc. This is the interface that the Platform Builder eXDI hardware debugger connects to. See cpus\arm\debugger.cpp for the implementation of this interface - it's quite straightforward.
4) If you are changing the JIT for some reason, and it stops working, open cpus\arm\armcpu.cpp and check out the comment block above "//#define GOOD_EMULATOR 1" - this feature allows you to run a known-good DeviceEmulator.exe and a known-bad DeviceEmulator.exe in lock-step with each other. Each one steps forward by one guest instruction, then they compare results and if there is a difference, assert. Please note that the two emulators quickly diverge if you enable guest hardware interrupts, as interrupt delivery is not slaved between the two emulators - timer interrupts will arrive at different times.