2

I'm developing an emulator for a toy CPU (TR3200 cpu). Actually I have a pure interpreter core, but I'm researching to develop a tracing JIT cpu core. I have doubts about how track if a source machine code that has been jited, is modified by the emulated code( self-modifing code or something like an OS loading a programming to RAM) I'm thinking in using an segment or interval tree for this, but I can't find any information or examples of how this is really handled.

In other words... I know how do JIT (I'm thinking on using asmjit), that I should store jitted code on a map that uses the begin address of the jitted block as index; I have some idea how handle cycle count and devices syncs with jitted code. But I not have a clear idea of how handle when guest program writes on a jitted memory block.

For example: We have an simple OS that has executed a program at addresses 0x100-0x500 and it returned correctly. The JIT cpu core, would (optimistic) generated a native machine code that represents these chunk of code. Now, if the OS loads another program and places it over 0x200-0x300 address block, not should invalidate the old jitted chunk of 0x100-0x500 as it got overwrite. Or a worst case, a program that self-rewrite itself, making jitted chunks invalid. How detect this ?

Zardoz89
  • 590
  • 5
  • 17

1 Answers1

4

The problem with using even a moderately complex data structure to track changes to guest memory is that it must be possible to query and update it from the jitted code. To avoid a ridiculous blowup in code size, you will probably need to insert a call to some function that writes to the interval tree (rather than inlining the write) whenever the jitted code executes a store instruction. Performance is likely to suffer to the point where you could just as well have interpreted the code to begin with. Furthermore, extreme care must be taken so that no other part of the emulator writes the corresponding pages without updating the data structure.

There's a slightly less portable approach that involves exploiting the host operating system's virtual memory facilities. When your JIT emits code for a chunk of guest memory, you mark the corresponding virtual memory pages as read-only. Any subsequent write attempt will trigger an exception (such as a SIGSEGV) that can be caught by your host program. Upon receiving this exception, your host program promptly invalidates all jitted code that was generated from guest memory that overlapped the faulting page and then re-enables write access to the page so that the store may complete.

While this solution can cope with most writes, it doesn't handle the situation where a piece of jitted code modifies an immediately following instruction. To see why, consider what will happen:

  1. The jitted code stores to the part of guest memory from which one of the immediately following instructions was jitted.
  2. An exception is raised and handled by the host program. All jitted code from the faulting page (including the code whose store triggered the exception in the first place) is invalidated. Write access is enabled again.
  3. The exception handler returns, but where? Why, to the faulting instruction, of course, which is part of the invalidated code! Uh-oh!

As far as I can tell there are two options. Either you ignore this problem, effectively making the behavior of code that modifies instructions in its immediate vicinity undefined, or you set some kind of invalidation flag in the exception handler and test it after each store, bailing out into the interpreter if the test fails.

A quick search suggests that QEMU might be using a similar mechanism to handle modifications to guest memory, but I'm unable to verify it.

Martin Törnwall
  • 9,299
  • 2
  • 28
  • 35