From 520dc99b1119f5bd869f0e590725f2f1ec5ded72 Mon Sep 17 00:00:00 2001 From: caheckman <48068198+caheckman@users.noreply.github.com> Date: Mon, 17 Jun 2024 21:49:17 +0000 Subject: [PATCH] GP-2559 Calculate maximum precision reaching floating-point operations --- .../Decompiler/certification.manifest | 1 + .../src/decompile/cpp/coreaction.cc | 18 +- .../Decompiler/src/decompile/cpp/float.cc | 54 +++- .../Decompiler/src/decompile/cpp/float.hh | 10 +- .../Decompiler/src/decompile/cpp/printc.cc | 16 +- .../src/decompile/cpp/ruleaction.cc | 93 +++++- .../src/decompile/cpp/ruleaction.hh | 26 +- .../Decompiler/src/decompile/cpp/space.cc | 9 +- .../Decompiler/src/decompile/cpp/subflow.cc | 271 +++++++++++++++++- .../Decompiler/src/decompile/cpp/subflow.hh | 21 +- .../Decompiler/src/decompile/cpp/typeop.cc | 47 ++- .../Decompiler/src/decompile/cpp/typeop.hh | 7 +- .../src/decompile/datatests/floatcast.xml | 49 ++++ .../src/decompile/datatests/floatprint.xml | 4 +- .../src/decompile/unittests/testfloatemu.cc | 33 ++- 15 files changed, 602 insertions(+), 57 deletions(-) create mode 100644 Ghidra/Features/Decompiler/src/decompile/datatests/floatcast.xml diff --git a/Ghidra/Features/Decompiler/certification.manifest b/Ghidra/Features/Decompiler/certification.manifest index fba3250745..8d3f7a6ae8 100644 --- a/Ghidra/Features/Decompiler/certification.manifest +++ b/Ghidra/Features/Decompiler/certification.manifest @@ -23,6 +23,7 @@ src/decompile/datatests/displayformat.xml||GHIDRA||||END| src/decompile/datatests/divopt.xml||GHIDRA||||END| src/decompile/datatests/dupptr.xml||GHIDRA||||END| src/decompile/datatests/elseif.xml||GHIDRA||||END| +src/decompile/datatests/floatcast.xml||GHIDRA||||END| src/decompile/datatests/floatprint.xml||GHIDRA||||END| src/decompile/datatests/forloop1.xml||GHIDRA||||END| src/decompile/datatests/forloop_loaditer.xml||GHIDRA||||END| diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc index a79c3030c3..c7c2e45f7a 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -563,7 +563,10 @@ bool ActionLaneDivide::processVarnode(Funcdata &data,Varnode *vn,const LanedRegi if (mode < 2) collectLaneSizes(vn,lanedRegister,checkLanes); else { - checkLanes.addLaneSize(4); // Default lane size + int4 defaultSize = data.getArch()->types->getSizeOfPointer(); // Default lane size + if (defaultSize != 4) + defaultSize = 8; + checkLanes.addLaneSize(defaultSize); } LanedRegister::const_iterator enditer = checkLanes.end(); for(LanedRegister::const_iterator iter=checkLanes.begin();iter!=enditer;++iter) { @@ -582,6 +585,7 @@ bool ActionLaneDivide::processVarnode(Funcdata &data,Varnode *vn,const LanedRegi int4 ActionLaneDivide::apply(Funcdata &data) { + data.setLanedRegGenerated(); map::const_iterator iter; for(int4 mode=0;mode<3;++mode) { bool allStorageProcessed = true; @@ -594,6 +598,10 @@ int4 ActionLaneDivide::apply(Funcdata &data) bool allVarnodesProcessed = true; while(viter != venditer) { Varnode *vn = *viter; + if (vn->hasNoDescend()) { + ++viter; + continue; + } if (processVarnode(data, vn, *lanedReg, mode)) { viter = data.beginLoc(sz,addr); venditer = data.endLoc(sz, addr); // Recalculate bounds @@ -610,7 +618,6 @@ int4 ActionLaneDivide::apply(Funcdata &data) if (allStorageProcessed) break; } data.clearLanedAccessMap(); - data.setLanedRegGenerated(); return 0; } @@ -1337,7 +1344,6 @@ int4 ActionVarnodeProps::apply(Funcdata &data) } } } - data.setLanedRegGenerated(); return 0; } @@ -5515,6 +5521,7 @@ void ActionDatabase::universalAction(Architecture *conf) actprop->addRule( new RuleOrMultiBool("analysis") ); actprop->addRule( new RuleXorSwap("analysis") ); actprop->addRule( new RuleLzcountShiftBool("analysis") ); + actprop->addRule( new RuleFloatSign("analysis") ); actprop->addRule( new RuleSubvarAnd("subvar") ); actprop->addRule( new RuleSubvarSubpiece("subvar") ); actprop->addRule( new RuleSplitFlow("subvar") ); @@ -5591,6 +5598,7 @@ void ActionDatabase::universalAction(Architecture *conf) actcleanup->addRule( new RuleAddUnsigned("cleanup") ); actcleanup->addRule( new Rule2Comp2Sub("cleanup") ); actcleanup->addRule( new RuleSubRight("cleanup") ); + actcleanup->addRule( new RuleFloatSignCleanup("cleanup") ); actcleanup->addRule( new RulePtrsubCharConstant("cleanup") ); actcleanup->addRule( new RuleExtensionPush("cleanup") ); actcleanup->addRule( new RulePieceStructure("cleanup") ); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/float.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/float.cc index 913af357ed..81df78ca91 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/float.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/float.cc @@ -5,9 +5,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -99,11 +99,11 @@ FloatFormat::floatclass FloatFormat::extractExpSig(double x,bool *sgn,uintb *sig x = -x; double norm = frexp(x,&e); // norm is between 1/2 and 1 norm = ldexp(norm,8*sizeof(uintb)-1); // norm between 2^62 and 2^63 - + *signif = (uintb)norm; // Convert to normalized integer *signif <<= 1; - e -= 1; // Consider normalization between 1 and 2 + e -= 1; // Consider normalization between 1 and 2 *exp = e; return normalized; } @@ -217,8 +217,9 @@ uintb FloatFormat::getNaNEncoding(bool sgn) const void FloatFormat::calcPrecision(void) { - float val = frac_size * 0.30103; - decimal_precision = (int4)floor(val + 0.5); + decimalMinPrecision = (int4)floor(frac_size * 0.30103); + // Precision needed to guarantee IEEE 754 binary -> decimal -> binary round trip conversion + decimalMaxPrecision = (int4)ceil((frac_size + 1) * 0.30103) + 1; } /// \param encoding is the encoding value @@ -417,6 +418,47 @@ uintb FloatFormat::convertEncoding(uintb encoding, return setSign(res, sgn); } +/// The string should be printed with the minimum number of digits to uniquely specify the underlying +/// binary value. This currently only works for the 32-bit and 64-bit IEEE 754 formats. +/// If the \b forcesci parameter is \b true, the string will always be printed using scientific notation. +/// \param host is the given value already converted to the host's \b double format. +/// \param forcesci is \b true if the value should be printed in scientific notation. +/// \return the decimal representation as a string +string FloatFormat::printDecimal(double host,bool forcesci) const + +{ + string res; + for(int4 prec=decimalMinPrecision;;++prec) { + ostringstream s; + if (forcesci) { + s.setf( ios::scientific ); // Set to scientific notation + s.precision(prec-1); // scientific doesn't include first digit in precision count + } + else { + s.unsetf( ios::floatfield ); // Use "default" notation to allow fewer digits to be printed if possible + s.precision(prec); + } + s << host; + if (prec == decimalMaxPrecision) { + return s.str(); + } + res = s.str(); + double roundtrip = 0.0; + istringstream t(res); + if (size <= 4) { + float tmp = 0.0; + t >> tmp; + roundtrip = tmp; + } + else { + t >> roundtrip; + } + if (roundtrip == host) + break; + } + return res; +} + // Currently we emulate floating point operations on the target // By converting the encoding to the host's encoding and then // performing the operation using the host's floating point unit diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/float.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/float.hh index 376cc5a34a..06429dc7fa 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/float.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/float.hh @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -48,7 +48,8 @@ private: int4 exp_size; ///< Number of bits in exponent int4 bias; ///< What to add to real exponent to get encoding int4 maxexponent; ///< Maximum possible exponent - int4 decimal_precision; ///< Number of decimal digits of precision + int4 decimalMinPrecision; ///< Minimum decimal digits of precision guaranteed by the format + int4 decimalMaxPrecision; ///< Maximum decimal digits of precision needed to uniquely represent value bool jbitimplied; ///< Set to \b true if integer bit of 1 is assumed static double createFloat(bool sign,uintb signif,int4 exp); ///< Create a double given sign, fractional, and exponent static floatclass extractExpSig(double x,bool *sgn,uintb *signif,int4 *exp); @@ -65,13 +66,14 @@ public: int4 getSize(void) const { return size; } ///< Get the size of the encoding in bytes double getHostFloat(uintb encoding,floatclass *type) const; ///< Convert an encoding into host's double uintb getEncoding(double host) const; ///< Convert host's double into \b this encoding - int4 getDecimalPrecision(void) const { return decimal_precision; } ///< Get number of digits of precision uintb convertEncoding(uintb encoding,const FloatFormat *formin) const; ///< Convert between two different formats uintb extractFractionalCode(uintb x) const; ///< Extract the fractional part of the encoding bool extractSign(uintb x) const; ///< Extract the sign bit from the encoding int4 extractExponentCode(uintb x) const; ///< Extract the exponent from the encoding + string printDecimal(double host,bool forcesci) const; ///< Print given value as a decimal string + // Operations on floating point values uintb opEqual(uintb a,uintb b) const; ///< Equality comparison (==) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc index 10a3431f9d..6042986698 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -1388,19 +1388,11 @@ void PrintC::push_float(uintb val,int4 sz,tagtype tag,const Varnode *vn,const Pc token = "NAN"; } else { - ostringstream t; if ((mods & force_scinote)!=0) { - t.setf( ios::scientific ); // Set to scientific notation - t.precision(format->getDecimalPrecision()-1); - t << floatval; - token = t.str(); + token = format->printDecimal(floatval, true); } else { - // Try to print "minimal" accurate representation of the float - t.unsetf( ios::floatfield ); // Use "default" notation - t.precision(format->getDecimalPrecision()); - t << floatval; - token = t.str(); + token = format->printDecimal(floatval, false); bool looksLikeFloat = false; for(int4 i=0;i ABS(f)` +/// -- 'x ^ 0x80000000 => -f` +/// +/// A Varnode is determined to be floating-point by participation in other floating-point operations, +/// not based on the data-type of the Varnode. +void RuleFloatSign::getOpList(vector &oplist) const + +{ + uint4 list[] = { CPUI_FLOAT_EQUAL, CPUI_FLOAT_NOTEQUAL, CPUI_FLOAT_LESS, CPUI_FLOAT_LESSEQUAL, CPUI_FLOAT_NAN, + CPUI_FLOAT_ADD, CPUI_FLOAT_DIV, CPUI_FLOAT_MULT, CPUI_FLOAT_SUB, CPUI_FLOAT_NEG, CPUI_FLOAT_ABS, + CPUI_FLOAT_SQRT, CPUI_FLOAT_FLOAT2FLOAT, CPUI_FLOAT_CEIL, CPUI_FLOAT_FLOOR, CPUI_FLOAT_ROUND, + CPUI_FLOAT_INT2FLOAT, CPUI_FLOAT_TRUNC }; + oplist.insert(oplist.end(),list,list+18); +} + +int4 RuleFloatSign::applyOp(PcodeOp *op,Funcdata &data) + +{ + int4 res = 0; + OpCode opc = op->code(); + if (opc != CPUI_FLOAT_INT2FLOAT) { + Varnode *vn = op->getIn(0); + if (vn->isWritten()) { + PcodeOp *signOp = vn->getDef(); + OpCode resCode = TypeOp::floatSignManipulation(signOp); + if (resCode != CPUI_MAX) { + data.opRemoveInput(signOp, 1); + data.opSetOpcode(signOp, resCode); + res = 1; + } + } + if (op->numInput() == 2) { + vn = op->getIn(1); + if (vn->isWritten()) { + PcodeOp *signOp = vn->getDef(); + OpCode resCode = TypeOp::floatSignManipulation(signOp); + if (resCode != CPUI_MAX) { + data.opRemoveInput(signOp, 1); + data.opSetOpcode(signOp, resCode); + res = 1; + } + } + } + } + if (op->isBoolOutput() || opc == CPUI_FLOAT_TRUNC) + return res; + list::const_iterator iter; + Varnode *outvn = op->getOut(); + for(iter=outvn->beginDescend();iter!=outvn->endDescend();++iter) { + PcodeOp *readOp = *iter; + OpCode resCode = TypeOp::floatSignManipulation(readOp); + if (resCode != CPUI_MAX) { + data.opRemoveInput(readOp, 1); + data.opSetOpcode(readOp, resCode); + res = 1; + } + } + return res; +} + +/// \class RuleFloatSignCleanup +/// \brief Convert floating-point \e sign bit manipulation into FLOAT_ABS or FLOAT_NEG +/// +/// A Varnode is determined to be floating-point by examining its data-type. +void RuleFloatSignCleanup::getOpList(vector &oplist) const + +{ + oplist.push_back(CPUI_INT_AND); + oplist.push_back(CPUI_INT_XOR); +} + +int4 RuleFloatSignCleanup::applyOp(PcodeOp *op,Funcdata &data) + +{ + if (op->getOut()->getType()->getMetatype() != TYPE_FLOAT) { + return 0; + } + OpCode opc = TypeOp::floatSignManipulation(op); + if (opc == CPUI_MAX) + return 0; + data.opRemoveInput(op, 1); + data.opSetOpcode(op, opc); + return 1; +} + } // End namespace ghidra diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh index b38af07623..d83484e2e8 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -1644,5 +1644,27 @@ public: virtual int4 applyOp(PcodeOp *op,Funcdata &data); }; +class RuleFloatSign : public Rule { +public: + RuleFloatSign(const string &g) : Rule( g, 0, "floatsign") {} ///< Constructor + virtual Rule *clone(const ActionGroupList &grouplist) const { + if (!grouplist.contains(getGroup())) return (Rule *)0; + return new RuleFloatSign(getGroup()); + } + virtual void getOpList(vector &oplist) const; + virtual int4 applyOp(PcodeOp *op,Funcdata &data); +}; + +class RuleFloatSignCleanup : public Rule { +public: + RuleFloatSignCleanup(const string &g) : Rule( g, 0, "floatsigncleanup") {} ///< Constructor + virtual Rule *clone(const ActionGroupList &grouplist) const { + if (!grouplist.contains(getGroup())) return (Rule *)0; + return new RuleFloatSignCleanup(getGroup()); + } + virtual void getOpList(vector &oplist) const; + virtual int4 applyOp(PcodeOp *op,Funcdata &data); +}; + } // End namespace ghidra #endif diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/space.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/space.cc index bda09fc94c..dbaa2e775f 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/space.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/space.cc @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -34,10 +34,13 @@ AttributeId ATTRIB_PIECE = AttributeId("piece",94); // Open slots 94-102 void AddrSpace::calcScaleMask(void) { - pointerLowerBound = (addressSize < 3) ? 0x100: 0x1000; highest = calc_mask(addressSize); // Maximum address highest = highest * wordsize + (wordsize-1); // Maximum byte address + pointerLowerBound = 0; pointerUpperBound = highest; + uintb bufferSize = (addressSize < 3) ? 0x100 : 0x1000; + pointerLowerBound += bufferSize; + pointerUpperBound -= bufferSize; } /// Initialize an address space with its basic attributes diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.cc index 3c179e97d1..b6f930210c 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.cc @@ -2567,7 +2567,133 @@ Datatype *SplitDatatype::getValueDatatype(PcodeOp *loadStore,int4 size,TypeFacto } else if (metain == TYPE_STRUCT || metain == TYPE_ARRAY) return tlst->getExactPiece(resType, baseOffset, size); - return (Datatype *)0;} + return (Datatype *)0; +} + +/// This method distinguishes between a floating-point variable with \e full precision, where all the +/// storage can vary (or is unknown), versus a value that is extended from a floating-point variable with +/// smaller storage. Within the data-flow above the given Varnode, we search for the maximum +/// precision coming through MULTIEQUAL, COPY, and unary floating-point operations. Binary operations +/// like FLOAT_ADD and FLOAT_MULT are not traversed and are assumed to produce a smaller precision. +/// If the method indicates \e full precision for the given Varnode, or if the data-flow does not involve +/// binary floating-point operations, it is accurate, otherwise it may under report the precision. +/// \param vn is the given Varnode +/// \return an approximation of the maximum precision +int4 SubfloatFlow::maxPrecision(Varnode *vn) + +{ + if (!vn->isWritten()) + return vn->getSize(); + PcodeOp *op = vn->getDef(); + switch(op->code()) { + case CPUI_MULTIEQUAL: + case CPUI_FLOAT_NEG: + case CPUI_FLOAT_ABS: + case CPUI_FLOAT_SQRT: + case CPUI_FLOAT_CEIL: + case CPUI_FLOAT_FLOOR: + case CPUI_FLOAT_ROUND: + case CPUI_COPY: + break; + case CPUI_FLOAT_ADD: + case CPUI_FLOAT_SUB: + case CPUI_FLOAT_MULT: + case CPUI_FLOAT_DIV: + return 0; // Delay checking other binary ops + case CPUI_FLOAT_FLOAT2FLOAT: + case CPUI_FLOAT_INT2FLOAT: // Treat integer as having precision matching its size + if (op->getIn(0)->getSize() > vn->getSize()) + return vn->getSize(); + return op->getIn(0)->getSize(); + default: + return vn->getSize(); + } + + map::const_iterator iter = maxPrecisionMap.find(op); + if (iter != maxPrecisionMap.end()) { + return (*iter).second; + } + vector opStack; + opStack.emplace_back(op); + op->setMark(); + int4 max = 0; + while(!opStack.empty()) { + State &state(opStack.back()); + if (state.slot >= state.op->numInput()) { + max = state.maxPrecision; + state.op->clearMark(); + maxPrecisionMap[state.op] = state.maxPrecision; + opStack.pop_back(); + if (!opStack.empty()) { + opStack.back().incorporateInputSize(max); + } + continue; + } + Varnode *nextVn = state.op->getIn(state.slot); + state.slot += 1; + if (!nextVn->isWritten()) { + state.incorporateInputSize(nextVn->getSize()); + continue; + } + PcodeOp *nextOp = nextVn->getDef(); + if (nextOp->isMark()) { + continue; // Truncate the cycle edge + } + switch(nextOp->code()) { + case CPUI_MULTIEQUAL: + case CPUI_FLOAT_NEG: + case CPUI_FLOAT_ABS: + case CPUI_FLOAT_SQRT: + case CPUI_FLOAT_CEIL: + case CPUI_FLOAT_FLOOR: + case CPUI_FLOAT_ROUND: + case CPUI_COPY: + iter = maxPrecisionMap.find(nextOp); + if (iter != maxPrecisionMap.end()) { + // Seen the op before, incorporate its cached precision information + state.incorporateInputSize((*iter).second); + break; + } + nextOp->setMark(); + opStack.emplace_back(nextOp); // Recursively push into the new op + break; + case CPUI_FLOAT_ADD: + case CPUI_FLOAT_SUB: + case CPUI_FLOAT_MULT: + case CPUI_FLOAT_DIV: + break; + case CPUI_FLOAT_FLOAT2FLOAT: + case CPUI_FLOAT_INT2FLOAT: // Treat integer as having precision matching its size + if (nextOp->getIn(0)->getSize() > nextVn->getSize()) + state.incorporateInputSize(nextVn->getSize()); + else + state.incorporateInputSize(nextOp->getIn(0)->getSize()); + break; + default: + state.incorporateInputSize(nextVn->getSize()); + break; + } + } + return max; +} + +/// This is called only for binary floating-point ops: FLOAT_ADD, FLOAT_MULT, FLOAT_LESS, etc. +/// If the maximum precision reaching both input operands exceeds the \b precision established +/// for \b this Rule, \b true is returned, indicating the op cannot be truncated without losing precision. +/// We count on the fact that this test is applied to all binary operations encountered during Rule application. +/// This method will correctly return \b true for the earliest operations whose inputs both exceed the +/// \b precision, but, because of the way maxPrecision() is calculated, it may incorrectly return \b false +/// for later operations. +/// \param op is the given binary floating-point PcodeOp +/// \return \b true if both input operands exceed the established \b precision +bool SubfloatFlow::exceedsPrecision(PcodeOp *op) + +{ + int4 val1 = maxPrecision(op->getIn(0)); + int4 val2 = maxPrecision(op->getIn(1)); + int4 min = (val1 < val2) ? val1 : val2; + return (min > precision); +} /// \brief Create and return a placeholder associated with the given Varnode /// @@ -2636,6 +2762,14 @@ bool SubfloatFlow::traceForward(TransformVar *rvn) if ((outvn!=(Varnode *)0)&&(outvn->isMark())) continue; switch(op->code()) { + case CPUI_FLOAT_ADD: + case CPUI_FLOAT_SUB: + case CPUI_FLOAT_MULT: + case CPUI_FLOAT_DIV: + if (exceedsPrecision(op)) + return false; + // fall through + case CPUI_MULTIEQUAL: case CPUI_COPY: case CPUI_FLOAT_CEIL: case CPUI_FLOAT_FLOOR: @@ -2643,11 +2777,6 @@ bool SubfloatFlow::traceForward(TransformVar *rvn) case CPUI_FLOAT_NEG: case CPUI_FLOAT_ABS: case CPUI_FLOAT_SQRT: - case CPUI_FLOAT_ADD: - case CPUI_FLOAT_SUB: - case CPUI_FLOAT_MULT: - case CPUI_FLOAT_DIV: - case CPUI_MULTIEQUAL: { TransformOp *rop = newOpReplace(op->numInput(), op->code(), op); TransformVar *outrvn = setReplacement(outvn); @@ -2670,6 +2799,8 @@ bool SubfloatFlow::traceForward(TransformVar *rvn) case CPUI_FLOAT_LESS: case CPUI_FLOAT_LESSEQUAL: { + if (exceedsPrecision(op)) + return false; int4 slot = op->getSlot(vn); TransformVar *rvn2 = setReplacement(op->getIn(1-slot)); if (rvn2 == (TransformVar *)0) return false; @@ -2715,6 +2846,13 @@ bool SubfloatFlow::traceBackward(TransformVar *rvn) if (op == (PcodeOp *)0) return true; // If vn is input switch(op->code()) { + case CPUI_FLOAT_ADD: + case CPUI_FLOAT_SUB: + case CPUI_FLOAT_MULT: + case CPUI_FLOAT_DIV: + if (exceedsPrecision(op)) + return false; + // fallthru case CPUI_COPY: case CPUI_FLOAT_CEIL: case CPUI_FLOAT_FLOOR: @@ -2722,10 +2860,6 @@ bool SubfloatFlow::traceBackward(TransformVar *rvn) case CPUI_FLOAT_NEG: case CPUI_FLOAT_ABS: case CPUI_FLOAT_SQRT: - case CPUI_FLOAT_ADD: - case CPUI_FLOAT_SUB: - case CPUI_FLOAT_MULT: - case CPUI_FLOAT_DIV: case CPUI_MULTIEQUAL: { TransformOp *rop = rvn->getDef(); @@ -3013,6 +3147,29 @@ bool LaneDivide::buildMultiequal(PcodeOp *op,TransformVar *outVars,int4 numLanes return true; } +/// \brief Split a given CPUI_INDIRECT operation into placeholders given the output lanes +/// +/// Create the CPUI_INDIRECTs for each lane, sharing the same affecting \e iop. +/// \param op is the original CPUI_MULTIEQUAL PcodeOp +/// \param outVars is the placeholder variables making up the lanes of the output +/// \param numLanes is the number of lanes in the output +/// \param skipLanes is the index of the least significant output lane within the global description +/// \return \b true if the operation was fully modeled +bool LaneDivide::buildIndirect(PcodeOp *op,TransformVar *outVars,int4 numLanes,int4 skipLanes) + +{ + TransformVar *inVn = setReplacement(op->getIn(0), numLanes, skipLanes); + if (inVn == (TransformVar *)0) return false; + for(int4 i=0;igetIn(1)),1); + rop->inheritIndirect(op); + } + return true; +} + /// \brief Split a given CPUI_STORE operation into a sequence of STOREs of individual lanes /// /// A new pointer is constructed for each individual lane into a temporary, then a @@ -3117,7 +3274,7 @@ bool LaneDivide::buildLoad(PcodeOp *op,TransformVar *outVars,int4 numLanes,int4 /// \param op is the given CPUI_INT_RIGHT PcodeOp /// \param outVars is the output placeholders for the RIGHT shift /// \param numLanes is the number of lanes the shift is split into -/// \param skipLanes is the starting lane (within the global description) of the value being loaded +/// \param skipLanes is the starting lane (within the global description) of the output value /// \return \b true if the CPUI_INT_RIGHT was successfully modeled on lanes bool LaneDivide::buildRightShift(PcodeOp *op,TransformVar *outVars,int4 numLanes,int4 skipLanes) @@ -3147,6 +3304,85 @@ bool LaneDivide::buildRightShift(PcodeOp *op,TransformVar *outVars,int4 numLanes return true; } +/// \brief Check that a CPUI_INT_LEFT respects the lanes then generate lane placeholders +/// +/// For the given lane scheme, check that the LEFT shift is copying whole lanes to each other. +/// If so, generate the placeholder COPYs that model the shift. +/// \param op is the given CPUI_INT_LEFT PcodeOp +/// \param outVars is the output placeholders for the LEFT shift +/// \param numLanes is the number of lanes the shift is split into +/// \param skipLanes is the starting lane (within the global description) of the output value +/// \return \b true if the CPUI_INT_RIGHT was successfully modeled on lanes +bool LaneDivide::buildLeftShift(PcodeOp *op,TransformVar *outVars,int4 numLanes,int4 skipLanes) + +{ + if (!op->getIn(1)->isConstant()) return false; + int4 shiftSize = (int4)op->getIn(1)->getOffset(); + if ((shiftSize & 7) != 0) return false; // Not a multiple of 8 + shiftSize /= 8; + int4 startPos = shiftSize + description.getPosition(skipLanes); + int4 startLane = description.getBoundary(startPos); + if (startLane < 0) return false; // Shift does not end on a lane boundary + int4 destLane = startLane; + int4 srcLane = skipLanes; + while(destLane - skipLanes < numLanes) { + if (description.getSize(srcLane) != description.getSize(destLane)) return false; + srcLane += 1; + destLane += 1; + } + TransformVar *inVars = setReplacement(op->getIn(0), numLanes, skipLanes); + if (inVars == (TransformVar *)0) return false; + for(int4 zeroLane=0;zeroLane < (startLane - skipLanes);++zeroLane) { + TransformOp *rop = newOpReplace(1, CPUI_COPY, op); + opSetOutput(rop,outVars + zeroLane); + opSetInput(rop,newConstant(description.getSize(zeroLane), 0, 0),0); + } + buildUnaryOp(CPUI_COPY, op, inVars, outVars + (startLane - skipLanes), numLanes - (startLane - skipLanes)); + return true; +} + +/// \brief Split a CPUI_INT_ZEXT into COPYs of lanes and COPYs of zero into lanes +/// +/// If the input to the INT_ZEXT matches the lane boundaries. Placeholder COPYs are generated from +/// the input Varnode to the least significant lanes. Additional COPYs are generated which place a zero +/// in the remaining most significant lanes. +/// \param op is the given CPUI_INT_ZEXT PcodeOp +/// \param outVars is the output placeholders for the extension +/// \param numLanes is the number of lanes the extension is split into +/// \param skipLanes is the starting lane (within the global description) of the output of the extension +/// \return \b true if the CPUI_INT_ZEXT was successfully modeled on lanes +bool LaneDivide::buildZext(PcodeOp *op,TransformVar *outVars,int4 numLanes,int4 skipLanes) + +{ + int4 inLanes,inSkip; + Varnode *invn = op->getIn(0); + if (!description.restriction(numLanes, skipLanes, 0, invn->getSize(), inLanes, inSkip)) { + return false; + } + // inSkip should always come back as equal to skipLanes + if (inLanes == 1) { + TransformOp *rop = newOpReplace(1, CPUI_COPY, op); + TransformVar *inVar = getPreexistingVarnode(invn); + opSetInput(rop,inVar,0); + opSetOutput(rop,outVars); + } + else { + TransformVar *inRvn = setReplacement(invn,inLanes,inSkip); + if (inRvn == (TransformVar *)0) return false; + for(int4 i=0;igetIn(0); @@ -3305,6 +3546,14 @@ bool LaneDivide::traceBackward(TransformVar *rvn,int4 numLanes,int4 skipLanes) if (!buildRightShift(op, rvn, numLanes, skipLanes)) return false; break; + case CPUI_INT_LEFT: + if (!buildLeftShift(op, rvn, numLanes, skipLanes)) + return false; + break; + case CPUI_INT_ZEXT: + if (!buildZext(op, rvn, numLanes, skipLanes)) + return false; + break; default: return false; } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh index 8ed7b07b17..06b11cc92f 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -211,10 +211,24 @@ public: /// and then rewrites the data-flow in terms of the lower precision, eliminating the /// precision conversions. class SubfloatFlow : public TransformManager { + /// \brief Internal state for walking floating-point data-flow and computing precision + class State { + public: + PcodeOp *op; ///< Operation being traversed + int4 slot; ///< Input edge being traversed + int4 maxPrecision; ///< Maximum precision traversed through inputs so far + State(PcodeOp *o) { + op = o; slot = 0; maxPrecision = 0; } ///< Constructor + /// \brief Accumulate precision coming in from an input Varnode to \b this node + void incorporateInputSize(int4 sz) { maxPrecision = (maxPrecision < sz) ? sz : maxPrecision; } + }; int4 precision; ///< Number of bytes of precision in the logical flow int4 terminatorCount; ///< Number of terminating nodes reachable via the root const FloatFormat *format; ///< The floating-point format of the logical value vector worklist; ///< Current list of placeholders that still need to be traced + map maxPrecisionMap; ///< Maximum precision flowing into a particular floating-point op + int4 maxPrecision(Varnode *vn); ///< Calculate maximum floating-point precision reaching a given Varnode + bool exceedsPrecision(PcodeOp *op); ///< Determine if the given op exceeds our \b precision TransformVar *setReplacement(Varnode *vn); bool traceForward(TransformVar *rvn); bool traceBackward(TransformVar *rvn); @@ -249,9 +263,12 @@ class LaneDivide : public TransformManager { void buildBinaryOp(OpCode opc,PcodeOp *op,TransformVar *in0Vars,TransformVar *in1Vars,TransformVar *outVars,int4 numLanes); bool buildPiece(PcodeOp *op,TransformVar *outVars,int4 numLanes,int4 skipLanes); bool buildMultiequal(PcodeOp *op,TransformVar *outVars,int4 numLanes,int4 skipLanes); + bool buildIndirect(PcodeOp *op,TransformVar *outVars,int4 numLanes,int4 skipLanes); bool buildStore(PcodeOp *op,int4 numLanes,int4 skipLanes); bool buildLoad(PcodeOp *op,TransformVar *outVars,int4 numLanes,int4 skipLanes); bool buildRightShift(PcodeOp *op,TransformVar *outVars,int4 numLanes,int4 skipLanes); + bool buildLeftShift(PcodeOp *op,TransformVar *outVars,int4 numLanes,int4 skipLanes); + bool buildZext(PcodeOp *op,TransformVar *outVars,int4 numLanes,int4 skipLanes); bool traceForward(TransformVar *rvn,int4 numLanes,int4 skipLanes); bool traceBackward(TransformVar *rvn,int4 numLanes,int4 skipLanes); bool processNextWork(void); ///< Process the next Varnode on the work list diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/typeop.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/typeop.cc index 3fb7fa9bb6..21c5275759 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/typeop.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/typeop.cc @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -146,6 +146,35 @@ void TypeOp::selectJavaOperators(vector &inst,bool val) } } +/// Return CPUI_FLOAT_NEG if the \e sign bit is flipped, or CPUI_FLOAT_ABS if the \e sign bit is zeroed out. +/// Otherwise CPUI_MAX is returned. +/// \param op is the given PcodeOp to test +/// \return the floating-point operation the PcodeOp is equivalent to, or CPUI_MAX +OpCode TypeOp::floatSignManipulation(PcodeOp *op) + +{ + OpCode opc = op->code(); + if (opc == CPUI_INT_AND) { + Varnode *cvn = op->getIn(1); + if (cvn->isConstant()) { + uintb val = calc_mask(cvn->getSize()); + val >>= 1; + if (val == cvn->getOffset()) + return CPUI_FLOAT_ABS; + } + } + else if (opc == CPUI_INT_XOR) { + Varnode *cvn = op->getIn(1); + if (cvn->isConstant()) { + uintb val = calc_mask(cvn->getSize()); + val = val ^ (val >> 1); + if (val == cvn->getOffset()) + return CPUI_FLOAT_NEG; + } + } + return CPUI_MAX; +} + /// \param t is the TypeFactory used to construct data-types /// \param opc is the op-code value the new object will represent /// \param n is the display name that will represent the op-code @@ -1333,7 +1362,12 @@ Datatype *TypeOpIntXor::getOutputToken(const PcodeOp *op,CastStrategy *castStrat Datatype *TypeOpIntXor::propagateType(Datatype *alttype,PcodeOp *op,Varnode *invn,Varnode *outvn, int4 inslot,int4 outslot) { - if (!alttype->isPowerOfTwo()) return (Datatype *)0; // Only propagate flag enums + if (!alttype->isPowerOfTwo()) { + if (alttype->getMetatype() != TYPE_FLOAT) + return (Datatype *)0; + if (floatSignManipulation(op) == CPUI_MAX) + return (Datatype *)0; + } Datatype *newtype; if (invn->isSpacebase()) { AddrSpace *spc = tlst->getArch()->getDefaultDataSpace(); @@ -1361,7 +1395,12 @@ Datatype *TypeOpIntAnd::getOutputToken(const PcodeOp *op,CastStrategy *castStrat Datatype *TypeOpIntAnd::propagateType(Datatype *alttype,PcodeOp *op,Varnode *invn,Varnode *outvn, int4 inslot,int4 outslot) { - if (!alttype->isPowerOfTwo()) return (Datatype *)0; // Only propagate flag enums + if (!alttype->isPowerOfTwo()) { + if (alttype->getMetatype() != TYPE_FLOAT) + return (Datatype *)0; + if (floatSignManipulation(op) == CPUI_MAX) + return (Datatype *)0; + } Datatype *newtype; if (invn->isSpacebase()) { AddrSpace *spc = tlst->getArch()->getDefaultDataSpace(); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/typeop.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/typeop.hh index b0df645c38..49bbf4c475 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/typeop.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/typeop.hh @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -177,6 +177,9 @@ public: /// \brief Toggle Java specific aspects of the op-code information static void selectJavaOperators(vector &inst,bool val); + + /// \brief Return the floating-point operation associated with the \e sign bit manipulation by the given PcodeOp + static OpCode floatSignManipulation(PcodeOp *op); }; // Major classes of operations diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/floatcast.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/floatcast.xml new file mode 100644 index 0000000000..3ed62a05e9 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/floatcast.xml @@ -0,0 +1,49 @@ + + + +f30f1efa0f28d0f20f10057100000083 +ff0a7408660fefc0f30f5ac2f20f1015 +6400000083fe0a7408660fefd2f30f5a +d1f20f5cc2f20f5ac0c3 + + + f30f1efaf30f +5ac983ff14741ff30f5ac0660f57054d +000000660f540d55000000660f2fc80f +97c00fb6c0c3f20f10052200000083fe +1475d8f20f100d1d000000ebce + + +17f25dd1adf9f13f891c09d1adf9f13f + + +dbccdd45cac0234033f68845cac02340 +00000000000000800000000000000000 +ffffffffffffff7f0000000000000000 + + + + + +fVar1 = \(float8\)a; +fVar2 = \(float8\)b; +fVar1 = 1\.1234567812345; +fVar2 = 1\.12345678; +return \(float4\)\(fVar1 \- fVar2\); +fVar1 = \(float8\)c; +fVar2 = \(float8\)d; +fVar1 = 9\.8765432198765; +fVar2 = 9\.87654321; +return.*\-fVar1 < ABS\(fVar2\) + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/floatprint.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/floatprint.xml index 85f266bf50..f8108d3d32 100644 --- a/Ghidra/Features/Decompiler/src/decompile/datatests/floatprint.xml +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/floatprint.xml @@ -53,7 +53,7 @@ bbbdd7d9df7cdb3d000000000000f03f print C quit -floatv1 = 0.3333333; +floatv1 = 0.33333334; floatv2 = 2.0; floatv3 = -0.001; floatv4 = 1e-06; @@ -66,5 +66,5 @@ bbbdd7d9df7cdb3d000000000000f03f double4 = 1e-10; double5 = INFINITY; double6 = -NAN; -double7 = 3.141592653589793e-06; +double7 = 3.1415926535897933e-06; diff --git a/Ghidra/Features/Decompiler/src/decompile/unittests/testfloatemu.cc b/Ghidra/Features/Decompiler/src/decompile/unittests/testfloatemu.cc index c35bde8774..2571f55f1a 100644 --- a/Ghidra/Features/Decompiler/src/decompile/unittests/testfloatemu.cc +++ b/Ghidra/Features/Decompiler/src/decompile/unittests/testfloatemu.cc @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -165,6 +165,35 @@ TEST(double_encoding_infinity) { ASSERT_DOUBLE_ENCODING(-std::numeric_limits::infinity()); } +TEST(float_decimal_precision) { + FloatFormat ff(4); + float f0 = floatFromRawBits(0x34000001); + ASSERT_EQUALS(ff.printDecimal(f0, false), "1.192093e-07") + float f1 = floatFromRawBits(0x34800000); + ASSERT_EQUALS(ff.printDecimal(f1, false), "2.3841858e-07") + float f2 = floatFromRawBits(0x3eaaaaab); + ASSERT_EQUALS(ff.printDecimal(f2, false), "0.33333334") + float f3 = floatFromRawBits(0x3e800000); + ASSERT_EQUALS(ff.printDecimal(f3, false), "0.25"); + float f4 = floatFromRawBits(0x3de3ee46); + ASSERT_EQUALS(ff.printDecimal(f4, false), "0.111294314") +} + +TEST(double_decimal_precision) { + FloatFormat ff(8); + double f0 = doubleFromRawBits(0x3fc5555555555555); + ASSERT_EQUALS(ff.printDecimal(f0, false), "0.16666666666666666"); + double f1 = doubleFromRawBits(0x7fefffffffffffff); + ASSERT_EQUALS(ff.printDecimal(f1, false), "1.79769313486232e+308"); + double f2 = doubleFromRawBits(0x3fd555555c7dda4b); + ASSERT_EQUALS(ff.printDecimal(f2, false), "0.33333334"); + double f3 = doubleFromRawBits(0x3fd0000000000000); + ASSERT_EQUALS(ff.printDecimal(f3, false), "0.25"); + double f4 = doubleFromRawBits(0x3fb999999999999a); + ASSERT_EQUALS(ff.printDecimal(f4, false), "0.1"); + double f5 = doubleFromRawBits(0x3fbf7ced916872b0); + ASSERT_EQUALS(ff.printDecimal(f5, true), "1.23000000000000e-01");} + TEST(float_midpoint_rounding) { FloatFormat ff(4); // IEEE754 recommends "round to nearest even" for binary formats, like single and double