This will take an emulated address that contains emulated x86 code and returns a pointer to a host address containing x64 code.
void* x64CPU::translateEip(U32 ip) {
BOXEDWINE_CRITICAL_SECTION_WITH_MUTEX(this->thread->memory->executableMemoryMutex);
void* result = translateEipInternal(NULL, ip);
makePendingCodePagesReadOnly();
return result;
}
translateEipInternal is a function that is called recursively to create chunks of x64 code.
translateEipInternal will:
- translate a single block (until jmp, call, retn, etc, conditional jumps will not end a block)
- a) we can’t translate the code after a jmp or call, because it may not be valid (think startup functions that don’t return)
- b) call/jmp to absolute address will be followed in link (3c below)
- c) call/jmp to reg/mem and retn will have code inserted to lookup and use the host op address.
- if the host op address is NULL, then when it is used it will cause an exception that will be handled
- the handled exception will call translateEipInternal on that address
- map each emulated instruction address in the translated block to a host instruction address so that we can find it later
- all the jumps that had dummy values placed in them will now be processed (link)
- a) if the address being jumped to is already mapped to a host instruction, then just use that host instruction
- b) if the address being jumped to is not valid (emulation memory is not mapped yet), then do nothing for now, this assumes it will be patched later (TODO when I find an example of this where it doesn’t get patched)
- c) call translateEipInternal on the address being jumped to
- d) write address into reserved spot
Full code loaded
- Examples: Most games behave this way
- This will call translateEip during startup and will translate all the code until a jump/call to a reg or mem is used.
Code with place holders
- Examples: F16
- blocks of 0’s will be in the code that get filled in later
- This is challenging because when this code gets patched by the app, we detect the write to the read only protected emulated memory space then try to re translate this op, since the previous translation was wrong and the patch op is not the same size as the previous op, we will need to re translate the entire block.
- Will need to make sure the emulated op to host op address mapping gets updated.
Code with simple patches that doesn’t change the instruction the emulated instruction size
- Examples: Diablo intro movie
- This is pretty easy to detect. The emulated code page is write protected. When the write exception happens we make the page read/write, then translate just the one or two ops this 1, 2 or 4 byte write affects. The emulated op does not change size so we don’t need to translate any other ops that are not directly affected by this write.
- The emulated op to host op address mapping does not change
- If the host instruction becomes small then nop’s will be added
- If the host instruction becomes bigger, I don’t know what to do yet (TODO when I find an example of this)
Code space get rewritten with new code
- Examples: None yet
- This will probably be handled the same as the code with place holders above.
TODO
- Currently if an emulated code page is unmapped, the memory associated with the host code for that page is not free’d. Host code is only free’d when the emulated process is free’d
- What to do if patched code results in a larger host instruction?
- What to do if we come across a jmp/call to an absolute address that doesn’t exist? Currently I assume it will be patched later.
- Figure a way to continue translating a block after a call/jmp, this would result in better performance because it would allow the complete translation of a function where that function would not need to check host to emulation instruction mapping.