diff --git a/arch/mips/arch_mips.cpp b/arch/mips/arch_mips.cpp index 0e4dc9ad7b..3a4229294a 100644 --- a/arch/mips/arch_mips.cpp +++ b/arch/mips/arch_mips.cpp @@ -1072,6 +1072,10 @@ class MipsArchitecture: public Architecture return "moveToCoprocessor2"; case MIPS_INTRIN_MTC_UNIMPLEMENTED: return "moveToCoprocessorUnimplemented"; + case MIPS_INTRIN_MTC1_UNPREDICTABLE_HIGH_WORD: + return "_mtc1UnpredictableHighWord"; + case MIPS_INTRIN_MADD_PS: + return "_madd_ps"; case MIPS_INTRIN_DMFC0: return "moveDwordFromCoprocessor0"; case MIPS_INTRIN_DMFC2: @@ -1193,6 +1197,8 @@ class MipsArchitecture: public Architecture MIPS_INTRIN_MFC_UNIMPLEMENTED, MIPS_INTRIN_MTC0, MIPS_INTRIN_MTC_UNIMPLEMENTED, + MIPS_INTRIN_MTC1_UNPREDICTABLE_HIGH_WORD, + MIPS_INTRIN_MADD_PS, MIPS_INTRIN_DMFC0, MIPS_INTRIN_DMFC_UNIMPLEMENTED, MIPS_INTRIN_DMTC0, @@ -1304,6 +1310,12 @@ class MipsArchitecture: public Architecture NameAndType("selector", Type::IntegerType(4, false)), NameAndType("value", Type::IntegerType(8, false)), }; + case MIPS_INTRIN_MADD_PS: + return { + NameAndType("fr", Type::IntegerType(8, false)), + NameAndType("fs", Type::IntegerType(8, false)), + NameAndType("ft", Type::IntegerType(8, false)), + }; case MIPS_INTRIN_SYNC: return { NameAndType("stype", Type::IntegerType(4, false)), @@ -1446,9 +1458,11 @@ class MipsArchitecture: public Architecture return {Type::IntegerType(8, false)}; case MIPS_INTRIN_MFC0: case MIPS_INTRIN_MFC_UNIMPLEMENTED: + 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: return {Type::IntegerType(8, false)}; case MIPS_INTRIN_HWR0: case MIPS_INTRIN_HWR1: @@ -2738,7 +2752,7 @@ class MipsArchitecture: public Architecture class MipsO32CallingConvention: public CallingConvention { public: - MipsO32CallingConvention(Architecture* arch): CallingConvention(arch, "o32") + MipsO32CallingConvention(Architecture* arch, const std::string& name = "o32"): CallingConvention(arch, name) { } @@ -2752,6 +2766,11 @@ class MipsO32CallingConvention: public CallingConvention return REG_V1; } + virtual std::optional GetReturnedIndirectReturnValuePointer() override + { + return Variable::Register(REG_V0); + } + virtual vector GetIntegerArgumentRegisters() override { return vector{ REG_A0, REG_A1, REG_A2, REG_A3 }; @@ -2796,6 +2815,217 @@ class MipsO32CallingConvention: public CallingConvention } }; +// o32 maps arguments through a shared, naturally aligned four-word argument area. Only the first two +// leading floating-point arguments use FPRs, so the default independent register allocators cannot model it. +class MipsO32HardFloatCallingConvention: public MipsO32CallingConvention +{ +public: + MipsO32HardFloatCallingConvention(Architecture* arch): MipsO32CallingConvention(arch, "o32-hard-float") + { + } + + virtual vector GetFloatArgumentRegisters() override + { + return vector{ FPREG_F12, FPREG_F14 }; + } + + virtual bool AreArgumentRegistersSharedIndex() override + { + return true; + } + + virtual uint32_t GetFloatReturnValueRegister() override + { + return FPREG_F0; + } + + virtual vector GetCallerSavedRegisters() override + { + vector result = MipsO32CallingConvention::GetCallerSavedRegisters(); + const uint32_t floatRegisters[] = { + FPREG_F0, FPREG_F1, FPREG_F2, FPREG_F3, FPREG_F4, FPREG_F5, FPREG_F6, FPREG_F7, + FPREG_F8, FPREG_F9, FPREG_F10, FPREG_F11, FPREG_F12, FPREG_F13, FPREG_F14, FPREG_F15, + FPREG_F16, FPREG_F17, FPREG_F18, FPREG_F19 + }; + result.insert(result.end(), std::begin(floatRegisters), std::end(floatRegisters)); + return result; + } + + virtual vector GetCalleeSavedRegisters() override + { + vector result = MipsO32CallingConvention::GetCalleeSavedRegisters(); + const uint32_t floatRegisters[] = { + FPREG_F20, FPREG_F21, FPREG_F22, FPREG_F23, FPREG_F24, FPREG_F25, + FPREG_F26, FPREG_F27, FPREG_F28, FPREG_F29, FPREG_F30, FPREG_F31 + }; + result.insert(result.end(), std::begin(floatRegisters), std::end(floatRegisters)); + return result; + } + + virtual bool IsReturnTypeRegisterCompatible(BinaryView*, Type* type) override + { + if (type && type->IsFloat()) + return type->GetWidth() == 4 || type->GetWidth() == 8; + return DefaultIsReturnTypeRegisterCompatible(type); + } + + virtual ValueLocation GetReturnValueLocation(BinaryView* view, const ReturnValue& returnValue) override + { + Ref type = returnValue.type.GetValue(); + if (!type || type->IsVoid()) + return ValueLocation(); + + if (type->GetClass() == NamedTypeReferenceClass && type->GetWidth() == 0) + return GetDefaultReturnValueLocation(view, returnValue); + + if (type->IsFloat()) + { + if (type->GetWidth() == 4) + return ValueLocation(Variable::Register(FPREG_F0)); + if (type->GetWidth() == 8) + { + return ValueLocation({ + {Variable::Register(FPREG_F0), 0, 4}, + {Variable::Register(FPREG_F1), 4, 4} + }); + } + } + + return GetDefaultReturnValueLocation(view, returnValue); + } + + virtual vector GetParameterLocations(BinaryView*, const std::optional& returnValue, + const vector& params, + const std::optional>& permittedRegs = std::nullopt) override + { + const uint32_t integerRegisters[] = { REG_A0, REG_A1, REG_A2, REG_A3 }; + const uint32_t floatRegisters[][2] = { + { FPREG_F12, FPREG_F13 }, + { FPREG_F14, FPREG_F15 } + }; + + vector result; + result.reserve(params.size()); + + uint64_t argumentOffset = 0; + uint64_t stackOffset = 16; + bool argumentRegistersAvailable = true; + bool leadingFloatArguments = true; + size_t leadingFloatCount = 0; + + if (returnValue.has_value() && returnValue->indirect) + { + argumentOffset = 4; + leadingFloatArguments = false; + } + + for (const auto& param : params) + { + Ref type = param.type.GetValue(); + uint64_t width = type ? type->GetWidth() : 4; + bool indirect = param.locationSource == PassByReferenceLocationSource; + if (indirect) + width = 4; + + uint64_t alignment = indirect ? 4 : (type ? type->GetAlignment() : 4); + if (alignment < 4) + alignment = 4; + if (alignment > 8) + alignment = 8; + if (argumentOffset % alignment != 0) + argumentOffset += alignment - (argumentOffset % alignment); + + uint64_t passedWidth = std::max(width, 4); + if (passedWidth % 4 != 0) + passedWidth += 4 - (passedWidth % 4); + + bool isFloat = type && type->IsFloat() && !indirect; + bool useFloatRegister = leadingFloatArguments && leadingFloatCount < 2 && isFloat + && (width == 4 || width == 8); + + if (param.locationSource == CustomLocationSource) + { + result.push_back(param.location); + } + else if (useFloatRegister) + { + const size_t registerCount = width / 4; + bool registersPermitted = argumentRegistersAvailable; + for (size_t i = 0; i < registerCount; i++) + { + if (permittedRegs.has_value() && !permittedRegs->contains(floatRegisters[leadingFloatCount][i])) + registersPermitted = false; + } + + if (registersPermitted) + { + if (registerCount == 1) + result.emplace_back(Variable::Register(floatRegisters[leadingFloatCount][0])); + else + { + result.emplace_back(ValueLocation({ + {Variable::Register(floatRegisters[leadingFloatCount][0]), 0, 4}, + {Variable::Register(floatRegisters[leadingFloatCount][1]), 4, 4} + })); + } + } + else + { + if (stackOffset % alignment != 0) + stackOffset += alignment - (stackOffset % alignment); + result.emplace_back(Variable::StackOffset(stackOffset)); + stackOffset += passedWidth; + argumentRegistersAvailable = false; + } + } + else + { + vector components; + bool registersPermitted = argumentRegistersAvailable; + for (uint64_t offset = 0; offset < passedWidth; offset += 4) + { + uint64_t componentOffset = argumentOffset + offset; + uint64_t componentSize = std::min(4, width > offset ? width - offset : 4); + if (componentOffset < 16) + { + uint32_t reg = integerRegisters[componentOffset / 4]; + if (permittedRegs.has_value() && !permittedRegs->contains(reg)) + registersPermitted = false; + components.emplace_back(Variable::Register(reg), offset, componentSize); + } + else + { + components.emplace_back(Variable::StackOffset(componentOffset), offset, componentSize); + } + } + + if (!registersPermitted) + { + if (stackOffset % alignment != 0) + stackOffset += alignment - (stackOffset % alignment); + result.emplace_back(Variable::StackOffset(stackOffset), indirect); + stackOffset += passedWidth; + argumentRegistersAvailable = false; + } + else if (components.size() == 1) + result.emplace_back(components[0].variable, indirect); + else + result.emplace_back(std::move(components), indirect); + } + + argumentOffset += passedWidth; + stackOffset = std::max(stackOffset, argumentOffset); + if (useFloatRegister) + leadingFloatCount++; + else + leadingFloatArguments = false; + } + + return result; + } +}; + + class MipsPS2CallingConvention: public CallingConvention { public: @@ -3753,7 +3983,6 @@ static Ref ElfFlagsRecognize(BinaryView* view, Metadata* metadata) // This needs to be after the R5900 check above or all R5900 binaries will load as MIPS III if ((flagsValue & EF_MIPS_ARCH) == EF_MIPS_ARCH_3) return Platform::GetByName(endianness == BigEndian ? "linux-mips3" : "linux-mipsel3"); - return nullptr; } @@ -3799,6 +4028,8 @@ extern "C" /* calling conventions */ MipsO32CallingConvention* o32LE = new MipsO32CallingConvention(mipsel); MipsO32CallingConvention* o32BE = new MipsO32CallingConvention(mipseb); + MipsO32HardFloatCallingConvention* o32HardFloatLE = new MipsO32HardFloatCallingConvention(mipsel); + MipsO32HardFloatCallingConvention* o32HardFloatBE = new MipsO32HardFloatCallingConvention(mipseb); MipsN64CallingConvention* n32LE = new MipsN64CallingConvention(mips64el, "n32"); MipsN64CallingConvention* n32BE = new MipsN64CallingConvention(mips64eb, "n32"); MipsN64CallingConvention* n64LE = new MipsN64CallingConvention(mips64el); @@ -3812,6 +4043,8 @@ extern "C" mipseb->SetDefaultCallingConvention(o32BE); mipsel->RegisterCallingConvention(o32LE); mipsel->SetDefaultCallingConvention(o32LE); + mipseb->RegisterCallingConvention(o32HardFloatBE); + mipsel->RegisterCallingConvention(o32HardFloatLE); mips3->RegisterCallingConvention(o32BE); mips3->SetDefaultCallingConvention(o32BE); mips3el->RegisterCallingConvention(o32LE); diff --git a/arch/mips/il.cpp b/arch/mips/il.cpp index 1d10d9dd5c..d2d31646be 100644 --- a/arch/mips/il.cpp +++ b/arch/mips/il.cpp @@ -1078,6 +1078,130 @@ static void SignExtendHiLo(LowLevelILFunction& il, size_t registerSize) } } +static ExprId GetFloatingComparePredicate(LowLevelILFunction& il, Operation operation, + size_t size, ExprId lhs, ExprId rhs) +{ + // MIPS32 Release 6.06, pp. 110-113, and MIPS64 Release 6.06, + // pp. 129-132: cond[2:0] select less, equal, and unordered. cond[3] + // only controls QNaN exception signaling, which LLIL does not model. + switch (operation) + { + case MIPS_C_F_S: + case MIPS_C_F_D: + case MIPS_C_F_PS: + case MIPS_C_SF_S: + case MIPS_C_SF_D: + case MIPS_C_SF_PS: + return il.Const(0, 0); + case MIPS_C_UN_S: + case MIPS_C_UN_D: + case MIPS_C_UN_PS: + case MIPS_C_NGLE_S: + case MIPS_C_NGLE_D: + case MIPS_C_NGLE_PS: + return il.FloatCompareUnordered(size, lhs, rhs); + case MIPS_C_EQ_S: + case MIPS_C_EQ_D: + case MIPS_C_EQ_PS: + case MIPS_C_SEQ_S: + case MIPS_C_SEQ_D: + case MIPS_C_SEQ_PS: + return il.FloatCompareEqual(size, lhs, rhs); + case MIPS_C_UEQ_S: + case MIPS_C_UEQ_D: + case MIPS_C_UEQ_PS: + case MIPS_C_NGL_S: + case MIPS_C_NGL_D: + case MIPS_C_NGL_PS: + return il.Or(0, il.FloatCompareUnordered(size, lhs, rhs), + il.FloatCompareEqual(size, lhs, rhs)); + case MIPS_C_OLT_S: + case MIPS_C_OLT_D: + case MIPS_C_OLT_PS: + case MIPS_C_LT_S: + case MIPS_C_LT_D: + case MIPS_C_LT_PS: + return il.FloatCompareLessThan(size, lhs, rhs); + case MIPS_C_ULT_S: + case MIPS_C_ULT_D: + case MIPS_C_ULT_PS: + case MIPS_C_NGE_S: + case MIPS_C_NGE_D: + case MIPS_C_NGE_PS: + return il.Or(0, il.FloatCompareUnordered(size, lhs, rhs), + il.FloatCompareLessThan(size, lhs, rhs)); + case MIPS_C_OLE_S: + case MIPS_C_OLE_D: + case MIPS_C_OLE_PS: + case MIPS_C_LE_S: + case MIPS_C_LE_D: + case MIPS_C_LE_PS: + return il.FloatCompareLessEqual(size, lhs, rhs); + case MIPS_C_ULE_S: + case MIPS_C_ULE_D: + case MIPS_C_ULE_PS: + case MIPS_C_NGT_S: + case MIPS_C_NGT_D: + case MIPS_C_NGT_PS: + return il.Or(0, il.FloatCompareUnordered(size, lhs, rhs), + il.FloatCompareLessEqual(size, lhs, rhs)); + default: + return il.Undefined(); + } +} + +static bool IsDoubleFloatingCompare(Operation operation) +{ + switch (operation) + { + case MIPS_C_F_D: + case MIPS_C_UN_D: + case MIPS_C_EQ_D: + case MIPS_C_UEQ_D: + case MIPS_C_OLT_D: + case MIPS_C_ULT_D: + case MIPS_C_OLE_D: + case MIPS_C_ULE_D: + case MIPS_C_SF_D: + case MIPS_C_NGLE_D: + case MIPS_C_SEQ_D: + case MIPS_C_NGL_D: + case MIPS_C_LT_D: + case MIPS_C_NGE_D: + case MIPS_C_LE_D: + case MIPS_C_NGT_D: + return true; + default: + return false; + } +} + +static bool IsPairedSingleFloatingCompare(Operation operation) +{ + switch (operation) + { + case MIPS_C_F_PS: + case MIPS_C_UN_PS: + case MIPS_C_EQ_PS: + case MIPS_C_UEQ_PS: + case MIPS_C_OLT_PS: + case MIPS_C_ULT_PS: + case MIPS_C_OLE_PS: + case MIPS_C_ULE_PS: + case MIPS_C_SF_PS: + case MIPS_C_NGLE_PS: + case MIPS_C_SEQ_PS: + case MIPS_C_NGL_PS: + case MIPS_C_LT_PS: + case MIPS_C_NGE_PS: + case MIPS_C_LE_PS: + case MIPS_C_NGT_PS: + return true; + default: + return false; + } +} + #define DEFINE_HILO1(op) \ auto hi = REG_HI; \ auto lo = REG_LO; \ @@ -1484,14 +1608,40 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu il.AddInstruction(MoveFromCoprocessor(0, il, 4, op1.reg, op2.immediate, op3.immediate, decomposeFlags)); 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. if (version == MIPS_R5900) { + // R5900 EE Core Instruction Set Manual, MFC1 (p. 364), + // sign-extends the FPR word to the low 64 bits of the GPR. il.AddInstruction( SetRegisterOrNop(il, 4, 8, op1.reg, il.Register(4, op2.reg))); - break; } else - il.AddInstruction(MoveFromCoprocessor(1, il, 4, op1.reg, op2.immediate, op3.immediate, decomposeFlags)); + il.AddInstruction(SetRegisterOrNop(il, 4, registerSize(op1), op1.reg, + il.Register(4, op2.reg))); + break; + case MIPS_DMFC1: + // MIPS64 Release 6.06, DMFC1 (p. 217), transfers all 64 FPR bits. + if (arch->GetRegisterInfo(op2.reg).size == 8 && registerSize(op1) >= 8) + il.AddInstruction(SetRegisterOrNop(il, 8, registerSize(op1), op1.reg, + il.Register(8, op2.reg))); + else + il.AddInstruction(il.Unknown()); + break; + case MIPS_MFHC1: + // MIPS32 Release 6.06, MFHC1 (p. 271), and MIPS64 Release + // 6.06, MFHC1 (p. 361), read bits 63:32 of ValueFPR(fs). + if (version == MIPS_R5900) + il.AddInstruction(il.Unknown()); + else if (arch->GetRegisterInfo(op2.reg).size == 8) + il.AddInstruction(SetRegisterOrNop(il, 4, registerSize(op1), op1.reg, + il.LowPart(4, il.LogicalShiftRight(8, il.Register(8, op2.reg), il.Const(1, 32))))); + else if ((op2.reg - FPREG_F0) & 1) + il.AddInstruction(il.Unknown()); + else + il.AddInstruction(SetRegisterOrNop(il, 4, registerSize(op1), op1.reg, + il.Register(4, op2.reg + 1))); break; case MIPS_DMFC2: il.AddInstruction(MoveFromCoprocessor(2, il, 8, op1.reg, op2.immediate, 0, decomposeFlags)); @@ -1506,16 +1656,59 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu il.AddInstruction(MoveToCoprocessor(0, il, 4, op2.immediate, op3.immediate, ReadILOperand(il, instr, 1, registerSize(op1)), decomposeFlags)); break; case MIPS_MTC1: + { + auto value = ReadILOperand(il, instr, 1, registerSize(op1), 4); + if (arch->GetRegisterInfo(op2.reg).size == 4) + { + // MIPS32 Release 6.06, MTC1 (p. 292), specifies a 32-bit + // StoreFPR. This is a complete write for modeled 32-bit FPRs. + il.AddInstruction(il.SetRegister(4, op2.reg, value)); + } + else + { + // MIPS64 Release 6.06, MTC1 (p. 385), defines FPR[fs][31:0] + // from GPR[rt][31:0], while FPR[fs][63:32] is UNPREDICTABLE. + il.AddInstruction(il.Intrinsic( + {RegisterOrFlag::Register(LLIL_TEMP(0))}, + MIPS_INTRIN_MTC1_UNPREDICTABLE_HIGH_WORD, {})); + il.AddInstruction(il.SetRegister(8, op2.reg, + il.Or(8, + il.ShiftLeft(8, + il.ZeroExtend(8, il.Register(4, LLIL_TEMP(0))), + il.Const(1, 32)), + il.ZeroExtend(8, value)))); + } + break; + } + case MIPS_DMTC1: + // MIPS64 Release 6.06, DMTC1 (p. 220), transfers all 64 GPR bits. + if (arch->GetRegisterInfo(op2.reg).size == 8 && registerSize(op1) >= 8) + il.AddInstruction(il.SetRegister(8, op2.reg, + ReadILOperand(il, instr, 1, registerSize(op1), 8))); + else + il.AddInstruction(il.Unknown()); + break; + case MIPS_MTHC1: + { + // MIPS32 Release 6.06, MTHC1 (p. 295), and MIPS64 Release + // 6.06, MTHC1 (p. 389), replace bits 63:32 of ValueFPR(fs). if (version == MIPS_R5900) + il.AddInstruction(il.Unknown()); + else if (arch->GetRegisterInfo(op2.reg).size == 8) { - il.AddInstruction( - il.SetRegister(4, op2.reg, - op1.reg == REG_ZERO ? il.Const(4, 0) : il.Register(4, op1.reg))); - break; + auto value = ReadILOperand(il, instr, 1, registerSize(op1), 4); + il.AddInstruction(il.SetRegister(8, op2.reg, + il.Or(8, + il.And(8, il.Register(8, op2.reg), il.Const(8, 0xffffffff)), + il.ShiftLeft(8, il.ZeroExtend(8, value), il.Const(1, 32))))); } + else if ((op2.reg - FPREG_F0) & 1) + il.AddInstruction(il.Unknown()); else - il.AddInstruction(MoveToCoprocessor(1, il, 4, op2.immediate, op3.immediate, ReadILOperand(il, instr, 1, registerSize(op1)), decomposeFlags)); + il.AddInstruction(il.SetRegister(4, op2.reg + 1, + ReadILOperand(il, instr, 1, registerSize(op1), 4))); break; + } case MIPS_DMTC2: il.AddInstruction(MoveToCoprocessor(2, il, 8, op2.immediate, 0, ReadILOperand(il, instr, 1, registerSize(op1)), decomposeFlags)); break; @@ -1525,6 +1718,58 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu case MIPS_MOVE: il.AddInstruction(SetRegisterOrNop(il, registerSize(op1), registerSize(op1), op1.reg, ReadILOperand(il, instr, 2, registerSize(op2)))); break; + case MIPS_MOVF: + case MIPS_MOVT: + { + // MIPS32 Release 6.06, MOVF (p. 276) and MOVT (p. 281), and + // MIPS64 Release 6.06 (pp. 367 and 373): conditionally copy rs + // according to FCC[cc], preserving rd when the condition is false. + auto condition = instr.operation == MIPS_MOVT ? + il.Flag(op3.reg) : il.Not(0, il.Flag(op3.reg)); + il.AddInstruction(il.If(condition, trueCode, falseCode)); + il.MarkLabel(trueCode); + il.AddInstruction(SetRegisterOrNop(il, registerSize(op2), registerSize(op1), + op1.reg, ReadILOperand(il, instr, 2, registerSize(op2)))); + il.MarkLabel(falseCode); + break; + } + case MIPS_MOVF_S: + case MIPS_MOVT_S: + { + // MIPS32 Release 6.06, MOVF.fmt (pp. 277-278) and MOVT.fmt + // (pp. 282-283), and MIPS64 Release 6.06 (pp. 368-369 and + // 374-375): copy fs only when FCC[cc] has the selected polarity. + auto condition = instr.operation == MIPS_MOVT_S ? + il.Flag(op3.reg) : il.Not(0, il.Flag(op3.reg)); + il.AddInstruction(il.If(condition, trueCode, falseCode)); + il.MarkLabel(trueCode); + il.AddInstruction(il.SetRegister(4, op1.reg, il.Register(4, op2.reg))); + il.MarkLabel(falseCode); + break; + } + case MIPS_MOVF_D: + case MIPS_MOVT_D: + { + if (arch->GetRegisterInfo(op1.reg).size != 8 && + (((op1.reg - FPREG_F0) & 1) || ((op2.reg - FPREG_F0) & 1))) + { + // In FR=0, fmt=D requires even-numbered FPR pair roots. + il.AddInstruction(il.Unknown()); + break; + } + + auto condition = instr.operation == MIPS_MOVT_D ? + il.Flag(op3.reg) : il.Not(0, il.Flag(op3.reg)); + il.AddInstruction(il.If(condition, trueCode, falseCode)); + il.MarkLabel(trueCode); + if (arch->GetRegisterInfo(op1.reg).size == 8) + il.AddInstruction(il.SetRegister(8, op1.reg, il.Register(8, op2.reg))); + else + il.AddInstruction(il.SetRegisterSplit(4, op1.reg + 1, op1.reg, + il.RegisterSplit(4, op2.reg + 1, op2.reg))); + il.MarkLabel(falseCode); + break; + } case MIPS_MOVN: il.AddInstruction(il.If(il.CompareNotEqual(registerSize(op3), ReadILOperand(il, instr, 3, registerSize(op3)), il.Const(registerSize(op3), 0)), trueCode, falseCode)); il.MarkLabel(trueCode); @@ -1772,26 +2017,25 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu break; } case MIPS_SWC1: - if (version == MIPS_R5900) + il.AddInstruction(il.Store(4, GetILOperandMemoryAddress(il, op2, addrSize), + il.Register(4, op1.reg))); + break; + case MIPS_SDC1: + if (version == MIPS_32) { - il.AddInstruction( - il.Store(4, GetILOperandMemoryAddress(il, op2, addrSize), - il.Register(4, op1.reg))); + // In FR=0 mode, an even FPR holds the low half and the following odd FPR holds the high half. + if ((op1.reg - FPREG_F0) & 1) + il.AddInstruction(il.Unknown()); + else + il.AddInstruction(il.Store(8, GetILOperandMemoryAddress(il, op2, addrSize), + il.RegisterSplit(4, op1.reg + 1, op1.reg))); } else { - il.AddInstruction(MoveFromCoprocessor(1, il, 4, LLIL_TEMP(0), op1.immediate, 0, decomposeFlags)); - il.AddInstruction(WriteILOperand(il, instr, 1, addrSize, il.Register(4, LLIL_TEMP(0)))); + il.AddInstruction(il.Store(8, GetILOperandMemoryAddress(il, op2, addrSize), + il.Register(8, op1.reg))); } break; - case MIPS_SDC1: - il.AddInstruction( - il.Store(4, GetILOperandMemoryAddress(il, op2, addrSize), - il.LowPart(4, il.Register(8, op1.reg)))); - il.AddInstruction( - il.Store(4, GetILOperandMemoryAddress(il, op2, addrSize, 4), - il.LogicalShiftRight(4, il.Register(8, op1.reg), il.Const(4, 32)))); - break; case MIPS_SDXC1: il.AddInstruction( il.Store(4, GetILOperandMemoryAddress(il, op2, addrSize), @@ -2306,6 +2550,101 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu else il.AddInstruction(SetRegisterOrNop(il, 8, registerSize(op1), op1.reg, il.FloatConvert(4, il.Register(registerSize(op2), op2.reg)))); break; + case MIPS_C_UEQ_S: + case MIPS_C_UEQ_D: + case MIPS_C_OLT_S: + case MIPS_C_OLT_D: + case MIPS_C_ULT_S: + case MIPS_C_ULT_D: + case MIPS_C_OLE_S: + case MIPS_C_OLE_D: + case MIPS_C_ULE_S: + case MIPS_C_ULE_D: + case MIPS_C_NGLE_S: + case MIPS_C_NGLE_D: + case MIPS_C_NGL_S: + case MIPS_C_NGL_D: + case MIPS_C_NGE_S: + case MIPS_C_NGE_D: + case MIPS_C_NGT_S: + case MIPS_C_NGT_D: + case MIPS_C_F_PS: + case MIPS_C_UN_PS: + case MIPS_C_EQ_PS: + case MIPS_C_UEQ_PS: + case MIPS_C_OLT_PS: + case MIPS_C_ULT_PS: + case MIPS_C_OLE_PS: + case MIPS_C_ULE_PS: + case MIPS_C_SF_PS: + case MIPS_C_NGLE_PS: + case MIPS_C_SEQ_PS: + case MIPS_C_NGL_PS: + case MIPS_C_LT_PS: + case MIPS_C_NGE_PS: + case MIPS_C_LE_PS: + case MIPS_C_NGT_PS: + { + uint32_t destinationFlag = op1.operandClass == FLAG ? op1.reg : FPCCREG_FCC0; + auto& lhsOperand = op1.operandClass == FLAG ? op2 : op1; + auto& rhsOperand = op1.operandClass == FLAG ? op3 : op2; + + if (IsPairedSingleFloatingCompare(instr.operation)) + { + // C.cond.PS writes the low-lane result to FCC[cc] and the + // high-lane result to FCC[cc+1]. MIPS32/64 Release 6.06, + // pp. 110-113/129-132, requires FR=1 and an even cc. + if (arch->GetRegisterInfo(lhsOperand.reg).size != 8 || + ((destinationFlag - FPCCREG_FCC0) & 1)) + { + il.AddInstruction(il.Unknown()); + break; + } + + auto lhs = il.Register(8, lhsOperand.reg); + auto rhs = il.Register(8, rhsOperand.reg); + il.AddInstruction(il.SetFlag(destinationFlag, + GetFloatingComparePredicate(il, instr.operation, 4, + il.LowPart(4, lhs), il.LowPart(4, rhs)))); + il.AddInstruction(il.SetFlag(destinationFlag + 1, + GetFloatingComparePredicate(il, instr.operation, 4, + il.LowPart(4, il.LogicalShiftRight(8, lhs, il.Const(1, 32))), + il.LowPart(4, il.LogicalShiftRight(8, rhs, il.Const(1, 32)))))); + break; + } + + if (IsDoubleFloatingCompare(instr.operation)) + { + ExprId lhs; + ExprId rhs; + if (arch->GetRegisterInfo(lhsOperand.reg).size == 8) + { + lhs = il.Register(8, lhsOperand.reg); + rhs = il.Register(8, rhsOperand.reg); + } + else + { + // In FR=0, fmt=D uses an even/odd FPR pair. Odd roots are + // UNPREDICTABLE (MIPS32 Release 6.06, p. 112). + if (((lhsOperand.reg - FPREG_F0) & 1) || ((rhsOperand.reg - FPREG_F0) & 1)) + { + il.AddInstruction(il.Unknown()); + break; + } + lhs = il.RegisterSplit(4, lhsOperand.reg + 1, lhsOperand.reg); + rhs = il.RegisterSplit(4, rhsOperand.reg + 1, rhsOperand.reg); + } + il.AddInstruction(il.SetFlag(destinationFlag, + GetFloatingComparePredicate(il, instr.operation, 8, lhs, rhs))); + } + else + { + il.AddInstruction(il.SetFlag(destinationFlag, + GetFloatingComparePredicate(il, instr.operation, 4, + il.Register(4, lhsOperand.reg), il.Register(4, rhsOperand.reg)))); + } + break; + } case MIPS_C_F_S: case MIPS_C_F_D: case MIPS_C_SF_S: @@ -2457,74 +2796,6 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu il.Register(8, op1.reg), il.Register(8, op2.reg)))); } break; - case MIPS_C_NGE_S: - if (op1.operandClass == FLAG) - { - il.AddInstruction(il.SetFlag(op1.reg, il.Not(4, il.FloatCompareGreaterEqual(4, - il.Register(4, op2.reg), il.Register(4, op3.reg))))); - } - else - { - il.AddInstruction(il.SetFlag(FPCCREG_FCC0, il.Not(4, il.FloatCompareGreaterEqual(4, - il.Register(4, op1.reg), il.Register(4, op2.reg))))); - } - break; - case MIPS_C_NGE_D: - if (op1.operandClass == FLAG) - { - if (registerSize(op2) < 8) - il.AddInstruction(il.SetFlag(op1.reg, il.Not(8, il.FloatCompareGreaterEqual(8, - il.RegisterSplit(4, op2.reg | 1, op2.reg & (~1)), - il.RegisterSplit(4, op3.reg | 1, op3.reg & (~1)))))); - else - il.AddInstruction(il.SetFlag(op1.reg, il.Not(8, il.FloatCompareGreaterEqual(8, - il.Register(8, op2.reg), il.Register(8, op3.reg))))); - } - else - { - if (registerSize(op2) < 8) - il.AddInstruction(il.SetFlag(FPCCREG_FCC0, il.Not(8, il.FloatCompareGreaterEqual(8, - il.RegisterSplit(4, op1.reg | 1, op1.reg & (~1)), - il.RegisterSplit(4, op2.reg | 1, op2.reg & (~1)))))); - else - il.AddInstruction(il.SetFlag(FPCCREG_FCC0, il.Not(8, il.FloatCompareGreaterEqual(8, - il.Register(8, op1.reg), il.Register(8, op2.reg))))); - } - break; - case MIPS_C_NGT_S: - if (op1.operandClass == FLAG) - { - il.AddInstruction(il.SetFlag(op1.reg, il.Not(4, il.FloatCompareGreaterThan(4, - il.Register(4, op2.reg), il.Register(4, op3.reg))))); - } - else - { - il.AddInstruction(il.SetFlag(FPCCREG_FCC0, il.Not(4, il.FloatCompareGreaterThan(4, - il.Register(4, op1.reg), il.Register(4, op2.reg))))); - } - break; - case MIPS_C_NGT_D: - if (op1.operandClass == FLAG) - { - if (registerSize(op2) < 8) - il.AddInstruction(il.SetFlag(op1.reg, il.Not(8, il.FloatCompareGreaterThan(8, - il.RegisterSplit(4, op2.reg | 1, op2.reg & (~1)), - il.RegisterSplit(4, op3.reg | 1, op3.reg & (~1)))))); - else - il.AddInstruction(il.SetFlag(op1.reg, il.Not(8, il.FloatCompareGreaterThan(8, - il.Register(8, op2.reg), il.Register(8, op3.reg))))); - } - else - { - if (registerSize(op2) < 8) - il.AddInstruction(il.SetFlag(FPCCREG_FCC0, il.Not(8, il.FloatCompareGreaterEqual(8, - il.RegisterSplit(4, op1.reg | 1, op1.reg & (~1)), - il.RegisterSplit(4, op2.reg | 1, op2.reg & (~1)))))); - else - il.AddInstruction(il.SetFlag(FPCCREG_FCC0, il.Not(8, il.FloatCompareGreaterEqual(8, - il.Register(8, op1.reg), il.Register(8, op2.reg))))); - } - break; case MIPS_SYNC: { uint64_t stype = 0; @@ -3173,25 +3444,27 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu ReadILOperand(il, instr, 2, registerSize(op2)), decomposeFlags)); break; case MIPS_LDC1: - case MIPS_LDC2: - case MIPS_LDC3: - { - unsigned cop = 0; - switch (instr.operation) + if (version == MIPS_32) { - case MIPS_LDC1: cop = 1; break; - case MIPS_LDC2: cop = 2; break; - case MIPS_LDC3: cop = 3; break; - // default: il.Fail("Unhandled LDC1/2/3 instruction"); - default: break; + // Odd FPR pair roots are architecturally unpredictable in FR=0 mode. + if ((op1.reg - FPREG_F0) & 1) + il.AddInstruction(il.Unknown()); + else + il.AddInstruction(il.SetRegisterSplit(4, op1.reg + 1, op1.reg, + il.Load(8, GetILOperandMemoryAddress(il, op2, addrSize)))); } - if (version == MIPS_R5900 && instr.operation == MIPS_LDC1) - il.AddInstruction( - il.SetRegister(8, op1.reg, - il.Load(8, GetILOperandMemoryAddress(il, op2, addrSize)))); else - il.AddInstruction(MoveToCoprocessor(cop, il, 8, op1.reg, op1.immediate, - ReadILOperand(il, instr, 2, registerSize(op2)), decomposeFlags)); + { + il.AddInstruction(il.SetRegister(8, op1.reg, + il.Load(8, GetILOperandMemoryAddress(il, op2, addrSize)))); + } + break; + case MIPS_LDC2: + case MIPS_LDC3: + { + unsigned cop = instr.operation == MIPS_LDC2 ? 2 : 3; + il.AddInstruction(MoveToCoprocessor(cop, il, 8, op1.reg, op1.immediate, + ReadILOperand(il, instr, 2, registerSize(op2)), decomposeFlags)); break; } case MIPS_MFSA: @@ -3243,6 +3516,57 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu il.FloatMult(4, il.Register(4, op2.reg), il.Register(4, op3.reg))))); break; } + // MIPS32 Release 6.06, MADD.fmt (pp. 256-257), and MIPS64 + // Release 6.06 (pp. 347-348) specify two rounding steps: + // fd = round(round(fs * ft) + fr). Nested LLIL operations retain + // the required non-fused behavior. + il.AddInstruction(il.SetRegister(4, op1.reg, + il.FloatAdd(4, + il.FloatMult(4, il.Register(4, op3.reg), il.Register(4, op4.reg)), + il.Register(4, op2.reg)))); + break; + case MIPS_MADD_D: + { + if (arch->GetRegisterInfo(op1.reg).size == 8) + { + il.AddInstruction(il.SetRegister(8, op1.reg, + il.FloatAdd(8, + il.FloatMult(8, il.Register(8, op3.reg), il.Register(8, op4.reg)), + il.Register(8, op2.reg)))); + } + else + { + // In FR=0, every fmt=D operand names an even/odd FPR pair. + 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.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_MADD_PS: + { + // Paired-single is defined only for 64-bit FPRs in FR=1. Keep + // the two independently rounded lane operations in an intrinsic + // so packed arithmetic remains concise in higher-level IL. + if (arch->GetRegisterInfo(op1.reg).size != 8) + { + il.AddInstruction(il.Unknown()); + break; + } + il.AddInstruction(il.Intrinsic( + {RegisterOrFlag::Register(op1.reg)}, MIPS_INTRIN_MADD_PS, + {il.Register(8, op2.reg), il.Register(8, op3.reg), il.Register(8, op4.reg)})); + break; + } case MIPS_MSUB_S: if (version == MIPS_R5900) { @@ -3780,9 +4104,7 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu break; } - case MIPS_MFHC1: case MIPS_MFHC2: - case MIPS_MOVT: case MIPS_MULR: //unimplemented system functions @@ -3794,7 +4116,6 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu case MIPS_DERET: case MIPS_DRET: case MIPS_JALX: //Special instruction for switching to MIPS32/microMIPS32/MIPS16e - case MIPS_MTHC1: case MIPS_MTHC2: case MIPS_PREFX: case MIPS_WRPGPR: @@ -3820,8 +4141,6 @@ bool GetLowLevelILForInstruction(Architecture* arch, uint64_t addr, LowLevelILFu case MIPS_NMSUB_D: case MIPS_NMSUB_PS: case MIPS_NMSUB_S: - case MIPS_MADD_D: - case MIPS_MADD_PS: case MIPS_MADDF_D: case MIPS_MADDF_S: // Unimplemented R5900 instructions diff --git a/arch/mips/il.h b/arch/mips/il.h index 2c7d041342..248782599b 100644 --- a/arch/mips/il.h +++ b/arch/mips/il.h @@ -87,6 +87,8 @@ enum MipsIntrinsic : uint32_t MIPS_INTRIN_R5900_VU0_CALLMSR, MIPS_INTRIN_COP0_CONDITION, + MIPS_INTRIN_MTC1_UNPREDICTABLE_HIGH_WORD, + MIPS_INTRIN_MADD_PS, MIPS_INTRIN_INVALID=0xFFFFFFFF, }; @@ -172,4 +174,4 @@ static inline const size_t get_register_width(size_t reg, MipsVersion version, s #ifdef __cplusplus } }//end namespace -#endif \ No newline at end of file +#endif diff --git a/arch/mips/mips/mips.c b/arch/mips/mips/mips.c index 37c56513d1..10a9d5f42f 100644 --- a/arch/mips/mips/mips.c +++ b/arch/mips/mips/mips.c @@ -2760,21 +2760,16 @@ uint32_t mips_decompose_instruction( instruction->operands[1].immediate = ins.f.ft; instruction->operands[1].reg = ins.f.fr; break; - case MIPS_LWC1: - case MIPS_SWC1: case MIPS_LDC1: case MIPS_SDC1: - // This special case for the R5900 seems wrong: it's trying to use a FP register for the base register - // instruction->operands[1].reg = version != MIPS_R5900 ? ins.i.rs : (FPREG_F0 + ins.f.fr); - if (version == MIPS_R5900) - { - instruction->operands[0].reg = FPREG_F0 + ins.f.ft; - instruction->operands[0].operandClass = REG; - instruction->operands[1].operandClass = MEM_IMM; - instruction->operands[1].reg = ins.i.rs; - instruction->operands[1].immediate = ins.i.immediate; - break; - } + case MIPS_LWC1: + case MIPS_SWC1: + instruction->operands[0].reg = FPREG_F0 + ins.i.rt; + instruction->operands[0].operandClass = REG; + instruction->operands[1].operandClass = MEM_IMM; + instruction->operands[1].reg = ins.i.rs; + instruction->operands[1].immediate = ins.i.immediate; + break; case MIPS_QMFC2: case MIPS_QMTC2: if (version == MIPS_R5900) diff --git a/arch/mips/test_lifting.py b/arch/mips/test_lifting.py new file mode 100644 index 0000000000..26e2c43f02 --- /dev/null +++ b/arch/mips/test_lifting.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python + +test_cases = [ + # 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) + ('mips32', b'\xc5\x94\x00\x10', 'LLIL_SET_REG.d($f20,LLIL_LOAD.d(LLIL_ADD.d(LLIL_REG.d($t4),LLIL_CONST.d(0x10))))'), + # swc1 $f20, 0x10($t4) + ('mips32', b'\xe5\x94\x00\x10', 'LLIL_STORE.d(LLIL_ADD.d(LLIL_REG.d($t4),LLIL_CONST.d(0x10)),LLIL_REG.d($f20))'), + # lwc1 $f20, 0x10($t4) + ('mipsel32', b'\x10\x00\x94\xc5', 'LLIL_SET_REG.d($f20,LLIL_LOAD.d(LLIL_ADD.d(LLIL_REG.d($t4),LLIL_CONST.d(0x10))))'), + # swc1 $f20, 0x10($t4) + ('mipsel32', b'\x10\x00\x94\xe5', 'LLIL_STORE.d(LLIL_ADD.d(LLIL_REG.d($t4),LLIL_CONST.d(0x10)),LLIL_REG.d($f20))'), + # ldc1 $f20, 0x10($t4) + ('mips32', b'\xd5\x94\x00\x10', 'LLIL_SET_REG_SPLIT.d($f21,$f20,LLIL_LOAD.q(LLIL_ADD.d(LLIL_REG.d($t4),LLIL_CONST.d(0x10))))'), + # sdc1 $f20, 0x10($t4) + ('mips32', b'\xf5\x94\x00\x10', 'LLIL_STORE.q(LLIL_ADD.d(LLIL_REG.d($t4),LLIL_CONST.d(0x10)),LLIL_REG_SPLIT.d($f21,$f20))'), + # ldc1 $f20, 0x10($t4) + ('mipsel32', b'\x10\x00\x94\xd5', 'LLIL_SET_REG_SPLIT.d($f21,$f20,LLIL_LOAD.q(LLIL_ADD.d(LLIL_REG.d($t4),LLIL_CONST.d(0x10))))'), + # sdc1 $f20, 0x10($t4) + ('mipsel32', b'\x10\x00\x94\xf5', 'LLIL_STORE.q(LLIL_ADD.d(LLIL_REG.d($t4),LLIL_CONST.d(0x10)),LLIL_REG_SPLIT.d($f21,$f20))'), + # ldc1 $f20, 0x10($t4) + ('mips64', b'\xd5\x94\x00\x10', 'LLIL_SET_REG.q($f20,LLIL_LOAD.q(LLIL_ADD.q(LLIL_REG.q($t4),LLIL_CONST.q(0x10))))'), + # sdc1 $f20, 0x10($t4) + ('mips64', b'\xf5\x94\x00\x10', 'LLIL_STORE.q(LLIL_ADD.q(LLIL_REG.q($t4),LLIL_CONST.q(0x10)),LLIL_REG.q($f20))'), + # ldc1 $f20, 0x10($t4) + ('mipsel64', b'\x10\x00\x94\xd5', 'LLIL_SET_REG.q($f20,LLIL_LOAD.q(LLIL_ADD.q(LLIL_REG.q($t4),LLIL_CONST.q(0x10))))'), + # sdc1 $f20, 0x10($t4) + ('mipsel64', b'\x10\x00\x94\xf5', 'LLIL_STORE.q(LLIL_ADD.q(LLIL_REG.q($t4),LLIL_CONST.q(0x10)),LLIL_REG.q($f20))'), + # ldc1 $f20, 0x10($t4) + ('r5900l', b'\x10\x00\x94\xd5', 'LLIL_SET_REG.q($f20,LLIL_LOAD.q(LLIL_ADD.d(LLIL_REG.d($t4),LLIL_CONST.d(0x10))))'), + # sdc1 $f20, 0x10($t4) + ('r5900l', b'\x10\x00\x94\xf5', 'LLIL_STORE.q(LLIL_ADD.d(LLIL_REG.d($t4),LLIL_CONST.d(0x10)),LLIL_REG.q($f20))'), + # ldc1 $f21, 0x10($t4) -- odd FPR pair roots are architecturally unpredictable in MIPS32 FR=0 mode + ('mips32', b'\xd5\x95\x00\x10', 'LLIL_UNKNOWN()'), + # sdc1 $f21, 0x10($t4) -- odd FPR pair roots are architecturally unpredictable in MIPS32 FR=0 mode + ('mips32', b'\xf5\x95\x00\x10', 'LLIL_UNKNOWN()'), + # ldc1 $f31, 0x10($t4) -- odd FPR pair roots are architecturally unpredictable in MIPS32 FR=0 mode + ('mips32', b'\xd5\x9f\x00\x10', 'LLIL_UNKNOWN()'), + # sdc1 $f31, 0x10($t4) -- odd FPR pair roots are architecturally unpredictable in MIPS32 FR=0 mode + ('mips32', b'\xf5\x9f\x00\x10', 'LLIL_UNKNOWN()'), + # mtc1 $t0, $f20 -- MIPS32 Release 6.06 MTC1 (p. 292): StoreFPR writes GPR[rt][31:0] + ('mips32', b'\x44\x88\xa0\x00', 'LLIL_SET_REG.d($f20,LLIL_REG.d($t0))'), + # mtc1 $t0, $f20 -- little-endian encoding of the same MIPS32 operation + ('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 + ('mips64', b'\x44\x88\xa0\x00', 'LLIL_INTRINSIC([temp0],_mtc1UnpredictableHighWord,[]); LLIL_SET_REG.q($f20,LLIL_OR.q(LLIL_LSL.q(LLIL_ZX.q(LLIL_REG.d(temp0)),LLIL_CONST.b(0x20)),LLIL_ZX.q(LLIL_REG.d($t0))))'), + # mtc1 $t0, $f20 -- little-endian MIPS64 has identical register-transfer semantics + ('mipsel64', b'\x00\xa0\x88\x44', 'LLIL_INTRINSIC([temp0],_mtc1UnpredictableHighWord,[]); LLIL_SET_REG.q($f20,LLIL_OR.q(LLIL_LSL.q(LLIL_ZX.q(LLIL_REG.d(temp0)),LLIL_CONST.b(0x20)),LLIL_ZX.q(LLIL_REG.d($t0))))'), + # mtc1 $t0, $f20 -- Cavium uses the same modeled 64-bit MIPS FPR semantics + ('cavium-mips64', b'\x44\x88\xa0\x00', 'LLIL_INTRINSIC([temp0],_mtc1UnpredictableHighWord,[]); LLIL_SET_REG.q($f20,LLIL_OR.q(LLIL_LSL.q(LLIL_ZX.q(LLIL_REG.d(temp0)),LLIL_CONST.b(0x20)),LLIL_ZX.q(LLIL_REG.d($t0))))'), + # mfc1 $t0, $f20 -- MIPS32 Release 6.06 MFC1 (p. 267): copy FPR[fs][31:0] + ('mips32', b'\x44\x08\xa0\x00', 'LLIL_SET_REG.d($t0,LLIL_REG.d($f20))'), + # mfc1 $t0, $f20 -- little-endian encoding of the same MIPS32 operation + ('mipsel32', b'\x00\xa0\x08\x44', 'LLIL_SET_REG.d($t0,LLIL_REG.d($f20))'), + # mfc1 $t0, $f20 -- MIPS64 Release 6.06 MFC1 (p. 357): sign-extend the FPR word + ('mips64', b'\x44\x08\xa0\x00', 'LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_REG.d($f20)))'), + # mfc1 $t0, $f20 -- R5900 EE Core Instruction Set Manual MFC1 (p. 364) + ('r5900l', b'\x00\xa0\x08\x44', 'LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_REG.d($f20)))'), + # dmfc1 $t0, $f20 -- MIPS64 Release 6.06 DMFC1 (p. 217): copy all 64 FPR bits + ('mips64', b'\x44\x28\xa0\x00', 'LLIL_SET_REG.q($t0,LLIL_REG.q($f20))'), + # dmfc1 $t0, $f20 -- little-endian encoding of the same MIPS64 operation + ('mipsel64', b'\x00\xa0\x28\x44', 'LLIL_SET_REG.q($t0,LLIL_REG.q($f20))'), + # mfhc1 $t0, $f20 -- MIPS32 Release 6.06 MFHC1 (p. 271): read the odd FPR in FR=0 + ('mips32', b'\x44\x68\xa0\x00', 'LLIL_SET_REG.d($t0,LLIL_REG.d($f21))'), + # mfhc1 $t0, $f21 -- odd FR=0 pair roots are architecturally unpredictable + ('mips32', b'\x44\x68\xa8\x00', 'LLIL_UNKNOWN()'), + # mfhc1 $t0, $f20 -- MIPS64 Release 6.06 MFHC1 (p. 361): sign-extend bits 63:32 + ('mips64', b'\x44\x68\xa0\x00', 'LLIL_SET_REG.q($t0,LLIL_SX.q(LLIL_LOW_PART.d(LLIL_LSR.q(LLIL_REG.q($f20),LLIL_CONST.b(0x20)))))'), + # dmtc1 $t0, $f20 -- MIPS64 Release 6.06 DMTC1 (p. 220): copy all 64 GPR bits + ('mipsel64', b'\x00\xa0\xa8\x44', 'LLIL_SET_REG.q($f20,LLIL_REG.q($t0))'), + # mthc1 $t0, $f20 -- MIPS32 Release 6.06 MTHC1 (p. 295): write the odd FPR in FR=0 + ('mips32', b'\x44\xe8\xa0\x00', 'LLIL_SET_REG.d($f21,LLIL_REG.d($t0))'), + # mthc1 $t0, $f21 -- odd FR=0 pair roots are architecturally unpredictable + ('mips32', b'\x44\xe8\xa8\x00', 'LLIL_UNKNOWN()'), + # mthc1 $t0, $f20 -- MIPS64 Release 6.06 MTHC1 (p. 389): preserve low, replace high + ('mipsel64', b'\x00\xa0\xe8\x44', 'LLIL_SET_REG.q($f20,LLIL_OR.q(LLIL_AND.q(LLIL_REG.q($f20),LLIL_CONST.q(0xFFFFFFFF)),LLIL_LSL.q(LLIL_ZX.q(LLIL_REG.d($t0)),LLIL_CONST.b(0x20))))'), + # c.ole.s $f12, $f0 -- MIPS32 Release 6.06 pp. 110-113: ordered less-or-equal + ('mipsel32', b'\x36\x60\x00\x46', 'LLIL_SET_FLAG($fcc0,LLIL_FCMP_LE.d(LLIL_REG.d($f12),LLIL_REG.d($f0)))'), + # c.ule.s $f21, $f20 -- unordered or less-or-equal + ('mipsel32', b'\x37\xa8\x14\x46', 'LLIL_SET_FLAG($fcc0,LLIL_OR(LLIL_FCMP_UO.d(LLIL_REG.d($f21),LLIL_REG.d($f20)),LLIL_FCMP_LE.d(LLIL_REG.d($f21),LLIL_REG.d($f20))))'), + # c.olt.s $f12, $f0 -- ordered less-than + ('mips32', b'\x46\x00\x60\x34', 'LLIL_SET_FLAG($fcc0,LLIL_FCMP_LT.d(LLIL_REG.d($f12),LLIL_REG.d($f0)))'), + # c.ult.s $f12, $f0 -- unordered or less-than + ('mipsel32', b'\x35\x60\x00\x46', 'LLIL_SET_FLAG($fcc0,LLIL_OR(LLIL_FCMP_UO.d(LLIL_REG.d($f12),LLIL_REG.d($f0)),LLIL_FCMP_LT.d(LLIL_REG.d($f12),LLIL_REG.d($f0))))'), + # c.ueq.s $f12, $f0 -- unordered or equal + ('mipsel32', b'\x33\x60\x00\x46', 'LLIL_SET_FLAG($fcc0,LLIL_OR(LLIL_FCMP_UO.d(LLIL_REG.d($f12),LLIL_REG.d($f0)),LLIL_FCMP_E.d(LLIL_REG.d($f12),LLIL_REG.d($f0))))'), + # c.ngle.s $f12, $f0 -- signaling unordered predicate + ('mipsel32', b'\x39\x60\x00\x46', 'LLIL_SET_FLAG($fcc0,LLIL_FCMP_UO.d(LLIL_REG.d($f12),LLIL_REG.d($f0)))'), + # c.ngl.s $f12, $f0 -- signaling unordered-or-equal predicate + ('mipsel32', b'\x3b\x60\x00\x46', 'LLIL_SET_FLAG($fcc0,LLIL_OR(LLIL_FCMP_UO.d(LLIL_REG.d($f12),LLIL_REG.d($f0)),LLIL_FCMP_E.d(LLIL_REG.d($f12),LLIL_REG.d($f0))))'), + # c.ole.d $f12, $f0 -- FR=0 doubles use even/odd FPR pairs + ('mipsel32', b'\x36\x60\x20\x46', 'LLIL_SET_FLAG($fcc0,LLIL_FCMP_LE.q(LLIL_REG_SPLIT.d($f13,$f12),LLIL_REG_SPLIT.d($f1,$f0)))'), + # c.ole.d $f13, $f0 -- odd FR=0 double roots are architecturally unpredictable + ('mipsel32', b'\x36\x68\x20\x46', 'LLIL_UNKNOWN()'), + # c.nge.d $f12, $f0 -- unordered or less-than + ('mipsel32', b'\x3d\x60\x20\x46', 'LLIL_SET_FLAG($fcc0,LLIL_OR(LLIL_FCMP_UO.q(LLIL_REG_SPLIT.d($f13,$f12),LLIL_REG_SPLIT.d($f1,$f0)),LLIL_FCMP_LT.q(LLIL_REG_SPLIT.d($f13,$f12),LLIL_REG_SPLIT.d($f1,$f0))))'), + # c.ngt.d $f12, $f0 -- unordered or less-than-or-equal + ('mipsel32', b'\x3f\x60\x20\x46', 'LLIL_SET_FLAG($fcc0,LLIL_OR(LLIL_FCMP_UO.q(LLIL_REG_SPLIT.d($f13,$f12),LLIL_REG_SPLIT.d($f1,$f0)),LLIL_FCMP_LE.q(LLIL_REG_SPLIT.d($f13,$f12),LLIL_REG_SPLIT.d($f1,$f0))))'), + # c.ole.s $fcc3, $f12, $f0 -- only the selected condition code is written + ('mipsel32', b'\x36\x63\x00\x46', 'LLIL_SET_FLAG($fcc3,LLIL_FCMP_LE.d(LLIL_REG.d($f12),LLIL_REG.d($f0)))'), + # c.ole.d $fcc3, $f12, $f0 -- explicit condition codes also apply to doubles + ('mipsel32', b'\x36\x63\x20\x46', 'LLIL_SET_FLAG($fcc3,LLIL_FCMP_LE.q(LLIL_REG_SPLIT.d($f13,$f12),LLIL_REG_SPLIT.d($f1,$f0)))'), + # c.ole.ps $f12, $f0 -- paired lanes write FCC0 and FCC1 in FR=1 + ('mipsel64', b'\x36\x60\xc0\x46', 'LLIL_SET_FLAG($fcc0,LLIL_FCMP_LE.d(LLIL_LOW_PART.d(LLIL_REG.q($f12)),LLIL_LOW_PART.d(LLIL_REG.q($f0)))); LLIL_SET_FLAG($fcc1,LLIL_FCMP_LE.d(LLIL_LOW_PART.d(LLIL_LSR.q(LLIL_REG.q($f12),LLIL_CONST.b(0x20))),LLIL_LOW_PART.d(LLIL_LSR.q(LLIL_REG.q($f0),LLIL_CONST.b(0x20)))))'), + # c.ule.ps $fcc2, $f12, $f0 -- unordered-inclusive predicates are applied independently per lane + ('mipsel64', b'\x37\x62\xc0\x46', 'LLIL_SET_FLAG($fcc2,LLIL_OR(LLIL_FCMP_UO.d(LLIL_LOW_PART.d(LLIL_REG.q($f12)),LLIL_LOW_PART.d(LLIL_REG.q($f0))),LLIL_FCMP_LE.d(LLIL_LOW_PART.d(LLIL_REG.q($f12)),LLIL_LOW_PART.d(LLIL_REG.q($f0))))); LLIL_SET_FLAG($fcc3,LLIL_OR(LLIL_FCMP_UO.d(LLIL_LOW_PART.d(LLIL_LSR.q(LLIL_REG.q($f12),LLIL_CONST.b(0x20))),LLIL_LOW_PART.d(LLIL_LSR.q(LLIL_REG.q($f0),LLIL_CONST.b(0x20)))),LLIL_FCMP_LE.d(LLIL_LOW_PART.d(LLIL_LSR.q(LLIL_REG.q($f12),LLIL_CONST.b(0x20))),LLIL_LOW_PART.d(LLIL_LSR.q(LLIL_REG.q($f0),LLIL_CONST.b(0x20))))))'), + # c.ole.ps $fcc1, $f12, $f0 -- paired-single requires an even condition code + ('mipsel64', b'\x36\x61\xc0\x46', 'LLIL_UNKNOWN()'), + # madd.s $f0, $f0, $f12, $f12 -- reported little-endian encoding + ('mipsel32', b'\x20\x60\x0c\x4c', 'LLIL_SET_REG.d($f0,LLIL_FADD.d(LLIL_FMUL.d(LLIL_REG.d($f12),LLIL_REG.d($f12)),LLIL_REG.d($f0)))'), + # madd.s $f2, $f4, $f6, $f8 -- MIPS32 Release 6.06 pp. 256-257: round(fs*ft), then add fr + ('mips32', b'\x4c\x88\x30\xa0', 'LLIL_SET_REG.d($f2,LLIL_FADD.d(LLIL_FMUL.d(LLIL_REG.d($f6),LLIL_REG.d($f8)),LLIL_REG.d($f4)))'), + # madd.d $f2, $f4, $f6, $f8 -- FR=0 doubles use even/odd FPR pairs + ('mipsel32', b'\xa1\x30\x88\x4c', 'LLIL_SET_REG_SPLIT.d($f3,$f2,LLIL_FADD.q(LLIL_FMUL.q(LLIL_REG_SPLIT.d($f7,$f6),LLIL_REG_SPLIT.d($f9,$f8)),LLIL_REG_SPLIT.d($f5,$f4)))'), + # madd.d $f3, $f4, $f6, $f8 -- odd FR=0 operands are architecturally unpredictable + ('mipsel32', b'\xe1\x30\x88\x4c', 'LLIL_UNKNOWN()'), + # madd.d $f3, $f4, $f6, $f8 -- odd FPRs are valid in the modeled FR=1 register file + ('mipsel64', b'\xe1\x30\x88\x4c', 'LLIL_SET_REG.q($f3,LLIL_FADD.q(LLIL_FMUL.q(LLIL_REG.q($f6),LLIL_REG.q($f8)),LLIL_REG.q($f4)))'), + # madd.ps $f2, $f4, $f6, $f8 -- paired-single operates independently on both FR=1 lanes + ('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()'), + # 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 + ('mipsel32', b'\x11\x60\x00\x46', 'LLIL_IF(LLIL_NOT(LLIL_FLAG($fcc0)),1,3); LLIL_SET_REG.d($f0,LLIL_REG.d($f12)); LLIL_GOTO(3)'), + # movt.s $f0, $f1, $fcc0 -- big-endian encoding + ('mips32', b'\x46\x01\x08\x11', 'LLIL_IF(LLIL_FLAG($fcc0),1,3); LLIL_SET_REG.d($f0,LLIL_REG.d($f1)); LLIL_GOTO(3)'), + # movt.s $f0, $f1, $fcc3 -- the encoded condition-code selector is honored + ('mipsel32', b'\x11\x08\x0d\x46', 'LLIL_IF(LLIL_FLAG($fcc3),1,3); LLIL_SET_REG.d($f0,LLIL_REG.d($f1)); LLIL_GOTO(3)'), + # movf.d $f2, $f4, $fcc0 -- FR=0 doubles move an even/odd FPR pair + ('mipsel32', b'\x91\x20\x20\x46', 'LLIL_IF(LLIL_NOT(LLIL_FLAG($fcc0)),1,3); LLIL_SET_REG_SPLIT.d($f3,$f2,LLIL_REG_SPLIT.d($f5,$f4)); LLIL_GOTO(3)'), + # movt.d $f1, $f3, $fcc0 -- odd FR=0 double roots are architecturally unpredictable + ('mipsel32', b'\x51\x18\x21\x46', 'LLIL_UNKNOWN()'), + # movt.d $f1, $f3, $fcc0 -- odd FPRs are valid in the modeled FR=1 register file + ('mipsel64', b'\x51\x18\x21\x46', 'LLIL_IF(LLIL_FLAG($fcc0),1,3); LLIL_SET_REG.q($f1,LLIL_REG.q($f3)); LLIL_GOTO(3)'), + # movf $at, $zero, $fcc0 -- MIPS32 Release 6.06 p. 276 + ('mipsel32', b'\x01\x08\x00\x00', 'LLIL_IF(LLIL_NOT(LLIL_FLAG($fcc0)),1,3); LLIL_SET_REG.d($at,LLIL_CONST.d(0x0)); LLIL_GOTO(3)'), + # movf $at, $zero, $fcc0 -- big-endian encoding + ('mips32', b'\x00\x00\x08\x01', 'LLIL_IF(LLIL_NOT(LLIL_FLAG($fcc0)),1,3); LLIL_SET_REG.d($at,LLIL_CONST.d(0x0)); LLIL_GOTO(3)'), + # movt $at, $t0, $fcc3 -- MIPS32 Release 6.06 p. 281 and explicit FCC selection + ('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)'), +] + +import sys +import binaryninja +from binaryninja import binaryview +from binaryninja import lowlevelil +from binaryninja.enums import Endianness, LowLevelILOperation + + +def il2str(il): + sz_lookup = {1: '.b', 2: '.w', 4: '.d', 8: '.q', 16: '.o'} + if isinstance(il, lowlevelil.LowLevelILInstruction): + size_code = sz_lookup.get(il.size, '?') if il.size else '' + flags_code = '' if not hasattr(il, 'flags') or not il.flags or il.flags == 'update0' else '{%s}' % il.flags + + if il.operation == LowLevelILOperation.LLIL_UNIMPL and il.raw_operands[0]: + return 'LLIL_UNKNOWN()' + if il.operation in [LowLevelILOperation.LLIL_CONST, LowLevelILOperation.LLIL_CONST_PTR] and il.size: + value = il.operands[0] + if value < 0: + value = (1 << (il.size * 8)) + value + value = '0x%X' % value + return 'LLIL_CONST%s(%s)' % (size_code, value) + return '%s%s%s(%s)' % (il.operation.name, size_code, flags_code, ','.join([il2str(op) for op in il.operands])) + if isinstance(il, list): + return '[' + ','.join([il2str(op) for op in il]) + ']' + if type(il) == lowlevelil.LowLevelILFlagCondition: + return 'LowLevelILFlagCondition.%s' % il.name + return str(il) + + +def instr_to_il(data, arch_name): + arch = binaryninja.Architecture[arch_name] + if arch.endianness == Endianness.LittleEndian: + return_instruction = b'\x08\x00\xe0\x03\x00\x00\x00\x00' + else: + return_instruction = b'\x03\xe0\x00\x08\x00\x00\x00\x00' + + bv = binaryview.BinaryView.new(data + return_instruction) + bv.add_function(0, plat=arch.standalone_platform) + assert len(bv.functions) == 1 + + result = [] + for block in bv.functions[0].lifted_il: + for il in block: + result.append(il2str(il)) + # Strip the return, its delay-slot nop, and the placeholder nop used while lifting the delay slot. + return '; '.join(result[:-3]) + + +def il_str_to_tree(ilstr): + result = '' + depth = 0 + for char in ilstr: + if char == '(': + result += '\n' + depth += 1 + result += ' ' * depth + elif char == ')': + depth -= 1 + elif char == ',': + result += '\n' + ' ' * depth + elif char == ';': + result += '\n' + depth = 0 + elif char != ' ': + result += char + return result + + +def fail_test(message): + raise AssertionError(message) + + +def run_all_tests(): + for test_i, (arch_name, data, expected) in enumerate(test_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) + 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' + % (test_i, arch_name, data.hex(), actual, il_str_to_tree(actual))) + + if actual != expected: + fail_test( + 'MISMATCH AT TEST %d!\n\t arch: %s\n\t input: %s\n\texpected: %s\n\t actual: %s\n\t tree:\n%s' + % (test_i, arch_name, data.hex(), expected, actual, il_str_to_tree(actual))) + + +def test_all(): + run_all_tests() + + +if __name__ == '__main__': + run_all_tests() + print('success!') + sys.exit(0) + +if __name__ == 'test_lifting': + test_all() + print('success!')