X86 : Removed special-casing for is_register (doesn't seem to be needed), added asserts everywhere we're unsure of the input, simplified EmuX86_Opcode_CMPXCHG accessing EAX, fixed logging O_MEM without a base.

This commit is contained in:
PatrickvL 2018-09-27 13:22:34 +02:00
parent 6e1432f644
commit 48cb6c8ba2
1 changed files with 150 additions and 118 deletions

View File

@ -370,10 +370,31 @@ inline void * EmuX86_GetRegisterPointer(const LPEXCEPTION_POINTERS e, const uint
return nullptr;
}
int EmuX86_DistormRegSize(const uint8_t reg)
{
switch (reg) {
case R_RAX: return 8; case R_RCX: return 8; case R_RDX: return 8; case R_RBX: return 8; case R_RSP: return 8; case R_RBP: return 8; case R_RSI: return 8; case R_RDI: return 8; case R_R8: return 8; case R_R9: return 8; case R_R10: return 8; case R_R11: return 8; case R_R12: return 8; case R_R13: return 8; case R_R14: return 8; case R_R15: return 8;
case R_EAX: return 4; case R_ECX: return 4; case R_EDX: return 4; case R_EBX: return 4; case R_ESP: return 4; case R_EBP: return 4; case R_ESI: return 4; case R_EDI: return 4; case R_R8D: return 4; case R_R9D: return 4; case R_R10D: return 4; case R_R11D: return 4; case R_R12D: return 4; case R_R13D: return 4; case R_R14D: return 4; case R_R15D: return 4;
case R_AX: return 2; case R_CX: return 2; case R_DX: return 2; case R_BX: return 2; case R_SP: return 2; case R_BP: return 2; case R_SI: return 2; case R_DI: return 2; case R_R8W: return 2; case R_R9W: return 2; case R_R10W: return 2; case R_R11W: return 2; case R_R12W: return 2; case R_R13W: return 2; case R_R14W: return 2; case R_R15W: return 2;
case R_AL: return 1; case R_CL: return 1; case R_DL: return 1; case R_BL: return 1; case R_AH: return 1; case R_CH: return 1; case R_DH: return 1; case R_BH: return 1; case R_R8B: return 1; case R_R9B: return 1; case R_R10B: return 1; case R_R11B: return 1; case R_R12B: return 1; case R_R13B: return 1; case R_R14B: return 1; case R_R15B: return 1;
case R_SPL: return 1; case R_BPL: return 1; case R_SIL: return 1; case R_DIL: return 1;
case R_ES: return 2; case R_CS: return 2; case R_SS: return 2; case R_DS: return 2; case R_FS: return 2; case R_GS: return 2;
case R_RIP: return 8;
case R_ST0: return 10; case R_ST1: return 10; case R_ST2: return 10; case R_ST3: return 10; case R_ST4: return 10; case R_ST5: return 10; case R_ST6: return 10; case R_ST7: return 10;
case R_MM0: return 10; case R_MM1: return 10; case R_MM2: return 10; case R_MM3: return 10; case R_MM4: return 10; case R_MM5: return 10; case R_MM6: return 10; case R_MM7: return 10;
case R_XMM0: return 16; case R_XMM1: return 16; case R_XMM2: return 16; case R_XMM3: return 16; case R_XMM4: return 16; case R_XMM5: return 16; case R_XMM6: return 16; case R_XMM7: return 16; case R_XMM8: return 16; case R_XMM9: return 16; case R_XMM10: return 16; case R_XMM11: return 16; case R_XMM12: return 16; case R_XMM13: return 16; case R_XMM14: return 16; case R_XMM15: return 16;
case R_YMM0: return 16; case R_YMM1: return 16; case R_YMM2: return 16; case R_YMM3: return 16; case R_YMM4: return 16; case R_YMM5: return 16; case R_YMM6: return 16; case R_YMM7: return 16; case R_YMM8: return 16; case R_YMM9: return 16; case R_YMM10: return 16; case R_YMM11: return 16; case R_YMM12: return 16; case R_YMM13: return 16; case R_YMM14: return 16; case R_YMM15: return 16;
case R_CR0: return 4; case R_UNUSED0: return 0; case R_CR2: return 4; case R_CR3: return 4; case R_CR4: return 4; case R_UNUSED1: return 0; case R_UNUSED2: return 0; case R_UNUSED3: return 0; case R_CR8: return 4;
case R_DR0: return 4; case R_DR1: return 4; case R_DR2: return 4; case R_DR3: return 4; case R_UNUSED4: return 0; case R_UNUSED5: return 0; case R_DR6: return 4; case R_DR7: return 4;
default: assert(false); return 0;
}
}
inline uint32_t EmuX86_GetRegisterValue32(const LPEXCEPTION_POINTERS e, const uint8_t reg)
{
if (reg != R_NONE)
{
if (reg != R_NONE) {
assert(EmuX86_DistormRegSize(reg) == sizeof(uint32_t));
void* regptr = EmuX86_GetRegisterPointer(e, reg);
if (regptr != nullptr)
return *(uint32_t *)regptr;
@ -382,104 +403,145 @@ inline uint32_t EmuX86_GetRegisterValue32(const LPEXCEPTION_POINTERS e, const ui
return 0;
}
xbaddr EmuX86_Distorm_O_SMEM_Addr(const LPEXCEPTION_POINTERS e, const _DInst& info, const int operand)
uint32_t EmuX86_Distorm_read_disp(const _DInst& info)
{
xbaddr base = EmuX86_GetRegisterValue32(e, info.ops[operand].index);
return base + (uint32_t)info.disp;
}
xbaddr EmuX86_Distorm_O_MEM_Addr(const LPEXCEPTION_POINTERS e, const _DInst& info, const int operand)
{
xbaddr base = EmuX86_GetRegisterValue32(e, info.base);
uint32_t index = EmuX86_GetRegisterValue32(e, info.ops[operand].index);
if (info.scale >= 2)
return base + (index * info.scale) + (uint32_t)info.disp;
else
return base + index + (uint32_t)info.disp;
#ifdef _DEBUG
// Assert disp is set according to expectations :
switch (info.dispSize) {
case 0:
assert(info.disp == 0); // Assume dispSize==0 implies disp==0
break;
case 8:
if (info.disp & 0x80) {
assert((info.disp & 0xFFFFFF80) == 0xFFFFFF80); // Assume 8 bit disp is sign-extended to 32 bit
}
else {
assert(info.disp < 0x80); // Assume positive signed 8 bit never reaches 0x80
}
break;
case 16:
if (info.disp & 0x8000) {
assert((info.disp & 0xFFFF8000) == 0xFFFF8000); // Assume 16 bit disp is sign-extended to 32 bit
}
else {
assert(info.disp < 0x8000); // Assume positive signed 16 bit never reaches 0x8000
}
break;
case 32:
break; // No checks for 32 bit
default:
assert(false); // Assume dispSize is either 0, 8, 16 or 32
break;
}
#endif
return (uint32_t)info.disp;
}
typedef struct {
xbaddr addr = 0;
bool is_internal_addr = false; // If set, addr points to a CPU context (or Distorm immedate value) member (instead of Xbox memory)
int size = 0; // Expressed in bytes, not bits!
bool is_register = false;
} OperandAddress;
bool EmuX86_Operand_Addr_ForReadOnly(const LPEXCEPTION_POINTERS e, const _DInst& info, const int operand, OperandAddress &opAddr)
{
opAddr.size = info.ops[operand].size / 8; // Convert size in bits into bytes
switch (info.ops[operand].type) {
case O_NONE:
case O_NONE: // operand is to be ignored.
{
// ignore operand
assert(opAddr.size == 0);
// Ignore O_NONE operand
return false;
}
case O_REG:
case O_REG: // index holds global register index.
{
assert(opAddr.size == EmuX86_DistormRegSize(info.ops[operand].index));
opAddr.is_internal_addr = true;
opAddr.addr = (xbaddr)EmuX86_GetRegisterPointer(e, info.ops[operand].index);
opAddr.is_register = true; // This is important, as we we need to offset the address by the operand size on write, to make sure the correct bits get written
return true;
{
}
case O_IMM:
case O_IMM: // instruction.imm.
{
assert(opAddr.size == sizeof(uint8_t) || opAddr.size == sizeof(uint16_t) || opAddr.size == sizeof(uint32_t));
opAddr.is_internal_addr = true;
opAddr.addr = (xbaddr)(&info.imm);
return true;
}
case O_IMM1:
case O_IMM1: // instruction.imm.ex.i1.
{
assert(opAddr.size == sizeof(uint8_t) || opAddr.size == sizeof(uint16_t) || opAddr.size == sizeof(uint32_t));
opAddr.is_internal_addr = true;
opAddr.addr = (xbaddr)(&info.imm.ex.i1);
return true;
}
case O_IMM2:
case O_IMM2: // instruction.imm.ex.i2.
{
assert(opAddr.size == sizeof(uint8_t) || opAddr.size == sizeof(uint16_t) || opAddr.size == sizeof(uint32_t));
opAddr.is_internal_addr = true;
opAddr.addr = (xbaddr)(&info.imm.ex.i2);
return true;
}
case O_DISP:
case O_DISP: // memory dereference with displacement only, instruction.disp.
{
assert(opAddr.size == sizeof(uint32_t));
opAddr.is_internal_addr = false;
// TODO : Does this operand require : opAddr.size = info.dispSize / 8; // ?
opAddr.addr = (xbaddr)info.disp;
opAddr.addr = EmuX86_Distorm_read_disp(info);
return true;
}
case O_SMEM:
case O_SMEM: // simple memory dereference with optional displacement(a single register memory dereference).
{
assert(opAddr.size == 0 || opAddr.size == sizeof(uint8_t) || opAddr.size == sizeof(uint16_t) || opAddr.size == sizeof(uint32_t)); // TODO : How to handle size == 0 further on?
opAddr.is_internal_addr = false;
// TODO : Does this operand require : opAddr.size = info.dispSize / 8; // ?
opAddr.addr = EmuX86_Distorm_O_SMEM_Addr(e, info, operand);
opAddr.addr = EmuX86_GetRegisterValue32(e, info.ops[operand].index)
+ EmuX86_Distorm_read_disp(info);
return true;
}
case O_MEM:
case O_MEM: // complex memory dereference(optional fields : s / i / b / disp).
{
assert(opAddr.size == 0 || opAddr.size == sizeof(uint8_t) || opAddr.size == sizeof(uint16_t) || opAddr.size == sizeof(uint32_t)); // TODO : How to handle size == 0 further on?
assert(info.scale < 3 || info.scale == 4 || info.scale == 8); // Assume scale is either 0, 1, 2, 4 or 8
uint32_t index = EmuX86_GetRegisterValue32(e, info.ops[operand].index);
if (info.scale >= 2)
index *= info.scale;
opAddr.is_internal_addr = false;
// TODO : Does this operand require : opAddr.size = info.dispSize / 8; // ?
opAddr.addr = EmuX86_Distorm_O_MEM_Addr(e, info, operand);
opAddr.addr = EmuX86_GetRegisterValue32(e, info.base) // Note : Returns 0 when base == R_NONE
+ index
+ EmuX86_Distorm_read_disp(info);
return true;
}
case O_PC:
case O_PC: // the relative address of a branch instruction(instruction.imm.addr).
{
assert(opAddr.size == sizeof(uint8_t) || opAddr.size == sizeof(uint32_t));
opAddr.is_internal_addr = false;
opAddr.addr = (xbaddr)e->ContextRecord->Eip + (xbaddr)INSTRUCTION_GET_TARGET(&info);
return true;
}
case O_PTR:
case O_PTR: // the absolute target address of a far branch instruction(instruction.imm.ptr.seg / off).
{
assert(opAddr.size == 0);
opAddr.is_internal_addr = false;
opAddr.addr = (xbaddr)info.imm.ptr.off; // TODO : What about info.imm.ptr.seg ?
opAddr.addr = (xbaddr)info.imm.ptr.off; // TODO : Needs test-case. What about info.imm.ptr.seg ?
return true;
}
default:
// UNREACHABLE(info.ops[operand].type);
assert(false);
return false;
}
assert(false);
return false;
}
@ -490,6 +552,7 @@ bool EmuX86_Operand_Addr_ForReadWrite(const LPEXCEPTION_POINTERS e, const _DInst
case O_IMM:
case O_IMM1:
case O_IMM2:
assert(false);
EmuLog(LOG_PREFIX, LOG_LEVEL::WARNING, "Refused operand write-access to immedate value address!");
return false;
}
@ -502,24 +565,6 @@ uint32_t EmuX86_Addr_Read(const OperandAddress &opAddr)
{
assert(opAddr.size == sizeof(uint8_t) || opAddr.size == sizeof(uint16_t) || opAddr.size == sizeof(uint32_t));
// If this is a register, and is not 4 bytes, we need to mask the correct bits
if (opAddr.is_register && opAddr.size < sizeof(uint32_t)) {
// Read the register as a 32-bit value
uint32_t regval = *(uint32_t*)opAddr.addr;
// Set the correct part of the register
switch (opAddr.size) {
case sizeof(uint8_t) :
regval = regval & 0xFF;
break;
case sizeof(uint16_t) :
regval = regval & 0xFFFF;
break;
}
return regval;
}
if (opAddr.is_internal_addr) {
return EmuX86_Mem_Read(opAddr.addr, opAddr.size);
}
@ -532,26 +577,6 @@ void EmuX86_Addr_Write(const OperandAddress &opAddr, const uint32_t value)
{
assert(opAddr.size == sizeof(uint8_t) || opAddr.size == sizeof(uint16_t) || opAddr.size == sizeof(uint32_t));
// If this is a register, and is not 4 bytes, we need to offset to set the correct bits
// Without this mov al, 1 would overwrite the first byte of eax rather than the last!
if (opAddr.is_register && opAddr.size < sizeof(uint32_t)) {
// Read the register as a 32-bit value
uint32_t regval = *(uint32_t*)opAddr.addr;
// Set the correct part of the register
switch (opAddr.size) {
case sizeof(uint8_t) :
regval = (regval & 0xFFFFFF00) | (value & 0xFF);
break;
case sizeof(uint16_t):
regval = (regval & 0xFFFF0000) | (value & 0xFFFF);
break;
}
// Write the register back as a 32-bit value
EmuX86_Mem_Write(opAddr.addr, regval, sizeof(uint32_t));
}
if (opAddr.is_internal_addr) {
EmuX86_Mem_Write(opAddr.addr, value, opAddr.size);
}
@ -816,24 +841,24 @@ bool EmuX86_Opcode_CMPXCHG(LPEXCEPTION_POINTERS e, _DInst& info)
if (!EmuX86_Operand_Read(e, info, 0, &dest))
return false;
// Setup read/write to EAX
// Write the destination operand into EAX
OperandAddress eaxOpAddr;
eaxOpAddr.addr = (uint32_t)EmuX86_GetRegisterPointer(e, R_EAX);
eaxOpAddr.is_internal_addr = true;
eaxOpAddr.is_register = true;
eaxOpAddr.size = info.ops[1].size;
uint32_t eaxVal = EmuX86_Addr_Read(eaxOpAddr);
if (eaxVal == dest) {
uint32_t mask;
switch (info.ops[0].size) {
case 8: mask = 0xFF; break; // TODO : Needs test-case
case 16: mask = 0xFFFF; break; // TODO : Needs test-case
case 32: mask = 0xFFFFFFFF; break;
default: assert(false); break;
}
uint32_t eaxVal = e->ContextRecord->Eax;
if ((eaxVal & mask) == (dest & mask)) {
// Write the source value to the destination operand
if (!EmuX86_Operand_Write(e, info, 0, src)) {
return false;
}
} else {
// Write the desintation operand to eax
EmuX86_Addr_Write(eaxOpAddr, dest);
// Write the destination operand to eax
e->ContextRecord->Eax = (e->ContextRecord->Eax & ~mask) | (dest & mask);
}
// Perform arithmatic operation for flag calculation
@ -980,10 +1005,10 @@ bool EmuX86_Opcode_INC(LPEXCEPTION_POINTERS e, _DInst& info)
bool EmuX86_Opcode_JMP(LPEXCEPTION_POINTERS e, _DInst& info)
{
OperandAddress opAddr;
EmuX86_Operand_Addr_ForReadOnly(e, info, 0, opAddr);
if (!EmuX86_Operand_Addr_ForReadOnly(e, info, 0, OUT opAddr))
assert(false);
e->ContextRecord->Eip = opAddr.addr;
return true;
}
@ -992,13 +1017,8 @@ bool EmuX86_Opcode_JMP(LPEXCEPTION_POINTERS e, _DInst& info)
// Returns true if branch was taken
bool EmuX86_Opcode_Jcc(LPEXCEPTION_POINTERS e, _DInst& info, bool condition)
{
if (condition) {
OperandAddress opAddr;
EmuX86_Operand_Addr_ForReadOnly(e, info, 0, opAddr);
e->ContextRecord->Eip = opAddr.addr;
return true;
}
if (condition)
return EmuX86_Opcode_JMP(e, info);
return false;
}
@ -2649,21 +2669,25 @@ char *Distorm_OpcodeString(const int opcode)
case I_XSAVEOPT: return "XSAVEOPT";
case I_XSAVEOPT64: return "XSAVEOPT64";
case I_XSETBV: return "XSETBV";
default: return "???";
default: assert(false); return "???";
}
}
void output_value(std::stringstream &output, int nibbles, uint32_t value)
{
if (value < 0xA)
// Show 0 upto 9 as 1 decimal digit
output << std::setw(1) << value;
else
output << std::setfill('0') << std::setw(nibbles) << std::right << value << 'h';
}
void output_value_disp(std::stringstream &output, int nibbles, uint32_t value)
void output_value_disp(std::stringstream &output, _DInst &info)
{
uint32_t value = EmuX86_Distorm_read_disp(info);
if (value != 0) {
int nibbles = info.dispSize / 4;
assert(nibbles == 2 || nibbles == 4 || nibbles == 8);
if ((int32_t)value < 0) {
output << '-';
output_value(output, nibbles, -value);
@ -2678,6 +2702,7 @@ void output_value_disp(std::stringstream &output, int nibbles, uint32_t value)
void EmuX86_DistormLogInstruction(const uint8_t *Eip, _DInst &info)
{
std::stringstream output;
output << "Disassembly : "
<< std::setfill('0') << std::setw(8) << std::right << std::hex << std::uppercase << (xbaddr)Eip;
for (int b = 0; b < MAX(7, info.size); b++) {
@ -2690,37 +2715,44 @@ void EmuX86_DistormLogInstruction(const uint8_t *Eip, _DInst &info)
output << " " << std::setfill(' ') << std::left << std::setw(11) << Distorm_OpcodeString(info.opcode);
for (int o = 0; o < 4 && info.ops[o].type != O_NONE; o++) {
// Convert size in bits to (hexadecimal) nibble count
int nr_nibbles = info.ops[o].size / 4;
// Convert size in bits to (hexadecimal) nibble count and a size-indicator string
int nr_nibbles;
std::string size_str;
output << std::setfill(' ') << std::setw(1) << std::right << ((o == 0) ? " " : ",");
switch (info.ops[o].size) {
case 8: size_str = "byte ptr "; break;
case 16: size_str = "word ptr "; break;
case 32: size_str = "dword ptr "; break;
default: nr_nibbles = 8; size_str = ""; break;
case 0: nr_nibbles = 8; size_str = ""; break;
case 8: nr_nibbles = 2; size_str = "byte ptr "; break;
case 16: nr_nibbles = 4; size_str = "word ptr "; break;
case 32: nr_nibbles = 8; size_str = "dword ptr "; break;
default: assert(false);
}
// Output register/operand separator
output << std::setfill(' ') << std::setw(1) << std::right << ((o == 0) ? " " : ",");
// Render operand to output
switch (info.ops[o].type) {
case O_REG: output << Distorm_RegStrings[info.ops[o].index]; break;
case O_IMM: output_value(output, nr_nibbles, info.imm.dword); break;
case O_IMM1: output_value(output, nr_nibbles, info.imm.ex.i1); break;
case O_IMM2: output_value(output, nr_nibbles, info.imm.ex.i2); break;
case O_IMM1: output_value(output, nr_nibbles, info.imm.ex.i1); break; // TODO : Needs test-case
case O_IMM2: output_value(output, nr_nibbles, info.imm.ex.i2); break; // TODO : Needs test-case
case O_DISP: output << size_str << "ds:[";
output_value(output, nr_nibbles, info.disp);
output_value(output, info.dispSize > 0 ? info.dispSize / 4 : 8, EmuX86_Distorm_read_disp(info));
output << "]"; break;
case O_SMEM: output << size_str << "[" << Distorm_RegStrings[info.ops[o].index];
output_value_disp(output, nr_nibbles, info.disp);
output_value_disp(output, info);
output << "]"; break;
case O_MEM: output << size_str << "[" << Distorm_RegStrings[info.base] << "+" << Distorm_RegStrings[info.ops[o].index];
if (info.scale >= 2) {
output << "*"; output_value(output, 1, info.scale);
}
output_value_disp(output, nr_nibbles, info.disp);
case O_MEM: output << size_str << "[";
if (info.base != R_NONE) output << Distorm_RegStrings[info.base] << "+";
output << Distorm_RegStrings[info.ops[o].index];
if (info.scale >= 2) { output << "*"; output_value(output, 1, info.scale); }
output_value_disp(output, info);
output << "]"; break;
case O_PC: output_value(output, 8, (xbaddr)Eip + (xbaddr)INSTRUCTION_GET_TARGET(&info)); break;
case O_PTR: output << "+" << std::setfill('0') << info.imm.ptr.seg << "/";
output_value(output, nr_nibbles, info.imm.ptr.off); break;
default: output << "?"; break;
output_value(output, nr_nibbles, info.imm.ptr.off); break; // TODO : Needs test-case
default:
assert(false);
output << "?"; break;
}
}