diff --git a/arch/mips/arch_mips.cpp b/arch/mips/arch_mips.cpp index 1644b0a8ca..05feb8f8e4 100644 --- a/arch/mips/arch_mips.cpp +++ b/arch/mips/arch_mips.cpp @@ -1,6 +1,7 @@ #define _CRT_SECURE_NO_WARNINGS #define NOMINMAX +#include #include #include #include @@ -233,6 +234,7 @@ class MipsArchitecture: public Architecture case MIPS_JR_HB: case MIPS_J: case MIPS_JAL: + case MIPS_JALX: case MIPS_JALR: case MIPS_JALR_HB: case MIPS_BC0F: @@ -241,12 +243,16 @@ class MipsArchitecture: public Architecture case MIPS_BC0TL: case MIPS_BC1F: case MIPS_BC1FL: + case MIPS_BC1EQZ: + case MIPS_BC1NEZ: case MIPS_BC1T: case MIPS_BC1TL: case MIPS_BC2FL: case MIPS_BC2TL: case MIPS_BC2F: case MIPS_BC2T: + case MIPS_BC2EQZ: + case MIPS_BC2NEZ: case CNMIPS_BBIT0: case CNMIPS_BBIT032: case CNMIPS_BBIT1: @@ -297,12 +303,16 @@ class MipsArchitecture: public Architecture case MIPS_BNEL: case MIPS_BC1F: case MIPS_BC1FL: + case MIPS_BC1EQZ: + case MIPS_BC1NEZ: case MIPS_BC1T: case MIPS_BC1TL: case MIPS_BC2FL: case MIPS_BC2TL: case MIPS_BC2F: case MIPS_BC2T: + case MIPS_BC2EQZ: + case MIPS_BC2NEZ: case CNMIPS_BBIT0: case CNMIPS_BBIT032: case CNMIPS_BBIT1: @@ -321,8 +331,6 @@ class MipsArchitecture: public Architecture switch (instr.operation) { - //case MIPS_JALX: //This case jumps to a different processor mode microMIPS32/MIPS32/MIPS16e - // break; //Branch/jump and link immediate case MIPS_BAL: if (instr.operands[0].immediate != addr + 8) @@ -332,6 +340,9 @@ class MipsArchitecture: public Architecture break; case MIPS_JAL: + case MIPS_JALX: + // TODO: Associate JALX with the appropriate microMIPS or MIPS16e + // target architecture once either alternate ISA mode is supported. result.AddBranch(CallDestination, instr.operands[0].immediate, nullptr, hasBranchDelay); break; @@ -410,6 +421,15 @@ class MipsArchitecture: public Architecture result.AddBranch(FalseBranch, addr + 8, nullptr, hasBranchDelay); break; + case MIPS_BC1EQZ: + case MIPS_BC1NEZ: + case MIPS_BC2EQZ: + case MIPS_BC2NEZ: + result.AddBranch(TrueBranch, instr.operands[1].immediate, nullptr, hasBranchDelay); + //need to jump over the branch delay slot and current instruction + result.AddBranch(FalseBranch, addr + 8, nullptr, hasBranchDelay); + break; + //Exception return instruction case MIPS_ERET: result.AddBranch(FunctionReturn, 0, nullptr, hasBranchDelay); @@ -582,21 +602,43 @@ class MipsArchitecture: public Architecture nop = il.Nop(); il.AddInstruction(nop); + size_t delayStart = il.GetInstructionCount(); GetLowLevelILForInstruction(this, addr + instr.size, il, secondInstr, GetAddressSize(), m_decomposeFlags, m_version); LowLevelILInstruction delayed; uint32_t clobbered = BN_INVALID_REGISTER; + uint32_t clobberedHigh = BN_INVALID_REGISTER; size_t instrIdx = il.GetInstructionCount(); - if (instrIdx != 0) + for (size_t i = instrIdx; i > delayStart; i--) { - // FIXME: this assumes that the instruction in the delay slot - // only changed registers in the last IL instruction that it - // added -- strictly speaking we should be starting from the - // first instruction that could have been added and follow all - // paths to the end of that instruction. - delayed = il.GetInstruction(instrIdx - 1); - if ((delayed.operation == LLIL_SET_REG) && (delayed.address == (addr + instr.size))) + // Conditional moves can end in control flow after the register write. + // FIXME: only the last register write (or register pair) is tracked. + delayed = il.GetInstruction(i - 1); + if (delayed.address != (addr + instr.size)) + continue; + if (delayed.operation == LLIL_SET_REG) + { clobbered = delayed.GetDestRegister(); + break; + } + if (delayed.operation == LLIL_SET_REG_SPLIT) + { + clobbered = delayed.GetLowRegister(); + clobberedHigh = delayed.GetHighRegister(); + break; + } + if (delayed.operation == LLIL_INTRINSIC) + { + for (auto output : delayed.GetOutputRegisterOrFlagList()) + { + if (output.isFlag || LLIL_REG_IS_TEMP(output.index)) + continue; + clobbered = output.index; + break; + } + if (clobbered != BN_INVALID_REGISTER) + break; + } } il.SetCurrentAddress(this, addr); @@ -614,21 +656,36 @@ class MipsArchitecture: public Architecture if (clobbered != BN_INVALID_REGISTER) { - // FIXME: this approach will break with any of the REG_SPLIT operations as well - // any use of partial registers -- this approach needs to be expanded substantially - // to be correct in the general case. also, it uses LLIL_TEMP(1) for the simple reason - // that the mips lifter only uses LLIL_TEMP(0) at the moment. + // FIXME: register aliases and partial writes still need general handling. + bool split = clobberedHigh != BN_INVALID_REGISTER; + // Intrinsics have no expression size. Preserve the old architectural + // register, even when the intrinsic returns a narrower value. + size_t savedSize = delayed.operation == LLIL_INTRINSIC ? GetRegisterInfo(clobbered).size : + delayed.size * (split ? 2 : 1); LowLevelILInstruction lifted = il.GetInstruction(instrIdx); - if ((lifted.operation == LLIL_IF || lifted.operation == LLIL_CALL) && (lifted.address == addr)) + if ((lifted.operation == LLIL_IF || lifted.operation == LLIL_CALL || lifted.operation == LLIL_JUMP || + lifted.operation == LLIL_RET || lifted.operation == LLIL_TAILCALL) && (lifted.address == addr)) { bool replace = false; + // Allocate after lifting the slot and branch to avoid their temporaries. + uint32_t savedReg = LLIL_TEMP(max(1u, il.GetTemporaryRegisterCount())); lifted.VisitExprs([&](const LowLevelILInstruction& expr) -> bool { - if (expr.operation == LLIL_REG && expr.GetSourceRegister() == clobbered) + if (expr.operation == LLIL_REG && + (expr.GetSourceRegister() == clobbered || + expr.GetSourceRegister() == clobberedHigh)) { // Replace all reads from the clobbered register to a temp register // that we're going to set (by replacing the earlier nop we added) - il.ReplaceExpr(expr.exprIndex, il.Register(expr.size, LLIL_TEMP(1))); + ExprId saved = il.Register(expr.size, savedReg); + if (split) + { + saved = il.Register(savedSize, savedReg); + if (expr.GetSourceRegister() == clobberedHigh) + saved = il.LogicalShiftRight(savedSize, saved, il.Const(1, delayed.size * 8)); + saved = il.LowPart(expr.size, saved); + } + il.ReplaceExpr(expr.exprIndex, saved); replace = true; } return true; @@ -640,7 +697,9 @@ class MipsArchitecture: public Architecture // instruction we added at the beginning with an assignment to the temp // register we rewrote in the LLIL_IF condition expression il.SetCurrentAddress(this, addr + instr.size); - il.ReplaceExpr(nop, il.SetRegister(delayed.size, LLIL_TEMP(1), il.Register(delayed.size, delayed.GetDestRegister()))); + ExprId original = split ? il.RegisterSplit(delayed.size, clobberedHigh, clobbered) : + il.Register(savedSize, clobbered); + il.ReplaceExpr(nop, il.SetRegister(savedSize, savedReg, original)); il.SetCurrentAddress(this, addr); } } @@ -1066,6 +1125,58 @@ class MipsArchitecture: public Architecture return "moveFromCoprocessor2"; case MIPS_INTRIN_MFC_UNIMPLEMENTED: return "moveFromCoprocessorUnimplemented"; + case MIPS_INTRIN_CFC1: + return "moveControlWordFromCoprocessor1"; + case MIPS_INTRIN_CFC2: + return "moveControlWordFromCoprocessor2"; + case MIPS_INTRIN_COP2: + return "coprocessor2Operation"; + case MIPS_INTRIN_CTC1: + return "moveControlWordToCoprocessor1"; + case MIPS_INTRIN_CTC2: + return "moveControlWordToCoprocessor2"; + case MIPS_INTRIN_MFHC0: + return "moveHighWordFromCoprocessor0"; + case MIPS_INTRIN_MFHC2: + return "moveHighWordFromCoprocessor2"; + case MIPS_INTRIN_MOV_PS: + return "_mov_ps"; + case MIPS_INTRIN_MOVF_PS: + return "_movf_ps"; + case MIPS_INTRIN_MOVT_PS: + return "_movt_ps"; + case MIPS_INTRIN_MSUB_PS: + return "_msub_ps"; + case MIPS_INTRIN_MTHC0: + return "moveHighWordToCoprocessor0"; + case MIPS_INTRIN_MTHC2: + return "moveHighWordToCoprocessor2"; + case MIPS_INTRIN_NEG_PS: + return "_neg_ps"; + case MIPS_INTRIN_NMADD_PS: + return "_nmadd_ps"; + case MIPS_INTRIN_NMSUB_PS: + return "_nmsub_ps"; + case MIPS_INTRIN_RDPGPR: + return "readGPRFromPreviousShadowSet"; + case MIPS_INTRIN_WRPGPR: + return "writeGPRToPreviousShadowSet"; + case MIPS_INTRIN_SUB_PS: + return "_sub_ps"; + case MIPS_INTRIN_ADD_PS: + return "_add_ps"; + case MIPS_INTRIN_MUL_PS: + return "_mul_ps"; + case MIPS_INTRIN_ABS_PS: + return "_abs_ps"; + case MIPS_INTRIN_ROUND_W_S: + return "_round_w_s"; + case MIPS_INTRIN_ROUND_W_D: + return "_round_w_d"; + case MIPS_INTRIN_ROUND_L_S: + return "_round_l_s"; + case MIPS_INTRIN_ROUND_L_D: + return "_round_l_d"; case MIPS_INTRIN_MTC0: return "moveToCoprocessor0"; case MIPS_INTRIN_MTC2: @@ -1195,6 +1306,32 @@ class MipsArchitecture: public Architecture MIPS_INTRIN_DSHD, MIPS_INTRIN_MFC0, MIPS_INTRIN_MFC_UNIMPLEMENTED, + MIPS_INTRIN_CFC1, + MIPS_INTRIN_CFC2, + MIPS_INTRIN_COP2, + MIPS_INTRIN_CTC1, + MIPS_INTRIN_CTC2, + MIPS_INTRIN_MFHC0, + MIPS_INTRIN_MFHC2, + MIPS_INTRIN_MOV_PS, + MIPS_INTRIN_MOVF_PS, + MIPS_INTRIN_MOVT_PS, + MIPS_INTRIN_MSUB_PS, + MIPS_INTRIN_MTHC0, + MIPS_INTRIN_MTHC2, + MIPS_INTRIN_NEG_PS, + MIPS_INTRIN_NMADD_PS, + MIPS_INTRIN_NMSUB_PS, + MIPS_INTRIN_RDPGPR, + MIPS_INTRIN_WRPGPR, + MIPS_INTRIN_SUB_PS, + MIPS_INTRIN_ADD_PS, + MIPS_INTRIN_MUL_PS, + MIPS_INTRIN_ABS_PS, + MIPS_INTRIN_ROUND_W_S, + MIPS_INTRIN_ROUND_W_D, + MIPS_INTRIN_ROUND_L_S, + MIPS_INTRIN_ROUND_L_D, MIPS_INTRIN_MTC0, MIPS_INTRIN_MTC_UNIMPLEMENTED, MIPS_INTRIN_MTC1_UNPREDICTABLE_HIGH_WORD, @@ -1270,6 +1407,100 @@ class MipsArchitecture: public Architecture return { NameAndType("register", Type::IntegerType(4, false)), }; + case MIPS_INTRIN_CFC1: + return { + NameAndType("controlRegister", Type::IntegerType(4, false)), + }; + case MIPS_INTRIN_CFC2: + return { + NameAndType("implementation", Type::IntegerType(2, false)), + }; + case MIPS_INTRIN_COP2: + return { + NameAndType("cofun", Type::IntegerType(4, false)), + }; + case MIPS_INTRIN_CTC1: + return { + NameAndType("controlRegister", Type::IntegerType(4, false)), + NameAndType("value", Type::IntegerType(4, false)), + }; + case MIPS_INTRIN_CTC2: + return { + NameAndType("implementation", Type::IntegerType(2, false)), + NameAndType("value", Type::IntegerType(4, false)), + }; + case MIPS_INTRIN_MFHC0: + return { + NameAndType("register", Type::IntegerType(4, false)), + NameAndType("selector", Type::IntegerType(4, false)), + }; + case MIPS_INTRIN_MFHC2: + return { + NameAndType("implementation", Type::IntegerType(2, false)), + }; + case MIPS_INTRIN_MOV_PS: + return { + NameAndType("value", Type::IntegerType(8, false)), + }; + case MIPS_INTRIN_MOVF_PS: + case MIPS_INTRIN_MOVT_PS: + return { + NameAndType("oldFd", Type::IntegerType(8, false)), + NameAndType("fs", Type::IntegerType(8, false)), + NameAndType("fccLow", Type::BoolType()), + NameAndType("fccHigh", Type::BoolType()), + }; + case MIPS_INTRIN_MSUB_PS: + return { + NameAndType("fr", Type::IntegerType(8, false)), + NameAndType("fs", Type::IntegerType(8, false)), + NameAndType("ft", Type::IntegerType(8, false)), + }; + case MIPS_INTRIN_MTHC0: + return { + NameAndType("register", Type::IntegerType(4, false)), + NameAndType("selector", Type::IntegerType(4, false)), + NameAndType("value", Type::IntegerType(4, false)), + }; + case MIPS_INTRIN_MTHC2: + return { + NameAndType("implementation", Type::IntegerType(2, false)), + NameAndType("value", Type::IntegerType(4, false)), + }; + case MIPS_INTRIN_NEG_PS: + case MIPS_INTRIN_ABS_PS: + return { + NameAndType("value", Type::IntegerType(8, false)), + }; + case MIPS_INTRIN_NMADD_PS: + case MIPS_INTRIN_NMSUB_PS: + return { + NameAndType("fr", Type::IntegerType(8, false)), + NameAndType("fs", Type::IntegerType(8, false)), + NameAndType("ft", Type::IntegerType(8, false)), + }; + case MIPS_INTRIN_RDPGPR: + return { + NameAndType("register", Type::IntegerType(4, false)), + }; + case MIPS_INTRIN_WRPGPR: + return { + NameAndType("register", Type::IntegerType(4, false)), + NameAndType("value", Type::IntegerType(m_bits == 64 ? 8 : 4, false)), + }; + case MIPS_INTRIN_SUB_PS: + case MIPS_INTRIN_ADD_PS: + case MIPS_INTRIN_MUL_PS: + return { + NameAndType("fs", Type::IntegerType(8, false)), + NameAndType("ft", Type::IntegerType(8, false)), + }; + case MIPS_INTRIN_ROUND_W_S: + case MIPS_INTRIN_ROUND_L_S: + return {NameAndType("fs", Type::FloatType(4))}; + case MIPS_INTRIN_ROUND_W_D: + case MIPS_INTRIN_ROUND_L_D: + return {NameAndType("fs", Type::FloatType(8))}; case MIPS_INTRIN_MFC_UNIMPLEMENTED: return { NameAndType("coprocessor", Type::IntegerType(4, false)), @@ -1458,12 +1689,35 @@ class MipsArchitecture: public Architecture return {Type::IntegerType(8, false)}; case MIPS_INTRIN_MFC0: case MIPS_INTRIN_MFC_UNIMPLEMENTED: + case MIPS_INTRIN_CFC1: + case MIPS_INTRIN_CFC2: + case MIPS_INTRIN_MFHC0: + case MIPS_INTRIN_MFHC2: case MIPS_INTRIN_MTC1_UNPREDICTABLE_HIGH_WORD: return {Type::IntegerType(4, false)}; case MIPS_INTRIN_DMFC0: case MIPS_INTRIN_DMFC_UNIMPLEMENTED: case MIPS_INTRIN_MADD_PS: + case MIPS_INTRIN_MOV_PS: + case MIPS_INTRIN_MOVF_PS: + case MIPS_INTRIN_MOVT_PS: + case MIPS_INTRIN_MSUB_PS: + case MIPS_INTRIN_NEG_PS: + case MIPS_INTRIN_NMADD_PS: + case MIPS_INTRIN_NMSUB_PS: + case MIPS_INTRIN_SUB_PS: + case MIPS_INTRIN_ADD_PS: + case MIPS_INTRIN_MUL_PS: + case MIPS_INTRIN_ABS_PS: return {Type::IntegerType(8, false)}; + case MIPS_INTRIN_ROUND_W_S: + case MIPS_INTRIN_ROUND_W_D: + return {Type::IntegerType(4, true)}; + case MIPS_INTRIN_ROUND_L_S: + case MIPS_INTRIN_ROUND_L_D: + return {Type::IntegerType(8, true)}; + case MIPS_INTRIN_RDPGPR: + return {Type::IntegerType(m_bits == 64 ? 8 : 4, false)}; case MIPS_INTRIN_HWR0: case MIPS_INTRIN_HWR1: case MIPS_INTRIN_HWR2: diff --git a/arch/mips/il.cpp b/arch/mips/il.cpp index d2d31646be..8cb7ab659a 100644 --- a/arch/mips/il.cpp +++ b/arch/mips/il.cpp @@ -496,6 +496,15 @@ ExprId GetConditionForInstruction(LowLevelILFunction& il, Instruction& instr, st if (instr.operands[0].operandClass == FLAG) return il.Flag(instr.operands[0].reg); return il.Flag(FPCCREG_FCC0); + case MIPS_BC1EQZ: + return il.Not(0, + il.TestBit(4, + ReadILOperand(il, instr, 1, registerSize(op1), 4), + il.Const(1, 0))); + case MIPS_BC1NEZ: + return il.TestBit(4, + ReadILOperand(il, instr, 1, registerSize(op1), 4), + il.Const(1, 0)); case MIPS_BC0F: case MIPS_BC0FL: return il.Not(0, il.Flag(CCREG_COC0)); @@ -508,6 +517,14 @@ ExprId GetConditionForInstruction(LowLevelILFunction& il, Instruction& instr, st case MIPS_BC2T: case MIPS_BC2TL: return il.Flag(CCREG_COC2); + case MIPS_BC2EQZ: + return il.CompareEqual(registerSize(op1), + ReadILOperand(il, instr, 1, registerSize(op1)), + il.Const(registerSize(op1), 0)); + case MIPS_BC2NEZ: + return il.CompareNotEqual(registerSize(op1), + ReadILOperand(il, instr, 1, registerSize(op1)), + il.Const(registerSize(op1), 0)); case CNMIPS_BBIT0: return il.CompareEqual(registerSize(op1), il.And(registerSize(op1), @@ -1005,6 +1022,15 @@ static ExprId MoveFromCoprocessor(unsigned cop, LowLevelILFunction& il, size_t l {il.Const(4, cop), il.Const(4, reg), il.Const(4, sel)}); } +static void MoveWordFromCoprocessorIntrinsic(LowLevelILFunction& il, size_t registerSize, uint32_t outReg, + MipsIntrinsic intrinsic, const std::vector& inputs) +{ + // Intrinsic output types do not sign-extend a word assigned to a full GPR. + // Keep the opaque read even when the architectural destination is $zero. + il.AddInstruction(il.Intrinsic({RegisterOrFlag::Register(LLIL_TEMP(0))}, intrinsic, inputs)); + il.AddInstruction(SetRegisterOrNop(il, 4, registerSize, outReg, il.Register(4, LLIL_TEMP(0)))); +} + static ExprId MoveToCoprocessor(unsigned cop, LowLevelILFunction& il, size_t storeSize, uint32_t reg, uint64_t sel, ExprId srcExpr, uint32_t decomposeFlags) { if (cop == 0) @@ -1294,6 +1320,32 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu ReadILOperand(il, instr, 2, registerSize(op2)), il.Operand(1, il.Const(4, 0x0000ffff & op3.immediate))), ZeroExtend)); break; + case MIPS_ALIGN: + case MIPS_DALIGN: + { + // MIPS64 Release 6.06, DALIGN (pp. 59-60): select bytes from rt:rs. + const size_t size = instr.operation == MIPS_DALIGN ? 8 : 4; + const uint64_t bytePosition = op4.immediate; + if (bytePosition == 0) + { + il.AddInstruction(SetRegisterOrNop(il, size, registerSize(op1), op1.reg, + ReadILOperand(il, instr, 3, registerSize(op3), size))); + } + else + { + const uint64_t leftShift = 8 * bytePosition; + const uint64_t rightShift = 8 * size - leftShift; + il.AddInstruction(SetRegisterOrNop(il, size, registerSize(op1), op1.reg, + il.Or(size, + il.ShiftLeft(size, + ReadILOperand(il, instr, 3, registerSize(op3), size), + il.Const(1, leftShift)), + il.LogicalShiftRight(size, + ReadILOperand(il, instr, 2, registerSize(op2), size), + il.Const(1, rightShift))))); + } + break; + } case MIPS_DIV: il.AddInstruction(il.SetRegister(get_register_width(REG_LO, version), REG_LO, il.DivSigned(4, @@ -1376,7 +1428,10 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu il.AddInstruction(DirectJump(arch, il, op1.immediate, addrSize)); break; case MIPS_JAL: + case MIPS_JALX: case MIPS_BAL: + // TODO: Model JALX's transition to microMIPS or MIPS16e once the + // corresponding target architecture is available. if (op1.immediate == (addr + 8)) // Get PC construct il.AddInstruction(il.SetRegister(addrSize, REG_RA, il.ConstPointer(addrSize ,addr + 8))); else @@ -1419,6 +1474,13 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu ConditionalJump(arch, il, il.Flag(FPCCREG_FCC0), addrSize, op1.immediate, addr + 8); return false; + case MIPS_BC1EQZ: + case MIPS_BC1NEZ: + case MIPS_BC2EQZ: + case MIPS_BC2NEZ: + ConditionalJump(arch, il, GetConditionForInstruction(il, instr, registerSize), addrSize, op2.immediate, addr + 8); + return false; + case MIPS_BC2F: case MIPS_BC2FL: if (op1.operandClass == FLAG) @@ -1607,6 +1669,12 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu case MIPS_MFC0: il.AddInstruction(MoveFromCoprocessor(0, il, 4, op1.reg, op2.immediate, op3.immediate, decomposeFlags)); break; + case MIPS_MFHC0: + // COP0 register presence and width are implementation-defined; retain + // both selector fields rather than inventing unavailable register state. + MoveWordFromCoprocessorIntrinsic(il, registerSize(op1), op1.reg, MIPS_INTRIN_MFHC0, + {il.Const(4, op2.immediate), il.Const(4, op3.immediate)}); + break; case MIPS_MFC1: // MIPS32 Release 6.06, MFC1 (p. 267), reads FPR[fs][31:0]. // MIPS64 Release 6.06, MFC1 (p. 357), sign-extends that word. @@ -1655,6 +1723,14 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu case MIPS_MTC0: il.AddInstruction(MoveToCoprocessor(0, il, 4, op2.immediate, op3.immediate, ReadILOperand(il, instr, 1, registerSize(op1)), decomposeFlags)); break; + case MIPS_MTHC0: + // COP0 register presence and width are implementation-defined; retain + // both selector fields and the high word being written. + il.AddInstruction(il.Intrinsic( + {}, MIPS_INTRIN_MTHC0, + {il.Const(4, op2.immediate), il.Const(4, op3.immediate), + ReadILOperand(il, instr, 1, registerSize(op1), 4)})); + break; case MIPS_MTC1: { auto value = ReadILOperand(il, instr, 1, registerSize(op1), 4); @@ -1782,6 +1858,65 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu il.AddInstruction(SetRegisterOrNop(il, registerSize(op1), registerSize(op1), op1.reg, ReadILOperand(il, instr, 2, registerSize(op2)))); il.MarkLabel(falseCode); break; + case MIPS_MOVN_S: + case MIPS_MOVZ_S: + case MIPS_MOVN_D: + case MIPS_MOVZ_D: + case MIPS_MOVN_PS: + case MIPS_MOVZ_PS: + { + // MIPS32 Release 6.06, MOVN.fmt (p. 280) and MOVZ.fmt (p. 285): + // test the GPR, not the FP value, and preserve fd when the condition is false. + bool isSingle = instr.operation == MIPS_MOVN_S || instr.operation == MIPS_MOVZ_S; + bool isPairedSingle = instr.operation == MIPS_MOVN_PS || instr.operation == MIPS_MOVZ_PS; + bool pairedRegisters = !isSingle && arch->GetRegisterInfo(op1.reg).size != 8; + if (pairedRegisters && (isPairedSingle || + ((op1.reg - FPREG_F0) & 1) || ((op2.reg - FPREG_F0) & 1))) + { + // FR=0 requires even D register pairs and cannot represent PS. + il.AddInstruction(il.Unknown()); + break; + } + + bool moveIfNonzero = instr.operation == MIPS_MOVN_S || instr.operation == MIPS_MOVN_D || + instr.operation == MIPS_MOVN_PS; + size_t conditionSize = registerSize(op3); + auto value = ReadILOperand(il, instr, 3, conditionSize); + auto condition = moveIfNonzero ? + il.CompareNotEqual(conditionSize, value, il.Const(conditionSize, 0)) : + il.CompareEqual(conditionSize, value, il.Const(conditionSize, 0)); + il.AddInstruction(il.If(condition, trueCode, falseCode)); + il.MarkLabel(trueCode); + if (pairedRegisters) + il.AddInstruction(il.SetRegisterSplit(4, op1.reg + 1, op1.reg, + il.RegisterSplit(4, op2.reg + 1, op2.reg))); + else + { + size_t size = isSingle ? 4 : 8; + il.AddInstruction(il.SetRegister(size, op1.reg, il.Register(size, op2.reg))); + } + il.MarkLabel(falseCode); + break; + } + case MIPS_MOVF_PS: + case MIPS_MOVT_PS: + { + // MIPS32 Release 6.06, MOVF.fmt (pp. 277-278) and MOVT.fmt + // (pp. 282-283): FCC[cc] selects the low lane, FCC[cc+1] the high. + // Both require FR=1 and an even cc; each unselected lane is preserved. + if (arch->GetRegisterInfo(op1.reg).size != 8 || ((op3.reg - FPCCREG_FCC0) & 1)) + { + il.AddInstruction(il.Unknown()); + break; + } + + uint32_t intrinsic = instr.operation == MIPS_MOVF_PS ? MIPS_INTRIN_MOVF_PS : MIPS_INTRIN_MOVT_PS; + il.AddInstruction(il.Intrinsic({RegisterOrFlag::Register(LLIL_TEMP(0))}, intrinsic, + {il.Register(8, op1.reg), il.Register(8, op2.reg), il.Flag(op3.reg), il.Flag(op3.reg + 1)})); + // Keep the destination write explicit for delay-slot clobber tracking. + il.AddInstruction(il.SetRegister(8, op1.reg, il.Register(8, LLIL_TEMP(0)))); + break; + } case MIPS_MSUB: //(HI,LO) = (HI,LO) - (GPR[rs] x GPR[rt]) // @@ -2436,6 +2571,17 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu case MIPS_NOP: il.AddInstruction(il.Nop()); break; + case MIPS_BITSWAP: + case MIPS_DBITSWAP: + { + // MIPS64 Release 6.06, DBITSWAP (pp. 112-113): reverse bits within each byte. + const size_t size = instr.operation == MIPS_DBITSWAP ? 8 : 4; + il.AddInstruction(SetRegisterOrNop(il, size, registerSize(op1), op1.reg, + il.ByteSwap(size, + il.ReverseBits(size, + ReadILOperand(il, instr, 2, registerSize(op2), size))))); + break; + } case MIPS_WSBH: il.AddInstruction(il.Intrinsic({RegisterOrFlag::Register(op1.reg)}, MIPS_INTRIN_WSBH, {ReadILOperand(il, instr, 2, registerSize(op2))})); break; @@ -2451,12 +2597,63 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu case MIPS_NEG_S: il.AddInstruction(il.SetRegister(4, op1.reg, il.FloatNeg(4, il.Register(4, op2.reg)))); break; + case MIPS_NEG_D: + if (arch->GetRegisterInfo(op1.reg).size == 8) + il.AddInstruction(il.SetRegister(8, op1.reg, + il.FloatNeg(8, il.Register(8, op2.reg)))); + else if (((op1.reg - FPREG_F0) & 1) || ((op2.reg - FPREG_F0) & 1)) + il.AddInstruction(il.Unknown()); + else + il.AddInstruction(il.SetRegisterSplit(4, op1.reg + 1, op1.reg, + il.FloatNeg(8, il.RegisterSplit(4, op2.reg + 1, op2.reg)))); + break; + case MIPS_NEG_PS: + // Paired-single negates two independent lanes in a modeled 64-bit FPR. + if (arch->GetRegisterInfo(op1.reg).size != 8) + il.AddInstruction(il.Unknown()); + else + il.AddInstruction(il.Intrinsic( + {RegisterOrFlag::Register(op1.reg)}, MIPS_INTRIN_NEG_PS, + {il.Register(8, op2.reg)})); + break; case MIPS_ABS_S: case MIPS_ABS_D: il.AddInstruction(il.SetRegister(registerSize(op1), op1.reg, il.FloatAbs(registerSize(op2), il.Register(registerSize(op2), op2.reg)))); break; + case MIPS_ABS_PS: + // MIPS32 Release 6.06, ABS.fmt (p. 32): two single-precision lanes. + // An intrinsic avoids assuming ABS2008 sign-bit-only semantics for NaNs. + if (arch->GetRegisterInfo(op1.reg).size != 8) + il.AddInstruction(il.Unknown()); + else + { + il.AddInstruction(il.Intrinsic({RegisterOrFlag::Register(LLIL_TEMP(0))}, MIPS_INTRIN_ABS_PS, + {il.Register(8, op2.reg)})); + il.AddInstruction(il.SetRegister(8, op1.reg, il.Register(8, LLIL_TEMP(0)))); + } + break; case MIPS_MOV_S: - il.AddInstruction(il.SetRegister(registerSize(op1), op1.reg, il.Register(registerSize(op2), op2.reg))); + // MOV.S transfers the single-precision value even when each modeled + // FPR is 64 bits wide. + il.AddInstruction(il.SetRegister(4, op1.reg, il.Register(4, op2.reg))); + break; + case MIPS_MOV_D: + if (arch->GetRegisterInfo(op1.reg).size == 8) + il.AddInstruction(il.SetRegister(8, op1.reg, il.Register(8, op2.reg))); + else if (((op1.reg - FPREG_F0) & 1) || ((op2.reg - FPREG_F0) & 1)) + il.AddInstruction(il.Unknown()); + else + il.AddInstruction(il.SetRegisterSplit(4, op1.reg + 1, op1.reg, + il.RegisterSplit(4, op2.reg + 1, op2.reg))); + break; + case MIPS_MOV_PS: + // Paired-single is only representable by the modeled 64-bit FR=1 FPRs. + if (arch->GetRegisterInfo(op1.reg).size != 8) + il.AddInstruction(il.Unknown()); + else + il.AddInstruction(il.Intrinsic( + {RegisterOrFlag::Register(op1.reg)}, MIPS_INTRIN_MOV_PS, + {il.Register(8, op2.reg)})); break; case MIPS_ADD_S: il.AddInstruction(il.SetRegister(4, op1.reg, il.FloatAdd(4, il.Register(4, op2.reg), il.Register(4, op3.reg)))); @@ -2469,17 +2666,47 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu else il.AddInstruction(il.SetRegister(registerSize(op1), op1.reg, il.FloatAdd(registerSize(op2), il.Register(registerSize(op2), op2.reg), il.Register(registerSize(op3), op3.reg)))); break; + case MIPS_ADD_PS: + // MIPS32 Release 6.06, ADD.fmt (p. 34): independently rounded single-precision sums. + if (arch->GetRegisterInfo(op1.reg).size != 8) + il.AddInstruction(il.Unknown()); + else + { + il.AddInstruction(il.Intrinsic({RegisterOrFlag::Register(LLIL_TEMP(0))}, MIPS_INTRIN_ADD_PS, + {il.Register(8, op2.reg), il.Register(8, op3.reg)})); + // Explicit writes keep intrinsic results visible to delay-slot clobber tracking. + il.AddInstruction(il.SetRegister(8, op1.reg, il.Register(8, LLIL_TEMP(0)))); + } + break; case MIPS_SUB_S: il.AddInstruction(il.SetRegister(4, op1.reg, il.FloatSub(4, il.Register(4, op2.reg), il.Register(4, op3.reg)))); break; case MIPS_SUB_D: - if (registerSize(op1) < 8) - il.AddInstruction(il.SetRegisterSplit(4, op1.reg | 1, op1.reg & (~1), - il.FloatSub(8, il.RegisterSplit(4, op2.reg | 1, op2.reg & (~1)), + if (arch->GetRegisterInfo(op1.reg).size < 8) + { + if (((op1.reg - FPREG_F0) & 1) || ((op2.reg - FPREG_F0) & 1) || + ((op3.reg - FPREG_F0) & 1)) + { + il.AddInstruction(il.Unknown()); + break; + } + il.AddInstruction(il.SetRegisterSplit(4, op1.reg + 1, op1.reg, + il.FloatSub(8, il.RegisterSplit(4, op2.reg + 1, op2.reg), il.RegisterSplit(4, op3.reg + 1, op3.reg)))); + } else - il.AddInstruction(il.SetRegister(registerSize(op1), op1.reg, il.FloatSub(registerSize(op2), - il.Register(registerSize(op2), op2.reg), il.Register(registerSize(op3), op3.reg)))); + il.AddInstruction(il.SetRegister(8, op1.reg, il.FloatSub(8, + il.Register(8, op2.reg), il.Register(8, op3.reg)))); + break; + case MIPS_SUB_PS: + // Paired-single subtraction operates independently on both 32-bit lanes + // and is only valid with the modeled 64-bit FR=1 FPRs. + if (arch->GetRegisterInfo(op1.reg).size != 8) + il.AddInstruction(il.Unknown()); + else + il.AddInstruction(il.Intrinsic( + {RegisterOrFlag::Register(op1.reg)}, MIPS_INTRIN_SUB_PS, + {il.Register(8, op2.reg), il.Register(8, op3.reg)})); break; case MIPS_MUL_S: il.AddInstruction(il.SetRegister(4, op1.reg, il.FloatMult(4, il.Register(4, op2.reg), il.Register(4, op3.reg)))); @@ -2493,6 +2720,17 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu il.AddInstruction(il.SetRegister(registerSize(op1), op1.reg, il.FloatMult(registerSize(op2), il.Register(registerSize(op2), op2.reg), il.Register(registerSize(op3), op3.reg)))); break; + case MIPS_MUL_PS: + // MIPS32 Release 6.06, MUL.fmt (p. 302): independently rounded single-precision products. + if (arch->GetRegisterInfo(op1.reg).size != 8) + il.AddInstruction(il.Unknown()); + else + { + il.AddInstruction(il.Intrinsic({RegisterOrFlag::Register(LLIL_TEMP(0))}, MIPS_INTRIN_MUL_PS, + {il.Register(8, op2.reg), il.Register(8, op3.reg)})); + il.AddInstruction(il.SetRegister(8, op1.reg, il.Register(8, LLIL_TEMP(0)))); + } + break; case MIPS_DIV_S: il.AddInstruction(il.SetRegister(4, op1.reg, il.FloatDiv(4, il.Register(4, op2.reg), il.Register(4, op3.reg)))); break; @@ -2536,6 +2774,108 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu else il.AddInstruction(SetRegisterOrNop(il, 4, registerSize(op1), op1.reg, il.FloatToInt(4, il.Register(registerSize(op2), op2.reg)))); break; + case MIPS_TRUNC_W_S: + il.AddInstruction(il.SetRegister(4, op1.reg, + il.FloatToInt(4, il.FloatTrunc(4, il.Register(4, op2.reg))))); + break; + case MIPS_TRUNC_W_D: + if (arch->GetRegisterInfo(op2.reg).size == 8) + { + il.AddInstruction(il.SetRegister(4, op1.reg, + il.FloatToInt(4, il.FloatTrunc(8, il.Register(8, op2.reg))))); + } + else if ((op2.reg - FPREG_F0) & 1) + { + // FR=0 double-precision operands must begin at an even FPR. + il.AddInstruction(il.Unknown()); + } + else + { + il.AddInstruction(il.SetRegister(4, op1.reg, + il.FloatToInt(4, il.FloatTrunc(8, + il.RegisterSplit(4, op2.reg + 1, op2.reg))))); + } + break; + case MIPS_TRUNC_L_S: + // Long fixed-point results are unpredictable in the FR=0 32-bit + // FPR model because they cannot be represented by a single FPR. + if (arch->GetRegisterInfo(op1.reg).size != 8) + il.AddInstruction(il.Unknown()); + else + il.AddInstruction(il.SetRegister(8, op1.reg, + il.FloatToInt(8, il.FloatTrunc(4, il.Register(4, op2.reg))))); + break; + case MIPS_TRUNC_L_D: + if (arch->GetRegisterInfo(op1.reg).size != 8) + il.AddInstruction(il.Unknown()); + else + il.AddInstruction(il.SetRegister(8, op1.reg, + il.FloatToInt(8, il.FloatTrunc(8, il.Register(8, op2.reg))))); + break; + case MIPS_ROUND_W_S: + case MIPS_ROUND_W_D: + case MIPS_ROUND_L_S: + case MIPS_ROUND_L_D: + case MIPS_CEIL_W_S: + case MIPS_CEIL_W_D: + case MIPS_CEIL_L_S: + case MIPS_CEIL_L_D: + case MIPS_FLOOR_W_S: + case MIPS_FLOOR_W_D: + case MIPS_FLOOR_L_S: + case MIPS_FLOOR_L_D: + { + // MIPS32 Release 6.06, CEIL pp. 127-128, FLOOR pp. 185-186, + // ROUND pp. 341-342: fixed rounding independent of FCSR.RM. + // Like TRUNC, this models the result, not FCSR exceptions or traps. + size_t sourceSize = (instr.operation == MIPS_ROUND_W_D || instr.operation == MIPS_ROUND_L_D || + instr.operation == MIPS_CEIL_W_D || instr.operation == MIPS_CEIL_L_D || + instr.operation == MIPS_FLOOR_W_D || instr.operation == MIPS_FLOOR_L_D) ? 8 : 4; + size_t resultSize = (instr.operation == MIPS_ROUND_L_S || instr.operation == MIPS_ROUND_L_D || + instr.operation == MIPS_CEIL_L_S || instr.operation == MIPS_CEIL_L_D || + instr.operation == MIPS_FLOOR_L_S || instr.operation == MIPS_FLOOR_L_D) ? 8 : 4; + bool pairedSource = sourceSize == 8 && arch->GetRegisterInfo(op2.reg).size != 8; + if ((resultSize == 8 && arch->GetRegisterInfo(op1.reg).size != 8) || + (pairedSource && ((op2.reg - FPREG_F0) & 1))) + { + // ValueFPR/StoreFPR (pp. 22-23): FR=0 forbids long results and odd double sources. + il.AddInstruction(il.Unknown()); + break; + } + + ExprId source = pairedSource ? il.RegisterSplit(4, op2.reg + 1, op2.reg) : + il.Register(sourceSize, op2.reg); + ExprId result; + switch (instr.operation) + { + case MIPS_CEIL_W_S: + case MIPS_CEIL_W_D: + case MIPS_CEIL_L_S: + case MIPS_CEIL_L_D: + result = il.FloatToInt(resultSize, il.Ceil(sourceSize, source)); + break; + case MIPS_FLOOR_W_S: + case MIPS_FLOOR_W_D: + case MIPS_FLOOR_L_S: + case MIPS_FLOOR_L_D: + result = il.FloatToInt(resultSize, il.Floor(sourceSize, source)); + break; + default: + { + // ROUND requires nearest/even. LLIL_ROUND_TO_INT does not specify + // tie-breaking, so keep that conversion explicit in an intrinsic. + uint32_t intrinsic = sourceSize == 4 ? + (resultSize == 4 ? MIPS_INTRIN_ROUND_W_S : MIPS_INTRIN_ROUND_L_S) : + (resultSize == 4 ? MIPS_INTRIN_ROUND_W_D : MIPS_INTRIN_ROUND_L_D); + il.AddInstruction(il.Intrinsic({RegisterOrFlag::Register(LLIL_TEMP(0))}, intrinsic, {source})); + result = il.Register(resultSize, LLIL_TEMP(0)); + break; + } + } + // A word result defines only the low 32 bits, even in a modeled 64-bit FPR. + il.AddInstruction(il.SetRegister(resultSize, op1.reg, result)); + break; + } case MIPS_CVT_D_S: if (registerSize(op2) < 8) il.AddInstruction(il.SetRegisterSplit(4, op1.reg | 1, op1.reg & (~1), @@ -2835,6 +3175,18 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu case MIPS_PREF: il.AddInstruction(il.Intrinsic({}, MIPS_INTRIN_PREFETCH, {il.Const(1, op1.immediate), GetILOperandMemoryAddress(il, op2, addrSize)})); break; + case MIPS_PREFX: + { + ExprId address; + if (op2.reg == REG_ZERO) + address = op2.immediate == REG_ZERO ? il.ConstPointer(addrSize, 0) : il.Register(addrSize, op2.immediate); + else if (op2.immediate == REG_ZERO) + address = il.Register(addrSize, op2.reg); + else + address = il.Add(addrSize, il.Register(addrSize, op2.reg), il.Register(addrSize, op2.immediate)); + il.AddInstruction(il.Intrinsic({}, MIPS_INTRIN_PREFETCH, {il.Const(1, op1.immediate), address})); + break; + } case MIPS_CACHE: il.AddInstruction(il.Intrinsic({}, MIPS_INTRIN_CACHE, {il.Const(1, op1.immediate), GetILOperandMemoryAddress(il, op2, addrSize)})); @@ -3575,6 +3927,149 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu il.FloatMult(4, il.Register(4, op2.reg), il.Register(4, op3.reg))))); break; } + // MSUB.fmt is non-fused: round the product, subtract fr, then + // round the result according to FCSR. + il.AddInstruction(il.SetRegister(4, op1.reg, + il.FloatSub(4, + il.FloatMult(4, il.Register(4, op3.reg), il.Register(4, op4.reg)), + il.Register(4, op2.reg)))); + break; + case MIPS_MSUB_D: + { + if (arch->GetRegisterInfo(op1.reg).size == 8) + { + il.AddInstruction(il.SetRegister(8, op1.reg, + il.FloatSub(8, + il.FloatMult(8, il.Register(8, op3.reg), il.Register(8, op4.reg)), + il.Register(8, op2.reg)))); + } + else + { + if (((op1.reg - FPREG_F0) & 1) || ((op2.reg - FPREG_F0) & 1) || + ((op3.reg - FPREG_F0) & 1) || ((op4.reg - FPREG_F0) & 1)) + { + il.AddInstruction(il.Unknown()); + break; + } + il.AddInstruction(il.SetRegisterSplit(4, op1.reg + 1, op1.reg, + il.FloatSub(8, + il.FloatMult(8, + il.RegisterSplit(4, op3.reg + 1, op3.reg), + il.RegisterSplit(4, op4.reg + 1, op4.reg)), + il.RegisterSplit(4, op2.reg + 1, op2.reg)))); + } + break; + } + case MIPS_MSUB_PS: + { + if (arch->GetRegisterInfo(op1.reg).size != 8) + { + il.AddInstruction(il.Unknown()); + break; + } + il.AddInstruction(il.Intrinsic( + {RegisterOrFlag::Register(op1.reg)}, MIPS_INTRIN_MSUB_PS, + {il.Register(8, op2.reg), il.Register(8, op3.reg), il.Register(8, op4.reg)})); + break; + } + case MIPS_NMADD_S: + // NMADD.fmt is non-fused: round the product, round the sum with fr, + // then negate the result by changing its sign. + il.AddInstruction(il.SetRegister(4, op1.reg, + il.FloatNeg(4, + il.FloatAdd(4, + il.FloatMult(4, il.Register(4, op3.reg), il.Register(4, op4.reg)), + il.Register(4, op2.reg))))); + break; + case MIPS_NMADD_D: + { + if (arch->GetRegisterInfo(op1.reg).size == 8) + { + il.AddInstruction(il.SetRegister(8, op1.reg, + il.FloatNeg(8, + il.FloatAdd(8, + il.FloatMult(8, il.Register(8, op3.reg), il.Register(8, op4.reg)), + il.Register(8, op2.reg))))); + } + else + { + if (((op1.reg - FPREG_F0) & 1) || ((op2.reg - FPREG_F0) & 1) || + ((op3.reg - FPREG_F0) & 1) || ((op4.reg - FPREG_F0) & 1)) + { + il.AddInstruction(il.Unknown()); + break; + } + il.AddInstruction(il.SetRegisterSplit(4, op1.reg + 1, op1.reg, + il.FloatNeg(8, + il.FloatAdd(8, + il.FloatMult(8, + il.RegisterSplit(4, op3.reg + 1, op3.reg), + il.RegisterSplit(4, op4.reg + 1, op4.reg)), + il.RegisterSplit(4, op2.reg + 1, op2.reg))))); + } + break; + } + case MIPS_NMADD_PS: + { + if (arch->GetRegisterInfo(op1.reg).size != 8) + { + il.AddInstruction(il.Unknown()); + break; + } + il.AddInstruction(il.Intrinsic( + {RegisterOrFlag::Register(op1.reg)}, MIPS_INTRIN_NMADD_PS, + {il.Register(8, op2.reg), il.Register(8, op3.reg), il.Register(8, op4.reg)})); + break; + } + case MIPS_NMSUB_S: + // NMSUB.fmt is non-fused: round the product, round the difference with fr, + // then negate the result by changing its sign. + il.AddInstruction(il.SetRegister(4, op1.reg, + il.FloatNeg(4, + il.FloatSub(4, + il.FloatMult(4, il.Register(4, op3.reg), il.Register(4, op4.reg)), + il.Register(4, op2.reg))))); + break; + case MIPS_NMSUB_D: + { + if (arch->GetRegisterInfo(op1.reg).size == 8) + { + il.AddInstruction(il.SetRegister(8, op1.reg, + il.FloatNeg(8, + il.FloatSub(8, + il.FloatMult(8, il.Register(8, op3.reg), il.Register(8, op4.reg)), + il.Register(8, op2.reg))))); + } + else + { + if (((op1.reg - FPREG_F0) & 1) || ((op2.reg - FPREG_F0) & 1) || + ((op3.reg - FPREG_F0) & 1) || ((op4.reg - FPREG_F0) & 1)) + { + il.AddInstruction(il.Unknown()); + break; + } + il.AddInstruction(il.SetRegisterSplit(4, op1.reg + 1, op1.reg, + il.FloatNeg(8, + il.FloatSub(8, + il.FloatMult(8, + il.RegisterSplit(4, op3.reg + 1, op3.reg), + il.RegisterSplit(4, op4.reg + 1, op4.reg)), + il.RegisterSplit(4, op2.reg + 1, op2.reg))))); + } + break; + } + case MIPS_NMSUB_PS: + { + if (arch->GetRegisterInfo(op1.reg).size != 8) + { + il.AddInstruction(il.Unknown()); + break; + } + il.AddInstruction(il.Intrinsic( + {RegisterOrFlag::Register(op1.reg)}, MIPS_INTRIN_NMSUB_PS, + {il.Register(8, op2.reg), il.Register(8, op3.reg), il.Register(8, op4.reg)})); + break; + } case MIPS_LQC2: if (version == MIPS_R5900) @@ -4089,37 +4584,101 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu break; } + case MIPS_CFC1: + if (version == MIPS_R5900) + // Read the modeled control register written by CTC1. Like MFC1, + // CFC1 sign-extends its word into the low 64 bits of the EE GPR. + il.AddInstruction(SetRegisterOrNop(il, 4, 8, op1.reg, il.Register(4, op2.reg))); + else + MoveWordFromCoprocessorIntrinsic(il, registerSize(op1), op1.reg, MIPS_INTRIN_CFC1, + {il.Register(4, op2.reg)}); + break; + case MIPS_CFC2: + if (version == MIPS_R5900) + il.AddInstruction(il.SetRegister(4, op1.reg, il.Register(4, op2.reg))); + else + MoveWordFromCoprocessorIntrinsic(il, registerSize(op1), op1.reg, MIPS_INTRIN_CFC2, + {il.Const(2, op2.immediate)}); + break; + case MIPS_COP2: + il.AddInstruction(il.Intrinsic( + {}, MIPS_INTRIN_COP2, {il.Const(4, op1.immediate)})); + break; case MIPS_CTC1: + if (version == MIPS_R5900) + // Define the control-register state explicitly so later CFC1 + // reads depend on this write rather than the incoming FCR value. + il.AddInstruction(il.SetRegister(4, op2.reg, + ReadILOperand(il, instr, 1, registerSize(op1), 4))); + else + il.AddInstruction(il.Intrinsic( + {}, MIPS_INTRIN_CTC1, + {il.Register(4, op2.reg), + ReadILOperand(il, instr, 1, registerSize(op1), 4)})); + break; case MIPS_CTC2: if (version == MIPS_R5900) - { il.AddInstruction(il.SetRegister(4, op2.reg, il.Register(4, op1.reg))); + else + il.AddInstruction(il.Intrinsic( + {}, MIPS_INTRIN_CTC2, + {il.Const(2, op2.immediate), + ReadILOperand(il, instr, 1, registerSize(op1), 4)})); + break; + + case MIPS_MFHC2: + // The full 16-bit COP2 register selector is implementation-defined. + MoveWordFromCoprocessorIntrinsic(il, registerSize(op1), op1.reg, MIPS_INTRIN_MFHC2, + {il.Const(2, op2.immediate)}); + break; + case MIPS_MTHC2: + // The full 16-bit COP2 register selector is implementation-defined. + il.AddInstruction(il.Intrinsic( + {}, MIPS_INTRIN_MTHC2, + {il.Const(2, op2.immediate), + ReadILOperand(il, instr, 1, registerSize(op1), 4)})); + break; + case MIPS_RDPGPR: + if (op1.reg == REG_ZERO) + { + il.AddInstruction(il.Nop()); break; } - case MIPS_CFC1: - case MIPS_CFC2: - if (version == MIPS_R5900) + // GPR0 is hardwired zero in every shadow set. + if (op2.reg == REG_ZERO) { - il.AddInstruction(il.SetRegister(4, op1.reg, il.Register(4, op2.reg))); + size_t size = registerSize(op1); + il.AddInstruction(il.SetRegister(size, op1.reg, il.Const(size, 0))); break; } - - case MIPS_MFHC2: + // SRSCtl.PSS selects the previous shadow set dynamically; retain the + // encoded rt field as a register number rather than reading the current GPR. + il.AddInstruction(il.Intrinsic( + {RegisterOrFlag::Register(op1.reg)}, MIPS_INTRIN_RDPGPR, + {il.Const(4, op2.reg - REG_ZERO)})); + break; + case MIPS_WRPGPR: + if (op1.reg == REG_ZERO) + { + il.AddInstruction(il.Nop()); + break; + } + // SRSCtl.PSS selects the previous shadow set dynamically; retain the + // encoded rd field as a register number and pass the current rt value. + il.AddInstruction(il.Intrinsic( + {}, MIPS_INTRIN_WRPGPR, + {il.Const(4, op1.reg - REG_ZERO), + ReadILOperand(il, instr, 2, registerSize(op2))})); + break; case MIPS_MULR: //unimplemented system functions case MIPS_BC1ANY2: case MIPS_BC1ANY4: case MIPS_C2: - case MIPS_COP2: case MIPS_COP3: case MIPS_DERET: case MIPS_DRET: - case MIPS_JALX: //Special instruction for switching to MIPS32/microMIPS32/MIPS16e - case MIPS_MTHC2: - case MIPS_PREFX: - case MIPS_WRPGPR: - case MIPS_RDPGPR: case MIPS_SUXC1: // Floating point instructions case MIPS_RSQRT_D: @@ -4135,12 +4694,6 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu case MIPS_RECIP1: case MIPS_RECIP2: case MIPS_RECIP: - case MIPS_NMADD_D: - case MIPS_NMADD_PS: - case MIPS_NMADD_S: - case MIPS_NMSUB_D: - case MIPS_NMSUB_PS: - case MIPS_NMSUB_S: case MIPS_MADDF_D: case MIPS_MADDF_S: // Unimplemented R5900 instructions diff --git a/arch/mips/il.h b/arch/mips/il.h index 248782599b..901e18946e 100644 --- a/arch/mips/il.h +++ b/arch/mips/il.h @@ -90,6 +90,36 @@ enum MipsIntrinsic : uint32_t MIPS_INTRIN_MTC1_UNPREDICTABLE_HIGH_WORD, MIPS_INTRIN_MADD_PS, + // Intrinsic IDs are persisted in IL. Append new entries here so the + // numeric values of existing intrinsics remain stable. + MIPS_INTRIN_CFC1, + MIPS_INTRIN_CFC2, + MIPS_INTRIN_COP2, + MIPS_INTRIN_CTC1, + MIPS_INTRIN_CTC2, + MIPS_INTRIN_MFHC0, + MIPS_INTRIN_MFHC2, + MIPS_INTRIN_MOV_PS, + MIPS_INTRIN_MSUB_PS, + MIPS_INTRIN_MTHC0, + MIPS_INTRIN_MTHC2, + MIPS_INTRIN_NEG_PS, + MIPS_INTRIN_NMADD_PS, + MIPS_INTRIN_NMSUB_PS, + MIPS_INTRIN_RDPGPR, + MIPS_INTRIN_WRPGPR, + MIPS_INTRIN_SUB_PS, + // Signed fixed-point conversions, rounding to nearest with ties to even. + MIPS_INTRIN_ROUND_W_S, + MIPS_INTRIN_ROUND_W_D, + MIPS_INTRIN_ROUND_L_S, + MIPS_INTRIN_ROUND_L_D, + MIPS_INTRIN_MOVF_PS, + MIPS_INTRIN_MOVT_PS, + MIPS_INTRIN_ADD_PS, + MIPS_INTRIN_MUL_PS, + MIPS_INTRIN_ABS_PS, + MIPS_INTRIN_INVALID=0xFFFFFFFF, }; diff --git a/arch/mips/mips/mips.c b/arch/mips/mips/mips.c index 10a9d5f42f..507658279e 100644 --- a/arch/mips/mips/mips.c +++ b/arch/mips/mips/mips.c @@ -621,6 +621,8 @@ static const char* const OperationStrings[] = { "daddi", "daddiu", "daddu", + "dalign", + "dbitswap", "dbshfl", "dclo", "dclz", @@ -1176,6 +1178,8 @@ static const char* const OperationStrings[] = { "mmi3", "lqc2", "sqc2", + "mfhc0", + "mthc0", }; static const char * const RegisterStrings[] = { @@ -1838,8 +1842,18 @@ uint32_t mips_decompose_instruction( case MIPS_DBSHFL: switch (ins.r.sa) { + // Release 6 additions to the MIPS64-only DBSHFL group. + case 0x00: instruction->operation = MIPS_DBITSWAP; break; case 0x02: instruction->operation = MIPS_DSBH; break; case 0x05: instruction->operation = MIPS_DSHD; break; + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + case 0x0f: instruction->operation = MIPS_DALIGN; break; default: return 1; } @@ -1888,14 +1902,34 @@ uint32_t mips_decompose_instruction( return 1; instruction->operation = MIPS_DMFC0; break; - case 2: instruction->operation = MIPS_CFC0; break; + case 2: + // MIPS32/64 Release 5 repurposed the legacy CFC0 encoding as MFHC0. + if (version == MIPS_32 || version == MIPS_64) + { + if (((ins.value >> 3) & 0xff) != 0) + return 1; + instruction->operation = MIPS_MFHC0; + } + else + instruction->operation = MIPS_CFC0; + break; case 4: instruction->operation = MIPS_MTC0; break; case 5: if (((ins.value >> 3) & 0xff) != 0) return 1; instruction->operation = MIPS_DMTC0; break; - case 6: instruction->operation = MIPS_CTC0; break; + case 6: + // MIPS32/64 Release 5 repurposed the legacy CTC0 encoding as MTHC0. + if (version == MIPS_32 || version == MIPS_64) + { + if (((ins.value >> 3) & 0xff) != 0) + return 1; + instruction->operation = MIPS_MTHC0; + } + else + instruction->operation = MIPS_CTC0; + break; case 8: { if (version != MIPS_R5900) @@ -1975,14 +2009,9 @@ uint32_t mips_decompose_instruction( case 3: instruction->operation = MIPS_BC1TL; break; } break; - case 9: - instruction->operation = MIPS_BC1ANY2; - if (ins.r.rs == 9) - instruction->operation = MIPS_BC1EQZ; - else if (ins.r.rs == 13) - instruction->operation = MIPS_BC1NEZ; - break; + case 9: instruction->operation = MIPS_BC1EQZ; break; case 10: instruction->operation = MIPS_BC1ANY4; break; + case 13: instruction->operation = MIPS_BC1NEZ; break; case 16: //S if (version == MIPS_R5900) instruction->operation = mips_r5900_cop1_S_table[ins.decode.func_hi][ins.decode.func_lo]; @@ -2264,7 +2293,7 @@ uint32_t mips_decompose_instruction( INS_1(IMM, ((ins.value >> 6) & 0xfffff)) break; case MIPS_JALX: - INS_1(LABEL, (ins.j.immediate<<2)); + INS_1(LABEL, ((address + 4) & 0xfffffffff0000000) + (((uint32_t)ins.j.immediate)<<2)); break; case MIPS_DI: case MIPS_EI: @@ -2547,6 +2576,7 @@ uint32_t mips_decompose_instruction( case MIPS_PABSW: INS_2(REG, ins.r.rd, REG, ins.r.rt) break; + case MIPS_DBITSWAP: case MIPS_PCPYH: case MIPS_PEXCH: case MIPS_PEXCW: @@ -2730,7 +2760,6 @@ uint32_t mips_decompose_instruction( // instruction->operands[1].immediate = MIPS_SQ; break; case MIPS_PREF: - case MIPS_PREFX: case MIPS_CACHE: instruction->operands[0].operandClass = HINT; instruction->operands[1].operandClass = MEM_IMM; @@ -2738,6 +2767,15 @@ uint32_t mips_decompose_instruction( instruction->operands[1].reg = ins.i.rs; instruction->operands[1].immediate = ins.i.immediate; break; + case MIPS_PREFX: + if (ins.f.fd != 0) + return 1; + instruction->operands[0].operandClass = HINT; + instruction->operands[1].operandClass = MEM_REG; + instruction->operands[0].immediate = ins.f.fs; + instruction->operands[1].reg = ins.f.fr; + instruction->operands[1].immediate = ins.f.ft; + break; case MIPS_SUXC1: case MIPS_SWXC1: case MIPS_SDXC1: @@ -3034,6 +3072,8 @@ uint32_t mips_decompose_instruction( case MIPS_DMTC0: INS_3(REG, ins.r.rt, IMM, ins.r.rd, IMM, (ins.r.function & 7)) break; + case MIPS_MFHC0: + case MIPS_MTHC0: case MIPS_MFC0: case MIPS_MTC0: if (version == MIPS_R5900) @@ -3089,6 +3129,9 @@ uint32_t mips_decompose_instruction( case MIPS_ALIGN: INS_4(REG, ins.r.rd, REG, ins.r.rs, REG, ins.r.rt, IMM, (ins.r.sa & 3)); break; + case MIPS_DALIGN: + INS_4(REG, ins.r.rd, REG, ins.r.rs, REG, ins.r.rt, IMM, (ins.r.sa & 7)); + break; case CNMIPS_BADDU: case CNMIPS_DMUL: diff --git a/arch/mips/mips/mips.h b/arch/mips/mips/mips.h index f2679e1fc2..04fe3dc47f 100644 --- a/arch/mips/mips/mips.h +++ b/arch/mips/mips/mips.h @@ -206,6 +206,8 @@ namespace mips MIPS_DADDI, MIPS_DADDIU, MIPS_DADDU, + MIPS_DALIGN, + MIPS_DBITSWAP, MIPS_DBSHFL, MIPS_DCLO, MIPS_DCLZ, @@ -766,6 +768,8 @@ namespace mips MIPS_LQC2, MIPS_SQC2, + MIPS_MFHC0, + MIPS_MTHC0, MIPS_OPERATION_END }; @@ -1532,4 +1536,3 @@ namespace mips } }//end namespace #endif - diff --git a/arch/mips/test_lifting.py b/arch/mips/test_lifting.py index 26e2c43f02..8ff6edc259 100644 --- a/arch/mips/test_lifting.py +++ b/arch/mips/test_lifting.py @@ -1,6 +1,242 @@ #!/usr/bin/env python test_cases = [ + # align $t0, $t1, $t2, 0 -- bp=0 is a register-to-register move from rt + ('mips32', b'\x7d\x2a\x42\x20', 'LLIL_SET_REG.d($t0,LLIL_REG.d($t2))'), + # align $t0, $t1, $t2, 1 -- concatenate rt:rs and select at byte position 1 + ('mipsel32', b'\x60\x42\x2a\x7d', 'LLIL_SET_REG.d($t0,LLIL_OR.d(LLIL_LSL.d(LLIL_REG.d($t2),LLIL_CONST.b(0x8)),LLIL_LSR.d(LLIL_REG.d($t1),LLIL_CONST.b(0x18))))'), + # align $t0, $t1, $t2, 2 -- select the middle two bytes from each source + ('mips32', b'\x7d\x2a\x42\xa0', 'LLIL_SET_REG.d($t0,LLIL_OR.d(LLIL_LSL.d(LLIL_REG.d($t2),LLIL_CONST.b(0x10)),LLIL_LSR.d(LLIL_REG.d($t1),LLIL_CONST.b(0x10))))'), + # align $t0, $t1, $t2, 3 -- concatenate rt:rs and select at byte position 3 + ('mipsel32', b'\xe0\x42\x2a\x7d', 'LLIL_SET_REG.d($t0,LLIL_OR.d(LLIL_LSL.d(LLIL_REG.d($t2),LLIL_CONST.b(0x18)),LLIL_LSR.d(LLIL_REG.d($t1),LLIL_CONST.b(0x8))))'), + # bitswap $t0, $t1 -- reverse the bits within each byte without moving the bytes + ('mips32', b'\x7c\x09\x40\x20', 'LLIL_SET_REG.d($t0,LLIL_BSWAP.d(LLIL_RBIT.d(LLIL_REG.d($t1))))'), + # bitswap $t1, $t1 -- little-endian encoding and an in-place operation + ('mipsel32', b'\x20\x48\x09\x7c', 'LLIL_SET_REG.d($t1,LLIL_BSWAP.d(LLIL_RBIT.d(LLIL_REG.d($t1))))'), + # dbitswap $t0, $t1 -- MIPS64 Release 6.06 pp. 112-113: reverse bits within all eight bytes + ('mips64', b'\x7c\x09\x40\x24', 'LLIL_SET_REG.q($t0,LLIL_BSWAP.q(LLIL_RBIT.q(LLIL_REG.q($t1))))'), + # dbitswap $t1, $t1 -- little-endian encoding and source/destination aliasing + ('mipsel64', b'\x24\x48\x09\x7c', 'LLIL_SET_REG.q($t1,LLIL_BSWAP.q(LLIL_RBIT.q(LLIL_REG.q($t1))))'), + # dbitswap $t0, $zero -- the zero source is not a register read + ('mips64', b'\x7c\x00\x40\x24', 'LLIL_SET_REG.q($t0,LLIL_BSWAP.q(LLIL_RBIT.q(LLIL_CONST.q(0x0))))'), + # dbitswap $zero, $t1 -- writes to the architectural zero register are discarded + ('mipsel64', b'\x24\x00\x09\x7c', 'LLIL_NOP()'), + # dbitswap $ra, $ra -- the final GPR and an in-place full-width operation + ('mips64', b'\x7c\x1f\xf8\x24', 'LLIL_SET_REG.q($ra,LLIL_BSWAP.q(LLIL_RBIT.q(LLIL_REG.q($ra))))'), + # dalign $t0, $t1, $t2, 0 -- MIPS64 Release 6.06 pp. 59-60: bp=0 copies rt without shifting by 64 + ('mips64', b'\x7d\x2a\x42\x24', 'LLIL_SET_REG.q($t0,LLIL_REG.q($t2))'), + # dalign $t0, $t1, $t2, 1 -- concatenate rt:rs, not rs:rt + ('mipsel64', b'\x64\x42\x2a\x7d', 'LLIL_SET_REG.q($t0,LLIL_OR.q(LLIL_LSL.q(LLIL_REG.q($t2),LLIL_CONST.b(0x8)),LLIL_LSR.q(LLIL_REG.q($t1),LLIL_CONST.b(0x38))))'), + # dalign $t0, $t1, $t2, 2 + ('mips64', b'\x7d\x2a\x42\xa4', 'LLIL_SET_REG.q($t0,LLIL_OR.q(LLIL_LSL.q(LLIL_REG.q($t2),LLIL_CONST.b(0x10)),LLIL_LSR.q(LLIL_REG.q($t1),LLIL_CONST.b(0x30))))'), + # dalign $t0, $t1, $t2, 3 + ('mipsel64', b'\xe4\x42\x2a\x7d', 'LLIL_SET_REG.q($t0,LLIL_OR.q(LLIL_LSL.q(LLIL_REG.q($t2),LLIL_CONST.b(0x18)),LLIL_LSR.q(LLIL_REG.q($t1),LLIL_CONST.b(0x28))))'), + # dalign $t0, $t1, $t2, 4 -- bp bit 2 must not be discarded + ('mips64', b'\x7d\x2a\x43\x24', 'LLIL_SET_REG.q($t0,LLIL_OR.q(LLIL_LSL.q(LLIL_REG.q($t2),LLIL_CONST.b(0x20)),LLIL_LSR.q(LLIL_REG.q($t1),LLIL_CONST.b(0x20))))'), + # dalign $t0, $t1, $t2, 5 + ('mipsel64', b'\x64\x43\x2a\x7d', 'LLIL_SET_REG.q($t0,LLIL_OR.q(LLIL_LSL.q(LLIL_REG.q($t2),LLIL_CONST.b(0x28)),LLIL_LSR.q(LLIL_REG.q($t1),LLIL_CONST.b(0x18))))'), + # dalign $t0, $t1, $t2, 6 + ('mips64', b'\x7d\x2a\x43\xa4', 'LLIL_SET_REG.q($t0,LLIL_OR.q(LLIL_LSL.q(LLIL_REG.q($t2),LLIL_CONST.b(0x30)),LLIL_LSR.q(LLIL_REG.q($t1),LLIL_CONST.b(0x10))))'), + # dalign $t0, $t1, $t2, 7 -- maximum byte position + ('mipsel64', b'\xe4\x43\x2a\x7d', 'LLIL_SET_REG.q($t0,LLIL_OR.q(LLIL_LSL.q(LLIL_REG.q($t2),LLIL_CONST.b(0x38)),LLIL_LSR.q(LLIL_REG.q($t1),LLIL_CONST.b(0x8))))'), + # dalign $t1, $t1, $t2, 7 -- destination aliases rs + ('mipsel64', b'\xe4\x4b\x2a\x7d', 'LLIL_SET_REG.q($t1,LLIL_OR.q(LLIL_LSL.q(LLIL_REG.q($t2),LLIL_CONST.b(0x38)),LLIL_LSR.q(LLIL_REG.q($t1),LLIL_CONST.b(0x8))))'), + # dalign $t2, $t1, $t2, 1 -- destination aliases rt + ('mips64', b'\x7d\x2a\x52\x64', 'LLIL_SET_REG.q($t2,LLIL_OR.q(LLIL_LSL.q(LLIL_REG.q($t2),LLIL_CONST.b(0x8)),LLIL_LSR.q(LLIL_REG.q($t1),LLIL_CONST.b(0x38))))'), + # dalign $t0, $zero, $t2, 7 -- zero rs supplies the low half of the concatenation + ('mips64', b'\x7c\x0a\x43\xe4', 'LLIL_SET_REG.q($t0,LLIL_OR.q(LLIL_LSL.q(LLIL_REG.q($t2),LLIL_CONST.b(0x38)),LLIL_LSR.q(LLIL_CONST.q(0x0),LLIL_CONST.b(0x8))))'), + # dalign $t0, $t1, $zero, 1 -- zero rt supplies the high half + ('mipsel64', b'\x64\x42\x20\x7d', 'LLIL_SET_REG.q($t0,LLIL_OR.q(LLIL_LSL.q(LLIL_CONST.q(0x0),LLIL_CONST.b(0x8)),LLIL_LSR.q(LLIL_REG.q($t1),LLIL_CONST.b(0x38))))'), + # dalign $t0, $t1, $zero, 0 -- bp=0 still selects rt when rt is zero + ('mips64', b'\x7d\x20\x42\x24', 'LLIL_SET_REG.q($t0,LLIL_CONST.q(0x0))'), + # dalign $t0, $zero, $zero, 4 -- both sources may be zero + ('mipsel64', b'\x24\x43\x00\x7c', 'LLIL_SET_REG.q($t0,LLIL_OR.q(LLIL_LSL.q(LLIL_CONST.q(0x0),LLIL_CONST.b(0x20)),LLIL_LSR.q(LLIL_CONST.q(0x0),LLIL_CONST.b(0x20))))'), + # dalign $zero, $t1, $t2, 7 -- the result is discarded + ('mips64', b'\x7d\x2a\x03\xe4', 'LLIL_NOP()'), + # align $t0, $t1, $t2, 0 -- the word variant still sign-extends on MIPS64 + ('mips64', b'\x7d\x2a\x42\x20', 'LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_REG.d($t2)))'), + # align $t0, $t1, $t2, 3 -- retain 32-bit shifts and result sign extension + ('mipsel64', b'\xe0\x42\x2a\x7d', 'LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_OR.d(LLIL_LSL.d(LLIL_REG.d($t2),LLIL_CONST.b(0x18)),LLIL_LSR.d(LLIL_REG.d($t1),LLIL_CONST.b(0x8)))))'), + # bitswap $t0, $t1 -- unlike DBITSWAP, this operates on and sign-extends a word + ('mips64', b'\x7c\x09\x40\x20', 'LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_BSWAP.d(LLIL_RBIT.d(LLIL_REG.d($t1)))))'), + # bnez $t0, 0xc; dbitswap $t0, $t1 -- preserve the pre-delay-slot full-width branch input + ('mips64', b'\x15\x00\x00\x02\x7c\x09\x40\x24', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($t0)); LLIL_SET_REG.q($t0,LLIL_BSWAP.q(LLIL_RBIT.q(LLIL_REG.q($t1)))); LLIL_IF(LLIL_CMP_NE.q(LLIL_REG.q(temp1),LLIL_CONST.q(0x0)),6,3)'), + # bnez $t1, 0xc; dalign $t1, $t1, $t2, 7 -- an aliased source still uses the original value + ('mipsel64', b'\x02\x00\x20\x15\xe4\x4b\x2a\x7d', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($t1)); LLIL_SET_REG.q($t1,LLIL_OR.q(LLIL_LSL.q(LLIL_REG.q($t2),LLIL_CONST.b(0x38)),LLIL_LSR.q(LLIL_REG.q($t1),LLIL_CONST.b(0x8)))); LLIL_IF(LLIL_CMP_NE.q(LLIL_REG.q(temp1),LLIL_CONST.q(0x0)),6,3)'), + # bc1eqz $f3, 0xc; mtc1 $zero, $f3 -- test pre-delay-slot bit 0 for equality + ('mips32', b'\x45\x23\x00\x02\x44\x80\x18\x00', 'LLIL_SET_REG.d(temp1,LLIL_REG.d($f3)); LLIL_SET_REG.d($f3,LLIL_CONST.d(0x0)); LLIL_IF(LLIL_NOT(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0))),6,3)'), + # bc1nez $f4, 0xc; mtc1 $zero, $f4 -- test pre-delay-slot bit 0 for inequality + ('mipsel32', b'\x02\x00\xa4\x45\x00\x20\x80\x44', 'LLIL_SET_REG.d(temp1,LLIL_REG.d($f4)); LLIL_SET_REG.d($f4,LLIL_CONST.d(0x0)); LLIL_IF(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0)),6,3)'), + # bc2eqz $3, 0xc; nop -- branch when COP2 condition selector 3 is zero + ('mips32', b'\x49\x23\x00\x02\x00\x00\x00\x00', 'LLIL_NOP(); LLIL_NOP(); LLIL_IF(LLIL_CMP_E.d(LLIL_REG.d($3),LLIL_CONST.d(0x0)),6,3)'), + # bc2nez $4, 0xc; nop -- branch when COP2 condition selector 4 is nonzero + ('mipsel32', b'\x02\x00\xa4\x49\x00\x00\x00\x00', 'LLIL_NOP(); LLIL_NOP(); LLIL_IF(LLIL_CMP_NE.d(LLIL_REG.d($4),LLIL_CONST.d(0x0)),6,3)'), + # beqz $t0, 0xc; cfc1 $t0, $fcr31 -- an intrinsic output clobbers the branch input + ('mips32', b'\x11\x00\x00\x02\x44\x48\xf8\x00', 'LLIL_SET_REG.d(temp1,LLIL_REG.d($t0)); LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr31)]); LLIL_SET_REG.d($t0,LLIL_REG.d(temp0)); LLIL_IF(LLIL_CMP_E.d(LLIL_REG.d(temp1),LLIL_CONST.d(0x0)),7,4)'), + # beqz $t0, 0xc; cfc1 $t0, $fcr31 -- little-endian encoding + ('mipsel32', b'\x02\x00\x00\x11\x00\xf8\x48\x44', 'LLIL_SET_REG.d(temp1,LLIL_REG.d($t0)); LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr31)]); LLIL_SET_REG.d($t0,LLIL_REG.d(temp0)); LLIL_IF(LLIL_CMP_E.d(LLIL_REG.d(temp1),LLIL_CONST.d(0x0)),7,4)'), + # beqz $t0, 0xc; cfc1 $t0, $fcr31 -- save all 64 old bits despite the word-sized intrinsic result + ('mips64', b'\x11\x00\x00\x02\x44\x48\xf8\x00', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($t0)); LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr31)]); LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_REG.d(temp0))); LLIL_IF(LLIL_CMP_E.q(LLIL_REG.q(temp1),LLIL_CONST.q(0x0)),7,4)'), + # bnez $t0, 0xc; cfc2 $t0, 0x1234 -- nonzero predicate and a full-width snapshot + ('mipsel64', b'\x02\x00\x00\x15\x34\x12\x48\x48', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($t0)); LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor2,[LLIL_CONST.w(0x1234)]); LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_REG.d(temp0))); LLIL_IF(LLIL_CMP_NE.q(LLIL_REG.q(temp1),LLIL_CONST.q(0x0)),7,4)'), + # beqz $t0, 0xc; mfhc0 $t0, $12, 3 -- high-word read also defines a GPR through an intrinsic + ('mips64', b'\x11\x00\x00\x02\x40\x48\x60\x03', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($t0)); LLIL_INTRINSIC([temp0],moveHighWordFromCoprocessor0,[LLIL_CONST.d(0xC),LLIL_CONST.d(0x3)]); LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_REG.d(temp0))); LLIL_IF(LLIL_CMP_E.q(LLIL_REG.q(temp1),LLIL_CONST.q(0x0)),7,4)'), + # beqz $t0, 0xc; mfhc2 $t0, 0x1234 -- preserve the original value before reading COP2 + ('mipsel32', b'\x02\x00\x00\x11\x34\x12\x68\x48', 'LLIL_SET_REG.d(temp1,LLIL_REG.d($t0)); LLIL_INTRINSIC([temp0],moveHighWordFromCoprocessor2,[LLIL_CONST.w(0x1234)]); LLIL_SET_REG.d($t0,LLIL_REG.d(temp0)); LLIL_IF(LLIL_CMP_E.d(LLIL_REG.d(temp1),LLIL_CONST.d(0x0)),7,4)'), + # beqz $t0, 0xc; rdpgpr $t0, $s1 -- the branch tests the current set, before the shadow-set read + ('mips32', b'\x11\x00\x00\x02\x41\x51\x40\x00', 'LLIL_SET_REG.d(temp1,LLIL_REG.d($t0)); LLIL_INTRINSIC([$t0],readGPRFromPreviousShadowSet,[LLIL_CONST.d(0x11)]); LLIL_IF(LLIL_CMP_E.d(LLIL_REG.d(temp1),LLIL_CONST.d(0x0)),6,3)'), + # beqz $t0, 0xc; rdpgpr $t0, $s1 -- little-endian 64-bit shadow-set transfer + ('mipsel64', b'\x02\x00\x00\x11\x00\x40\x51\x41', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($t0)); LLIL_INTRINSIC([$t0],readGPRFromPreviousShadowSet,[LLIL_CONST.d(0x11)]); LLIL_IF(LLIL_CMP_E.q(LLIL_REG.q(temp1),LLIL_CONST.q(0x0)),6,3)'), + # beq $t0, $t1, 0xc; cfc1 $t1, $fcr31 -- only the clobbered second branch operand is replaced + ('mips32', b'\x11\x09\x00\x02\x44\x49\xf8\x00', 'LLIL_SET_REG.d(temp1,LLIL_REG.d($t1)); LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr31)]); LLIL_SET_REG.d($t1,LLIL_REG.d(temp0)); LLIL_IF(LLIL_CMP_E.d(LLIL_REG.d($t0),LLIL_REG.d(temp1)),7,4)'), + # beqz $t0, 0xc; cfc1 $t1, $fcr31 -- an unrelated intrinsic output needs no snapshot + ('mips32', b'\x11\x00\x00\x02\x44\x49\xf8\x00', 'LLIL_NOP(); LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr31)]); LLIL_SET_REG.d($t1,LLIL_REG.d(temp0)); LLIL_IF(LLIL_CMP_E.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),7,4)'), + # beqz $t0, 0xc; ctc1 $t0, $fcr31 -- reading a branch input is not a clobber + ('mips32', b'\x11\x00\x00\x02\x44\xc8\xf8\x00', 'LLIL_NOP(); LLIL_INTRINSIC([],moveControlWordToCoprocessor1,[LLIL_REG.d($fcr31),LLIL_REG.d($t0)]); LLIL_IF(LLIL_CMP_E.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),6,3)'), + # beqzl $t0, 0xc; cfc1 $t0, $fcr31 -- likely branches evaluate the predicate before the delay slot + ('mips32', b'\x51\x00\x00\x02\x44\x48\xf8\x00', 'LLIL_IF(LLIL_CMP_E.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),1,4); LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr31)]); LLIL_SET_REG.d($t0,LLIL_REG.d(temp0)); LLIL_GOTO(5)'), + # jalr $t0; cfc1 $t0, $fcr31 -- an indirect call also uses its pre-delay-slot target + ('mips32', b'\x01\x00\xf8\x09\x44\x48\xf8\x00', 'LLIL_SET_REG.d(temp1,LLIL_REG.d($t0)); LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr31)]); LLIL_SET_REG.d($t0,LLIL_REG.d(temp0)); LLIL_CALL(LLIL_REG.d(temp1))'), + # bc1nez $f2, 0xc; sub.ps $f2, $f4, $f6 -- preserve a directly written paired-single destination + ('mips64', b'\x45\xa2\x00\x02\x46\xc6\x20\x81', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($f2)); LLIL_INTRINSIC([$f2],_sub_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6)]); LLIL_IF(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0)),6,3)'), + # bc1eqz $f2, 0xc; neg.ps $f2, $f4 -- false-polarity FP predicate and little-endian encoding + ('mipsel64', b'\x02\x00\x22\x45\x87\x20\xc0\x46', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($f2)); LLIL_INTRINSIC([$f2],_neg_ps,[LLIL_REG.q($f4)]); LLIL_IF(LLIL_NOT(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0))),6,3)'), + # bc1nez $f2, 0xc; mov.ps $f2, $f2 -- snapshot rewriting must not replace the delay-slot source + ('mips64', b'\x45\xa2\x00\x02\x46\xc0\x10\x86', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($f2)); LLIL_INTRINSIC([$f2],_mov_ps,[LLIL_REG.q($f2)]); LLIL_IF(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0)),6,3)'), + # bc1nez $f2, 0xc; msub.ps $f2, $f4, $f6, $f8 -- direct three-input intrinsic + ('mipsel64', b'\x02\x00\xa2\x45\xae\x30\x88\x4c', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($f2)); LLIL_INTRINSIC([$f2],_msub_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6),LLIL_REG.q($f8)]); LLIL_IF(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0)),6,3)'), + # bc1nez $f2, 0xc; nmadd.ps $f2, $f4, $f6, $f8 + ('mips64', b'\x45\xa2\x00\x02\x4c\x88\x30\xb6', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($f2)); LLIL_INTRINSIC([$f2],_nmadd_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6),LLIL_REG.q($f8)]); LLIL_IF(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0)),6,3)'), + # bc1nez $f2, 0xc; nmsub.ps $f2, $f4, $f6, $f8 + ('mipsel64', b'\x02\x00\xa2\x45\xbe\x30\x88\x4c', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($f2)); LLIL_INTRINSIC([$f2],_nmsub_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6),LLIL_REG.q($f8)]); LLIL_IF(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0)),6,3)'), + # cfc1 $t0, $fcr31 -- read an FPU control word through the CFC1 intrinsic + ('mips32', b'\x44\x48\xf8\x00', 'LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr31)]); LLIL_SET_REG.d($t0,LLIL_REG.d(temp0))'), + # cfc1 $t1, $fcr0 -- little-endian encoding and a different control register + ('mipsel32', b'\x00\x00\x49\x44', 'LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr0)]); LLIL_SET_REG.d($t1,LLIL_REG.d(temp0))'), + # cfc1 $t0, $fcr31 -- explicitly sign-extend the word result to the full MIPS64 GPR + ('mips64', b'\x44\x48\xf8\x00', 'LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr31)]); LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_REG.d(temp0)))'), + # cfc1 $t1, $fcr0 -- little-endian MIPS64 + ('mipsel64', b'\x00\x00\x49\x44', 'LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr0)]); LLIL_SET_REG.q($t1,LLIL_SX.q(LLIL_REG.d(temp0)))'), + # cfc1 $zero, $fcr31 -- retain the opaque read but discard its result + ('mips32', b'\x44\x40\xf8\x00', 'LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr31)]); LLIL_NOP()'), + ('mipsel64', b'\x00\xf8\x40\x44', 'LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr31)]); LLIL_NOP()'), + # cfc2 $t0, 0x1234 -- preserve the full implementation-defined selector + ('mips32', b'\x48\x48\x12\x34', 'LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor2,[LLIL_CONST.w(0x1234)]); LLIL_SET_REG.d($t0,LLIL_REG.d(temp0))'), + # cfc2 $t1, 0xabcd -- little-endian encoding of another opaque selector + ('mipsel32', b'\xcd\xab\x49\x48', 'LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor2,[LLIL_CONST.w(0xABCD)]); LLIL_SET_REG.d($t1,LLIL_REG.d(temp0))'), + # cfc2 $t0, 0x1234 -- preserve the selector while sign-extending the word result + ('mips64', b'\x48\x48\x12\x34', 'LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor2,[LLIL_CONST.w(0x1234)]); LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_REG.d(temp0)))'), + # cfc2 $t1, 0xabcd -- little-endian MIPS64 + ('mipsel64', b'\xcd\xab\x49\x48', 'LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor2,[LLIL_CONST.w(0xABCD)]); LLIL_SET_REG.q($t1,LLIL_SX.q(LLIL_REG.d(temp0)))'), + # cfc2 $zero, 0xabcd -- no architectural zero-register definition + ('mipsel32', b'\xcd\xab\x40\x48', 'LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor2,[LLIL_CONST.w(0xABCD)]); LLIL_NOP()'), + ('mips64', b'\x48\x40\xab\xcd', 'LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor2,[LLIL_CONST.w(0xABCD)]); LLIL_NOP()'), + # ctc1 $t0, $fcr31 -- write the low GPR word through the CTC1 intrinsic + ('mips32', b'\x44\xc8\xf8\x00', 'LLIL_INTRINSIC([],moveControlWordToCoprocessor1,[LLIL_REG.d($fcr31),LLIL_REG.d($t0)])'), + # ctc1 $t1, $fcr0 -- little-endian encoding and a different control register + ('mipsel32', b'\x00\x00\xc9\x44', 'LLIL_INTRINSIC([],moveControlWordToCoprocessor1,[LLIL_REG.d($fcr0),LLIL_REG.d($t1)])'), + # ctc1 $t0, $fcr31 -- R5900 writes the modeled control register from the low GPR word + ('r5900l', b'\x00\xf8\xc8\x44', 'LLIL_SET_REG.d($fcr31,LLIL_REG.d($t0))'), + # cfc1 $v0, $fcr31 -- R5900 reads that state and sign-extends into the low 64 GPR bits + ('r5900l', b'\x00\xf8\x42\x44', 'LLIL_SET_REG.q($v0,LLIL_SX.q(LLIL_REG.d($fcr31)))'), + # cfc1 $v0, $fcr0 -- the implementation/revision register is also a modeled control-register read + ('r5900l', b'\x00\x00\x42\x44', 'LLIL_SET_REG.q($v0,LLIL_SX.q(LLIL_REG.d($fcr0)))'), + # ctc1 $zero, $fcr31 -- clear the modeled control register without reading $zero + ('r5900l', b'\x00\xf8\xc0\x44', 'LLIL_SET_REG.d($fcr31,LLIL_CONST.d(0x0))'), + # cfc1 $zero, $fcr31 -- discard a transfer to the architectural zero register + ('r5900l', b'\x00\xf8\x40\x44', 'LLIL_NOP()'), + # ctc1 $t0, $fcr31; cfc1 $v0, $fcr31 -- reproduce the R5900 write/read regression + ('r5900l', b'\x00\xf8\xc8\x44\x00\xf8\x42\x44', 'LLIL_SET_REG.d($fcr31,LLIL_REG.d($t0)); LLIL_SET_REG.q($v0,LLIL_SX.q(LLIL_REG.d($fcr31)))'), + # ctc1 $t0, $fcr31; cfc1 $t0, $fcr31 -- source and destination GPR may alias + ('r5900l', b'\x00\xf8\xc8\x44\x00\xf8\x48\x44', 'LLIL_SET_REG.d($fcr31,LLIL_REG.d($t0)); LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_REG.d($fcr31)))'), + # beqz $t0, 0xc; cfc1 $t0, $fcr31 -- the restored direct read preserves the old branch input + ('r5900l', b'\x02\x00\x00\x11\x00\xf8\x48\x44', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($t0)); LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_REG.d($fcr31))); LLIL_IF(LLIL_CMP_E.q(LLIL_REG.q(temp1),LLIL_CONST.q(0x0)),6,3)'), + # beqz $t0, 0xc; ctc1 $t0, $fcr31 -- writing an FCR must not clobber the source GPR + ('r5900l', b'\x02\x00\x00\x11\x00\xf8\xc8\x44', 'LLIL_NOP(); LLIL_SET_REG.d($fcr31,LLIL_REG.d($t0)); LLIL_IF(LLIL_CMP_E.q(LLIL_REG.q($t0),LLIL_CONST.q(0x0)),6,3)'), + # ctc2 $t0, 0x1234 -- preserve the full implementation-defined selector + ('mips32', b'\x48\xc8\x12\x34', 'LLIL_INTRINSIC([],moveControlWordToCoprocessor2,[LLIL_CONST.w(0x1234),LLIL_REG.d($t0)])'), + # ctc2 $t1, 0xabcd -- little-endian encoding and low-word source transfer + ('mipsel32', b'\xcd\xab\xc9\x48', 'LLIL_INTRINSIC([],moveControlWordToCoprocessor2,[LLIL_CONST.w(0xABCD),LLIL_REG.d($t1)])'), + # cop2 0x1234567 -- pass all 25 implementation-defined cofun bits to the intrinsic + ('mips32', b'\x4b\x23\x45\x67', 'LLIL_INTRINSIC([],coprocessor2Operation,[LLIL_CONST.d(0x1234567)])'), + # cop2 0x1abcdef -- little-endian encoding with the high cofun bit set + ('mipsel32', b'\xef\xcd\xab\x4b', 'LLIL_INTRINSIC([],coprocessor2Operation,[LLIL_CONST.d(0x1ABCDEF)])'), + # jalx 0; nop -- delayed call into the alternate ISA mode, targeting within the view + ('mips32', b'\x74\x00\x00\x00\x00\x00\x00\x00', 'LLIL_NOP(); LLIL_NOP(); LLIL_CALL(LLIL_CONST.d(0x0))'), + # jalx 0; nop -- little-endian encoding + ('mipsel32', b'\x00\x00\x00\x74\x00\x00\x00\x00', 'LLIL_NOP(); LLIL_NOP(); LLIL_CALL(LLIL_CONST.d(0x0))'), + # mfhc0 $t0, $12, 3 -- read the high word of a selected COP0 register + ('mips32', b'\x40\x48\x60\x03', 'LLIL_INTRINSIC([temp0],moveHighWordFromCoprocessor0,[LLIL_CONST.d(0xC),LLIL_CONST.d(0x3)]); LLIL_SET_REG.d($t0,LLIL_REG.d(temp0))'), + # mfhc0 $t1, $5, 1 -- little-endian encoding with a different selector + ('mipsel32', b'\x01\x28\x49\x40', 'LLIL_INTRINSIC([temp0],moveHighWordFromCoprocessor0,[LLIL_CONST.d(0x5),LLIL_CONST.d(0x1)]); LLIL_SET_REG.d($t1,LLIL_REG.d(temp0))'), + # mfhc0 $t0, $12, 3 -- sign-extend the transferred high word to a full MIPS64 GPR + ('mips64', b'\x40\x48\x60\x03', 'LLIL_INTRINSIC([temp0],moveHighWordFromCoprocessor0,[LLIL_CONST.d(0xC),LLIL_CONST.d(0x3)]); LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_REG.d(temp0)))'), + # mfhc0 $t1, $5, 1 -- little-endian MIPS64 + ('mipsel64', b'\x01\x28\x49\x40', 'LLIL_INTRINSIC([temp0],moveHighWordFromCoprocessor0,[LLIL_CONST.d(0x5),LLIL_CONST.d(0x1)]); LLIL_SET_REG.q($t1,LLIL_SX.q(LLIL_REG.d(temp0)))'), + # mfhc0 $zero, $12, 3 -- retain both selectors and discard the result + ('mips32', b'\x40\x40\x60\x03', 'LLIL_INTRINSIC([temp0],moveHighWordFromCoprocessor0,[LLIL_CONST.d(0xC),LLIL_CONST.d(0x3)]); LLIL_NOP()'), + ('mipsel64', b'\x03\x60\x40\x40', 'LLIL_INTRINSIC([temp0],moveHighWordFromCoprocessor0,[LLIL_CONST.d(0xC),LLIL_CONST.d(0x3)]); LLIL_NOP()'), + # mfhc2 $t0, 0x1234 -- preserve the implementation-defined COP2 selector + ('mips32', b'\x48\x68\x12\x34', 'LLIL_INTRINSIC([temp0],moveHighWordFromCoprocessor2,[LLIL_CONST.w(0x1234)]); LLIL_SET_REG.d($t0,LLIL_REG.d(temp0))'), + # mfhc2 $t1, 0xabcd -- little-endian encoding of another opaque selector + ('mipsel32', b'\xcd\xab\x69\x48', 'LLIL_INTRINSIC([temp0],moveHighWordFromCoprocessor2,[LLIL_CONST.w(0xABCD)]); LLIL_SET_REG.d($t1,LLIL_REG.d(temp0))'), + # mfhc2 $t0, 0x1234 -- sign-extend the transferred word rather than zero-extending it + ('mips64', b'\x48\x68\x12\x34', 'LLIL_INTRINSIC([temp0],moveHighWordFromCoprocessor2,[LLIL_CONST.w(0x1234)]); LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_REG.d(temp0)))'), + # mfhc2 $t1, 0xabcd -- little-endian MIPS64 + ('mipsel64', b'\xcd\xab\x69\x48', 'LLIL_INTRINSIC([temp0],moveHighWordFromCoprocessor2,[LLIL_CONST.w(0xABCD)]); LLIL_SET_REG.q($t1,LLIL_SX.q(LLIL_REG.d(temp0)))'), + # mfhc2 $zero, 0xabcd -- preserve the read without writing $zero + ('mipsel32', b'\xcd\xab\x60\x48', 'LLIL_INTRINSIC([temp0],moveHighWordFromCoprocessor2,[LLIL_CONST.w(0xABCD)]); LLIL_NOP()'), + ('mips64', b'\x48\x60\xab\xcd', 'LLIL_INTRINSIC([temp0],moveHighWordFromCoprocessor2,[LLIL_CONST.w(0xABCD)]); LLIL_NOP()'), + # mthc0 $t0, $12, 3 -- write a GPR word to the selected COP0 high half + ('mips32', b'\x40\xc8\x60\x03', 'LLIL_INTRINSIC([],moveHighWordToCoprocessor0,[LLIL_CONST.d(0xC),LLIL_CONST.d(0x3),LLIL_REG.d($t0)])'), + # mthc0 $t1, $5, 1 -- little-endian encoding with a different selector + ('mipsel32', b'\x01\x28\xc9\x40', 'LLIL_INTRINSIC([],moveHighWordToCoprocessor0,[LLIL_CONST.d(0x5),LLIL_CONST.d(0x1),LLIL_REG.d($t1)])'), + # mthc2 $t0, 0x1234 -- preserve the implementation-defined COP2 selector + ('mips32', b'\x48\xe8\x12\x34', 'LLIL_INTRINSIC([],moveHighWordToCoprocessor2,[LLIL_CONST.w(0x1234),LLIL_REG.d($t0)])'), + # mthc2 $t1, 0xabcd -- little-endian encoding of another opaque selector + ('mipsel32', b'\xcd\xab\xe9\x48', 'LLIL_INTRINSIC([],moveHighWordToCoprocessor2,[LLIL_CONST.w(0xABCD),LLIL_REG.d($t1)])'), + # mfhc1 $t0, $f20 -- little-endian FR=0 reads the paired odd FPR + ('mipsel32', b'\x00\xa0\x68\x44', 'LLIL_SET_REG.d($t0,LLIL_REG.d($f21))'), + # mfhc1 $t0, $f20 -- little-endian FR=1 reads and sign-extends bits 63:32 + ('mipsel64', b'\x00\xa0\x68\x44', 'LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_LOW_PART.d(LLIL_LSR.q(LLIL_REG.q($f20),LLIL_CONST.b(0x20)))))'), + # prefx 6, $t1($t0) -- combine the base and index registers for the prefetch address + ('mips32', b'\x4d\x09\x30\x0f', 'LLIL_INTRINSIC([],_prefetch,[LLIL_CONST.b(0x6),LLIL_ADD.d(LLIL_REG.d($t0),LLIL_REG.d($t1))])'), + # prefx 25, $s1($s0) -- little-endian MIPS64 uses a 64-bit effective address + ('mipsel64', b'\x0f\xc8\x11\x4e', 'LLIL_INTRINSIC([],_prefetch,[LLIL_CONST.b(0x19),LLIL_ADD.q(LLIL_REG.q($s0),LLIL_REG.q($s1))])'), + # rdpgpr $t0, $s1 -- read shadow GPR 17 into the current 32-bit destination + ('mips32', b'\x41\x51\x40\x00', 'LLIL_INTRINSIC([$t0],readGPRFromPreviousShadowSet,[LLIL_CONST.d(0x11)])'), + # rdpgpr $a1, $ra -- little-endian MIPS64 returns the full GPR width + ('mipsel64', b'\x00\x28\x5f\x41', 'LLIL_INTRINSIC([$a1],readGPRFromPreviousShadowSet,[LLIL_CONST.d(0x1F)])'), + # wrpgpr $t0, $s1 -- write the current 32-bit GPR value to shadow GPR 8 + ('mips32', b'\x41\xd1\x40\x00', 'LLIL_INTRINSIC([],writeGPRToPreviousShadowSet,[LLIL_CONST.d(0x8),LLIL_REG.d($s1)])'), + # wrpgpr $a1, $ra -- little-endian MIPS64 writes the full current GPR value + ('mipsel64', b'\x00\x28\xdf\x41', 'LLIL_INTRINSIC([],writeGPRToPreviousShadowSet,[LLIL_CONST.d(0x5),LLIL_REG.q($ra)])'), + # rdpgpr $t0, $zero -- every shadow set has a hardwired zero register + ('mips32', b'\x41\x40\x40\x00', 'LLIL_SET_REG.d($t0,LLIL_CONST.d(0x0))'), + ('mipsel32', b'\x00\x40\x40\x41', 'LLIL_SET_REG.d($t0,LLIL_CONST.d(0x0))'), + # The complete MIPS64 GPR is cleared, including its high word. + ('mips64', b'\x41\x40\x40\x00', 'LLIL_SET_REG.q($t0,LLIL_CONST.q(0x0))'), + ('mipsel64', b'\x00\x40\x40\x41', 'LLIL_SET_REG.q($t0,LLIL_CONST.q(0x0))'), + # rdpgpr $zero, $t0 -- discard the read without defining the current $zero register + ('mips32', b'\x41\x48\x00\x00', 'LLIL_NOP()'), + # rdpgpr $zero, $ra -- the final shadow register is also ignored for a zero destination + ('mipsel64', b'\x00\x00\x5f\x41', 'LLIL_NOP()'), + # rdpgpr $zero, $zero -- neither a register definition nor an opaque read is needed + ('mips32', b'\x41\x40\x00\x00', 'LLIL_NOP()'), + # wrpgpr $zero, $t0 -- writes to zero in the previous shadow set are discarded + ('mips32', b'\x41\xc8\x00\x00', 'LLIL_NOP()'), + ('mipsel32', b'\x00\x00\xc8\x41', 'LLIL_NOP()'), + ('mips64', b'\x41\xc8\x00\x00', 'LLIL_NOP()'), + ('mipsel64', b'\x00\x00\xc8\x41', 'LLIL_NOP()'), + # wrpgpr $zero, $zero -- also discarded when both operands are zero + ('mipsel64', b'\x00\x00\xc0\x41', 'LLIL_NOP()'), + # wrpgpr $t0, $zero -- a zero source still writes a nonzero shadow-register destination + ('mips32', b'\x41\xc0\x40\x00', 'LLIL_INTRINSIC([],writeGPRToPreviousShadowSet,[LLIL_CONST.d(0x8),LLIL_CONST.d(0x0)])'), + # wrpgpr $ra, $zero -- preserve the full-width zero write and highest register selector + ('mipsel64', b'\x00\xf8\xc0\x41', 'LLIL_INTRINSIC([],writeGPRToPreviousShadowSet,[LLIL_CONST.d(0x1F),LLIL_CONST.q(0x0)])'), + # bnez $t0, 0xc; rdpgpr $t0, $zero -- clearing the GPR must not change the branch predicate + ('mips32', b'\x15\x00\x00\x02\x41\x40\x40\x00', 'LLIL_SET_REG.d(temp1,LLIL_REG.d($t0)); LLIL_SET_REG.d($t0,LLIL_CONST.d(0x0)); LLIL_IF(LLIL_CMP_NE.d(LLIL_REG.d(temp1),LLIL_CONST.d(0x0)),6,3)'), + ('mipsel64', b'\x02\x00\x00\x15\x00\x40\x40\x41', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($t0)); LLIL_SET_REG.q($t0,LLIL_CONST.q(0x0)); LLIL_IF(LLIL_CMP_NE.q(LLIL_REG.q(temp1),LLIL_CONST.q(0x0)),6,3)'), # lwc1 $f0, 0x328($at) ('mipsel32', b'\x28\x03\x20\xc4', 'LLIL_SET_REG.d($f0,LLIL_LOAD.d(LLIL_ADD.d(LLIL_REG.d($at),LLIL_CONST.d(0x328))))'), # lwc1 $f20, 0x10($t4) @@ -45,10 +281,6 @@ ('mipsel32', b'\x00\xa0\x88\x44', 'LLIL_SET_REG.d($f20,LLIL_REG.d($t0))'), # mtc1 $zero, $f31 -- the architectural zero register transfers a zero word ('mipsel32', b'\x00\xf8\x80\x44', 'LLIL_SET_REG.d($f31,LLIL_CONST.d(0x0))'), - # mtc1 $t0, $f20 -- this MIPS III architecture models its FPRs as 32-bit registers - ('mips3', b'\x44\x88\xa0\x00', 'LLIL_SET_REG.d($f20,LLIL_REG.d($t0))'), - # mtc1 $t0, $f20 -- little-endian MIPS III uses the same modeled 32-bit FPR write - ('mipsel3', b'\x00\xa0\x88\x44', 'LLIL_SET_REG.d($f20,LLIL_REG.d($t0))'), # mtc1 $t0, $f20 -- R5900 EE Core Instruction Set Manual MTC1 (p. 371) ('r5900l', b'\x00\xa0\x88\x44', 'LLIL_SET_REG.d($f20,LLIL_REG.d($t0))'), # mtc1 $t0, $f20 -- MIPS64 Release 6.06 MTC1 (p. 385): high word is UNPREDICTABLE @@ -129,6 +361,122 @@ ('mipsel64', b'\xa6\x30\x88\x4c', 'LLIL_INTRINSIC([$f2],_madd_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6),LLIL_REG.q($f8)])'), # madd.ps $f2, $f4, $f6, $f8 -- paired-single is unpredictable with modeled 32-bit FPRs ('mipsel32', b'\xa6\x30\x88\x4c', 'LLIL_UNKNOWN()'), + # add.ps $f2, $f4, $f6 -- MIPS32 Release 6.06 p. 34: two independent single-precision sums + ('mips64', b'\x46\xc6\x20\x80', 'LLIL_INTRINSIC([temp0],_add_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0))'), + # add.ps $f2, $f4, $f6 -- little-endian encoding has the same packed operands + ('mipsel64', b'\x80\x20\xc6\x46', 'LLIL_INTRINSIC([temp0],_add_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0))'), + # add.ps $f31, $f3, $f31 -- odd FPRs are valid in FR=1, including an aliased ft + ('mipsel64', b'\xc0\x1f\xdf\x46', 'LLIL_INTRINSIC([temp0],_add_ps,[LLIL_REG.q($f3),LLIL_REG.q($f31)]); LLIL_SET_REG.q($f31,LLIL_REG.q(temp0))'), + # add.ps $f4, $f4, $f6 -- read both source lanes before overwriting fs + ('mips64', b'\x46\xc6\x21\x00', 'LLIL_INTRINSIC([temp0],_add_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6)]); LLIL_SET_REG.q($f4,LLIL_REG.q(temp0))'), + # add.ps $f2, $f4, $f6 -- even register numbers do not make PS valid in FR=0 + ('mips32', b'\x46\xc6\x20\x80', 'LLIL_UNKNOWN()'), + # add.ps $f2, $f4, $f6 -- little-endian FR=0 is also unpredictable + ('mipsel32', b'\x80\x20\xc6\x46', 'LLIL_UNKNOWN()'), + # bc1nez $f2, 0xc; add.ps $f2, $f4, $f6 -- retain the pre-delay-slot branch value + ('mips64', b'\x45\xa2\x00\x02\x46\xc6\x20\x80', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($f2)); LLIL_INTRINSIC([temp0],_add_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0)); LLIL_IF(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0)),7,4)'), + # mul.ps $f2, $f4, $f6 -- MIPS32 Release 6.06 p. 302: two independent single-precision products + ('mips64', b'\x46\xc6\x20\x82', 'LLIL_INTRINSIC([temp0],_mul_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0))'), + # mul.ps $f2, $f4, $f6 -- little-endian encoding + ('mipsel64', b'\x82\x20\xc6\x46', 'LLIL_INTRINSIC([temp0],_mul_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0))'), + # mul.ps $f31, $f3, $f31 -- odd FPRs and destination/ft aliasing are valid in FR=1 + ('mipsel64', b'\xc2\x1f\xdf\x46', 'LLIL_INTRINSIC([temp0],_mul_ps,[LLIL_REG.q($f3),LLIL_REG.q($f31)]); LLIL_SET_REG.q($f31,LLIL_REG.q(temp0))'), + # mul.ps $f4, $f4, $f6 -- destination/fs aliasing preserves both original source lanes + ('mips64', b'\x46\xc6\x21\x02', 'LLIL_INTRINSIC([temp0],_mul_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6)]); LLIL_SET_REG.q($f4,LLIL_REG.q(temp0))'), + # mul.ps $f2, $f4, $f6 -- paired-single is unpredictable in FR=0 + ('mips32', b'\x46\xc6\x20\x82', 'LLIL_UNKNOWN()'), + # mul.ps $f2, $f4, $f6 -- little-endian FR=0 is also unpredictable + ('mipsel32', b'\x82\x20\xc6\x46', 'LLIL_UNKNOWN()'), + # bc1nez $f2, 0xc; mul.ps $f2, $f4, $f6 -- the result temporary must not replace the branch snapshot + ('mipsel64', b'\x02\x00\xa2\x45\x82\x20\xc6\x46', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($f2)); LLIL_INTRINSIC([temp0],_mul_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0)); LLIL_IF(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0)),7,4)'), + # sub.s $f2, $f4, $f6 -- scalar subtraction is already representable directly + ('mips32', b'\x46\x06\x20\x81', 'LLIL_SET_REG.d($f2,LLIL_FSUB.d(LLIL_REG.d($f4),LLIL_REG.d($f6)))'), + # sub.d $f2, $f4, $f6 -- FR=0 doubles use even/odd FPR pairs + ('mipsel32', b'\x81\x20\x26\x46', 'LLIL_SET_REG_SPLIT.d($f3,$f2,LLIL_FSUB.q(LLIL_REG_SPLIT.d($f5,$f4),LLIL_REG_SPLIT.d($f7,$f6)))'), + # sub.d $f3, $f4, $f6 -- odd FR=0 operands are architecturally unpredictable + ('mips32', b'\x46\x26\x20\xc1', 'LLIL_UNKNOWN()'), + # sub.d $f2, $f4, $f6 -- FR=1 uses directly modeled 64-bit FPRs + ('mipsel64', b'\x81\x20\x26\x46', 'LLIL_SET_REG.q($f2,LLIL_FSUB.q(LLIL_REG.q($f4),LLIL_REG.q($f6)))'), + # sub.ps $f2, $f4, $f6 -- paired lanes are modeled by an intrinsic + ('mipsel64', b'\x81\x20\xc6\x46', 'LLIL_INTRINSIC([$f2],_sub_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6)])'), + # sub.ps $f2, $f4, $f6 -- paired-single is unpredictable with modeled 32-bit FPRs + ('mips32', b'\x46\xc6\x20\x81', 'LLIL_UNKNOWN()'), + # msub.s $f2, $f4, $f6, $f8 -- round(fs*ft), then subtract fr + ('mips32', b'\x4c\x88\x30\xa8', 'LLIL_SET_REG.d($f2,LLIL_FSUB.d(LLIL_FMUL.d(LLIL_REG.d($f6),LLIL_REG.d($f8)),LLIL_REG.d($f4)))'), + # msub.d $f2, $f4, $f6, $f8 -- FR=0 doubles use even/odd FPR pairs + ('mipsel32', b'\xa9\x30\x88\x4c', 'LLIL_SET_REG_SPLIT.d($f3,$f2,LLIL_FSUB.q(LLIL_FMUL.q(LLIL_REG_SPLIT.d($f7,$f6),LLIL_REG_SPLIT.d($f9,$f8)),LLIL_REG_SPLIT.d($f5,$f4)))'), + # msub.d $f3, $f4, $f6, $f8 -- odd FR=0 operands are architecturally unpredictable + ('mipsel32', b'\xe9\x30\x88\x4c', 'LLIL_UNKNOWN()'), + # msub.d $f3, $f4, $f6, $f8 -- odd FPRs are valid in the modeled FR=1 register file + ('mipsel64', b'\xe9\x30\x88\x4c', 'LLIL_SET_REG.q($f3,LLIL_FSUB.q(LLIL_FMUL.q(LLIL_REG.q($f6),LLIL_REG.q($f8)),LLIL_REG.q($f4)))'), + # msub.ps $f2, $f4, $f6, $f8 -- packed lanes are modeled by an intrinsic + ('mipsel64', b'\xae\x30\x88\x4c', 'LLIL_INTRINSIC([$f2],_msub_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6),LLIL_REG.q($f8)])'), + # msub.ps $f2, $f4, $f6, $f8 -- paired-single is unpredictable with modeled 32-bit FPRs + ('mipsel32', b'\xae\x30\x88\x4c', 'LLIL_UNKNOWN()'), + # neg.s $f2, $f4 -- negate a single-precision FPR value + ('mips32', b'\x46\x00\x20\x87', 'LLIL_SET_REG.d($f2,LLIL_FNEG.d(LLIL_REG.d($f4)))'), + # neg.d $f2, $f4 -- FR=0 doubles use even/odd FPR pairs + ('mipsel32', b'\x87\x20\x20\x46', 'LLIL_SET_REG_SPLIT.d($f3,$f2,LLIL_FNEG.q(LLIL_REG_SPLIT.d($f5,$f4)))'), + # neg.d $f3, $f4 -- odd FR=0 destinations are architecturally unpredictable + ('mips32', b'\x46\x20\x20\xc7', 'LLIL_UNKNOWN()'), + # neg.d $f3, $f5 -- odd FPRs are valid in the modeled FR=1 register file + ('mipsel64', b'\xc7\x28\x20\x46', 'LLIL_SET_REG.q($f3,LLIL_FNEG.q(LLIL_REG.q($f5)))'), + # neg.ps $f2, $f4 -- packed lanes are modeled by an intrinsic + ('mipsel64', b'\x87\x20\xc0\x46', 'LLIL_INTRINSIC([$f2],_neg_ps,[LLIL_REG.q($f4)])'), + # neg.ps $f2, $f4 -- paired-single is unpredictable with modeled 32-bit FPRs + ('mips32', b'\x46\xc0\x20\x87', 'LLIL_UNKNOWN()'), + # abs.ps $f2, $f4 -- MIPS32 Release 6.06 p. 32: independent absolute values, not a double-precision ABS + ('mips64', b'\x46\xc0\x20\x85', 'LLIL_INTRINSIC([temp0],_abs_ps,[LLIL_REG.q($f4)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0))'), + # abs.ps $f2, $f4 -- little-endian encoding + ('mipsel64', b'\x85\x20\xc0\x46', 'LLIL_INTRINSIC([temp0],_abs_ps,[LLIL_REG.q($f4)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0))'), + # abs.ps $f31, $f3 -- odd FPRs, including the final register, are valid in FR=1 + ('mips64', b'\x46\xc0\x1f\xc5', 'LLIL_INTRINSIC([temp0],_abs_ps,[LLIL_REG.q($f3)]); LLIL_SET_REG.q($f31,LLIL_REG.q(temp0))'), + # abs.ps $f31, $f31 -- in-place operation reads both original lanes + ('mipsel64', b'\xc5\xff\xc0\x46', 'LLIL_INTRINSIC([temp0],_abs_ps,[LLIL_REG.q($f31)]); LLIL_SET_REG.q($f31,LLIL_REG.q(temp0))'), + # abs.ps $f2, $f4 -- paired-single is unpredictable in FR=0 + ('mips32', b'\x46\xc0\x20\x85', 'LLIL_UNKNOWN()'), + # abs.ps $f2, $f4 -- little-endian FR=0 is also unpredictable + ('mipsel32', b'\x85\x20\xc0\x46', 'LLIL_UNKNOWN()'), + # bc1nez $f2, 0xc; abs.ps $f2, $f4 -- retain the original branch value across the intrinsic + ('mipsel64', b'\x02\x00\xa2\x45\x85\x20\xc0\x46', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($f2)); LLIL_INTRINSIC([temp0],_abs_ps,[LLIL_REG.q($f4)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0)); LLIL_IF(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0)),7,4)'), + # nmadd.s $f2, $f4, $f6, $f8 -- negate the rounded multiply-plus-add result + ('mips32', b'\x4c\x88\x30\xb0', 'LLIL_SET_REG.d($f2,LLIL_FNEG.d(LLIL_FADD.d(LLIL_FMUL.d(LLIL_REG.d($f6),LLIL_REG.d($f8)),LLIL_REG.d($f4))))'), + # nmadd.d $f2, $f4, $f6, $f8 -- FR=0 doubles use even/odd FPR pairs + ('mipsel32', b'\xb1\x30\x88\x4c', 'LLIL_SET_REG_SPLIT.d($f3,$f2,LLIL_FNEG.q(LLIL_FADD.q(LLIL_FMUL.q(LLIL_REG_SPLIT.d($f7,$f6),LLIL_REG_SPLIT.d($f9,$f8)),LLIL_REG_SPLIT.d($f5,$f4))))'), + # nmadd.d $f3, $f4, $f6, $f8 -- odd FR=0 destinations are architecturally unpredictable + ('mipsel32', b'\xf1\x30\x88\x4c', 'LLIL_UNKNOWN()'), + # nmadd.d $f3, $f4, $f6, $f8 -- odd FPRs are valid in the modeled FR=1 register file + ('mipsel64', b'\xf1\x30\x88\x4c', 'LLIL_SET_REG.q($f3,LLIL_FNEG.q(LLIL_FADD.q(LLIL_FMUL.q(LLIL_REG.q($f6),LLIL_REG.q($f8)),LLIL_REG.q($f4))))'), + # nmadd.ps $f2, $f4, $f6, $f8 -- packed lanes are modeled by an intrinsic + ('mipsel64', b'\xb6\x30\x88\x4c', 'LLIL_INTRINSIC([$f2],_nmadd_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6),LLIL_REG.q($f8)])'), + # nmadd.ps $f2, $f4, $f6, $f8 -- paired-single is unpredictable with modeled 32-bit FPRs + ('mipsel32', b'\xb6\x30\x88\x4c', 'LLIL_UNKNOWN()'), + # nmsub.s $f2, $f4, $f6, $f8 -- negate the rounded multiply-minus-subtract result + ('mips32', b'\x4c\x88\x30\xb8', 'LLIL_SET_REG.d($f2,LLIL_FNEG.d(LLIL_FSUB.d(LLIL_FMUL.d(LLIL_REG.d($f6),LLIL_REG.d($f8)),LLIL_REG.d($f4))))'), + # nmsub.d $f2, $f4, $f6, $f8 -- FR=0 doubles use even/odd FPR pairs + ('mipsel32', b'\xb9\x30\x88\x4c', 'LLIL_SET_REG_SPLIT.d($f3,$f2,LLIL_FNEG.q(LLIL_FSUB.q(LLIL_FMUL.q(LLIL_REG_SPLIT.d($f7,$f6),LLIL_REG_SPLIT.d($f9,$f8)),LLIL_REG_SPLIT.d($f5,$f4))))'), + # nmsub.d $f3, $f4, $f6, $f8 -- odd FR=0 destinations are architecturally unpredictable + ('mipsel32', b'\xf9\x30\x88\x4c', 'LLIL_UNKNOWN()'), + # nmsub.d $f3, $f4, $f6, $f8 -- odd FPRs are valid in the modeled FR=1 register file + ('mipsel64', b'\xf9\x30\x88\x4c', 'LLIL_SET_REG.q($f3,LLIL_FNEG.q(LLIL_FSUB.q(LLIL_FMUL.q(LLIL_REG.q($f6),LLIL_REG.q($f8)),LLIL_REG.q($f4))))'), + # nmsub.ps $f2, $f4, $f6, $f8 -- packed lanes are modeled by an intrinsic + ('mipsel64', b'\xbe\x30\x88\x4c', 'LLIL_INTRINSIC([$f2],_nmsub_ps,[LLIL_REG.q($f4),LLIL_REG.q($f6),LLIL_REG.q($f8)])'), + # nmsub.ps $f2, $f4, $f6, $f8 -- paired-single is unpredictable with modeled 32-bit FPRs + ('mipsel32', b'\xbe\x30\x88\x4c', 'LLIL_UNKNOWN()'), + # mov.s $f2, $f4 -- single-precision transfer between modeled 32-bit FPRs + ('mips32', b'\x46\x00\x20\x86', 'LLIL_SET_REG.d($f2,LLIL_REG.d($f4))'), + # mov.s $f2, $f4 -- only the low word is transferred with modeled 64-bit FPRs + ('mipsel64', b'\x86\x20\x00\x46', 'LLIL_SET_REG.d($f2,LLIL_REG.d($f4))'), + # mov.d $f2, $f4 -- FR=0 doubles use even/odd FPR pairs + ('mipsel32', b'\x86\x20\x20\x46', 'LLIL_SET_REG_SPLIT.d($f3,$f2,LLIL_REG_SPLIT.d($f5,$f4))'), + # mov.d $f3, $f4 -- odd FR=0 pair roots are architecturally unpredictable + ('mips32', b'\x46\x20\x20\xc6', 'LLIL_UNKNOWN()'), + # mov.d $f3, $f5 -- odd FPRs are valid in the modeled FR=1 register file + ('mips64', b'\x46\x20\x28\xc6', 'LLIL_SET_REG.q($f3,LLIL_REG.q($f5))'), + # mov.ps $f2, $f4 -- paired-single transfer through the packed intrinsic + ('mipsel64', b'\x86\x20\xc0\x46', 'LLIL_INTRINSIC([$f2],_mov_ps,[LLIL_REG.q($f4)])'), + # mov.ps $f2, $f4 -- paired-single is unpredictable with modeled 32-bit FPRs + ('mips32', b'\x46\xc0\x20\x86', 'LLIL_UNKNOWN()'), # movt.s $f0, $f1, $fcc0 -- MIPS32 Release 6.06 pp. 282-283 ('mipsel32', b'\x11\x08\x01\x46', 'LLIL_IF(LLIL_FLAG($fcc0),1,3); LLIL_SET_REG.d($f0,LLIL_REG.d($f1)); LLIL_GOTO(3)'), # movf.s $f0, $f12, $fcc0 -- MIPS32 Release 6.06 pp. 277-278 @@ -151,6 +499,232 @@ ('mipsel32', b'\x01\x08\x0d\x01', 'LLIL_IF(LLIL_FLAG($fcc3),1,3); LLIL_SET_REG.d($at,LLIL_REG.d($t0)); LLIL_GOTO(3)'), # movt $at, $t0, $fcc0 -- MIPS64 Release 6.06 p. 373 uses full-width GPRs ('mipsel64', b'\x01\x08\x01\x01', 'LLIL_IF(LLIL_FLAG($fcc0),1,3); LLIL_SET_REG.q($at,LLIL_REG.q($t0)); LLIL_GOTO(3)'), + # movn.s $f2, $f4, $t0 -- MIPS32 Release 6.06 p. 280: copy only for a nonzero GPR + ('mips32', b'\x46\x08\x20\x93', 'LLIL_IF(LLIL_CMP_NE.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),1,3); LLIL_SET_REG.d($f2,LLIL_REG.d($f4)); LLIL_GOTO(3)'), + # movn.s $f2, $f4, $t0 -- little-endian encoding + ('mipsel32', b'\x93\x20\x08\x46', 'LLIL_IF(LLIL_CMP_NE.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),1,3); LLIL_SET_REG.d($f2,LLIL_REG.d($f4)); LLIL_GOTO(3)'), + # movn.s $f31, $f31, $ra -- full 64-bit GPR test, but only a 32-bit FPR transfer + ('mipsel64', b'\xd3\xff\x1f\x46', 'LLIL_IF(LLIL_CMP_NE.q(LLIL_REG.q($ra),LLIL_CONST.q(0x0)),1,3); LLIL_SET_REG.d($f31,LLIL_REG.d($f31)); LLIL_GOTO(3)'), + # movn.s $f3, $f5, $zero -- the architectural zero register never selects the source + ('mips32', b'\x46\x00\x28\xd3', 'LLIL_IF(LLIL_CMP_NE.d(LLIL_CONST.d(0x0),LLIL_CONST.d(0x0)),1,3); LLIL_SET_REG.d($f3,LLIL_REG.d($f5)); LLIL_GOTO(3)'), + # movz.s $f2, $f4, $t0 -- MIPS32 Release 6.06 p. 285: copy only for a zero GPR + ('mipsel32', b'\x92\x20\x08\x46', 'LLIL_IF(LLIL_CMP_E.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),1,3); LLIL_SET_REG.d($f2,LLIL_REG.d($f4)); LLIL_GOTO(3)'), + # movz.s $f2, $f4, $t0 -- big-endian encoding + ('mips32', b'\x46\x08\x20\x92', 'LLIL_IF(LLIL_CMP_E.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),1,3); LLIL_SET_REG.d($f2,LLIL_REG.d($f4)); LLIL_GOTO(3)'), + # movz.s $f31, $f31, $ra -- odd and aliased FPRs are valid for single precision + ('mips64', b'\x46\x1f\xff\xd2', 'LLIL_IF(LLIL_CMP_E.q(LLIL_REG.q($ra),LLIL_CONST.q(0x0)),1,3); LLIL_SET_REG.d($f31,LLIL_REG.d($f31)); LLIL_GOTO(3)'), + # movz.s $f3, $f5, $zero -- the architectural zero register always selects the source + ('mipsel32', b'\xd2\x28\x00\x46', 'LLIL_IF(LLIL_CMP_E.d(LLIL_CONST.d(0x0),LLIL_CONST.d(0x0)),1,3); LLIL_SET_REG.d($f3,LLIL_REG.d($f5)); LLIL_GOTO(3)'), + # movn.d $f2, $f4, $t0 -- FR=0 copies the complete even/odd FPR pair + ('mipsel32', b'\x93\x20\x28\x46', 'LLIL_IF(LLIL_CMP_NE.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),1,3); LLIL_SET_REG_SPLIT.d($f3,$f2,LLIL_REG_SPLIT.d($f5,$f4)); LLIL_GOTO(3)'), + # movn.d $f2, $f4, $t0 -- big-endian FR=0 uses the same register-pair ordering + ('mips32', b'\x46\x28\x20\x93', 'LLIL_IF(LLIL_CMP_NE.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),1,3); LLIL_SET_REG_SPLIT.d($f3,$f2,LLIL_REG_SPLIT.d($f5,$f4)); LLIL_GOTO(3)'), + # movn.d $f3, $f5, $t1 -- odd FPRs and full-width GPR conditions in FR=1 + ('mipsel64', b'\xd3\x28\x29\x46', 'LLIL_IF(LLIL_CMP_NE.q(LLIL_REG.q($t1),LLIL_CONST.q(0x0)),1,3); LLIL_SET_REG.q($f3,LLIL_REG.q($f5)); LLIL_GOTO(3)'), + # movn.d $f3, $f4, $t0 -- odd FR=0 destination pair roots are unpredictable + ('mips32', b'\x46\x28\x20\xd3', 'LLIL_UNKNOWN()'), + # movn.d $f2, $f5, $t0 -- odd FR=0 source pair roots are unpredictable + ('mipsel32', b'\x93\x28\x28\x46', 'LLIL_UNKNOWN()'), + # movn.d $f3, $f5, $zero -- a false predicate does not waive format restrictions + ('mipsel32', b'\xd3\x28\x20\x46', 'LLIL_UNKNOWN()'), + # movn.d $f4, $f4, $t0 -- an aliased even/odd pair is copied atomically + ('mipsel32', b'\x13\x21\x28\x46', 'LLIL_IF(LLIL_CMP_NE.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),1,3); LLIL_SET_REG_SPLIT.d($f5,$f4,LLIL_REG_SPLIT.d($f5,$f4)); LLIL_GOTO(3)'), + # movz.d $f2, $f4, $t0 -- the false path preserves both destination words + ('mips32', b'\x46\x28\x20\x92', 'LLIL_IF(LLIL_CMP_E.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),1,3); LLIL_SET_REG_SPLIT.d($f3,$f2,LLIL_REG_SPLIT.d($f5,$f4)); LLIL_GOTO(3)'), + # movz.d $f2, $f4, $t0 -- little-endian FR=0 encoding + ('mipsel32', b'\x92\x20\x28\x46', 'LLIL_IF(LLIL_CMP_E.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),1,3); LLIL_SET_REG_SPLIT.d($f3,$f2,LLIL_REG_SPLIT.d($f5,$f4)); LLIL_GOTO(3)'), + # movz.d $f3, $f5, $t1 -- FR=1 copies a complete 64-bit FPR + ('mips64', b'\x46\x29\x28\xd2', 'LLIL_IF(LLIL_CMP_E.q(LLIL_REG.q($t1),LLIL_CONST.q(0x0)),1,3); LLIL_SET_REG.q($f3,LLIL_REG.q($f5)); LLIL_GOTO(3)'), + # movz.d $f3, $f4, $t0 -- odd FR=0 destination pair roots are unpredictable + ('mipsel32', b'\xd2\x20\x28\x46', 'LLIL_UNKNOWN()'), + # movz.d $f2, $f5, $t0 -- odd FR=0 source pair roots are unpredictable + ('mips32', b'\x46\x28\x28\x92', 'LLIL_UNKNOWN()'), + # movz.d $f3, $f5, $zero -- a true predicate does not waive format restrictions + ('mips32', b'\x46\x20\x28\xd2', 'LLIL_UNKNOWN()'), + # movz.d $f4, $f4, $t0 -- source and destination may name the same valid pair + ('mips32', b'\x46\x28\x21\x12', 'LLIL_IF(LLIL_CMP_E.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),1,3); LLIL_SET_REG_SPLIT.d($f5,$f4,LLIL_REG_SPLIT.d($f5,$f4)); LLIL_GOTO(3)'), + # movn.ps $f2, $f4, $t0 -- one full-width GPR condition controls both FR=1 lanes + ('mips64', b'\x46\xc8\x20\x93', 'LLIL_IF(LLIL_CMP_NE.q(LLIL_REG.q($t0),LLIL_CONST.q(0x0)),1,3); LLIL_SET_REG.q($f2,LLIL_REG.q($f4)); LLIL_GOTO(3)'), + # movn.ps $f3, $f5, $t1 -- little-endian encoding with odd FPRs + ('mipsel64', b'\xd3\x28\xc9\x46', 'LLIL_IF(LLIL_CMP_NE.q(LLIL_REG.q($t1),LLIL_CONST.q(0x0)),1,3); LLIL_SET_REG.q($f3,LLIL_REG.q($f5)); LLIL_GOTO(3)'), + # movn.ps $f3, $f5, $zero -- both lanes are preserved for a zero predicate + ('mipsel64', b'\xd3\x28\xc0\x46', 'LLIL_IF(LLIL_CMP_NE.q(LLIL_CONST.q(0x0),LLIL_CONST.q(0x0)),1,3); LLIL_SET_REG.q($f3,LLIL_REG.q($f5)); LLIL_GOTO(3)'), + # movn.ps $f2, $f4, $t0 -- PS is unpredictable in the modeled FR=0 register file + ('mips32', b'\x46\xc8\x20\x93', 'LLIL_UNKNOWN()'), + # movz.ps $f3, $f5, $t1 -- odd FPRs are valid in FR=1 + ('mipsel64', b'\xd2\x28\xc9\x46', 'LLIL_IF(LLIL_CMP_E.q(LLIL_REG.q($t1),LLIL_CONST.q(0x0)),1,3); LLIL_SET_REG.q($f3,LLIL_REG.q($f5)); LLIL_GOTO(3)'), + # movz.ps $f2, $f4, $t0 -- big-endian encoding + ('mips64', b'\x46\xc8\x20\x92', 'LLIL_IF(LLIL_CMP_E.q(LLIL_REG.q($t0),LLIL_CONST.q(0x0)),1,3); LLIL_SET_REG.q($f2,LLIL_REG.q($f4)); LLIL_GOTO(3)'), + # movz.ps $f3, $f5, $zero -- both lanes are selected for a zero predicate + ('mips64', b'\x46\xc0\x28\xd2', 'LLIL_IF(LLIL_CMP_E.q(LLIL_CONST.q(0x0),LLIL_CONST.q(0x0)),1,3); LLIL_SET_REG.q($f3,LLIL_REG.q($f5)); LLIL_GOTO(3)'), + # movz.ps $f2, $f4, $t0 -- even FPRs do not make PS valid in FR=0 + ('mipsel32', b'\x92\x20\xc8\x46', 'LLIL_UNKNOWN()'), + # movt.ps $f2, $f4, $fcc0 -- MIPS32 Release 6.06 pp. 282-283: FCC0/FCC1 select low/high independently + ('mips64', b'\x46\xc1\x20\x91', 'LLIL_INTRINSIC([temp0],_movt_ps,[LLIL_REG.q($f2),LLIL_REG.q($f4),LLIL_FLAG($fcc0),LLIL_FLAG($fcc1)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0))'), + # movt.ps $f2, $f4, $fcc2 -- little-endian encoding and a non-default FCC pair + ('mipsel64', b'\x91\x20\xc9\x46', 'LLIL_INTRINSIC([temp0],_movt_ps,[LLIL_REG.q($f2),LLIL_REG.q($f4),LLIL_FLAG($fcc2),LLIL_FLAG($fcc3)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0))'), + # movt.ps $f3, $f5, $fcc4 -- odd FPRs with a valid even FCC selector + ('mips64', b'\x46\xd1\x28\xd1', 'LLIL_INTRINSIC([temp0],_movt_ps,[LLIL_REG.q($f3),LLIL_REG.q($f5),LLIL_FLAG($fcc4),LLIL_FLAG($fcc5)]); LLIL_SET_REG.q($f3,LLIL_REG.q(temp0))'), + # movt.ps $f31, $f31, $fcc6 -- aliasing and the highest valid FCC pair + ('mipsel64', b'\xd1\xff\xd9\x46', 'LLIL_INTRINSIC([temp0],_movt_ps,[LLIL_REG.q($f31),LLIL_REG.q($f31),LLIL_FLAG($fcc6),LLIL_FLAG($fcc7)]); LLIL_SET_REG.q($f31,LLIL_REG.q(temp0))'), + # movt.ps $f2, $f4, $fcc1 -- odd FCC selectors are unpredictable + ('mips64', b'\x46\xc5\x20\x91', 'LLIL_UNKNOWN()'), + # movt.ps $f2, $f4, $fcc7 -- FCC7 must not wrap to FCC0 or reference FCC8 + ('mipsel64', b'\x91\x20\xdd\x46', 'LLIL_UNKNOWN()'), + # movt.ps $f2, $f4, $fcc0 -- paired-single conditional moves require FR=1 + ('mips32', b'\x46\xc1\x20\x91', 'LLIL_UNKNOWN()'), + # movf.ps $f2, $f4, $fcc2 -- MIPS32 Release 6.06 pp. 277-278: unselected lanes retain their old values + ('mipsel64', b'\x91\x20\xc8\x46', 'LLIL_INTRINSIC([temp0],_movf_ps,[LLIL_REG.q($f2),LLIL_REG.q($f4),LLIL_FLAG($fcc2),LLIL_FLAG($fcc3)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0))'), + # movf.ps $f2, $f4, $fcc0 -- big-endian encoding and both false-polarity lane predicates + ('mips64', b'\x46\xc0\x20\x91', 'LLIL_INTRINSIC([temp0],_movf_ps,[LLIL_REG.q($f2),LLIL_REG.q($f4),LLIL_FLAG($fcc0),LLIL_FLAG($fcc1)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0))'), + # movf.ps $f3, $f5, $fcc4 -- odd FPRs with a valid even FCC selector + ('mips64', b'\x46\xd0\x28\xd1', 'LLIL_INTRINSIC([temp0],_movf_ps,[LLIL_REG.q($f3),LLIL_REG.q($f5),LLIL_FLAG($fcc4),LLIL_FLAG($fcc5)]); LLIL_SET_REG.q($f3,LLIL_REG.q(temp0))'), + # movf.ps $f31, $f31, $fcc6 -- an in-place move preserves both lanes for every FCC combination + ('mipsel64', b'\xd1\xff\xd8\x46', 'LLIL_INTRINSIC([temp0],_movf_ps,[LLIL_REG.q($f31),LLIL_REG.q($f31),LLIL_FLAG($fcc6),LLIL_FLAG($fcc7)]); LLIL_SET_REG.q($f31,LLIL_REG.q(temp0))'), + # movf.ps $f2, $f4, $fcc1 -- odd FCC selectors are unpredictable + ('mipsel64', b'\x91\x20\xc4\x46', 'LLIL_UNKNOWN()'), + # movf.ps $f2, $f4, $fcc7 -- the highest odd FCC selector is also unpredictable + ('mips64', b'\x46\xdc\x20\x91', 'LLIL_UNKNOWN()'), + # movf.ps $f2, $f4, $fcc0 -- paired-single is unpredictable in FR=0 + ('mipsel32', b'\x91\x20\xc0\x46', 'LLIL_UNKNOWN()'), + # bc1nez $f2, 0xc; movn.s $f2, $f4, $t0 -- preserve the branch value before conditional delay-slot control flow + ('mipsel32', b'\x02\x00\xa2\x45\x93\x20\x08\x46', 'LLIL_SET_REG.d(temp1,LLIL_REG.d($f2)); LLIL_IF(LLIL_CMP_NE.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),2,4); LLIL_SET_REG.d($f2,LLIL_REG.d($f4)); LLIL_GOTO(4); LLIL_IF(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0)),8,5)'), + # bc1nez $f2, 0xc; movz.s $f2, $f4, $t0 -- big-endian zero-predicate delay slot + ('mips32', b'\x45\xa2\x00\x02\x46\x08\x20\x92', 'LLIL_SET_REG.d(temp1,LLIL_REG.d($f2)); LLIL_IF(LLIL_CMP_E.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),2,4); LLIL_SET_REG.d($f2,LLIL_REG.d($f4)); LLIL_GOTO(4); LLIL_IF(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0)),8,5)'), + # bc1nez $f2, 0xc; movn.d $f2, $f4, $t0 -- the branch reads the old low half of the FR=0 pair + ('mipsel32', b'\x02\x00\xa2\x45\x93\x20\x28\x46', 'LLIL_SET_REG.q(temp1,LLIL_REG_SPLIT.d($f3,$f2)); LLIL_IF(LLIL_CMP_NE.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),2,4); LLIL_SET_REG_SPLIT.d($f3,$f2,LLIL_REG_SPLIT.d($f5,$f4)); LLIL_GOTO(4); LLIL_IF(LLIL_TEST_BIT.d(LLIL_LOW_PART.d(LLIL_REG.q(temp1)),LLIL_CONST.b(0x0)),8,5)'), + # bc1nez $f3, 0xc; movz.d $f2, $f4, $t0 -- preserve the old high half, without swapping the pair + ('mips32', b'\x45\xa3\x00\x02\x46\x28\x20\x92', 'LLIL_SET_REG.q(temp1,LLIL_REG_SPLIT.d($f3,$f2)); LLIL_IF(LLIL_CMP_E.d(LLIL_REG.d($t0),LLIL_CONST.d(0x0)),2,4); LLIL_SET_REG_SPLIT.d($f3,$f2,LLIL_REG_SPLIT.d($f5,$f4)); LLIL_GOTO(4); LLIL_IF(LLIL_TEST_BIT.d(LLIL_LOW_PART.d(LLIL_LSR.q(LLIL_REG.q(temp1),LLIL_CONST.b(0x20))),LLIL_CONST.b(0x0)),8,5)'), + # bc1nez $f2, 0xc; movn.ps $f2, $f4, $t0 -- snapshot the packed destination before its conditional write + ('mips64', b'\x45\xa2\x00\x02\x46\xc8\x20\x93', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($f2)); LLIL_IF(LLIL_CMP_NE.q(LLIL_REG.q($t0),LLIL_CONST.q(0x0)),2,4); LLIL_SET_REG.q($f2,LLIL_REG.q($f4)); LLIL_GOTO(4); LLIL_IF(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0)),8,5)'), + # bc1nez $f2, 0xc; movz.ps $f2, $f4, $t0 -- the branch predicate is independent of the move predicate + ('mipsel64', b'\x02\x00\xa2\x45\x92\x20\xc8\x46', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($f2)); LLIL_IF(LLIL_CMP_E.q(LLIL_REG.q($t0),LLIL_CONST.q(0x0)),2,4); LLIL_SET_REG.q($f2,LLIL_REG.q($f4)); LLIL_GOTO(4); LLIL_IF(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0)),8,5)'), + # bc1nez $f2, 0xc; movt.ps $f2, $f4, $fcc0 -- snapshot before either lane can change the branch value + ('mipsel64', b'\x02\x00\xa2\x45\x91\x20\xc1\x46', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($f2)); LLIL_INTRINSIC([temp0],_movt_ps,[LLIL_REG.q($f2),LLIL_REG.q($f4),LLIL_FLAG($fcc0),LLIL_FLAG($fcc1)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0)); LLIL_IF(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0)),7,4)'), + # bc1nez $f2, 0xc; movf.ps $f2, $f4, $fcc0 -- false-polarity lane selection also preserves the branch value + ('mips64', b'\x45\xa2\x00\x02\x46\xc0\x20\x91', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($f2)); LLIL_INTRINSIC([temp0],_movf_ps,[LLIL_REG.q($f2),LLIL_REG.q($f4),LLIL_FLAG($fcc0),LLIL_FLAG($fcc1)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0)); LLIL_IF(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0)),7,4)'), + # trunc.w.s $f2, $f4 -- truncate a single to a signed 32-bit fixed-point value + ('mips32', b'\x46\x00\x20\x8d', 'LLIL_SET_REG.d($f2,LLIL_FLOAT_TO_INT.d(LLIL_FTRUNC.d(LLIL_REG.d($f4))))'), + # trunc.w.s $f2, $f4 -- little-endian encoding + ('mipsel32', b'\x8d\x20\x00\x46', 'LLIL_SET_REG.d($f2,LLIL_FLOAT_TO_INT.d(LLIL_FTRUNC.d(LLIL_REG.d($f4))))'), + # trunc.w.d $f2, $f4 -- FR=0 reads an even/odd double source pair + ('mipsel32', b'\x8d\x20\x20\x46', 'LLIL_SET_REG.d($f2,LLIL_FLOAT_TO_INT.d(LLIL_FTRUNC.q(LLIL_REG_SPLIT.d($f5,$f4))))'), + # trunc.w.d $f2, $f4 -- FR=1 reads a directly modeled 64-bit FPR + ('mipsel64', b'\x8d\x20\x20\x46', 'LLIL_SET_REG.d($f2,LLIL_FLOAT_TO_INT.d(LLIL_FTRUNC.q(LLIL_REG.q($f4))))'), + # trunc.w.d $f3, $f5 -- odd FR=0 double roots are architecturally unpredictable + ('mipsel32', b'\xcd\x28\x20\x46', 'LLIL_UNKNOWN()'), + # trunc.w.d $f3, $f5 -- odd FPRs are valid in the modeled FR=1 register file + ('mipsel64', b'\xcd\x28\x20\x46', 'LLIL_SET_REG.d($f3,LLIL_FLOAT_TO_INT.d(LLIL_FTRUNC.q(LLIL_REG.q($f5))))'), + # trunc.l.s $f2, $f4 -- truncate a single to a signed 64-bit fixed-point value + ('mips64', b'\x46\x00\x20\x89', 'LLIL_SET_REG.q($f2,LLIL_FLOAT_TO_INT.q(LLIL_FTRUNC.d(LLIL_REG.d($f4))))'), + # trunc.l.s $f2, $f4 -- little-endian encoding + ('mipsel64', b'\x89\x20\x00\x46', 'LLIL_SET_REG.q($f2,LLIL_FLOAT_TO_INT.q(LLIL_FTRUNC.d(LLIL_REG.d($f4))))'), + # trunc.l.d $f2, $f4 -- truncate a double to a signed 64-bit fixed-point value + ('mipsel64', b'\x89\x20\x20\x46', 'LLIL_SET_REG.q($f2,LLIL_FLOAT_TO_INT.q(LLIL_FTRUNC.q(LLIL_REG.q($f4))))'), + # trunc.l.d $f3, $f5 -- odd FPRs are valid in the modeled FR=1 register file + ('mipsel64', b'\xc9\x28\x20\x46', 'LLIL_SET_REG.q($f3,LLIL_FLOAT_TO_INT.q(LLIL_FTRUNC.q(LLIL_REG.q($f5))))'), + # trunc.l.s $f2, $f4 -- a long result is unpredictable in the FR=0 FPR model + ('mips32', b'\x46\x00\x20\x89', 'LLIL_UNKNOWN()'), + # trunc.l.d $f2, $f4 -- FR=0 cannot hold the 64-bit result in one FPR + ('mipsel32', b'\x89\x20\x20\x46', 'LLIL_UNKNOWN()'), + # round.w.s $f2, $f4 -- MIPS32 Release 6.06 p. 342: nearest/even, independent of FCSR.RM + ('mips32', b'\x46\x00\x20\x8c', 'LLIL_INTRINSIC([temp0],_round_w_s,[LLIL_REG.d($f4)]); LLIL_SET_REG.d($f2,LLIL_REG.d(temp0))'), + # round.w.s $f2, $f4 -- little-endian encoding + ('mipsel32', b'\x8c\x20\x00\x46', 'LLIL_INTRINSIC([temp0],_round_w_s,[LLIL_REG.d($f4)]); LLIL_SET_REG.d($f2,LLIL_REG.d(temp0))'), + # round.w.s $f31, $f31 -- in-place conversion reads and writes only a word in FR=1 + ('mipsel64', b'\xcc\xff\x00\x46', 'LLIL_INTRINSIC([temp0],_round_w_s,[LLIL_REG.d($f31)]); LLIL_SET_REG.d($f31,LLIL_REG.d(temp0))'), + # round.w.d $f2, $f4 -- FR=0 reads an even/odd double source pair + ('mips32', b'\x46\x20\x20\x8c', 'LLIL_INTRINSIC([temp0],_round_w_d,[LLIL_REG_SPLIT.d($f5,$f4)]); LLIL_SET_REG.d($f2,LLIL_REG.d(temp0))'), + # round.w.d $f2, $f4 -- little-endian FR=0 has the same register-pair ordering + ('mipsel32', b'\x8c\x20\x20\x46', 'LLIL_INTRINSIC([temp0],_round_w_d,[LLIL_REG_SPLIT.d($f5,$f4)]); LLIL_SET_REG.d($f2,LLIL_REG.d(temp0))'), + # round.w.d $f2, $f4 -- FR=1 reads a double but writes a 32-bit integer + ('mipsel64', b'\x8c\x20\x20\x46', 'LLIL_INTRINSIC([temp0],_round_w_d,[LLIL_REG.q($f4)]); LLIL_SET_REG.d($f2,LLIL_REG.d(temp0))'), + # round.w.d $f3, $f4 -- an odd word destination is valid in FR=0 + ('mipsel32', b'\xcc\x20\x20\x46', 'LLIL_INTRINSIC([temp0],_round_w_d,[LLIL_REG_SPLIT.d($f5,$f4)]); LLIL_SET_REG.d($f3,LLIL_REG.d(temp0))'), + # round.w.d $f3, $f5 -- an odd FR=0 double source is unpredictable + ('mipsel32', b'\xcc\x28\x20\x46', 'LLIL_UNKNOWN()'), + # round.w.d $f3, $f5 -- odd double sources are valid in FR=1 + ('mipsel64', b'\xcc\x28\x20\x46', 'LLIL_INTRINSIC([temp0],_round_w_d,[LLIL_REG.q($f5)]); LLIL_SET_REG.d($f3,LLIL_REG.d(temp0))'), + # round.w.d $f5, $f4 -- read the pair before overwriting its high word + ('mipsel32', b'\x4c\x21\x20\x46', 'LLIL_INTRINSIC([temp0],_round_w_d,[LLIL_REG_SPLIT.d($f5,$f4)]); LLIL_SET_REG.d($f5,LLIL_REG.d(temp0))'), + # bc1nez $f5, 0xc; round.w.d $f5, $f4 -- keep the pre-delay-slot branch value separate from the result temp + ('mipsel32', b'\x02\x00\xa5\x45\x4c\x21\x20\x46', 'LLIL_SET_REG.d(temp1,LLIL_REG.d($f5)); LLIL_INTRINSIC([temp0],_round_w_d,[LLIL_REG_SPLIT.d($f5,$f4)]); LLIL_SET_REG.d($f5,LLIL_REG.d(temp0)); LLIL_IF(LLIL_TEST_BIT.d(LLIL_REG.d(temp1),LLIL_CONST.b(0x0)),7,4)'), + # round.l.s $f2, $f4 -- MIPS32 Release 6.06 p. 341: nearest/even to a signed long + ('mips64', b'\x46\x00\x20\x88', 'LLIL_INTRINSIC([temp0],_round_l_s,[LLIL_REG.d($f4)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0))'), + # round.l.s $f2, $f4 -- little-endian encoding + ('mipsel64', b'\x88\x20\x00\x46', 'LLIL_INTRINSIC([temp0],_round_l_s,[LLIL_REG.d($f4)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0))'), + # round.l.d $f2, $f4 -- double source and 64-bit integer result + ('mips64', b'\x46\x20\x20\x88', 'LLIL_INTRINSIC([temp0],_round_l_d,[LLIL_REG.q($f4)]); LLIL_SET_REG.q($f2,LLIL_REG.q(temp0))'), + # round.l.d $f3, $f5 -- odd source and destination are valid in FR=1 + ('mipsel64', b'\xc8\x28\x20\x46', 'LLIL_INTRINSIC([temp0],_round_l_d,[LLIL_REG.q($f5)]); LLIL_SET_REG.q($f3,LLIL_REG.q(temp0))'), + # round.l.s $f2, $f4 -- long results are unpredictable in FR=0 + ('mips32', b'\x46\x00\x20\x88', 'LLIL_UNKNOWN()'), + # round.l.d $f2, $f4 -- even register numbers do not make long results valid in FR=0 + ('mipsel32', b'\x88\x20\x20\x46', 'LLIL_UNKNOWN()'), + # ceil.w.s $f2, $f4 -- MIPS32 Release 6.06 p. 128: toward positive infinity, independent of FCSR.RM + ('mips32', b'\x46\x00\x20\x8e', 'LLIL_SET_REG.d($f2,LLIL_FLOAT_TO_INT.d(LLIL_CEIL.d(LLIL_REG.d($f4))))'), + # ceil.w.s $f2, $f4 -- little-endian encoding + ('mipsel32', b'\x8e\x20\x00\x46', 'LLIL_SET_REG.d($f2,LLIL_FLOAT_TO_INT.d(LLIL_CEIL.d(LLIL_REG.d($f4))))'), + # ceil.w.s $f31, $f31 -- in-place conversion reads and writes only a word in FR=1 + ('mipsel64', b'\xce\xff\x00\x46', 'LLIL_SET_REG.d($f31,LLIL_FLOAT_TO_INT.d(LLIL_CEIL.d(LLIL_REG.d($f31))))'), + # ceil.w.d $f2, $f4 -- FR=0 reads an even/odd double source pair + ('mips32', b'\x46\x20\x20\x8e', 'LLIL_SET_REG.d($f2,LLIL_FLOAT_TO_INT.d(LLIL_CEIL.q(LLIL_REG_SPLIT.d($f5,$f4))))'), + # ceil.w.d $f2, $f4 -- little-endian FR=0 has the same register-pair ordering + ('mipsel32', b'\x8e\x20\x20\x46', 'LLIL_SET_REG.d($f2,LLIL_FLOAT_TO_INT.d(LLIL_CEIL.q(LLIL_REG_SPLIT.d($f5,$f4))))'), + # ceil.w.d $f2, $f4 -- FR=1 reads a double but writes a 32-bit integer + ('mipsel64', b'\x8e\x20\x20\x46', 'LLIL_SET_REG.d($f2,LLIL_FLOAT_TO_INT.d(LLIL_CEIL.q(LLIL_REG.q($f4))))'), + # ceil.w.d $f3, $f4 -- an odd word destination is valid in FR=0 + ('mipsel32', b'\xce\x20\x20\x46', 'LLIL_SET_REG.d($f3,LLIL_FLOAT_TO_INT.d(LLIL_CEIL.q(LLIL_REG_SPLIT.d($f5,$f4))))'), + # ceil.w.d $f3, $f5 -- an odd FR=0 double source is unpredictable + ('mipsel32', b'\xce\x28\x20\x46', 'LLIL_UNKNOWN()'), + # ceil.w.d $f3, $f5 -- odd double sources are valid in FR=1 + ('mipsel64', b'\xce\x28\x20\x46', 'LLIL_SET_REG.d($f3,LLIL_FLOAT_TO_INT.d(LLIL_CEIL.q(LLIL_REG.q($f5))))'), + # ceil.w.d $f5, $f4 -- read the pair before overwriting its high word + ('mipsel32', b'\x4e\x21\x20\x46', 'LLIL_SET_REG.d($f5,LLIL_FLOAT_TO_INT.d(LLIL_CEIL.q(LLIL_REG_SPLIT.d($f5,$f4))))'), + # ceil.l.s $f2, $f4 -- MIPS32 Release 6.06 p. 127: toward positive infinity to a signed long + ('mips64', b'\x46\x00\x20\x8a', 'LLIL_SET_REG.q($f2,LLIL_FLOAT_TO_INT.q(LLIL_CEIL.d(LLIL_REG.d($f4))))'), + # ceil.l.s $f2, $f4 -- little-endian encoding + ('mipsel64', b'\x8a\x20\x00\x46', 'LLIL_SET_REG.q($f2,LLIL_FLOAT_TO_INT.q(LLIL_CEIL.d(LLIL_REG.d($f4))))'), + # ceil.l.d $f2, $f4 -- double source and 64-bit integer result + ('mips64', b'\x46\x20\x20\x8a', 'LLIL_SET_REG.q($f2,LLIL_FLOAT_TO_INT.q(LLIL_CEIL.q(LLIL_REG.q($f4))))'), + # ceil.l.d $f3, $f5 -- odd source and destination are valid in FR=1 + ('mipsel64', b'\xca\x28\x20\x46', 'LLIL_SET_REG.q($f3,LLIL_FLOAT_TO_INT.q(LLIL_CEIL.q(LLIL_REG.q($f5))))'), + # ceil.l.s $f2, $f4 -- long results are unpredictable in FR=0 + ('mips32', b'\x46\x00\x20\x8a', 'LLIL_UNKNOWN()'), + # ceil.l.d $f2, $f4 -- even register numbers do not make long results valid in FR=0 + ('mipsel32', b'\x8a\x20\x20\x46', 'LLIL_UNKNOWN()'), + # floor.w.s $f2, $f4 -- MIPS32 Release 6.06 p. 186: toward negative infinity, independent of FCSR.RM + ('mips32', b'\x46\x00\x20\x8f', 'LLIL_SET_REG.d($f2,LLIL_FLOAT_TO_INT.d(LLIL_FLOOR.d(LLIL_REG.d($f4))))'), + # floor.w.s $f2, $f4 -- little-endian encoding + ('mipsel32', b'\x8f\x20\x00\x46', 'LLIL_SET_REG.d($f2,LLIL_FLOAT_TO_INT.d(LLIL_FLOOR.d(LLIL_REG.d($f4))))'), + # floor.w.s $f31, $f31 -- in-place conversion reads and writes only a word in FR=1 + ('mipsel64', b'\xcf\xff\x00\x46', 'LLIL_SET_REG.d($f31,LLIL_FLOAT_TO_INT.d(LLIL_FLOOR.d(LLIL_REG.d($f31))))'), + # floor.w.d $f2, $f4 -- FR=0 reads an even/odd double source pair + ('mips32', b'\x46\x20\x20\x8f', 'LLIL_SET_REG.d($f2,LLIL_FLOAT_TO_INT.d(LLIL_FLOOR.q(LLIL_REG_SPLIT.d($f5,$f4))))'), + # floor.w.d $f2, $f4 -- little-endian FR=0 has the same register-pair ordering + ('mipsel32', b'\x8f\x20\x20\x46', 'LLIL_SET_REG.d($f2,LLIL_FLOAT_TO_INT.d(LLIL_FLOOR.q(LLIL_REG_SPLIT.d($f5,$f4))))'), + # floor.w.d $f2, $f4 -- FR=1 reads a double but writes a 32-bit integer + ('mipsel64', b'\x8f\x20\x20\x46', 'LLIL_SET_REG.d($f2,LLIL_FLOAT_TO_INT.d(LLIL_FLOOR.q(LLIL_REG.q($f4))))'), + # floor.w.d $f3, $f4 -- an odd word destination is valid in FR=0 + ('mipsel32', b'\xcf\x20\x20\x46', 'LLIL_SET_REG.d($f3,LLIL_FLOAT_TO_INT.d(LLIL_FLOOR.q(LLIL_REG_SPLIT.d($f5,$f4))))'), + # floor.w.d $f3, $f5 -- an odd FR=0 double source is unpredictable + ('mipsel32', b'\xcf\x28\x20\x46', 'LLIL_UNKNOWN()'), + # floor.w.d $f3, $f5 -- odd double sources are valid in FR=1 + ('mipsel64', b'\xcf\x28\x20\x46', 'LLIL_SET_REG.d($f3,LLIL_FLOAT_TO_INT.d(LLIL_FLOOR.q(LLIL_REG.q($f5))))'), + # floor.w.d $f5, $f4 -- read the pair before overwriting its high word + ('mipsel32', b'\x4f\x21\x20\x46', 'LLIL_SET_REG.d($f5,LLIL_FLOAT_TO_INT.d(LLIL_FLOOR.q(LLIL_REG_SPLIT.d($f5,$f4))))'), + # floor.l.s $f2, $f4 -- MIPS32 Release 6.06 p. 185: toward negative infinity to a signed long + ('mips64', b'\x46\x00\x20\x8b', 'LLIL_SET_REG.q($f2,LLIL_FLOAT_TO_INT.q(LLIL_FLOOR.d(LLIL_REG.d($f4))))'), + # floor.l.s $f2, $f4 -- little-endian encoding + ('mipsel64', b'\x8b\x20\x00\x46', 'LLIL_SET_REG.q($f2,LLIL_FLOAT_TO_INT.q(LLIL_FLOOR.d(LLIL_REG.d($f4))))'), + # floor.l.d $f2, $f4 -- double source and 64-bit integer result + ('mips64', b'\x46\x20\x20\x8b', 'LLIL_SET_REG.q($f2,LLIL_FLOAT_TO_INT.q(LLIL_FLOOR.q(LLIL_REG.q($f4))))'), + # floor.l.d $f3, $f5 -- odd source and destination are valid in FR=1 + ('mipsel64', b'\xcb\x28\x20\x46', 'LLIL_SET_REG.q($f3,LLIL_FLOAT_TO_INT.q(LLIL_FLOOR.q(LLIL_REG.q($f5))))'), + # floor.l.s $f2, $f4 -- long results are unpredictable in FR=0 + ('mips32', b'\x46\x00\x20\x8b', 'LLIL_UNKNOWN()'), + # floor.l.d $f2, $f4 -- even register numbers do not make long results valid in FR=0 + ('mipsel32', b'\x8b\x20\x20\x46', 'LLIL_UNKNOWN()'), ] import sys @@ -160,6 +734,35 @@ from binaryninja.enums import Endianness, LowLevelILOperation +# These terminate the function themselves, so lift the instruction pair directly +# instead of appending and stripping a synthetic return as instr_to_il does. +terminating_test_cases = [ + # jr $t0; cfc1 $t0, $fcr31 -- preserve an indirect jump target + ('mips32', b'\x01\x00\x00\x08\x44\x48\xf8\x00', 'LLIL_SET_REG.d(temp1,LLIL_REG.d($t0)); LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr31)]); LLIL_SET_REG.d($t0,LLIL_REG.d(temp0)); LLIL_JUMP(LLIL_REG.d(temp1))'), + # jr $ra; cfc1 $ra, $fcr31 -- preserve a return target + ('mips32', b'\x03\xe0\x00\x08\x44\x5f\xf8\x00', 'LLIL_SET_REG.d(temp1,LLIL_REG.d($ra)); LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr31)]); LLIL_SET_REG.d($ra,LLIL_REG.d(temp0)); LLIL_RET(LLIL_REG.d(temp1))'), + # jr $t0; cfc1 $t0, $fcr31 -- little-endian full-width jump target + ('mipsel64', b'\x08\x00\x00\x01\x00\xf8\x48\x44', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($t0)); LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr31)]); LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_REG.d(temp0))); LLIL_JUMP(LLIL_REG.q(temp1))'), + # jr $ra; cfc1 $ra, $fcr31 -- little-endian full-width return target + ('mipsel64', b'\x08\x00\xe0\x03\x00\xf8\x5f\x44', 'LLIL_SET_REG.q(temp1,LLIL_REG.q($ra)); LLIL_INTRINSIC([temp0],moveControlWordFromCoprocessor1,[LLIL_REG.d($fcr31)]); LLIL_SET_REG.q($ra,LLIL_SX.q(LLIL_REG.d(temp0))); LLIL_RET(LLIL_REG.q(temp1))'), + # jr $t0; pcpyh $t0, $t1 -- the snapshot must survive a slot that uses both temp0 and temp1 + ('r5900l', b'\x08\x00\x00\x01\xe9\x46\x09\x70', + 'LLIL_SET_REG.o(temp2,LLIL_REG.o($t0)); ' + 'LLIL_SET_REG.q(temp0,LLIL_REG.w($t1)); ' + 'LLIL_SET_REG.q(temp1,LLIL_LSR.w(LLIL_REG.o($t1),LLIL_CONST.b(0x40))); ' + 'LLIL_SET_REG.q(temp0,LLIL_OR.q(LLIL_REG.q(temp0),LLIL_LSL.q(LLIL_REG.q(temp0),LLIL_CONST.b(0x10)))); ' + 'LLIL_SET_REG.q(temp1,LLIL_OR.q(LLIL_REG.q(temp1),LLIL_LSL.q(LLIL_REG.q(temp1),LLIL_CONST.b(0x10)))); ' + 'LLIL_SET_REG.q(temp0,LLIL_OR.q(LLIL_REG.q(temp0),LLIL_LSL.q(LLIL_REG.q(temp0),LLIL_CONST.b(0x10)))); ' + 'LLIL_SET_REG.q(temp1,LLIL_OR.q(LLIL_REG.q(temp1),LLIL_LSL.q(LLIL_REG.q(temp1),LLIL_CONST.b(0x10)))); ' + 'LLIL_SET_REG.q(temp0,LLIL_OR.q(LLIL_REG.q(temp0),LLIL_LSL.q(LLIL_REG.q(temp0),LLIL_CONST.b(0x10)))); ' + 'LLIL_SET_REG.q(temp1,LLIL_OR.q(LLIL_REG.q(temp1),LLIL_LSL.q(LLIL_REG.q(temp1),LLIL_CONST.b(0x10)))); ' + 'LLIL_SET_REG.q(temp0,LLIL_OR.q(LLIL_REG.q(temp0),LLIL_LSL.q(LLIL_REG.q(temp0),LLIL_CONST.b(0x10)))); ' + 'LLIL_SET_REG.q(temp1,LLIL_OR.q(LLIL_REG.q(temp1),LLIL_LSL.q(LLIL_REG.q(temp1),LLIL_CONST.b(0x10)))); ' + 'LLIL_SET_REG.o($t0,LLIL_OR.o(LLIL_REG.q(temp0),LLIL_LSL.o(LLIL_REG.q(temp1),LLIL_CONST.b(0x40)))); ' + 'LLIL_JUMP(LLIL_REG.d(temp2))'), +] + + def il2str(il): sz_lookup = {1: '.b', 2: '.w', 4: '.d', 8: '.q', 16: '.o'} if isinstance(il, lowlevelil.LowLevelILInstruction): @@ -201,6 +804,14 @@ def instr_to_il(data, arch_name): return '; '.join(result[:-3]) +def instruction_pair_to_il(data, arch_name): + arch = binaryninja.Architecture[arch_name] + il = lowlevelil.LowLevelILFunction(arch) + arch.get_instruction_low_level_il(data, 0, il) + il.finalize() + return '; '.join(il2str(il[i]) for i in range(len(il))) + + def il_str_to_tree(ilstr): result = '' depth = 0 @@ -225,14 +836,14 @@ def fail_test(message): raise AssertionError(message) -def run_all_tests(): - for test_i, (arch_name, data, expected) in enumerate(test_cases): +def run_tests(cases, lift): + for test_i, (arch_name, data, expected) in enumerate(cases): if '?' in expected: fail_test( 'INVALID EXPECTED LLIL AT TEST %d!\n\t arch: %s\n\t input: %s\n\texpected: %s' % (test_i, arch_name, data.hex(), expected)) - actual = instr_to_il(data, arch_name) + actual = lift(data, arch_name) if '?' in actual: fail_test( 'INVALID ACTUAL LLIL AT TEST %d!\n\t arch: %s\n\t input: %s\n\t actual: %s\n\t tree:\n%s' @@ -244,6 +855,11 @@ def run_all_tests(): % (test_i, arch_name, data.hex(), expected, actual, il_str_to_tree(actual))) +def run_all_tests(): + run_tests(test_cases, instr_to_il) + run_tests(terminating_test_cases, instruction_pair_to_il) + + def test_all(): run_all_tests()