Merge pull request #1440 from LukeUsher/interpret-larger-blocks

Interpret larger blocks during exception handler to reduce the number of exceptions per-frame
This commit is contained in:
PatrickvL 2018-09-18 10:50:57 +02:00 committed by GitHub
commit 338879ba96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 344 additions and 131 deletions

View File

@ -127,7 +127,7 @@ NT_TIB *GetNtTib()
} }
xboxkrnl::KPCR* KeGetPcr(); xboxkrnl::KPCR* WINAPI KeGetPcr();
uint32_t fs_lock = 0; uint32_t fs_lock = 0;

View File

@ -131,7 +131,7 @@ xboxkrnl::PLIST_ENTRY RemoveTailList(xboxkrnl::PLIST_ENTRY pListHead)
// * Declaring this in a header causes errors with xboxkrnl // * Declaring this in a header causes errors with xboxkrnl
// * namespace, so we must declare it within any file that uses it // * namespace, so we must declare it within any file that uses it
// ****************************************************************** // ******************************************************************
xboxkrnl::KPCR* KeGetPcr(); xboxkrnl::KPCR* WINAPI KeGetPcr();
// Interrupts // Interrupts

View File

@ -124,7 +124,8 @@ XBSYSAPI EXPORTNUM(8) xboxkrnl::ULONG _cdecl xboxkrnl::DbgPrint
vsprintf(szBuffer, Format, argp); vsprintf(szBuffer, Format, argp);
va_end(argp); va_end(argp);
printf(szBuffer); // Note : missing newlines can occur // Allow DbgPrint to be disabled
EmuLog(LOG_PREFIX, LOG_LEVEL::INFO, "%s", szBuffer);
fflush(stdout); fflush(stdout);
} }

View File

@ -76,7 +76,7 @@ LIST_ENTRY_DEFINE_HEAD(ShutdownRoutineList);
// * Declaring this in a header causes errors with xboxkrnl // * Declaring this in a header causes errors with xboxkrnl
// * namespace, so we must declare it within any file that uses it // * namespace, so we must declare it within any file that uses it
// ****************************************************************** // ******************************************************************
xboxkrnl::KPCR* KeGetPcr(); xboxkrnl::KPCR* WINAPI KeGetPcr();
// ****************************************************************** // ******************************************************************
// * 0x0009 - HalReadSMCTrayState() // * 0x0009 - HalReadSMCTrayState()

View File

@ -186,7 +186,7 @@ void FASTCALL KiWaitSatisfyAll
// * NOTE: This is a macro on the Xbox, however we implement it // * NOTE: This is a macro on the Xbox, however we implement it
// * as a function so it can suit our emulated KPCR structure // * as a function so it can suit our emulated KPCR structure
// ****************************************************************** // ******************************************************************
xboxkrnl::KPCR* KeGetPcr() xboxkrnl::KPCR* WINAPI KeGetPcr()
{ {
xboxkrnl::PKPCR Pcr; xboxkrnl::PKPCR Pcr;

View File

@ -119,34 +119,46 @@ void EmuX86_IOWrite(xbaddr addr, uint32_t value, int size)
uint32_t EmuX86_Mem_Read(xbaddr addr, int size) uint32_t EmuX86_Mem_Read(xbaddr addr, int size)
{ {
switch (size) { __try {
case sizeof(uint32_t) :
return *(uint32_t*)addr; switch (size) {
case sizeof(uint16_t) : case sizeof(uint32_t) :
return *(uint16_t*)addr; return *(uint32_t*)addr;
case sizeof(uint8_t) : case sizeof(uint16_t) :
return *(uint8_t*)addr; return *(uint16_t*)addr;
default: case sizeof(uint8_t) :
// UNREACHABLE(size); return *(uint8_t*)addr;
default:
// UNREACHABLE(size);
return 0;
}
}
__except (true) {
EmuLog(LOG_PREFIX, LOG_LEVEL::WARNING, "EmuX86_Mem_Read Failed (0x%08X, %d)", addr, size);
return 0; return 0;
} }
} }
void EmuX86_Mem_Write(xbaddr addr, uint32_t value, int size) void EmuX86_Mem_Write(xbaddr addr, uint32_t value, int size)
{ {
switch (size) { __try {
case sizeof(uint32_t) : switch (size) {
*(uint32_t*)addr = (uint32_t)value; case sizeof(uint32_t) :
break; *(uint32_t*)addr = (uint32_t)value;
case sizeof(uint16_t) : break;
*(uint16_t*)addr = (uint16_t)value; case sizeof(uint16_t) :
break; *(uint16_t*)addr = (uint16_t)value;
case sizeof(uint8_t) : break;
*(uint8_t*)addr = (uint8_t)value; case sizeof(uint8_t) :
break; *(uint8_t*)addr = (uint8_t)value;
default: break;
// UNREACHABLE(size); default:
return; // UNREACHABLE(size);
return;
}
}
__except (true) {
EmuLog(LOG_PREFIX, LOG_LEVEL::WARNING, "EmuX86_Mem_Write Failed (0x%08X, 0x%08X, %d)", addr, value, size);
} }
} }
@ -192,13 +204,8 @@ uint32_t EmuX86_Read(xbaddr addr, int size)
return value; return value;
} }
if (g_bEmuException) { //pass the memory-access through to normal memory :
EmuLog(LOG_PREFIX, LOG_LEVEL::WARNING, "EmuX86_Read(0x%08X, %d) [Unknown address]", addr, size); value = EmuX86_Mem_Read(addr, size);
value = 0;
} else {
// Outside EmuException, pass the memory-access through to normal memory :
value = EmuX86_Mem_Read(addr, size);
}
DbgPrintf(LOG_PREFIX, "Read(0x%08X, %d) = 0x%08X\n", addr, size, value); DbgPrintf(LOG_PREFIX, "Read(0x%08X, %d) = 0x%08X\n", addr, size, value);
} }
@ -224,12 +231,7 @@ void EmuX86_Write(xbaddr addr, uint32_t value, int size)
return; return;
} }
if (g_bEmuException) { // Pass the memory-access through to normal memory :
EmuLog(LOG_PREFIX, LOG_LEVEL::WARNING, "EmuX86_Write(0x%08X, 0x%08X) [Unknown address]", addr, value);
return;
}
// Outside EmuException, pass the memory-access through to normal memory :
DbgPrintf(LOG_PREFIX, "Write(0x%.8X, 0x%.8X, %d)\n", addr, value, size); DbgPrintf(LOG_PREFIX, "Write(0x%.8X, 0x%.8X, %d)\n", addr, value, size);
EmuX86_Mem_Write(addr, value, size); EmuX86_Mem_Write(addr, value, size);
} }
@ -862,6 +864,33 @@ bool EmuX86_Opcode_INC(LPEXCEPTION_POINTERS e, _DInst& info)
return true; return true;
} }
bool EmuX86_Opcode_JMP(LPEXCEPTION_POINTERS e, _DInst& info)
{
OperandAddress opAddr;
EmuX86_Operand_Addr_ForReadOnly(e, info, 0, opAddr);
e->ContextRecord->Eip += opAddr.addr;
return true;
}
// Jump if condition is met
// Returns true if branch was taken
bool EmuX86_Opcode_JXX(LPEXCEPTION_POINTERS e, _DInst& info, DWORD flag, bool condition)
{
OperandAddress opAddr;
EmuX86_Operand_Addr_ForReadOnly(e, info, 0, opAddr);
// Branch if flag = condition
bool flagValue = (e->ContextRecord->EFlags & flag);
if (flagValue == condition) {
e->ContextRecord->Eip += opAddr.addr;
return true;
}
return false;
}
bool EmuX86_Opcode_MOV(LPEXCEPTION_POINTERS e, _DInst& info) bool EmuX86_Opcode_MOV(LPEXCEPTION_POINTERS e, _DInst& info)
{ {
// MOV reads value from source : // MOV reads value from source :
@ -924,6 +953,32 @@ bool EmuX86_Opcode_MOVZX(LPEXCEPTION_POINTERS e, _DInst& info)
return true; return true;
} }
bool EmuX86_Opcode_NOT(LPEXCEPTION_POINTERS e, _DInst& info)
{
// NOT reads and writes the same operand :
OperandAddress opAddr;
if (!EmuX86_Operand_Addr_ForReadWrite(e, info, 0, OUT opAddr))
return false;
uint32_t dest = EmuX86_Addr_Read(opAddr);
// NOT Destination with
uint32_t result = ~dest;
// Write back the result
EmuX86_Addr_Write(opAddr, result);
// The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the result. The state of the AF flag is undefined.
EmuX86_SetFlags_OSZPC(e,
/*EMUX86_EFLAG_OF*/0,
/*EMUX86_EFLAG_SF*/SFCalc(result),
/*EMUX86_EFLAG_ZF*/ZFCalc(result),
/*EMUX86_EFLAG_PF*/PFCalc(result),
/*EMUX86_EFLAG_CF*/0);
return true;
}
bool EmuX86_Opcode_OR(LPEXCEPTION_POINTERS e, _DInst& info) bool EmuX86_Opcode_OR(LPEXCEPTION_POINTERS e, _DInst& info)
{ {
// Read value from Source and Destination // Read value from Source and Destination
@ -971,7 +1026,79 @@ bool EmuX86_Opcode_OUT(LPEXCEPTION_POINTERS e, _DInst& info)
// Note : OUT instructions never update CPU flags // Note : OUT instructions never update CPU flags
return true; return true;
}
bool EmuX86_Opcode_SBB(LPEXCEPTION_POINTERS e, _DInst& info)
{
// Read value from Source and Destination
uint32_t src = 0;
if (!EmuX86_Operand_Read(e, info, 1, &src))
return false;
// SBB reads and writes the same operand :
OperandAddress opAddr;
if (!EmuX86_Operand_Addr_ForReadWrite(e, info, 0, OUT opAddr))
return false;
uint32_t dest = EmuX86_Addr_Read(opAddr);
// SUB Destination with src
uint64_t result = (uint64_t)dest - (uint64_t)src;
// If the carry flag is set, subtract an additional 1
if (e->ContextRecord->EFlags & BITMASK(EMUX86_EFLAG_CF)) {
result -= 1;
}
// Write result back
EmuX86_Addr_Write(opAddr, static_cast<uint32_t>(result));
// The OF, SF, ZF, AF, PF, and CF flags are set according to the result.
EmuX86_SetFlags_OSZAPC(e,
/*EMUX86_EFLAG_OF*/OF_Sub(result, src, dest),
/*EMUX86_EFLAG_SF*/SFCalc(result),
/*EMUX86_EFLAG_ZF*/ZFCalc(result),
/*EMUX86_EFLAG_AF*/AFCalc(result, src, dest),
/*EMUX86_EFLAG_PF*/PFCalc(result),
/*EMUX86_EFLAG_CF*/CFCalc(result));
return true;
}
bool EmuX86_Opcode_SHR(LPEXCEPTION_POINTERS e, _DInst& info)
{
// Read value from Source and Destination
uint32_t src = 0;
if (!EmuX86_Operand_Read(e, info, 1, &src))
return false;
// SHR reads and writes the same operand :
OperandAddress opAddr;
if (!EmuX86_Operand_Addr_ForReadWrite(e, info, 0, OUT opAddr))
return false;
uint32_t dest = EmuX86_Addr_Read(opAddr);
// Shift Destination with src
uint8_t carryBit = dest & 1;
uint64_t result = (uint64_t)dest >> (uint64_t)src;
// Write result back
EmuX86_Addr_Write(opAddr, static_cast<uint32_t>(result));
// The OF, SF, ZF, AF, PF, and CF flags are set according to the result.
EmuX86_SetFlags_OSZAPC(e,
/*EMUX86_EFLAG_OF*/OF_Sub(result, src, dest),
/*EMUX86_EFLAG_SF*/SFCalc(result),
/*EMUX86_EFLAG_ZF*/ZFCalc(result),
/*EMUX86_EFLAG_AF*/AFCalc(result, src, dest),
/*EMUX86_EFLAG_PF*/PFCalc(result),
/*EMUX86_EFLAG_CF*/carryBit);
return true;
} }
bool EmuX86_Opcode_SUB(LPEXCEPTION_POINTERS e, _DInst& info) bool EmuX86_Opcode_SUB(LPEXCEPTION_POINTERS e, _DInst& info)
@ -1035,6 +1162,37 @@ bool EmuX86_Opcode_TEST(LPEXCEPTION_POINTERS e, _DInst& info)
return true; return true;
} }
bool EmuX86_Opcode_XOR(LPEXCEPTION_POINTERS e, _DInst& info)
{
// Read value from Source and Destination
uint32_t src = 0;
if (!EmuX86_Operand_Read(e, info, 1, &src))
return false;
// XOR reads and writes the same operand :
OperandAddress opAddr;
if (!EmuX86_Operand_Addr_ForReadWrite(e, info, 0, OUT opAddr))
return false;
uint32_t dest = EmuX86_Addr_Read(opAddr);
// XOR Destination with src
uint32_t result = dest ^ src;
// Write back the result
EmuX86_Addr_Write(opAddr, result);
// The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the result. The state of the AF flag is undefined.
EmuX86_SetFlags_OSZPC(e,
/*EMUX86_EFLAG_OF*/0,
/*EMUX86_EFLAG_SF*/SFCalc(result),
/*EMUX86_EFLAG_ZF*/ZFCalc(result),
/*EMUX86_EFLAG_PF*/PFCalc(result),
/*EMUX86_EFLAG_CF*/0);
return true;
}
void EmuX86_Opcode_CLI() void EmuX86_Opcode_CLI()
{ {
g_bEnableAllInterrupts = false; g_bEnableAllInterrupts = false;
@ -1082,97 +1240,151 @@ bool EmuX86_DecodeException(LPEXCEPTION_POINTERS e)
// However, if for any reason, an opcode operand cannot be read from or written to, // However, if for any reason, an opcode operand cannot be read from or written to,
// that case may be logged, but it shouldn't fail the opcode handler. // that case may be logged, but it shouldn't fail the opcode handler.
_DInst info; _DInst info;
if (!EmuX86_DecodeOpcode((uint8_t*)e->ContextRecord->Eip, OUT info)) {
EmuLog(LOG_PREFIX, LOG_LEVEL::WARNING, "Error decoding opcode at 0x%08X", e->ContextRecord->Eip);
return false;
}
switch (info.opcode) { // Keep these cases alphabetically ordered and condensed // Execute op-codes until we hit an unhandled instruction, or an error occurs
case I_ADD: while (true) {
if (EmuX86_Opcode_ADD(e, info)) break; if (!EmuX86_DecodeOpcode((uint8_t*)e->ContextRecord->Eip, info)) {
goto opcode_error; EmuLog(LOG_PREFIX, LOG_LEVEL::WARNING, "Error decoding opcode at 0x%08X", e->ContextRecord->Eip);
case I_AND: return false;
if (EmuX86_Opcode_AND(e, info)) break; }
goto opcode_error;
case I_CLI: { switch (info.opcode) { // Keep these cases alphabetically ordered and condensed
// Disable all interrupts // Exit and branch Opcodes come first, for clarity/visibility
EmuX86_Opcode_CLI(); case I_JA:
break; if (EmuX86_Opcode_JXX(e, info, BITMASK(EMUX86_EFLAG_CF) | BITMASK(EMUX86_EFLAG_ZF), false)) {
} continue;
case I_CMP: }
if (EmuX86_Opcode_CMP(e, info)) break; break;
goto opcode_error; case I_JAE:
case I_CMPXCHG: if (EmuX86_Opcode_JXX(e, info, BITMASK(EMUX86_EFLAG_CF), false)) {
if (EmuX86_Opcode_CMPXCHG(e, info)) break; continue;
goto opcode_error; }
case I_CPUID: break;
EmuX86_Opcode_CPUID(e, info); case I_JB:
break; if (EmuX86_Opcode_JXX(e, info, BITMASK(EMUX86_EFLAG_CF), true)) {
case I_DEC: continue;
if (EmuX86_Opcode_DEC(e, info)) break; }
goto opcode_error; break;
case I_IN: case I_JBE:
if (EmuX86_Opcode_IN(e, info)) break; if (EmuX86_Opcode_JXX(e, info, BITMASK(EMUX86_EFLAG_CF) | BITMASK(EMUX86_EFLAG_ZF), true)) {
goto opcode_error; continue;
case I_INC: }
if (EmuX86_Opcode_INC(e, info)) break; break;
goto opcode_error; case I_JNZ:
case I_INVD: // Flush internal caches; initiate flushing of external caches. if (EmuX86_Opcode_JXX(e, info, BITMASK(EMUX86_EFLAG_ZF), false)) {
break; // We can safely ignore this continue;
case I_INVLPG: { }
// This instruction invalidates the TLB entry specified with the source operand. Since we don't emulate break;
// the TLB yet, we can safely ignore this. Test case: Fable. case I_JMP:
break; if (EmuX86_Opcode_JMP(e, info)) {
} continue;
case I_MOV: }
if (EmuX86_Opcode_MOV(e, info)) break; break;
goto opcode_error; case I_JZ:
case I_MOVSX: if (EmuX86_Opcode_JXX(e, info, BITMASK(EMUX86_EFLAG_ZF), true)) {
if (EmuX86_Opcode_MOVSX(e, info)) break; continue;
goto opcode_error; }
case I_MOVZX: break;
if (EmuX86_Opcode_MOVZX(e, info)) break; case I_CALL: case I_RET:
goto opcode_error; // RET and CALL always signify the end of a code block
case I_OR: return true;
if (EmuX86_Opcode_OR(e, info)) break; case I_PUSH: case I_POP:
goto opcode_error; // TODO: Implement these instructions
case I_OUT: // currently stubbed to prevent firing the unimplemented instruction handler
if (EmuX86_Opcode_OUT(e, info)) break; return true;
goto opcode_error; case I_ADD:
case I_STI: { if (EmuX86_Opcode_ADD(e, info)) break;
// Enable all interrupts goto opcode_error;
EmuX86_Opcode_STI(); case I_AND:
break; if (EmuX86_Opcode_AND(e, info)) break;
} goto opcode_error;
case I_SUB: case I_CLI: {
if (EmuX86_Opcode_SUB(e, info)) break; // Disable all interrupts
goto opcode_error; EmuX86_Opcode_CLI();
case I_TEST: break;
if (EmuX86_Opcode_TEST(e, info)) break; }
goto opcode_error; case I_CMP:
case I_WBINVD: // Write back and flush internal caches; initiate writing-back and flushing of external caches. if (EmuX86_Opcode_CMP(e, info)) break;
break; // We can safely ignore this goto opcode_error;
case I_WRMSR: case I_CMPXCHG:
// We do not emulate processor specific registers just yet if (EmuX86_Opcode_CMPXCHG(e, info)) break;
// Some titles attempt to manually set the TSC via this instruction goto opcode_error;
// This needs fixing eventually, but should be acceptible to ignore for now! case I_CPUID:
// Chase: Hollywood Stunt Driver hits this EmuX86_Opcode_CPUID(e, info);
EmuLog(LOG_PREFIX, LOG_LEVEL::WARNING, "WRMSR instruction ignored"); break;
break; case I_DEC:
default: if (EmuX86_Opcode_DEC(e, info)) break;
EmuLog(LOG_PREFIX, LOG_LEVEL::WARNING, "Unhandled instruction : %u", info.opcode); goto opcode_error;
case I_IN:
if (EmuX86_Opcode_IN(e, info)) break;
goto opcode_error;
case I_INC:
if (EmuX86_Opcode_INC(e, info)) break;
goto opcode_error;
case I_INVD: // Flush internal caches; initiate flushing of external caches.
break; // We can safely ignore this
case I_INVLPG: {
// This instruction invalidates the TLB entry specified with the source operand. Since we don't emulate
// the TLB yet, we can safely ignore this. Test case: Fable.
break;
}
case I_MOV:
if (EmuX86_Opcode_MOV(e, info)) break;
goto opcode_error;
case I_MOVSX:
if (EmuX86_Opcode_MOVSX(e, info)) break;
goto opcode_error;
case I_MOVZX:
if (EmuX86_Opcode_MOVZX(e, info)) break;
goto opcode_error;
case I_NOT:
if (EmuX86_Opcode_NOT(e, info)) break;
goto opcode_error;
case I_OR:
if (EmuX86_Opcode_OR(e, info)) break;
goto opcode_error;
case I_OUT:
if (EmuX86_Opcode_OUT(e, info)) break;
goto opcode_error;
case I_STI: {
// Enable all interrupts
EmuX86_Opcode_STI();
break;
}
case I_SBB:
if (EmuX86_Opcode_SBB(e, info)) break;
goto opcode_error;
case I_SHR:
if (EmuX86_Opcode_SHR(e, info)) break;
goto opcode_error;
case I_SUB:
if (EmuX86_Opcode_SUB(e, info)) break;
goto opcode_error;
case I_TEST:
if (EmuX86_Opcode_TEST(e, info)) break;
goto opcode_error;
case I_WBINVD: // Write back and flush internal caches; initiate writing-back and flushing of external caches.
break; // We can safely ignore this
case I_WRMSR:
// We do not emulate processor specific registers just yet
// Some titles attempt to manually set the TSC via this instruction
// This needs fixing eventually, but should be acceptible to ignore for now!
// Chase: Hollywood Stunt Driver hits this
EmuLog(LOG_PREFIX, LOG_LEVEL::WARNING, "WRMSR instruction ignored");
break;
case I_XOR:
if (EmuX86_Opcode_XOR(e, info)) break;
goto opcode_error;
default:
EmuLog(LOG_PREFIX, LOG_LEVEL::WARNING, "Unhandled instruction : %u", info.opcode);
return true;
}
e->ContextRecord->Eip += info.size; e->ContextRecord->Eip += info.size;
return false;
} }
// When falling through here, the instruction was handled correctly,
// skip over the instruction and continue execution :
e->ContextRecord->Eip += info.size;
return true;
opcode_error: opcode_error:
EmuLog(LOG_PREFIX, LOG_LEVEL::WARNING, "0x%08X: Error while handling instruction %u", e->ContextRecord->Eip, info.opcode); // TODO : format decodedInstructions[0] EmuLog(LOG_PREFIX, LOG_LEVEL::WARNING, "0x%08X: Error while handling instruction %u", e->ContextRecord->Eip, info.opcode); // TODO : format decodedInstructions[0]
e->ContextRecord->Eip += info.size;
return false; return false;
} }