diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewEquate.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewEquate.java index ff978d74b1..0fef8fb14e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewEquate.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewEquate.java @@ -16,6 +16,7 @@ package ghidra.trace.database.program; import java.util.Collection; +import java.util.List; import com.google.common.collect.Range; @@ -85,6 +86,11 @@ public class DBTraceProgramViewEquate implements Equate { return refs.toArray(new EquateReference[refs.size()]); } + @Override + public List getReferences(Address refAddr) { + return equate.getReferences(refAddr); + } + @Override public void removeReference(Address refAddr, int opndPosition) { TraceEquateReference ref = equate.getReference(program.snap, null, refAddr, opndPosition); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceEquate.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceEquate.java index a400fe9ff1..7c4ef386d1 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceEquate.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceEquate.java @@ -16,6 +16,7 @@ package ghidra.trace.database.symbol; import java.util.Collection; +import java.util.List; import com.google.common.collect.Range; @@ -23,6 +24,7 @@ import db.DBRecord; import ghidra.program.model.address.Address; import ghidra.program.model.data.Enum; import ghidra.program.model.pcode.Varnode; +import ghidra.program.model.symbol.EquateReference; import ghidra.trace.model.symbol.TraceEquate; import ghidra.trace.model.symbol.TraceEquateReference; import ghidra.trace.model.thread.TraceThread; @@ -114,6 +116,10 @@ public class DBTraceEquate extends DBAnnotatedObject implements TraceEquate { return null; } + public List getReferences(Address refAddr) { + return null; + } + @Override public TraceEquateReference getReference(long snap, TraceThread thread, Address address, int operandIndex) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bean/SetEquateDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bean/SetEquateDialog.java index f732c08a32..b42a1e404d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bean/SetEquateDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bean/SetEquateDialog.java @@ -479,6 +479,17 @@ public class SetEquateDialog extends DialogComponentProvider { overwriteExistingEquates.setEnabled(!applyToCurrent.isSelected()); } + /** + * For using the dialog outside of the EquatePlugin, the "Apply to Current" radio button + * can be selected and the other buttons disabled. + */ + public void disableHasSelection() { + applyToAll.setEnabled(false); + applyToSelection.setEnabled(false); + applyToSelection.setSelected(false); + overwriteExistingEquates.setEnabled(false); + } + /** * Sets the dialogs status display to the given message. */ diff --git a/Ghidra/Features/Decompiler/certification.manifest b/Ghidra/Features/Decompiler/certification.manifest index 9a6aa4192b..3d1ca8132b 100644 --- a/Ghidra/Features/Decompiler/certification.manifest +++ b/Ghidra/Features/Decompiler/certification.manifest @@ -9,6 +9,7 @@ src/decompile/.project||GHIDRA||||END| src/decompile/cpp/.gitignore||GHIDRA||||END| src/decompile/cpp/Doxyfile||GHIDRA|||Most of this file is autogenerated by doxygen which falls under the GPL - output from GPL products are NOT GPL! - mjbell4|END| src/decompile/cpp/Makefile||GHIDRA||||END| +src/decompile/datatests/convert.xml||GHIDRA||||END| src/decompile/datatests/deadvolatile.xml||GHIDRA||||END| src/decompile/datatests/floatprint.xml||GHIDRA||||END| src/decompile/datatests/forloop1.xml||GHIDRA||||END| diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/database.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/database.cc index 0821ced293..2363a94b02 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/database.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/database.cc @@ -639,6 +639,21 @@ void FunctionSymbol::restoreXml(const Element *el) } } +/// Create a symbol either to associate a name with a constant or to force a display conversion +/// +/// \param sc is the scope owning the new symbol +/// \param nm is the name of the equate (an empty string can be used for a convert) +/// \param format is the desired display conversion (0 for no conversion) +/// \param val is the constant value whose display is being altered +EquateSymbol::EquateSymbol(Scope *sc,const string &nm,uint4 format,uintb val) + : Symbol(sc, nm, (Datatype *)0) +{ + value = val; + category = 1; + type = sc->getArch()->types->getBase(1,TYPE_UNKNOWN); + dispflags |= format; +} + /// An EquateSymbol should survive certain kinds of transforms during decompilation, /// such as negation, twos-complementing, adding or subtracting 1. /// Return \b true if the given value looks like a transform of this type relative @@ -688,7 +703,6 @@ void EquateSymbol::restoreXml(const Element *el) TypeFactory *types = scope->getArch()->types; type = types->getBase(1,TYPE_UNKNOWN); - checkSizeTypeLock(); } /// Label symbols don't really have a data-type, so we just put @@ -1661,6 +1675,26 @@ Symbol *Scope::addDynamicSymbol(const string &nm,Datatype *ct,const Address &cad return sym; } +/// \brief Create a symbol that forces a constant display conversion +/// +/// \param format is the type of conversion (Symbol::force_hex, Symbol::force_dec, etc.) +/// \param value is the constant value being converted +/// \param addr is the address of the p-code op reading the constant +/// \param hash is the dynamic hash identifying the constant +/// \return the new EquateSymbol +Symbol *Scope::addConvertSymbol(uint4 format,uintb value,Address &addr,uint8 hash) + +{ + Symbol *sym; + + sym = new EquateSymbol(owner,"",format,value); + RangeList rnglist; + if (!addr.isInvalid()) + rnglist.insertRange(addr.getSpace(),addr.getOffset(),addr.getOffset()); + addDynamicMapInternal(sym,Varnode::mapped,hash,0,1,rnglist); + return sym; +} + /// Create default name given information in the Symbol and possibly a representative Varnode. /// This method extracts the crucial properties and then uses the buildVariableName method to /// construct the actual name. diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/database.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/database.hh index e254246b52..eb81f73c7d 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/database.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/database.hh @@ -270,6 +270,7 @@ public: class EquateSymbol : public Symbol { uintb value; ///< Value of the constant being equated public: + EquateSymbol(Scope *sc,const string &nm,uint4 format,uintb val); ///< Constructor EquateSymbol(Scope *sc) : Symbol(sc) { value = 0; category = 1; } ///< Constructor for use with restoreXml uintb getValue(void) const { return value; } ///< Get the constant value bool isValueClose(uintb op2Value,int4 size) const; ///< Is the given value similar to \b this equate @@ -712,6 +713,7 @@ public: ExternRefSymbol *addExternalRef(const Address &addr,const Address &refaddr,const string &nm); LabSymbol *addCodeLabel(const Address &addr,const string &nm); Symbol *addDynamicSymbol(const string &nm,Datatype *ct,const Address &caddr,uint8 hash); + Symbol *addConvertSymbol(uint4 format,uintb value,Address &addr,uint8 hash); string buildDefaultName(Symbol *sym,int4 &base,Varnode *vn) const; ///< Create a default name for the given Symbol bool isReadOnly(const Address &addr,int4 size,const Address &usepoint) const; void printBounds(ostream &s) const { rangetree.printBounds(s); } ///< Print a description of \b this Scope's \e owned memory ranges diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc index 4956f44da0..be29596d06 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc @@ -1207,6 +1207,10 @@ bool Funcdata::attemptDynamicMappingLate(SymbolEntry *entry,DynamicHash &dhash) return false; if (vn->getSymbolEntry() == entry) return false; // Already applied it Symbol *sym = entry->getSymbol(); + if (sym->getCategory() == 1) { // Equate symbol does not depend on size + vn->setSymbolEntry(entry); + return true; + } if (vn->getSize() != entry->getSize()) { ostringstream s; s << "Unable to use symbol "; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.cc index 883e07ba31..215b15a4bf 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.cc @@ -57,6 +57,7 @@ void IfaceDecompCapability::registerCommands(IfaceStatus *status) status->registerCom(new IfcMapfunction(),"map","function"); status->registerCom(new IfcMapexternalref(),"map","externalref"); status->registerCom(new IfcMaplabel(),"map","label"); + status->registerCom(new IfcMapconvert(),"map","convert"); status->registerCom(new IfcPrintdisasm(),"disassemble"); status->registerCom(new IfcDecompile(),"decompile"); status->registerCom(new IfcDump(),"dump"); @@ -665,6 +666,45 @@ void IfcMaplabel::execute(istream &s) scope->setAttribute(sym,Varnode::namelock|Varnode::typelock); } +/// \class IfcMapconvert +/// \brief Create an convert directive: `map convert
` +/// +/// Creates a \e convert directive that causes a targeted constant value to be displayed +/// with the specified integer format. The constant is specified by \e value, and the +/// \e address of the p-code op using the constant plus a dynamic \e hash is also given. +void IfcMapconvert::execute(istream &s) + +{ + if (dcp->fd == (Funcdata *)0) + throw IfaceExecutionError("No function loaded"); + string name; + uintb value; + uint8 hash; + int4 size; + uint4 format = 0; + + s >> name; // Parse the format token + if (name == "hex") + format = Symbol::force_hex; + else if (name == "dec") + format = Symbol::force_dec; + else if (name == "bin") + format = Symbol::force_bin; + else if (name == "oct") + format = Symbol::force_oct; + else if (name == "char") + format = Symbol::force_char; + else + throw IfaceParseError("Bad convert format"); + + s >> ws >> hex >> value; + Address addr = parse_machaddr(s,size,*dcp->conf->types); // Read pc address of hash + + s >> hex >> hash; // Parse the hash value + + dcp->fd->getScopeLocal()->addConvertSymbol(format, value, addr, hash); +} + /// \class IfcPrintdisasm /// \brief Print disassembly of a memory range: `disassemble [ ]` /// diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.hh index 16f9f89838..d68da85044 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.hh @@ -178,6 +178,11 @@ public: virtual void execute(istream &s); }; +class IfcMapconvert : public IfaceDecompCommand { +public: + virtual void execute(istream &s); +}; + class IfcPrintdisasm : public IfaceDecompCommand { public: virtual void execute(istream &s); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc index 79da4cfa31..ee7e605f41 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc @@ -1099,7 +1099,7 @@ void PrintC::push_integer(uintb val,int4 sz,bool sign, displayFormat = sym->getDisplayFormat(); } } - if (sign) { // Print the constant as signed + if (sign && displayFormat != Symbol::force_char) { // Print the constant as signed uintb mask = calc_mask(sz); uintb flip = val^mask; print_negsign = (flip < val); @@ -1136,21 +1136,14 @@ void PrintC::push_integer(uintb val,int4 sz,bool sign, else if (displayFormat == Symbol::force_oct) t << oct << '0' << val; else if (displayFormat == Symbol::force_char) { - int4 internalSize = 4; - if (val < 256) - internalSize = 1; - else if (val < 65536) - internalSize = 2; - if ((internalSize==1)&&((val<7)||(val>0x7e)||((val>13)&&(val<0x20)))) { // not a good character constant - t << dec << val; // Just emit as decimal - } - else { - if (doEmitWideCharPrefix() && internalSize > 1) - t << 'L'; // Print symbol indicating wide character - t << '\''; // char is surrounded with single quotes + if (doEmitWideCharPrefix() && sz > 1) + t << 'L'; // Print symbol indicating wide character + t << '\''; // char is surrounded with single quotes + if (sz == 1 && val >= 0x80) + printCharHexEscape(t,(int4)val); + else printUnicode(t,(int4)val); - t << '\''; - } + t << '\''; } else { // Must be Symbol::force_bin t << "0b"; @@ -1270,15 +1263,8 @@ void PrintC::printUnicode(ostream &s,int4 onechar) const s << "\\\'"; return; } - // Generic unicode escape - if (onechar < 256) { - s << "\\x" << setfill('0') << setw(2) << hex << onechar; - } - else if (onechar < 65536) { - s << "\\x" << setfill('0') << setw(4) << hex << onechar; - } - else - s << "\\x" << setfill('0') << setw(8) << hex << onechar; + // Generic escape code + printCharHexEscape(s, onechar); return; } StringManager::writeUtf8(s, onechar); // emit normally @@ -1321,6 +1307,22 @@ bool PrintC::doEmitWideCharPrefix(void) const return true; } +/// Print the given value using the standard character hexadecimal escape sequence. +/// \param s is the stream to write to +/// \param val is the given value +void PrintC::printCharHexEscape(ostream &s,int4 val) + +{ + if (val < 256) { + s << "\\x" << setfill('0') << setw(2) << hex << val; + } + else if (val < 65536) { + s << "\\x" << setfill('0') << setw(4) << hex << val; + } + else + s << "\\x" << setfill('0') << setw(8) << hex << val; +} + /// \brief Print a quoted (unicode) string at the given address. /// /// Data for the string is obtained directly from the LoadImage. The bytes are checked @@ -1397,28 +1399,50 @@ void PrintC::resetDefaultsPrintC(void) /// \param ct is data-type attached to the value /// \param vn is the Varnode holding the value /// \param op is the PcodeOp using the value -void PrintC::pushCharConstant(uintb val,const TypeChar *ct,const Varnode *vn,const PcodeOp *op) +void PrintC::pushCharConstant(uintb val,const Datatype *ct,const Varnode *vn,const PcodeOp *op) { - ostringstream t; + uint4 displayFormat = 0; + bool isSigned = (ct->getMetatype() == TYPE_INT); + if ((vn != (const Varnode *)0)&&(!vn->isAnnotation())) { + Symbol *sym = vn->getHigh()->getSymbol(); + if (sym != (Symbol *)0) { + if (sym->isNameLocked() && (sym->getCategory() == 1)) { + if (pushEquate(val,vn->getSize(),(EquateSymbol *)sym,vn,op)) + return; + } + displayFormat = sym->getDisplayFormat(); + if (displayFormat == Symbol::force_bin || displayFormat == Symbol::force_dec || displayFormat == Symbol::force_oct) { + push_integer(val, ct->getSize(), isSigned, vn, op); + return; + } + } + } if ((ct->getSize()==1)&&(val >= 0x80)) { // For byte characters, the encoding is assumed to be ASCII, UTF-8, or some other // code-page that extends ASCII. At 0x80 and above, we cannot treat the value as a // unicode code-point. Its either part of a multi-byte UTF-8 encoding or an unknown - // code-page value. In either case, we print it as an integer. - push_integer(val,1,true,vn,op); + // code-page value. In either case, we print as an integer or an escape sequence. + if (displayFormat != Symbol::force_hex && displayFormat != Symbol::force_char) { + push_integer(val, 1, isSigned, vn, op); + return; + } + displayFormat = Symbol::force_hex; // Fallthru but force a hex representation } - else { - // From here we assume, the constant value is a direct unicode code-point. - // The value could be an illegal code-point (surrogates or beyond the max code-point), - // but this will just be emitted as an escape sequence. - if (doEmitWideCharPrefix() && ct->getSize() > 1) - t << 'L'; // Print symbol indicating wide character - t << '\''; // char is surrounded with single quotes + ostringstream t; + // From here we assume, the constant value is a direct unicode code-point. + // The value could be an illegal code-point (surrogates or beyond the max code-point), + // but this will just be emitted as an escape sequence. + if (doEmitWideCharPrefix() && ct->getSize() > 1) + t << 'L'; // Print symbol indicating wide character + t << '\''; // char is surrounded with single quotes + if (displayFormat == Symbol::force_hex) { + printCharHexEscape(t,(int4)val); + } + else printUnicode(t,(int4)val); - t << '\''; - pushAtom(Atom(t.str(),vartoken,EmitXml::const_color,op,vn)); - } + t << '\''; + pushAtom(Atom(t.str(),vartoken,EmitXml::const_color,op,vn)); } /// \brief Push an enumerated value to the RPN stack diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/printc.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/printc.hh index 92afe1ee1e..ad94657001 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/printc.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/printc.hh @@ -131,7 +131,7 @@ protected: virtual void pushTypeEnd(const Datatype *ct); ///< Push the tail ends of a data-type declaration onto the RPN stack void pushBoolConstant(uintb val,const TypeBase *ct,const Varnode *vn, const PcodeOp *op); - void pushCharConstant(uintb val,const TypeChar *ct,const Varnode *vn, + void pushCharConstant(uintb val,const Datatype *ct,const Varnode *vn, const PcodeOp *op); void pushEnumConstant(uintb val,const TypeEnum *ct,const Varnode *vn, const PcodeOp *op); @@ -162,6 +162,7 @@ protected: void opFunc(const PcodeOp *op); ///< Push a \e functional expression based on the given p-code op to the RPN stack void opTypeCast(const PcodeOp *op); ///< Push the given p-code op using type-cast syntax to the RPN stack void opHiddenFunc(const PcodeOp *op); ///< Push the given p-code op as a hidden token + static void printCharHexEscape(ostream &s,int4 val); ///< Print value as an escaped hex sequence bool printCharacterConstant(ostream &s,const Address &addr,Datatype *charType) const; int4 getHiddenThisSlot(const PcodeOp *op,FuncProto *fc); ///< Get position of "this" pointer needing to be hidden void resetDefaultsPrintC(void); ///< Set default values for options specific to PrintC diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/printlanguage.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/printlanguage.cc index 2fb81a93c0..30eb781412 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/printlanguage.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/printlanguage.cc @@ -431,7 +431,7 @@ bool PrintLanguage::unicodeNeedsEscape(int4 codepoint) if (codepoint > 0xa0) { // Printable codepoints A1-FF return false; } - return true; + return true; // Delete + C1 Control characters } if (codepoint >= 0x2fa20) { // Up to last currently defined language return true; @@ -483,6 +483,8 @@ bool PrintLanguage::unicodeNeedsEscape(int4 codepoint) return true; // zero width non-breaking space } if (codepoint >= 0xfff0 && codepoint <= 0xffff) { + if ((codepoint == 0xfffc || codepoint == 0xfffd)) + return false; return true; // interlinear specials } return false; @@ -772,11 +774,11 @@ void PrintLanguage::formatBinary(ostream &s,uintb val) s << '0'; return; } - else if (pos < 7) + else if (pos <= 7) pos = 7; - else if (pos < 15) + else if (pos <= 15) pos = 15; - else if (pos < 31) + else if (pos <= 31) pos = 31; else pos = 63; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc index a1c01498ed..a9c28db6c2 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc @@ -2464,6 +2464,7 @@ int4 RuleZextEliminate::applyOp(PcodeOp *op,Funcdata &data) val = vn2->getOffset(); if ((val>>(8*smallsize))==0) { // Is zero extension unnecessary newvn = data.newConstant(smallsize,val); + newvn->copySymbolIfValid(vn2); data.opSetInput(op,zext->getIn(0),zextslot); data.opSetInput(op,newvn,otherslot); return 1; @@ -3696,7 +3697,9 @@ int4 RuleXorCollapse::applyOp(PcodeOp *op,Funcdata &data) } coeff2 = xorvn->getOffset(); if (coeff2 == 0) return 0; - data.opSetInput(op,data.newConstant(op->getIn(1)->getSize(),coeff1^coeff2),1); + Varnode *constvn = data.newConstant(op->getIn(1)->getSize(),coeff1^coeff2); + constvn->copySymbolIfValid(xorvn); + data.opSetInput(op,constvn,1); data.opSetInput(op,xorop->getIn(0),0); return 1; } @@ -3757,6 +3760,10 @@ int4 RuleAddMultCollapse::applyOp(PcodeOp *op,Funcdata &data) uintb val = op->getOpcode()->evaluateBinary(c[0]->getSize(),c[0]->getSize(),c[0]->getOffset(),c[1]->getOffset()); newvn = data.newConstant(c[0]->getSize(),val); + if (c[0]->getSymbolEntry() != (SymbolEntry *)0) + newvn->copySymbolIfValid(c[0]); + else if (c[1]->getSymbolEntry() != (SymbolEntry *)0) + newvn->copySymbolIfValid(c[1]); PcodeOp *newop = data.newOp(2,op->getAddr()); data.opSetOpcode(newop,CPUI_INT_ADD); Varnode *newout = data.newUniqueOut(c[0]->getSize(),newop); @@ -3774,6 +3781,10 @@ int4 RuleAddMultCollapse::applyOp(PcodeOp *op,Funcdata &data) uintb val = op->getOpcode()->evaluateBinary(c[0]->getSize(),c[0]->getSize(),c[0]->getOffset(),c[1]->getOffset()); newvn = data.newConstant(c[0]->getSize(),val); + if (c[0]->getSymbolEntry() != (SymbolEntry *)0) + newvn->copySymbolIfValid(c[0]); + else if (c[1]->getSymbolEntry() != (SymbolEntry *)0) + newvn->copySymbolIfValid(c[1]); data.opSetInput(op,newvn,1); // Replace c[0] with c[0]+c[1] or c[0]*c[1] data.opSetInput(op,sub2,0); // Replace sub with sub2 return 1; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.cc index bbb3f2ca70..a4d37d7e3a 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.cc @@ -83,7 +83,7 @@ SubvariableFlow::ReplaceVarnode *SubvariableFlow::setReplacement(Varnode *vn,uin if (sextval != cval) return (ReplaceVarnode *)0; } - return addConstant((ReplaceOp *)0,mask,0,vn->getOffset()); + return addConstant((ReplaceOp *)0,mask,0,vn); } if (vn->isFree()) @@ -628,7 +628,7 @@ bool SubvariableFlow::traceBackward(ReplaceVarnode *rvn) sa = doesAndClear(op,rvn->mask); if (sa != -1) { rop = createOp(CPUI_COPY,1,rvn); - addConstant(rop,rvn->mask,0,op->getIn(sa)->getOffset()); + addConstant(rop,rvn->mask,0,op->getIn(sa)); } else { rop = createOp(CPUI_INT_AND,2,rvn); @@ -640,7 +640,7 @@ bool SubvariableFlow::traceBackward(ReplaceVarnode *rvn) sa = doesOrSet(op,rvn->mask); if (sa != -1) { rop = createOp(CPUI_COPY,1,rvn); - addConstant(rop,rvn->mask,0,op->getIn(sa)->getOffset()); + addConstant(rop,rvn->mask,0,op->getIn(sa)); } else { rop = createOp(CPUI_INT_OR,2,rvn); @@ -676,7 +676,7 @@ bool SubvariableFlow::traceBackward(ReplaceVarnode *rvn) newmask = rvn->mask >> sa; // What mask looks like before shift if (newmask == 0) { // Subvariable filled with shifted zero rop = createOp(CPUI_COPY,1,rvn); - addConstant(rop,rvn->mask,0,(uintb)0); + addNewConstant(rop,0,(uintb)0); return true; } if ((newmask<mask) @@ -690,7 +690,7 @@ bool SubvariableFlow::traceBackward(ReplaceVarnode *rvn) newmask = (rvn->mask << sa) & calc_mask(op->getIn(0)->getSize()); if (newmask == 0) { // Subvariable filled with shifted zero rop = createOp(CPUI_COPY,1,rvn); - addConstant(rop,rvn->mask,0,(uintb)0); + addNewConstant(rop,0,(uintb)0); return true; } if ((newmask>>sa) != rvn->mask) @@ -772,7 +772,7 @@ bool SubvariableFlow::traceBackward(ReplaceVarnode *rvn) if ((rvn->mask&1)==1) break; // Not normal variable flow // Variable is filled with zero rop = createOp(CPUI_COPY,1,rvn); - addConstant(rop,rvn->mask,0,(uintb)0); + addNewConstant(rop,0,(uintb)0); return true; default: break; // Everything else we abort @@ -825,7 +825,7 @@ bool SubvariableFlow::traceForwardSext(ReplaceVarnode *rvn) if (!op->getIn(1)->isConstant()) return false; // Right now we only deal with constant shifts rop = createOpDown(CPUI_INT_SRIGHT,2,op,rvn,0); if (!createLink(rop,rvn->mask,-1,outvn)) return false; // Keep the same mask size - addConstant(rop,calc_mask(op->getIn(1)->getSize()),1,op->getIn(1)->getOffset()); // Preserve the shift amount + addConstant(rop,calc_mask(op->getIn(1)->getSize()),1,op->getIn(1)); // Preserve the shift amount hcount += 1; break; case CPUI_SUBPIECE: @@ -916,7 +916,7 @@ bool SubvariableFlow::traceBackwardSext(ReplaceVarnode *rvn) rop = createOp(CPUI_INT_SRIGHT,2,rvn); if (!createLink(rop,rvn->mask,0,op->getIn(0))) return false; // Keep the same mask if (rop->input.size()==1) - addConstant(rop,calc_mask(op->getIn(1)->getSize()),1,op->getIn(1)->getOffset()); // Preserve the shift amount + addConstant(rop,calc_mask(op->getIn(1)->getSize()),1,op->getIn(1)); // Preserve the shift amount return true; case CPUI_CALL: case CPUI_CALLIND: @@ -993,23 +993,40 @@ bool SubvariableFlow::createCompareBridge(PcodeOp *op,ReplaceVarnode *inrvn,int4 /// \brief Add a constant variable node to the logical subgraph /// -/// Unlike other subgraph variable nodes, this one does not maintain a mirror with the original containing Varnode. /// \param rop is the logical operation taking the constant as input /// \param mask is the set of bits holding the logical value (within a bigger value) /// \param slot is the input slot to the operation -/// \param val is the bigger constant value holding the logical value +/// \param constvn is the original constant SubvariableFlow::ReplaceVarnode *SubvariableFlow::addConstant(ReplaceOp *rop,uintb mask, - uint4 slot,uintb val) -{ // Add a constant to the replacement tree + uint4 slot,Varnode *constvn) +{ newvarlist.emplace_back(); ReplaceVarnode *res = &newvarlist.back(); - res->vn = (Varnode *)0; + res->vn = constvn; res->replacement = (Varnode *)0; res->mask = mask; // Calculate the actual constant value int4 sa = leastsigbit_set(mask); - res->val = (mask & val) >> sa; + res->val = (mask & constvn->getOffset()) >> sa; + res->def = (ReplaceOp *)0; + if (rop != (ReplaceOp *)0) { + while(rop->input.size() <= slot) + rop->input.push_back((ReplaceVarnode *)0); + rop->input[slot] = res; + } + return res; +} + +SubvariableFlow::ReplaceVarnode *SubvariableFlow::addNewConstant(ReplaceOp *rop,uint4 slot,uintb val) + +{ + newvarlist.emplace_back(); + ReplaceVarnode *res = &newvarlist.back(); + res->vn = (Varnode *)0; + res->replacement = (Varnode *)0; + res->mask = 0; + res->val = val; res->def = (ReplaceOp *)0; if (rop != (ReplaceOp *)0) { while(rop->input.size() <= slot) @@ -1214,13 +1231,17 @@ Varnode *SubvariableFlow::getReplaceVarnode(ReplaceVarnode *rvn) { if (rvn->replacement != (Varnode *)0) return rvn->replacement; - // Only a constant if BOTH replacement and vn fields are null if (rvn->vn == (Varnode *)0) { - if (rvn->def==(ReplaceOp *)0) // A constant + if (rvn->def==(ReplaceOp *)0) // A constant that did not come from an original Varnode return fd->newConstant(flowsize,rvn->val); rvn->replacement = fd->newUnique(flowsize); return rvn->replacement; } + if (rvn->vn->isConstant()) { + Varnode *newVn = fd->newConstant(flowsize,rvn->val); + newVn->copySymbolIfValid(rvn->vn); + return newVn; + } bool isinput = rvn->vn->isInput(); if (useSameAddress(rvn)) { diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh index c289890038..d3c5983b8e 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh @@ -111,7 +111,8 @@ class SubvariableFlow { void addBooleanPatch(PcodeOp *pullop,ReplaceVarnode *rvn,int4 slot); void addSuggestedPatch(ReplaceVarnode *rvn,PcodeOp *pushop,int4 sa); void addComparePatch(ReplaceVarnode *in1,ReplaceVarnode *in2,PcodeOp *op); - ReplaceVarnode *addConstant(ReplaceOp *rop,uintb mask,uint4 slot,uintb val); + ReplaceVarnode *addConstant(ReplaceOp *rop,uintb mask,uint4 slot,Varnode *constvn); + ReplaceVarnode *addNewConstant(ReplaceOp *rop,uint4 slot,uintb val); void createNewOut(ReplaceOp *rop,uintb mask); void replaceInput(ReplaceVarnode *rvn); bool useSameAddress(ReplaceVarnode *rvn); diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/convert.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/convert.xml new file mode 100644 index 0000000000..7c9823c426 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/convert.xml @@ -0,0 +1,66 @@ + + + + + 554889e5bf0001 +0000e883ffffffbf00feffffe879ffff +ffbf00030000e893ffffffbf00ffffff +e889ffffffbfe8030000e85bffffffbf +48f4ffffe851ffffffbf70170000e86b +ffffffbf00ea56fae861ffffffbfff01 +0000e833ffffffbf25ffffffe829ffff +ffbf6d010000e843ffffffbffeffffff +e839ffffffbf88000000e80bffffffbf +3433ffffe801ffffffbfeeee0000e81b +ffffffbfdff7ffffe811ffffffbf6100 +0000e8e3feffff905dc3 + + + + + + +recv_signed\(256\) +recv_signed\(-512\) +recv_unsigned\(768\) +recv_unsigned\(4294967040\) +recv_signed\(0x3e8\) +recv_signed\(-0xbb8\) +recv_unsigned\(0x1770\) +recv_unsigned\(0xfa56ea00\) +recv_signed\(0777\) +recv_signed\(-0333\) +recv_unsigned\(0555\) +recv_unsigned\(037777777776\) +recv_signed\(0b10001000\) +recv_signed\(-0b1100110011001100\) +recv_unsigned\(0b1110111011101110\) +recv_unsigned\(0b11111111111111111111011111011111\) +recv_signed\('a'\) + diff --git a/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml b/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml index 6c46f58a86..8b692f5a17 100644 --- a/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml +++ b/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml @@ -2456,12 +2456,14 @@
Constant Annotations - Ghidra provides some ways to control how specific constants shown in disassembly are formatted or displayed. - These annotations are attached to constants as operands of specific machine instructions. To the extent - possible, the decompiler applies these annotations to the matching constant in the decompiler output. - The constant may be transformed from its value in the original machine instruction during the decompiler's - analysis. The decompiler will follow the constant through simple transformations, but if the transformed - is too far from the original value, the annotation will not be applied. The transforms followed are: + Ghidra provides numerous actions to control how specific constants are formatted or displayed. + An annotation can be applied directly to a constant in the Decompiler Window, which always affects + decompiler output. Or, an annotation can be applied to the constant operand of a specific machine + instruction displayed in the Listing Window. In this case, to the extent possible, the decompiler + attempts to track the operand and apply the annotation to the matching constant in the decompiler output. + However, the constant may be transformed from its value in the original machine instruction during the decompiler's + analysis. The decompiler will follow the constant through simple transformations, but if the constant strays + too far from its original value, the annotation will not be applied. The transforms followed are: Signed or zero extension @@ -2475,39 +2477,53 @@ Equates Ghidra can create an association between a name and a constant, called an equate. - The constant must be a constant operand of a specific machine instruction, and the equate - is applied directly to the operand from the Listing Window using the - Set Equate menu. - Once applied, the equate's name is displayed instead of the numeric representation of the constant. - Equates across the entire Program can be viewed from the + An equate is a descriptive string that is intended to replace the numeric form of the constant, and equates + across the entire Program can be viewed from the Equate Table. - When analyzing a function, the decompiler attempts to follow any constant in the function with an attached - equate to the matching constant in the final output. If successful, the equate's name is printed instead of - the numeric form of the matching constant. If the constant was transformed from its original value, the - matching constant is printed as an expression, where the transforming operations are applied to the equate - symbol (representing the original constant). + An equate can be applied to a machine instruction with a constant + operand by using the Set Equate + menu from the Listing Window. If the decompiler successfully follows the operand to a matching constant, + the equate's name is displayed as part of the decompiler's output as well as in the Listing Window. + A transformed operand is displayed as an expression, where the transforming operations are applied to + the equate symbol (representing the original constant). + + + Alternately an equate can be applied directly to a constant from the Decompiler Window using its + menu. The constant may or may not have a corresponding instruction + operand but will be displayed in decompiler output using the descriptive string. + + + Format Conversions - Ghidra can apply a format conversion to override how an integer operand - is displayed in a specific machine instruction. Conversions are generally applied from the Listing Window using the - Convert menu option. When analyzing a - function containing a machine instruction that has a format conversion applied, the decompiler will attempt - to trace the constant to a matching constant in the final output. If successful, the format conversion is also - applied to the matching constant. + Ghidra can apply a format conversion to integer constants that are displayed + in decompiler output. + + + A conversion can be applied to the machine instruction containing the constant + as an operand using the Convert menu option + from the Listing Window. If the decompiler successfully traces the operand to a matching constant, + the format conversion is applied in the decompiler output as well as in the Listing Window. + + + Alternately, a conversion can be applied directly to an integer constant in the + Decompiler Window using its menu option. The constant may or may not + have a corresponding instruction operand but is displayed in decompiler output using the conversion. Conversions applied by the decompiler are currently limited to: - Binary - 0b10100111 - Decimal- 167 - Hexadecimal - 0xa7 - Octal - 0247 + Binary - 0b01100001 + Decimal- 97 + Hexadecimal - 0x61 + Octal - 0141 + Char - 'a' An appropriate header matching the format is prepended to the representation string, either "0b", "0x" or just @@ -4020,6 +4036,63 @@ + + Convert + + Change the displayed encoding for the integer or character constant under the cursor. + + + Various encodings are possible. + + + + + + + + + + + + + + + + + + + + + + + + + + +
Binary:0b01100001
Decimal:97
Hexadecimal:0x61
Octal:0141
Char:'a'
+
+
+ + This command primarily targets a constant token in the Decompiler window, but + if there is a scalar operand in an instruction that corresponds + with the selected constant, the same conversion is also applied to the scalar in the Listing + window. This is equivalent to selecting the + Convert command from the + Listing. There may not be a scalar operand directly corresponding to the selected constant, in + which case the conversion will be applied only in the Decompiler window. + + + The data-type of the selected token is not changed by the action, and it retains its + meaning in the decompiler output. This slightly limits the possible conversions as signed + values cannot be forced into an unsigned representation, and vice versa. + + + The constant's encoding can be changed by selecting a different Convert + command, or it can be returned to its default encoding by selecting + the command. + +
+ Copy/Copy Special ... @@ -4322,6 +4395,21 @@ + + Remove Convert/Equate + + Remove the display conversion or equate from + the constant under the cursor. + + + The selected constant must have had either a or a + command applied to it. After applying this command, + the conversion is no longer applied, and the selected constant will be displayed + using the decompiler's default strategy, which depends on the data-type of the constant and + other display settings (See ). + + + Remove Signature Override @@ -4563,6 +4651,38 @@ + + Set Equate ... + + Change the display of the integer or character constant under the cursor to an equate + string. + + + An equate is a descriptive string associated with a specific constant value, which is managed globally for + a Program by the Equate plug-in (See Equates). + + + The Set Equate command, brings up a dialog listing other equates + that have previously been associated with the selected constant. The user can choose an equate from this list + or define a new equate for the constant by typing in the text field at the top of the dialog. Alternately, + the text field can be used as a filter to narrow down possibilities prior to selecting from the list. Clicking + OK, completes the action, and the selected equate is substituted for its constant. + + + This command primarily targets a constant token in the Decompiler window, but + if there is a scalar operand in an instruction that corresponds + with the selected constant, the same equate is also applied to the scalar in the Listing + window. This is equivalent to selecting the + Set Equate command from the + Listing. There may not be a scalar operand directly corresponding to the selected constant, in + which case the equate will be applied only in the Decompiler window. + + + Once an equate is applied, the constant can be returned to its default display + by selecting the command. + + + Split Out As New Variable diff --git a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerAnnotations.html b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerAnnotations.html index 3e310b2ac6..101ef19df7 100644 --- a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerAnnotations.html +++ b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerAnnotations.html @@ -1214,12 +1214,14 @@ Constant Annotations

- Ghidra provides some ways to control how specific constants shown in disassembly are formatted or displayed. - These annotations are attached to constants as operands of specific machine instructions. To the extent - possible, the decompiler applies these annotations to the matching constant in the decompiler output. - The constant may be transformed from its value in the original machine instruction during the decompiler's - analysis. The decompiler will follow the constant through simple transformations, but if the transformed - is too far from the original value, the annotation will not be applied. The transforms followed are: + Ghidra provides numerous actions to control how specific constants are formatted or displayed. + An annotation can be applied directly to a constant in the Decompiler Window, which always affects + decompiler output. Or, an annotation can be applied to the constant operand of a specific machine + instruction displayed in the Listing Window. In this case, to the extent possible, the decompiler + attempts to track the operand and apply the annotation to the matching constant in the decompiler output. + However, the constant may be transformed from its value in the original machine instruction during the decompiler's + analysis. The decompiler will follow the constant through simple transformations, but if the constant strays + too far from its original value, the annotation will not be applied. The transforms followed are:

    @@ -1237,19 +1239,25 @@

    Ghidra can create an association between a name and a constant, called an equate. - The constant must be a constant operand of a specific machine instruction, and the equate - is applied directly to the operand from the Listing Window using the - Set Equate menu. - Once applied, the equate's name is displayed instead of the numeric representation of the constant. - Equates across the entire Program can be viewed from the + An equate is a descriptive string that is intended to replace the numeric form of the constant, and equates + across the entire Program can be viewed from the Equate Table.

    - When analyzing a function, the decompiler attempts to follow any constant in the function with an attached - equate to the matching constant in the final output. If successful, the equate's name is printed instead of - the numeric form of the matching constant. If the constant was transformed from its original value, the - matching constant is printed as an expression, where the transforming operations are applied to the equate - symbol (representing the original constant). + An equate can be applied to a machine instruction with a constant + operand by using the Set Equate + menu from the Listing Window. If the decompiler successfully follows the operand to a matching constant, + the equate's name is displayed as part of the decompiler's output as well as in the Listing Window. + A transformed operand is displayed as an expression, where the transforming operations are applied to + the equate symbol (representing the original constant). +

    +

    + Alternately an equate can be applied directly to a constant from the Decompiler Window using its + “Set Equate ...” menu. The constant may or may not have a corresponding instruction + operand but will be displayed in decompiler output using the descriptive string. +

    +

    +

@@ -1257,22 +1265,30 @@ Format Conversions

- Ghidra can apply a format conversion to override how an integer operand - is displayed in a specific machine instruction. Conversions are generally applied from the Listing Window using the - Convert menu option. When analyzing a - function containing a machine instruction that has a format conversion applied, the decompiler will attempt - to trace the constant to a matching constant in the final output. If successful, the format conversion is also - applied to the matching constant. + Ghidra can apply a format conversion to integer constants that are displayed + in decompiler output. +

+

+ A conversion can be applied to the machine instruction containing the constant + as an operand using the Convert menu option + from the Listing Window. If the decompiler successfully traces the operand to a matching constant, + the format conversion is applied in the decompiler output as well as in the Listing Window. +

+

+ Alternately, a conversion can be applied directly to an integer constant in the + Decompiler Window using its “Convert” menu option. The constant may or may not + have a corresponding instruction operand but is displayed in decompiler output using the conversion.

Conversions applied by the decompiler are currently limited to:

    -
  • Binary - 0b10100111
  • -
  • Decimal- 167
  • -
  • Hexadecimal - 0xa7
  • -
  • Octal - 0247
  • +
  • Binary - 0b01100001
  • +
  • Decimal- 97
  • +
  • Hexadecimal - 0x61
  • +
  • Octal - 0141
  • +
  • Char - 'a'

diff --git a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerWindow.html b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerWindow.html index e11f54d743..ff476e9ea8 100644 --- a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerWindow.html +++ b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerWindow.html @@ -620,6 +620,69 @@

+
+

+Convert

+ +

+ Change the displayed encoding for the integer or character constant under the cursor. +

+

+ Various encodings are possible. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Binary:0b01100001
Decimal:97
Hexadecimal:0x61
Octal:0141
Char:'a'
+
+
+

+

+

+ This command primarily targets a constant token in the Decompiler window, but + if there is a scalar operand in an instruction that corresponds + with the selected constant, the same conversion is also applied to the scalar in the Listing + window. This is equivalent to selecting the + Convert command from the + Listing. There may not be a scalar operand directly corresponding to the selected constant, in + which case the conversion will be applied only in the Decompiler window. +

+

+ The data-type of the selected token is not changed by the action, and it retains its + meaning in the decompiler output. This slightly limits the possible conversions as signed + values cannot be forced into an unsigned representation, and vice versa. +

+

+ The constant's encoding can be changed by selecting a different Convert + command, or it can be returned to its default encoding by selecting + the “Remove Convert/Equate” command. +

+
+

Copy/Copy Special ...

@@ -921,6 +984,23 @@
+
+

+Remove Convert/Equate

+ +

+ Remove the display conversion or equate from + the constant under the cursor. +

+

+ The selected constant must have had either a “Convert” or a + “Set Equate ...” command applied to it. After applying this command, + the conversion is no longer applied, and the selected constant will be displayed + using the decompiler's default strategy, which depends on the data-type of the constant and + other display settings (See Integer format). +

+
+

Remove Signature Override

@@ -1180,6 +1260,40 @@

+
+

+Set Equate ...

+ +

+ Change the display of the integer or character constant under the cursor to an equate + string. +

+

+ An equate is a descriptive string associated with a specific constant value, which is managed globally for + a Program by the Equate plug-in (See Equates). +

+

+ The Set Equate command, brings up a dialog listing other equates + that have previously been associated with the selected constant. The user can choose an equate from this list + or define a new equate for the constant by typing in the text field at the top of the dialog. Alternately, + the text field can be used as a filter to narrow down possibilities prior to selecting from the list. Clicking + OK, completes the action, and the selected equate is substituted for its constant. +

+

+ This command primarily targets a constant token in the Decompiler window, but + if there is a scalar operand in an instruction that corresponds + with the selected constant, the same equate is also applied to the scalar in the Listing + window. This is equivalent to selecting the + Set Equate command from the + Listing. There may not be a scalar operand directly corresponding to the selected constant, in + which case the equate will be applied only in the Decompiler window. +

+

+ Once an equate is applied, the constant can be returned to its default display + by selecting the “Remove Convert/Equate” command. +

+
+

Split Out As New Variable

diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java index 765ed0543a..d1958f4810 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java @@ -63,43 +63,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter static final ImageIcon C_SOURCE_ICON = ResourceManager.loadImage("images/decompileFunction.gif"); - private DockingAction defUseHighlightAction; - private DockingAction forwardSliceAction; - private DockingAction forwardSliceToOpsAction; - private DockingAction backwardSliceAction; - private DockingAction backwardSliceToOpsAction; - private DockingAction lockProtoAction; - private DockingAction lockLocalAction; - private DockingAction renameLocalAction; - private DockingAction renameGlobalAction; - private DockingAction renameFieldAction; - private DockingAction retypeLocalAction; - private DockingAction retypeGlobalAction; - private DockingAction retypeReturnAction; - private DockingAction retypeFieldAction; - private DockingAction isolateVarAction; - private DockingAction specifyCProtoAction; - private DockingAction overrideSigAction; - private DockingAction deleteSigAction; - private DockingAction debugFunctionAction; - private DockingAction convertAction; private DockingAction graphASTControlFlowAction; - private DockingAction findAction; - private DockingAction propertiesAction; - private DockingAction editDataTypeAction; - private DockingAction decompilerCreateStructureAction; - private DockingAction listingCreateStructureAction; - private DockingAction renameFunctionAction; - private DockingAction findReferencesAction; - - private CloneDecompilerAction cloneDecompilerAction; - - private SelectAllAction selectAllAction; - - private SetSecondaryHighlightAction setSecondaryHighlightAction; - private SetSecondaryHighlightColorChooserAction setSecondaryHighlightColorChooserAction; - private RemoveAllSecondaryHighlightsAction removeAllSecondadryHighlightsAction; - private RemoveSecondaryHighlightAction removeSecondaryHighlightAction; private final DecompilePlugin plugin; private ClipboardService clipboardService; @@ -753,7 +717,8 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter private void createActions(boolean isConnected) { String owner = plugin.getName(); - selectAllAction = new SelectAllAction(owner, controller.getDecompilerPanel()); + SelectAllAction selectAllAction = + new SelectAllAction(owner, controller.getDecompilerPanel()); DockingAction refreshAction = new DockingAction("Refresh", owner) { @Override @@ -796,16 +761,16 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter String functionGroup = "1 - Function Group"; int subGroupPosition = 0; - specifyCProtoAction = new SpecifyCPrototypeAction(); + SpecifyCPrototypeAction specifyCProtoAction = new SpecifyCPrototypeAction(); setGroupInfo(specifyCProtoAction, functionGroup, subGroupPosition++); - overrideSigAction = new OverridePrototypeAction(); + OverridePrototypeAction overrideSigAction = new OverridePrototypeAction(); setGroupInfo(overrideSigAction, functionGroup, subGroupPosition++); - deleteSigAction = new DeletePrototypeOverrideAction(); + DeletePrototypeOverrideAction deleteSigAction = new DeletePrototypeOverrideAction(); setGroupInfo(deleteSigAction, functionGroup, subGroupPosition++); - renameFunctionAction = new RenameFunctionAction(); + RenameFunctionAction renameFunctionAction = new RenameFunctionAction(); setGroupInfo(renameFunctionAction, functionGroup, subGroupPosition++); // @@ -814,41 +779,42 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter String variableGroup = "2 - Variable Group"; subGroupPosition = 0; // reset for the next group - renameLocalAction = new RenameLocalAction(); + RenameLocalAction renameLocalAction = new RenameLocalAction(); setGroupInfo(renameLocalAction, variableGroup, subGroupPosition++); - renameGlobalAction = new RenameGlobalAction(); + RenameGlobalAction renameGlobalAction = new RenameGlobalAction(); setGroupInfo(renameGlobalAction, variableGroup, subGroupPosition++); - renameFieldAction = new RenameFieldAction(); + RenameFieldAction renameFieldAction = new RenameFieldAction(); setGroupInfo(renameFieldAction, variableGroup, subGroupPosition++); - retypeLocalAction = new RetypeLocalAction(); + RetypeLocalAction retypeLocalAction = new RetypeLocalAction(); setGroupInfo(retypeLocalAction, variableGroup, subGroupPosition++); - retypeGlobalAction = new RetypeGlobalAction(); + RetypeGlobalAction retypeGlobalAction = new RetypeGlobalAction(); setGroupInfo(retypeGlobalAction, variableGroup, subGroupPosition++); - retypeReturnAction = new RetypeReturnAction(); + RetypeReturnAction retypeReturnAction = new RetypeReturnAction(); setGroupInfo(retypeReturnAction, variableGroup, subGroupPosition++); - retypeFieldAction = new RetypeFieldAction(); + RetypeFieldAction retypeFieldAction = new RetypeFieldAction(); setGroupInfo(retypeFieldAction, variableGroup, subGroupPosition++); - isolateVarAction = new IsolateVariableAction(); + IsolateVariableAction isolateVarAction = new IsolateVariableAction(); setGroupInfo(isolateVarAction, variableGroup, subGroupPosition++); - decompilerCreateStructureAction = + DecompilerStructureVariableAction decompilerCreateStructureAction = new DecompilerStructureVariableAction(owner, tool, controller); setGroupInfo(decompilerCreateStructureAction, variableGroup, subGroupPosition++); - editDataTypeAction = new EditDataTypeAction(); + EditDataTypeAction editDataTypeAction = new EditDataTypeAction(); setGroupInfo(editDataTypeAction, variableGroup, subGroupPosition++); // // Listing action for Creating Structure on a Variable // - listingCreateStructureAction = new ListingStructureVariableAction(owner, tool, controller); + ListingStructureVariableAction listingCreateStructureAction = + new ListingStructureVariableAction(owner, tool, controller); // // Commit @@ -856,10 +822,10 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter String commitGroup = "3 - Commit Group"; subGroupPosition = 0; // reset for the next group - lockProtoAction = new CommitParamsAction(); + CommitParamsAction lockProtoAction = new CommitParamsAction(); setGroupInfo(lockProtoAction, commitGroup, subGroupPosition++); - lockLocalAction = new CommitLocalsAction(); + CommitLocalsAction lockLocalAction = new CommitLocalsAction(); setGroupInfo(lockLocalAction, commitGroup, subGroupPosition++); subGroupPosition = 0; // reset for the next group @@ -869,34 +835,61 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter // String highlightGroup = "4a - Highlight Group"; tool.setMenuGroup(new String[] { "Highlight" }, highlightGroup); - defUseHighlightAction = new HighlightDefinedUseAction(); + HighlightDefinedUseAction defUseHighlightAction = new HighlightDefinedUseAction(); setGroupInfo(defUseHighlightAction, highlightGroup, subGroupPosition++); - forwardSliceAction = new ForwardSliceAction(); + ForwardSliceAction forwardSliceAction = new ForwardSliceAction(); setGroupInfo(forwardSliceAction, highlightGroup, subGroupPosition++); - backwardSliceAction = new BackwardsSliceAction(); + BackwardsSliceAction backwardSliceAction = new BackwardsSliceAction(); setGroupInfo(backwardSliceAction, highlightGroup, subGroupPosition++); - forwardSliceToOpsAction = new ForwardSliceToPCodeOpsAction(); + ForwardSliceToPCodeOpsAction forwardSliceToOpsAction = new ForwardSliceToPCodeOpsAction(); setGroupInfo(forwardSliceToOpsAction, highlightGroup, subGroupPosition++); - backwardSliceToOpsAction = new BackwardsSliceToPCodeOpsAction(); + BackwardsSliceToPCodeOpsAction backwardSliceToOpsAction = + new BackwardsSliceToPCodeOpsAction(); setGroupInfo(backwardSliceToOpsAction, highlightGroup, subGroupPosition++); tool.setMenuGroup(new String[] { "Secondary Highlight" }, highlightGroup); - setSecondaryHighlightAction = new SetSecondaryHighlightAction(); + SetSecondaryHighlightAction setSecondaryHighlightAction = new SetSecondaryHighlightAction(); setGroupInfo(setSecondaryHighlightAction, highlightGroup, subGroupPosition++); - setSecondaryHighlightColorChooserAction = new SetSecondaryHighlightColorChooserAction(); + SetSecondaryHighlightColorChooserAction setSecondaryHighlightColorChooserAction = + new SetSecondaryHighlightColorChooserAction(); setGroupInfo(setSecondaryHighlightColorChooserAction, highlightGroup, subGroupPosition++); - removeSecondaryHighlightAction = new RemoveSecondaryHighlightAction(); + RemoveSecondaryHighlightAction removeSecondaryHighlightAction = + new RemoveSecondaryHighlightAction(); setGroupInfo(removeSecondaryHighlightAction, highlightGroup, subGroupPosition++); - removeAllSecondadryHighlightsAction = new RemoveAllSecondaryHighlightsAction(); + RemoveAllSecondaryHighlightsAction removeAllSecondadryHighlightsAction = + new RemoveAllSecondaryHighlightsAction(); setGroupInfo(removeAllSecondadryHighlightsAction, highlightGroup, subGroupPosition++); + String convertGroup = "7 - Convert Group"; + subGroupPosition = 0; + RemoveEquateAction removeEquateAction = new RemoveEquateAction(); + setGroupInfo(removeEquateAction, convertGroup, subGroupPosition++); + + SetEquateAction setEquateAction = new SetEquateAction(plugin); + setGroupInfo(setEquateAction, convertGroup, subGroupPosition++); + + ConvertBinaryAction convertBinaryAction = new ConvertBinaryAction(plugin); + setGroupInfo(convertBinaryAction, convertGroup, subGroupPosition++); + + ConvertDecAction convertDecAction = new ConvertDecAction(plugin); + setGroupInfo(convertDecAction, convertGroup, subGroupPosition++); + + ConvertHexAction convertHexAction = new ConvertHexAction(plugin); + setGroupInfo(convertHexAction, convertGroup, subGroupPosition++); + + ConvertOctAction convertOctAction = new ConvertOctAction(plugin); + setGroupInfo(convertOctAction, convertGroup, subGroupPosition++); + + ConvertCharAction convertCharAction = new ConvertCharAction(plugin); + setGroupInfo(convertCharAction, convertGroup, subGroupPosition++); + // // Comments // @@ -910,7 +903,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter String searchGroup = "comment2 - Search Group"; subGroupPosition = 0; // reset for the next group - findAction = new FindAction(); + FindAction findAction = new FindAction(); setGroupInfo(findAction, searchGroup, subGroupPosition++); // @@ -920,7 +913,8 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter // note: set the menu group so that the 'References' group is with the 'Find' action String referencesParentGroup = searchGroup; - findReferencesAction = new FindReferencesToDataTypeAction(owner, tool, controller); + FindReferencesToDataTypeAction findReferencesAction = + new FindReferencesToDataTypeAction(owner, tool, controller); setGroupInfo(findReferencesAction, searchGroup, subGroupPosition++); findReferencesAction.getPopupMenuData().setParentMenuGroup(referencesParentGroup); @@ -942,15 +936,15 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter String optionsGroup = "comment6 - Options Group"; subGroupPosition = 0; // reset for the next group - propertiesAction = new EditPropertiesAction(owner, tool); + EditPropertiesAction propertiesAction = new EditPropertiesAction(owner, tool); setGroupInfo(propertiesAction, optionsGroup, subGroupPosition++); // // These actions are not in the popup menu // - debugFunctionAction = new DebugDecompilerAction(controller); - convertAction = new ExportToCAction(); - cloneDecompilerAction = new CloneDecompilerAction(); + DebugDecompilerAction debugFunctionAction = new DebugDecompilerAction(controller); + ExportToCAction convertAction = new ExportToCAction(); + CloneDecompilerAction cloneDecompilerAction = new CloneDecompilerAction(); addLocalAction(refreshAction); addLocalAction(selectAllAction); @@ -968,6 +962,13 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter addLocalAction(setSecondaryHighlightColorChooserAction); addLocalAction(removeSecondaryHighlightAction); addLocalAction(removeAllSecondadryHighlightsAction); + addLocalAction(convertBinaryAction); + addLocalAction(convertDecAction); + addLocalAction(convertHexAction); + addLocalAction(convertOctAction); + addLocalAction(convertCharAction); + addLocalAction(setEquateAction); + addLocalAction(removeEquateAction); addLocalAction(retypeLocalAction); addLocalAction(retypeGlobalAction); addLocalAction(retypeReturnAction); diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertBinaryAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertBinaryAction.java new file mode 100644 index 0000000000..fab9f9d6ac --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertBinaryAction.java @@ -0,0 +1,82 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.decompile.actions; + +import docking.action.MenuData; +import ghidra.app.plugin.core.decompile.DecompilePlugin; +import ghidra.app.util.HelpTopics; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.EquateSymbol; +import ghidra.program.model.scalar.Scalar; +import ghidra.util.HelpLocation; +import ghidra.util.StringUtilities; + +/** + * Convert a selected constant in the decompiler to a binary representation. + */ +public class ConvertBinaryAction extends ConvertConstantAction { + + public ConvertBinaryAction(DecompilePlugin plugin) { + super(plugin, "Convert To Binary", EquateSymbol.FORMAT_BIN); + setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionConvert")); + setPopupMenuData(new MenuData(new String[] { "Binary" }, "Decompile")); + } + + @Override + public String getMenuPrefix() { + return "Binary: "; + } + + @Override + public String getMenuDisplay(long value, int size, boolean isSigned) { + Scalar scalar = new Scalar(size * 8, value); + long v; + String prefix = "0b"; + if (isSigned) { + v = scalar.getSignedValue(); + if (v < 0) { + v = -v; + prefix = "-0b"; + } + } + else { + v = scalar.getUnsignedValue(); + + } + String bitString = Long.toBinaryString(v); + int bitlen = bitString.length(); + if (bitlen <= 8) { + bitlen = 8; + } + else if (bitlen <= 16) { + bitlen = 16; + } + else if (bitlen <= 32) { + bitlen = 32; + } + else { + bitlen = 64; + } + return prefix + StringUtilities.pad(bitString, '0', bitlen); + } + + @Override + public String getEquateName(long value, int size, boolean isSigned, Program program) { + String valueStr = Long.toBinaryString(value); + valueStr = StringUtilities.pad(valueStr, '0', size * 8); + return valueStr + "b"; + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertCharAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertCharAction.java new file mode 100644 index 0000000000..157f08b223 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertCharAction.java @@ -0,0 +1,152 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.decompile.actions; + +import docking.action.MenuData; +import ghidra.app.plugin.core.decompile.DecompilePlugin; +import ghidra.app.util.HelpTopics; +import ghidra.program.model.data.ByteDataType; +import ghidra.program.model.data.StringDataInstance; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.EquateSymbol; +import ghidra.util.BigEndianDataConverter; +import ghidra.util.HelpLocation; + +/** + * Convert a selected constant in the decompiler window to a character representation. + */ +public class ConvertCharAction extends ConvertConstantAction { + + public ConvertCharAction(DecompilePlugin plugin) { + super(plugin, "Convert To Char", EquateSymbol.FORMAT_CHAR); + setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionConvert")); + setPopupMenuData(new MenuData(new String[] { "Char" }, "Decompile")); + } + + @Override + public String getMenuPrefix() { + return "Char: "; + } + + private static void generateHexEscape(StringBuilder buffer, int value) { + String res = Integer.toHexString(value); + int pad = res.length(); + if (pad > 4) { + pad = 8; + } + else if (pad > 2) { + pad = 4; + } + else { + pad = 2; + } + pad = pad - res.length(); + buffer.append("'\\x"); + for (int i = 0; i < pad; ++i) { + buffer.append('0'); + } + buffer.append(res.toLowerCase()); + buffer.append('\''); + } + + @Override + public String getEquateName(long value, int size, boolean isSigned, Program program) { + byte[] bytes = new byte[size]; + BigEndianDataConverter.INSTANCE.putValue(value, size, bytes, 0); + return StringDataInstance.getCharRepresentation(ByteDataType.dataType, bytes, null); + } + + /** + * Return true for any unicode codepoint that needs to be represented with an escape sequence + * @param codepoint is the code point value + * @return true if the codepoint needs to be escaped + */ + private static boolean codePointNeedsEscape(int codepoint) { + int characterClass = Character.getType(codepoint); + switch (characterClass) { + case Character.SPACE_SEPARATOR: + if (codepoint == 0x20) { + return false; // Only the ASCII space is not escaped + } + return true; + case Character.COMBINING_SPACING_MARK: + case Character.CONTROL: + case Character.ENCLOSING_MARK: + case Character.FORMAT: + case Character.LINE_SEPARATOR: + case Character.NON_SPACING_MARK: + case Character.PARAGRAPH_SEPARATOR: + case Character.PRIVATE_USE: + case Character.SURROGATE: + case Character.UNASSIGNED: + return true; + } + return false; + } + + @Override + public String getMenuDisplay(long value, int size, boolean isSigned) { + StringBuilder buffer = new StringBuilder(); + if (size > 1) { + buffer.append('L'); + } + if ((size == 1 && value >= 0x7f) || codePointNeedsEscape((int) value)) { + switch ((int) value) { + case 0: + buffer.append("'\\0'"); + break; + case 7: + buffer.append("'\\a'"); + break; + case 8: + buffer.append("'\\b'"); + break; + case 9: + buffer.append("'\\t'"); + break; + case 10: + buffer.append("'\\n'"); + break; + case 11: + buffer.append("'\\v'"); + break; + case 12: + buffer.append("'\\f'"); + break; + case 13: + buffer.append("'\\r'"); + break; + case '"': + buffer.append("\\\""); + break; + case 92: + buffer.append("\\\\"); + break; + case '\'': + buffer.append("\\'"); + break; + default: + // Generic unicode escape + generateHexEscape(buffer, (int) value); + break; + } + } + else { + buffer.append('\'').append((char) value).append('\''); + } + return buffer.toString(); + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertConstantAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertConstantAction.java new file mode 100644 index 0000000000..84d7e89818 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertConstantAction.java @@ -0,0 +1,370 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.decompile.actions; + +import java.awt.Font; +import java.awt.FontMetrics; +import java.util.List; + +import javax.swing.JMenuItem; + +import ghidra.app.decompiler.ClangToken; +import ghidra.app.decompiler.ClangVariableToken; +import ghidra.app.plugin.core.decompile.DecompilePlugin; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.program.model.address.Address; +import ghidra.program.model.block.CodeBlock; +import ghidra.program.model.block.SimpleBlockModel; +import ghidra.program.model.data.*; +import ghidra.program.model.data.Enum; +import ghidra.program.model.listing.Instruction; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.*; +import ghidra.program.model.scalar.Scalar; +import ghidra.program.model.symbol.*; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * Abstract pop-up menu convert action for the decompiler. If triggered, it lays down + * a new EquateReference that forces the selected constant to be displayed using + * the desired integer format. + */ +public abstract class ConvertConstantAction extends AbstractDecompilerAction { + + /** + * Max instructions to search through, when looking for a scalar match in the listing + * that corresponds with the selected constant in the decompiler window. + */ + private final static int MAX_INSTRUCTION_WINDOW = 20; + protected DecompilePlugin plugin; + private FontMetrics metrics = null; + private int convertType; // The EquateSymbol conversion type performed by the action + + /** + * A helper class describing a (matching) scalar operand + */ + private static class ScalarMatch { + Address refAddr; // Address of instruction + Scalar scalar; + int opIndex; + + public ScalarMatch(Address addr, Scalar value, int index) { + refAddr = addr; + scalar = value; + opIndex = index; + } + } + + public ConvertConstantAction(DecompilePlugin plugin, String name, int convertType) { + super(name); + this.plugin = plugin; + this.convertType = convertType; + } + + private int stringWidth(String s) { + if (metrics == null) { + JMenuItem item = new JMenuItem(); + Font font = item.getFont(); + metrics = plugin.getTool().getActiveWindow().getFontMetrics(font); + } + int w = metrics.stringWidth(s); + if (w == 0) { + // use default computation if metrics report 0 + return 10 * s.length(); + } + return w; + } + + protected String getStandardLengthString(String baseString) { + int baseWidth = stringWidth(baseString); + int spaceWidth = stringWidth(" "); + int paddingSize = (140 - baseWidth) / spaceWidth; + if (paddingSize <= 0) { + return baseString; + } + StringBuilder buf = new StringBuilder(baseString); + for (int i = 0; i < paddingSize; i++) { + buf.append(" "); + } + return buf.toString(); + } + + /** + * Find a scalar in the instruction matching one of the given values. + * Return an object describing the match or null if there is no match. + * @param instr is the instruction + * @param values is an array of the given values + * @return the Scalar and + */ + private ScalarMatch findScalarInInstruction(Instruction instr, long values[]) { + int numOperands = instr.getNumOperands(); + ScalarMatch scalarMatch = null; + for (int i = 0; i < numOperands; i++) { + for (Object obj : instr.getOpObjects(i)) { + if (obj instanceof Scalar) { + Scalar scalar = (Scalar) obj; + for (long value : values) { + if (scalar.getUnsignedValue() != value) { + continue; + } + if (scalarMatch != null) { + scalarMatch.opIndex = -1; // non-unique scalar operand value - can't identify operand + return scalarMatch; + } + scalarMatch = new ScalarMatch(instr.getAddress(), scalar, i); + } + } + } + } + return scalarMatch; + } + + /** + * Find a scalar (instruction operand) that matches the given constant Varnode. + * We walk backward from the starting address inspecting operands until a match is found. + * The search is terminated if either a match is found, the beginning of the basic block + * is reached, or if 20 instructions are traversed. The scalar can be a "near" match, meaning + * off by 1 or the negated value. + * @param program is the Program + * @param startAddress is the starting address to search backward from + * @param constVn is the given constant Varnode + * @param monitor is the TaskMonitor + * @return a description of the scalar match, or null if there is no match + * @throws CancelledException if the user cancels + */ + private ScalarMatch findScalarMatch(Program program, Address startAddress, Varnode constVn, + TaskMonitor monitor) throws CancelledException { + long value = constVn.getOffset(); + long mask = -1; + if (constVn.getSize() < 8) { + mask = mask >>> (8 - constVn.getSize()) * 8; + } + long values[] = new long[4]; + values[0] = value; + values[1] = (value - 1) & mask; + values[2] = (value + 1) & mask; + values[3] = (-value) & mask; + int count = 0; + ScalarMatch scalarMatch = null; + Instruction curInst = program.getListing().getInstructionAt(startAddress); + if (curInst == null) { + return null; + } + SimpleBlockModel model = new SimpleBlockModel(program); + CodeBlock basicBlock = model.getFirstCodeBlockContaining(startAddress, monitor); + if (basicBlock == null) { + return null; + } + while (count < MAX_INSTRUCTION_WINDOW) { + count += 1; + ScalarMatch newMatch = findScalarInInstruction(curInst, values); + if (newMatch != null) { + if (scalarMatch != null) { + return null; // Matches at more than one address + } + if (newMatch.opIndex < 0) { + return null; // Matches at more than one operand + } + scalarMatch = newMatch; + } + curInst = curInst.getPrevious(); + if (curInst == null) { + break; + } + if (!basicBlock.contains(curInst.getAddress())) { + break; + } + } + return scalarMatch; + } + + /** + * Given the context, set up the task object that will execute the conversion. + * If setupFinal toggle is false, only enough of the task is set up to complete + * the isEnabled test for the action. Otherwise the whole task is set up, ready for runTask(). + * If the context is not suitable for a conversion, null is returned. + * @param context is the given context for the action + * @param setupFinal is true if a full task setup is needed + * @return the task object or null + */ + protected ConvertConstantTask establishTask(DecompilerActionContext context, + boolean setupFinal) { + ClangToken tokenAtCursor = context.getTokenAtCursor(); + if (!(tokenAtCursor instanceof ClangVariableToken)) { + return null; + } + Varnode convertVn = tokenAtCursor.getVarnode(); + if (convertVn == null || !convertVn.isConstant()) { + return null; + } + HighSymbol symbol = convertVn.getHigh().getSymbol(); + EquateSymbol convertSymbol = null; + if (symbol != null) { + if (symbol instanceof EquateSymbol) { + convertSymbol = (EquateSymbol) symbol; + int type = convertSymbol.getConvert(); + if (type == convertType || type == EquateSymbol.FORMAT_DEFAULT) { + return null; + } + } + else { + return null; // Something already attached to constant + } + } + DataType convertDataType = convertVn.getHigh().getDataType(); + boolean convertIsSigned = false; + if (convertDataType instanceof AbstractIntegerDataType) { + if (convertDataType instanceof BooleanDataType) { + return null; + } + convertIsSigned = ((AbstractIntegerDataType) convertDataType).isSigned(); + } + else if (convertDataType instanceof Enum) { + return null; + } + if (!setupFinal) { + return new ConvertConstantTask(convertVn, convertIsSigned); + } + + ConvertConstantTask task = null; + + String equateName = getEquateName(convertVn.getOffset(), convertVn.getSize(), + convertIsSigned, context.getProgram()); + if (equateName == null) { // A null is a user cancel + return null; + } + Program program = context.getProgram(); + Address convertAddr; + long convertHash; + if (convertSymbol != null) { + convertAddr = convertSymbol.getPCAddress(); + convertHash = 0; + int convertIndex = -1; + boolean foundEquate = false; + EquateTable equateTable = program.getEquateTable(); + List equates = equateTable.getEquates(convertAddr); + for (Equate equate : equates) { + if (equate.getValue() != convertVn.getOffset()) { + continue; + } + for (EquateReference equateRef : equate.getReferences(convertAddr)) { + convertHash = equateRef.getDynamicHashValue(); + convertIndex = equateRef.getOpIndex(); + foundEquate = true; + break; + } + break; + } + if (!foundEquate) { + Msg.error(this, "Symbol does not have matching entry in equate table"); + return null; + } + task = new ConvertConstantTask(context, equateName, convertAddr, convertVn, convertHash, + convertIndex); + } + else { + PcodeOp op = convertVn.getLoneDescend(); + convertAddr = op.getSeqnum().getTarget(); + + DynamicHash dynamicHash = new DynamicHash(convertVn, 0); + convertHash = dynamicHash.getHash(); + task = new ConvertConstantTask(context, equateName, convertAddr, convertVn, convertHash, + -1); + try { + ScalarMatch scalarMatch = findScalarMatch(context.getProgram(), convertAddr, + convertVn, TaskMonitor.DUMMY); + if (scalarMatch != null) { + long value = scalarMatch.scalar.getUnsignedValue(); + int size = scalarMatch.scalar.bitLength() / 8; + if (size == 0) { + size = 1; + } + String altName = getEquateName(value, size, convertIsSigned, null); + if (altName == null) { + altName = equateName; + } + // Don't create a named equate if the varnode and the instruction operand differ + // as the name was selected specifically for the varnode + if (convertType != EquateSymbol.FORMAT_DEFAULT || + value == convertVn.getOffset()) { + task.setAlternate(altName, scalarMatch.refAddr, scalarMatch.opIndex, value); + } + } + } + catch (CancelledException e) { + // scalar match is not added to task + } + } + return task; + } + + @Override + protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { + ConvertConstantTask task = establishTask(context, false); + if (task == null) { + return false; + } + String convDisplay = getMenuDisplay(task.getValue(), task.getSize(), task.isSigned()); + if (convDisplay.equals(context.getTokenAtCursor().getText())) { + return false; + } + String menuString = getStandardLengthString(getMenuPrefix()) + convDisplay; + getPopupMenuData().setMenuItemName(menuString); + return true; + } + + @Override + protected void decompilerActionPerformed(DecompilerActionContext context) { + ConvertConstantTask task = establishTask(context, true); + if (task == null) { + return; + } + task.runTask(); + } + + /** + * The menu option for this kind of action is intended to look like: + * {@literal Hexadecimal: 0x2408} + * This method establishes the first part of this string, up to the colon. + * @return the menu prefix + */ + public abstract String getMenuPrefix(); + + /** + * The menu option for this kind of action is intended to look like: + * {@literal Hexadecimal: 0x2408} + * This method constructs the final part of this string, after the colon by + * formatting the actual value that is to be converted. + * @param value is the actual value + * @param size is the number of bytes used for the constant Varnode + * @param isSigned is true if the constant represents a signed data-type + * @return the formatted String + */ + public abstract String getMenuDisplay(long value, int size, boolean isSigned); + + /** + * Construct the name of the Equate, either absolutely for a conversion or + * by preventing the user with a dialog to select a name. + * @param value is the value being converted + * @param size is the number of bytes used for the constant Varnode + * @param isSigned is true if the constant represents a signed data-type + * @param program is the current Program + * @return the equate name + */ + public abstract String getEquateName(long value, int size, boolean isSigned, Program program); +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertConstantTask.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertConstantTask.java new file mode 100644 index 0000000000..621b552e89 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertConstantTask.java @@ -0,0 +1,319 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.decompile.actions; + +import java.util.List; + +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.*; +import ghidra.program.model.symbol.*; +import ghidra.util.Msg; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.exception.InvalidInputException; +import utility.function.Callback; + +/** + * Create an equate in the table for the specific Address and hash value. + * The equate is not assumed to be attached to a particular instruction operand and + * uses the dynamic hash value to identify the particular constant (within p-code) to label. + * + * If altAddress is non-null and the other alt* fields are filled in, the task attempts + * to set the equation on the altAddress first to get the representation of the p-code + * constant at convertAddress to change. After the decompilation finishes, the representation + * is checked, and if it did not change, the alt* equate is removed and an equate is created + * directly for the convertAddress; + */ +public class ConvertConstantTask implements Callback { + private DecompilerActionContext context; + private Program program; + private Address convertAddress; // The primary address of the Equate + private String convertName; // The primary name to use in the Equate table + private Varnode convertVn; // The Varnode holding the constant value being equated + private long convertHash; // A dynamic hash locating the constant Varnode in data-flow + private int convertIndex; // The scalar index associated with the primary Equate (or -1) + private boolean convertSigned; + + private Address altAddress = null; // Alternate location of constant + private int altIndex; // Index of alternate scalar + private String altName = null; // Alternate equate name + private long altValue; // Alternate value + + public ConvertConstantTask(Varnode vn, boolean isSigned) { + convertVn = vn; + convertSigned = isSigned; + } + + /** + * Construct a primary Equate task + * @param context is the action context for the task + * @param name is the primary Equate name + * @param addr is the primary address of the Equate + * @param vn is the constant Varnode being equated + * @param hash is the dynamic hash + * @param index is the operand index if the Equate is known to label an instruction operand + */ + public ConvertConstantTask(DecompilerActionContext context, String name, Address addr, + Varnode vn, long hash, int index) { + this.context = context; + program = context.getProgram(); + convertName = name; + convertAddress = addr; + convertVn = vn; + convertHash = hash; + convertIndex = index; + } + + /** + * Establish an alternate Equate to try before falling back on the primary Equate + * @param name is the alternate name of the Equate + * @param addr is the alternate address + * @param index is the operand index + * @param value is the alternate constant value to equate + */ + public void setAlternate(String name, Address addr, int index, long value) { + altName = name; + altAddress = addr; + altValue = value; + altIndex = index; + } + + /** + * @return the primary value being equated + */ + public long getValue() { + return convertVn.getOffset(); + } + + /** + * @return the size of constant (Varnode) being equated + */ + public int getSize() { + return convertVn.getSize(); + } + + /** + * @return true if the constant value is treated as a signed integer + */ + public boolean isSigned() { + return convertSigned; + } + + /** + * Remove any preexisting equate reference with the same address and hash as the + * primate equate. + */ + private void removePrimaryReference() { + EquateTable equateTable = program.getEquateTable(); + List equates = equateTable.getEquates(convertAddress); + for (Equate equate : equates) { + List references = equate.getReferences(convertAddress); + for (EquateReference ref : references) { + if (ref.getDynamicHashValue() == convertHash) { + if (equate.getReferenceCount() <= 1) { + equateTable.removeEquate(equate.getName()); + } + else { + equate.removeReference(convertHash, convertAddress); + } + return; + } + } + } + } + + /** + * Remove and preexisting equate reference with the same address and hash as the + * alternate equate. + */ + private void removeAlternateReference() { + EquateTable equateTable = program.getEquateTable(); + List equates = equateTable.getEquates(altAddress); + for (Equate equate : equates) { + List references = equate.getReferences(altAddress); + for (EquateReference ref : references) { + if (ref.getOpIndex() == altIndex) { + if (equate.getReferenceCount() <= 1) { + equateTable.removeEquate(equate.getName()); + } + else { + equate.removeReference(altAddress, altIndex); + } + return; + } + } + } + } + + /** + * Add equate based on the alternate constant information: altAddress, altName, altIndex + * @throws DuplicateNameException if there is already an equate with same name but different value + * @throws InvalidInputException if the equate name is illegal + */ + private void addPrimaryEquate() throws DuplicateNameException, InvalidInputException { + EquateTable equateTable = program.getEquateTable(); + Equate equate = equateTable.getEquate(convertName); + + if (equate != null && equate.getValue() != convertVn.getOffset()) { + String msg = "Equate named " + convertName + " already exists with value of " + + equate.getValue() + "."; + throw new DuplicateNameException(msg); + } + + if (equate == null) { + equate = equateTable.createEquate(convertName, convertVn.getOffset()); + } + + // Add reference to existing equate + if (convertHash != 0) { + equate.addReference(convertHash, convertAddress); + } + else { + equate.addReference(convertAddress, convertIndex); + } + } + + /** + * Add equate based on the direct constant information: convertAddress, convertName, convertHash + * @throws DuplicateNameException if there is already an equate with same name but different value + * @throws InvalidInputException if the equate name is illegal + */ + private void addAlternateEquate() throws InvalidInputException, DuplicateNameException { + EquateTable equateTable = program.getEquateTable(); + Equate equate = equateTable.getEquate(altName); + + if (equate != null && equate.getValue() != altValue) { + String msg = "Equate named " + altName + " already exists with value of " + + equate.getValue() + "."; + throw new DuplicateNameException(msg); + } + + if (equate == null) { + equate = equateTable.createEquate(altName, altValue); + } + + equate.addReference(altAddress, altIndex); + } + + /** + * Create a reference to primary equate, removing any previous reference. + * If an alternate equate is given, remove any existing reference to it as well. + */ + private void applyPrimaryEquate() { + + int transaction = program.startTransaction("Convert constant"); + boolean commit = false; + try { + if (altAddress != null) { + removeAlternateReference(); + } + removePrimaryReference(); + addPrimaryEquate(); + commit = true; + } + catch (DuplicateNameException e) { + Msg.showError(this, null, "Convert Failed", e.getMessage()); + } + catch (InvalidInputException e) { + Msg.showError(this, null, "Convert Failed", e.getMessage()); + } + finally { + program.endTransaction(transaction, commit); + } + } + + /** + * Create a reference to the alternate equate. + */ + private void applyAlternateEquate() { + int transaction = program.startTransaction("Convert constant"); + boolean commit = false; + try { + addAlternateEquate(); + commit = true; + } + catch (DuplicateNameException e) { + Msg.showError(this, null, "Convert Failed", e.getMessage()); + } + catch (InvalidInputException e) { + Msg.showError(this, null, "Convert Failed", e.getMessage()); + } + finally { + program.endTransaction(transaction, commit); + } + } + + /** + * Look for the EquateSymbol pointing to the altAddress, attached to the constant + * @return true if we find the EquateSymbol, false otherwise + */ + private boolean isAlternatePlaced() { + HighFunction highFunction = context.getHighFunction(); // Get the updated HighFunction + if (highFunction == null) { + return false; + } + // Varnode itself should be unchanged + Varnode vn = DynamicHash.findVarnode(highFunction, convertAddress, convertHash); + if (vn == null) { + return false; + } + HighSymbol symbol = vn.getHigh().getSymbol(); // But now it should have an equate on it + if (!(symbol instanceof EquateSymbol)) { + return false; + } + EquateSymbol eqSymbol = (EquateSymbol) symbol; + if (!eqSymbol.getPCAddress().equals(altAddress)) { + return false; + } + return true; + } + + /** + * Callback executed after the alternative equate is placed and the DecompilerProvider has updated its window. + * We check to see if the equate reached the desired constant in the decompiler. + * If not, we remove the alternate equate and place a direct equate + */ + @Override + public void call() { + if (isAlternatePlaced()) { + return; + } + applyPrimaryEquate(); + } + + /** + * Run the convert task. If the task is given an alternate equate, this is placed, otherwise + * the primary equate is placed. If an alternate is placed, a thread is scheduled to check if + * the alternate equate reached the constant Varnode. If not the alternate equate reference is + * removed, and the task falls back and places the primary equate. + */ + public void runTask() { + if (altAddress != null) { + applyAlternateEquate(); + try { + Thread.sleep(50); // Let the decompiler get going + } + catch (InterruptedException e) { + return; + } + context.getComponentProvider().doWheNotBusy(this); + } + else { + applyPrimaryEquate(); + } + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertDecAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertDecAction.java new file mode 100644 index 0000000000..dde6f49ef3 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertDecAction.java @@ -0,0 +1,54 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.decompile.actions; + +import docking.action.MenuData; +import ghidra.app.plugin.core.decompile.DecompilePlugin; +import ghidra.app.util.HelpTopics; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.EquateSymbol; +import ghidra.program.model.scalar.Scalar; +import ghidra.util.HelpLocation; + +/** + * Convert a selected constant in the decompiler to a decimal representation. + */ +public class ConvertDecAction extends ConvertConstantAction { + public ConvertDecAction(DecompilePlugin plugin) { + super(plugin, "Convert To Decimal", EquateSymbol.FORMAT_DEC); + setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionConvert")); + setPopupMenuData(new MenuData(new String[] { "Decimal" }, "Decompile")); + } + + @Override + public String getMenuPrefix() { + return "Decimal: "; + } + + @Override + public String getMenuDisplay(long value, int size, boolean isSigned) { + return getEquateName(value, size, isSigned, null); + } + + @Override + public String getEquateName(long value, int size, boolean isSigned, Program program) { + Scalar scalar = new Scalar(size * 8, value); + if (isSigned) { + return Long.toString(scalar.getSignedValue()); + } + return Long.toString(scalar.getUnsignedValue()); + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertHexAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertHexAction.java new file mode 100644 index 0000000000..14c669b7fd --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertHexAction.java @@ -0,0 +1,74 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.decompile.actions; + +import docking.action.MenuData; +import ghidra.app.plugin.core.decompile.DecompilePlugin; +import ghidra.app.util.HelpTopics; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.EquateSymbol; +import ghidra.program.model.scalar.Scalar; +import ghidra.util.HelpLocation; + +/** + * Convert a selected constant in the decompiler to a hexadecimal representation. + */ +public class ConvertHexAction extends ConvertConstantAction { + + public ConvertHexAction(DecompilePlugin plugin) { + super(plugin, "Convert To Hexadecimal", EquateSymbol.FORMAT_HEX); + setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionConvert")); + setPopupMenuData(new MenuData(new String[] { "Hexadecimal" }, "Decompile")); + } + + @Override + public String getMenuPrefix() { + return "Hexadecimal: "; + } + + @Override + public String getMenuDisplay(long value, int size, boolean isSigned) { + Scalar scalar = new Scalar(size * 8, value); + if (isSigned) { + long v = scalar.getSignedValue(); + String valueStr = Long.toString(v, 16); + if (v < 0) { + // use of substring removes '-' prefix for negative value + return "-0x" + valueStr.substring(1); + } + return "0x" + valueStr; + } + String valueStr = Long.toHexString(scalar.getUnsignedValue()); + return "0x" + valueStr; + } + + @Override + public String getEquateName(long value, int size, boolean isSigned, Program program) { + Scalar scalar = new Scalar(size * 8, value); + if (isSigned) { + long v = scalar.getSignedValue(); + String valueStr = Long.toString(v, 16).toUpperCase(); + if (v < 0) { + // use of substring removes '-' prefix for negative value + return "-0x" + valueStr.substring(1); + } + return "0x" + valueStr; + } + String valueStr = Long.toHexString(scalar.getUnsignedValue()).toUpperCase(); + // Instructions rely on equate which uses 0x prefix (consistent with default scalar formatting) + return "0x" + valueStr; + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertOctAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertOctAction.java new file mode 100644 index 0000000000..654e2be4bb --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ConvertOctAction.java @@ -0,0 +1,63 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.decompile.actions; + +import docking.action.MenuData; +import ghidra.app.plugin.core.decompile.DecompilePlugin; +import ghidra.app.util.HelpTopics; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.EquateSymbol; +import ghidra.program.model.scalar.Scalar; +import ghidra.util.HelpLocation; + +/** + * Convert a selected constant in the decompiler to an octal representation. + */ +public class ConvertOctAction extends ConvertConstantAction { + + public ConvertOctAction(DecompilePlugin plugin) { + super(plugin, "Convert To Octal", EquateSymbol.FORMAT_OCT); + setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionConvert")); + setPopupMenuData(new MenuData(new String[] { "Octal" }, "Decompile")); + } + + @Override + public String getMenuPrefix() { + return "Octal: "; + } + + @Override + public String getMenuDisplay(long value, int size, boolean isSigned) { + Scalar scalar = new Scalar(size * 8, value); + if (isSigned) { + long v = scalar.getSignedValue(); + String valueStr = Long.toString(v, 8); + if (v < 0) { + // use of substring removes '-' prefix for negative value + return "-0" + valueStr.substring(1); + } + return "0" + valueStr; + } + String valueStr = Long.toOctalString(scalar.getUnsignedValue()); + return "0" + valueStr; + } + + @Override + public String getEquateName(long value, int size, boolean isSigned, Program program) { + Scalar scalar = new Scalar(size * 8, value); + return Long.toOctalString(scalar.getUnsignedValue()) + "o"; + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveEquateAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveEquateAction.java new file mode 100644 index 0000000000..8675dd347f --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveEquateAction.java @@ -0,0 +1,104 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.decompile.actions; + +import java.util.List; + +import docking.action.MenuData; +import ghidra.app.decompiler.ClangToken; +import ghidra.app.decompiler.ClangVariableToken; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.util.HelpTopics; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.*; +import ghidra.program.model.symbol.Equate; +import ghidra.program.model.symbol.EquateTable; +import ghidra.util.HelpLocation; + +public class RemoveEquateAction extends AbstractDecompilerAction { + + public RemoveEquateAction() { + super("Remove Equate"); + setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionRemoveEquate")); + setPopupMenuData(new MenuData(new String[] { "Remove Convert/Equate" }, "Decompile")); + } + + @Override + protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { + ClangToken tokenAtCursor = context.getTokenAtCursor(); + if (!(tokenAtCursor instanceof ClangVariableToken)) { + return false; + } + Varnode convertVn = tokenAtCursor.getVarnode(); + if (convertVn == null || !convertVn.isConstant()) { + return false; + } + HighSymbol symbol = convertVn.getHigh().getSymbol(); + return (symbol instanceof EquateSymbol); + } + + private void removeReference(Program program, Equate equate, Address refAddr, + long convertHash) { + int transaction = program.startTransaction("Remove Equate Reference"); + boolean commit = false; + try { + if (equate.getReferenceCount() <= 1) { + program.getEquateTable().removeEquate(equate.getName()); + } + else { + equate.removeReference(convertHash, refAddr); + } + commit = true; + } + finally { + program.endTransaction(transaction, commit); + } + + } + + @Override + protected void decompilerActionPerformed(DecompilerActionContext context) { + ClangToken tokenAtCursor = context.getTokenAtCursor(); + if (!(tokenAtCursor instanceof ClangVariableToken)) { + return; + } + Varnode convertVn = tokenAtCursor.getVarnode(); + if (convertVn == null || !convertVn.isConstant()) { + return; + } + HighSymbol convertSymbol = convertVn.getHigh().getSymbol(); + if (convertSymbol instanceof EquateSymbol) { + Address convertAddr = convertSymbol.getPCAddress(); + SymbolEntry entry = convertSymbol.getFirstWholeMap(); + if (!(entry instanceof DynamicEntry)) { + return; + } + long convertHash = ((DynamicEntry) entry).getHash(); + Program program = context.getProgram(); + EquateTable equateTable = program.getEquateTable(); + List equates = equateTable.getEquates(convertAddr); + for (Equate equate : equates) { + if (equate.getValue() != convertVn.getOffset()) { + continue; + } + removeReference(program, equate, convertAddr, convertHash); + break; + } + } + } + +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SetEquateAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SetEquateAction.java new file mode 100644 index 0000000000..a6c0c54d15 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SetEquateAction.java @@ -0,0 +1,68 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.decompile.actions; + +import docking.action.MenuData; +import ghidra.app.plugin.core.decompile.DecompilePlugin; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.util.HelpTopics; +import ghidra.app.util.bean.SetEquateDialog; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.EquateSymbol; +import ghidra.program.model.scalar.Scalar; +import ghidra.util.HelpLocation; + +public class SetEquateAction extends ConvertConstantAction { + + public SetEquateAction(DecompilePlugin plugin) { + super(plugin, "Set Equate", EquateSymbol.FORMAT_DEFAULT); + setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionSetEquate")); + setPopupMenuData(new MenuData(new String[] { "Set Equate..." }, "Decompile")); + } + + @Override + public String getMenuPrefix() { + return null; // Menu isn't tailored for this action + } + + @Override + public String getMenuDisplay(long value, int size, boolean isSigned) { + return null; // Menu isn't tailored for this action + } + + @Override + public String getEquateName(long value, int size, boolean isSigned, Program program) { + if (program == null) { + return null; + } + Scalar scalar = new Scalar(size * 8, value, isSigned); + SetEquateDialog dialog = new SetEquateDialog(plugin.getTool(), program, scalar); + dialog.disableHasSelection(); + int res = dialog.showSetDialog(); + String name = null; + if (res != SetEquateDialog.CANCELED) { + name = dialog.getEquateName(); + } + dialog.dispose(); + return name; + } + + @Override + protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { + ConvertConstantTask task = establishTask(context, false); + return (task != null); + } +} diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/EquateTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/EquateTest.java new file mode 100644 index 0000000000..7167ab281c --- /dev/null +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/EquateTest.java @@ -0,0 +1,282 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.decompile; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Test; + +import ghidra.app.decompiler.ClangToken; +import ghidra.app.decompiler.ClangVariableToken; +import ghidra.app.decompiler.component.ClangTextField; +import ghidra.app.plugin.core.decompile.actions.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.pcode.*; +import ghidra.program.model.symbol.*; + +public class EquateTest extends AbstractDecompilerTest { + + private static class EquateNameForce extends SetEquateAction { + + private String nameForce; // Simulate name chosen by the equate dialog + + public EquateNameForce(DecompilePlugin plugin, String nm) { + super(plugin); + nameForce = nm; + } + + @Override + public String getEquateName(long value, int size, boolean isSigned, Program program) { + if (program == null) { + return null; + } + return nameForce; + } + } + + @Override + protected String getProgramName() { + return "Winmine__XP.exe.gzf"; + } + + private DecompilerActionContext getContext() { + Function function = getHighFunction().getFunction(); + return new DecompilerActionContext(getDecompilerProvider(), function.getEntryPoint(), + false); + } + + /** + * Simulate the action of "converting" the current token to the given format + * @param convertType is the given format + */ + private void convertToken(int convertType) { + checkInitialToken(); + ConvertConstantAction action; + switch (convertType) { + case EquateSymbol.FORMAT_DEC: + action = new ConvertDecAction(decompiler); + break; + case EquateSymbol.FORMAT_BIN: + action = new ConvertBinaryAction(decompiler); + break; + case EquateSymbol.FORMAT_OCT: + action = new ConvertOctAction(decompiler); + break; + case EquateSymbol.FORMAT_CHAR: + action = new ConvertCharAction(decompiler); + break; + case EquateSymbol.FORMAT_HEX: + action = new ConvertHexAction(decompiler); + break; + default: + action = null; + } + modifyProgram(p -> { + action.actionPerformed(getContext()); + }); + waitForDecompiler(); + } + + /** + * Simulate setting an equate on the current token with the given name + * @param nm is the given name + */ + private void convertToken(String nm) { + checkInitialToken(); + EquateNameForce action = new EquateNameForce(decompiler, nm); + modifyProgram(p -> { + action.actionPerformed(getContext()); + }); + waitForDecompiler(); + } + + private void checkInitialToken() { + ClangToken token = getToken(); + assertTrue(token instanceof ClangVariableToken); + HighVariable variable = token.getHighVariable(); + assertTrue(variable instanceof HighConstant); + } + + private void verifyMatch(String eqName, String text, long address, boolean seeInListing) { + ClangToken token = getToken(); + assertTrue(token instanceof ClangVariableToken); + assertEquals(token.getText(), text); + HighSymbol symbol = token.getHighVariable().getSymbol(); + assertTrue(symbol instanceof EquateSymbol); + EquateSymbol eqSym = (EquateSymbol) symbol; + SymbolEntry entry = eqSym.getFirstWholeMap(); + assertTrue(entry instanceof DynamicEntry); + DynamicEntry dynEntry = (DynamicEntry) entry; + assertEquals(dynEntry.getPCAdress().getOffset(), address); + EquateTable equateTable = program.getEquateTable(); + assertNotNull(equateTable); + Equate equate = equateTable.getEquate(eqName); + assertNotNull(equate); + List references = equate.getReferences(eqSym.getPCAddress()); + assertEquals(references.size(), 1); + EquateReference ref = references.get(0); + assertEquals(equate.getValue(), eqSym.getValue()); + boolean foundHash = false; + if (ref.getDynamicHashValue() == 0) { + Instruction instr = program.getListing().getInstructionAt(ref.getAddress()); + long values[] = DynamicHash.calcConstantHash(instr, equate.getValue()); + for (long value : values) { + if (value == dynEntry.getHash()) { + foundHash = true; + break; + } + } + } + else { + foundHash = (dynEntry.getHash() == ref.getDynamicHashValue()); + } + assertTrue(foundHash); + assertEquals(ref.getOpIndex() >= 0, seeInListing); + } + + @Test + public void testEquate_basicConversion() { + + decompile("10016fa"); + + ClangTextField line = getLineContaining("0x53"); + setDecompilerLocation(line.getLineNumber(), 17); + + convertToken(EquateSymbol.FORMAT_DEC); + line = getLineContaining("83"); + setDecompilerLocation(line.getLineNumber(), 15); + verifyMatch("83", "83", 0x1001700, true); + } + + @Test + public void testEquate_offByOne() { + decompile("10016fa"); + + ClangTextField line = getLineContaining("< 3)"); + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf('3')); + convertToken(EquateSymbol.FORMAT_BIN); + line = getLineContaining("0b00000011"); // Binary format of "3" + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf("0b")); + verifyMatch("00000000000000000000000000000010b", "0b00000011", 0x1001732, true); + } + + @Test + public void testEquate_decompilerInvented() { + decompile("10016fa"); + + ClangTextField line = getLineContaining("0x111)"); + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf("0x111")); + + convertToken(EquateSymbol.FORMAT_OCT); + line = getLineContaining("0421"); + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf("0421")); + verifyMatch("421o", "0421", 0x100171f, false); + } + + @Test + public void testEquate_oneOffNearby() { + decompile("1002bc2"); + + ClangTextField line = getLineContaining("9,0,0,1"); + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf("0,0")); + + convertToken("MYZERO"); + line = getLineContaining("9,MYZERO"); // Make sure equate comes right after '9' + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf("MYZERO")); + // The operand of the nearby "PUSH 0x1" instruction gets selected as a possible + // candidate for the equate, but it doesn't propagate to the desired zero. + verifyMatch("MYZERO", "MYZERO", 0x1002c8a, false); + } + + @Test + public void testEquate_namedMinus() { + decompile("1002825"); + + ClangTextField line = getLineContaining("0x38"); + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf("0x38")); + + convertToken("MYMINUS"); + line = getLineContaining("+ MYMINUS"); + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf("MYMINUS")); + // Make sure the named equate applies to the negative number in the decompiler window + // NOT the positive variant in the listing + verifyMatch("MYMINUS", "MYMINUS", 0x1002862, false); + } + + @Test + public void testEquate_unnamedMinus() { + decompile("1002825"); + + ClangTextField line = getLineContaining("0x2b"); + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf("0x2b")); + + convertToken(EquateSymbol.FORMAT_DEC); + line = getLineContaining("43"); + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf("43")); + // Conversion should be attached to the positive formatting, but should affect both listing and decompiler + verifyMatch("43", "-43", 0x1002882, true); + } + + @Test + public void testEquate_escapechar() { + decompile("1002785"); + + ClangTextField line = getLineContaining("/ 10)"); + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf("10)")); + + convertToken(EquateSymbol.FORMAT_CHAR); + line = getLineContaining("\\n"); + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf("\\n")); + verifyMatch("'\\n'", "L'\\n'", 0x10027d3, true); + } + + @Test + public void testEquate_convertChar() { + decompile("1003d76"); + + ClangTextField line = getLineContaining("'.'"); + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf("'.'")); + + convertToken(EquateSymbol.FORMAT_HEX); + line = getLineContaining("x2e"); + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf("x2e")); + verifyMatch("0x2E", "'\\x2e'", 0x1003db9, true); + } + + @Test + public void testEquate_actionNoShow() { + decompile("1001915"); + + ClangTextField line = getLineContaining("GetSystemMetrics(0x4e)"); + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf("4e")); + ConvertHexAction action = new ConvertHexAction(decompiler); + assertFalse(action.isEnabledForContext(getContext())); + } + + @Test + public void testEquate_charNonAscii() { + decompile("1002eab"); + + ClangTextField line = getLineContaining("0xe0"); + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf("0xe0")); + convertToken(EquateSymbol.FORMAT_CHAR); + line = getLineContaining("xe0"); + setDecompilerLocation(line.getLineNumber(), line.getText().indexOf("xe0")); + verifyMatch("E0h", "'\\xe0'", 0x1002ec3, true); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/EquateDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/EquateDB.java index 50154b5f9b..58af59e2d7 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/EquateDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/EquateDB.java @@ -16,6 +16,8 @@ package ghidra.program.database.symbol; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import db.DBRecord; import ghidra.program.database.DBObjectCache; @@ -73,15 +75,18 @@ public class EquateDB extends DatabaseObject implements Equate { try { Instruction instr = equateMgr.getProgram().getCodeManager().getInstructionAt(refAddr); long dynamicHash; - if (instr == null) + if (instr == null) { dynamicHash = 0; + } else { long value = record.getLongValue(EquateDBAdapter.VALUE_COL); - long hashArray[] = DynamicHash.calcConstantHash(instr, value); - if (hashArray.length != 1) - dynamicHash = 0; - else - dynamicHash = hashArray[0]; + long hashArray[] = DynamicHash.calcConstantHash(instr, value); + if (hashArray.length != 1) { + dynamicHash = 0; + } + else { + dynamicHash = hashArray[0]; + } } equateMgr.addReference(key, refAddr, (short) opIndex, dynamicHash); } @@ -112,9 +117,11 @@ public class EquateDB extends DatabaseObject implements Equate { } long value = record.getLongValue(EquateDBAdapter.VALUE_COL); long checkHash[] = DynamicHash.calcConstantHash(instr, value); - for (long element : checkHash) - if (element == dynamicHash) + for (long element : checkHash) { + if (element == dynamicHash) { return findScalarOpIndex(instr); + } + } return -1; } @@ -201,6 +208,27 @@ public class EquateDB extends DatabaseObject implements Equate { return new EquateReference[0]; } + /** + * @see ghidra.program.model.symbol.Equate#getReferences(Address) + */ + @Override + public List getReferences(Address refAddr) { + Lock lock = equateMgr.getLock(); + lock.acquire(); + try { + if (checkIsValid()) { + return equateMgr.getReferences(key, refAddr); + } + } + catch (IOException e) { + equateMgr.getProgram().dbError(e); + } + finally { + lock.release(); + } + return new ArrayList<>(); + } + /** * @see ghidra.program.model.symbol.Equate#getValue() */ diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/EquateManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/EquateManager.java index 4442b38850..99944dcfad 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/EquateManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/EquateManager.java @@ -24,8 +24,7 @@ import ghidra.program.database.*; import ghidra.program.database.map.AddressKeyAddressIterator; import ghidra.program.database.map.AddressMap; import ghidra.program.model.address.*; -import ghidra.program.model.symbol.Equate; -import ghidra.program.model.symbol.EquateTable; +import ghidra.program.model.symbol.*; import ghidra.program.util.ChangeManager; import ghidra.program.util.EquateInfo; import ghidra.util.Lock; @@ -328,11 +327,10 @@ public class EquateManager implements EquateTable, ErrorHandler, ManagerDB { list.add(ref); } } - for (int i = 0; i < list.size(); i++) { + for (EquateRefDB ref : list) { if (monitor.isCancelled()) { throw new CancelledException(); } - EquateRefDB ref = list.get(i); EquateDB equateDB = getEquateDB(ref.getEquateID()); removeRef(equateDB, ref); @@ -454,6 +452,22 @@ public class EquateManager implements EquateTable, ErrorHandler, ManagerDB { return getReferences(equateID).length; } + List getReferences(long equateID, Address reference) throws IOException { + List refs = new ArrayList<>(); + long refAddr = addrMap.getKey(reference, false); + if (refAddr == AddressMap.INVALID_ADDRESS_KEY) { + return refs; + } + Field[] keys = refAdapter.getRecordKeysForAddr(refAddr); + for (Field key : keys) { + EquateRefDB ref = getEquateRefDB(key.getLongValue()); + if (ref.getEquateID() == equateID) { + refs.add(ref); + } + } + return refs; + } + void removeReference(EquateDB equateDB, Address refAddr, short opIndex) throws IOException { Field[] keys = refAdapter.getRecordKeysForEquateID(equateDB.getKey()); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/EquateSymbol.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/EquateSymbol.java index aa81f85dce..15fa6fd8fa 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/EquateSymbol.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/EquateSymbol.java @@ -32,7 +32,7 @@ public class EquateSymbol extends HighSymbol { private long value; // Value of the equate private int convert; // Non-zero if this is a conversion equate - + public EquateSymbol(HighFunction func) { super(func); } @@ -46,7 +46,7 @@ public class EquateSymbol extends HighSymbol { DynamicEntry entry = new DynamicEntry(this, addr, hash); addMapEntry(entry); } - + public EquateSymbol(long uniqueId, int conv, long val, HighFunction func, Address addr, long hash) { super(uniqueId, "", DataType.DEFAULT, func); @@ -57,7 +57,9 @@ public class EquateSymbol extends HighSymbol { addMapEntry(entry); } - public long getValue() { return value; } + public long getValue() { + return value; + } public int getConvert() { return convert; @@ -121,8 +123,18 @@ public class EquateSymbol extends HighSymbol { buf.append("\n"); buf.append("\n"); } - - public static int convertName(String nm,long val) { + + /** + * Determine what format a given equate name is in. + * Integer format conversions are stored using an Equate object, where the name of the equate + * is the actual conversion String. So the only way to tell what kind of conversion is being performed + * is by examining the name of the equate. The format code of the conversion is returned, or if + * the name is not a conversion, FORMAT_DEFAULT is returned indicating a normal String equate. + * @param nm is the name of the equate + * @param val is the value being equated + * @return the format code for the conversion or FORMAT_DEFAULT if not a conversion + */ + public static int convertName(String nm, long val) { int pos = 0; char firstChar = nm.charAt(pos++); if (firstChar == '-') { @@ -133,32 +145,53 @@ public class EquateSymbol extends HighSymbol { return FORMAT_DEFAULT; // Bad equate name, just print number normally } } - if (firstChar == '\'') { - return FORMAT_CHAR; - } - if (firstChar == '"') { // Multi-character conversion - return FORMAT_DEC; // not currently supported, just format in decimal - } - if (firstChar < '0' || firstChar > '9') { - return -1; // Don't treat as a conversion - } - char lastChar = nm.charAt(nm.length() - 1); - if (lastChar == 'b') { - return FORMAT_BIN; - } - else if (lastChar == 'o') { - return FORMAT_OCT; - } - int format = FORMAT_DEC; - if (firstChar == '0') { - format = FORMAT_DEC; - if (nm.length() >= (pos + 1)) { - char c = nm.charAt(pos); - if (c == 'x') { - format = FORMAT_HEX; + switch (firstChar) { + case '\'': + case '"': + return FORMAT_CHAR; + case '0': + if (nm.length() >= (pos + 1) && nm.charAt(pos) == 'x') { + return FORMAT_HEX; } - } + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + if (nm.length() >= 3 && nm.charAt(2) == 'h') { + char secondChar = nm.charAt(1); + if (secondChar >= '0' && secondChar <= '9') { + return FORMAT_CHAR; + } + if (secondChar >= 'A' && secondChar <= 'F') { + return FORMAT_CHAR; + } + } + return FORMAT_DEFAULT; + default: + return FORMAT_DEFAULT; // Don't treat as a conversion } - return format; + switch (nm.charAt(nm.length() - 1)) { + case 'b': + return FORMAT_BIN; + case 'o': + return FORMAT_OCT; + case '\'': + case '"': + case 'h': // The 'h' encoding is used for "unrepresentable" characters + return FORMAT_CHAR; + } + return FORMAT_DEC; } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/LocalSymbolMap.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/LocalSymbolMap.java index 436670ed45..1794daa825 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/LocalSymbolMap.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/LocalSymbolMap.java @@ -50,8 +50,8 @@ public class LocalSymbolMap { public LocalSymbolMap(HighFunction highFunc, String spcname) { func = highFunc; spacename = spcname; - addrMappedSymbols = new HashMap(); - symbolMap = new HashMap(); + addrMappedSymbols = new HashMap<>(); + symbolMap = new HashMap<>(); paramSymbols = new HighSymbol[0]; uniqueSymbolId = 0; } @@ -79,7 +79,7 @@ public class LocalSymbolMap { * @return the new name to symbol map */ public Map getNameToSymbolMap() { - Map newMap = new TreeMap(); + Map newMap = new TreeMap<>(); for (HighSymbol highSymbol : symbolMap.values()) { newMap.put(highSymbol.getName(), highSymbol); } @@ -171,7 +171,7 @@ public class LocalSymbolMap { // An indication of names like "name", "name@1", "name@2" if (name.charAt(name.length() - 1) == '1') { if (mergeNames == null) { - mergeNames = new ArrayList(); + mergeNames = new ArrayList<>(); } mergeNames.add(name); } @@ -207,7 +207,7 @@ public class LocalSymbolMap { Address pcaddr = dbFunction.getEntryPoint(); pcaddr = pcaddr.subtractWrap(1); - List paramList = new ArrayList(); + List paramList = new ArrayList<>(); for (int i = 0; i < p.length; ++i) { Parameter var = p[i]; if (!var.isValid()) { @@ -220,7 +220,7 @@ public class LocalSymbolMap { // An indication of names like "name", "name@1", "name@2" if (name.charAt(name.length() - 1) == '1') { if (mergeNames == null) { - mergeNames = new ArrayList(); + mergeNames = new ArrayList<>(); } mergeNames.add(name); } @@ -297,13 +297,12 @@ public class LocalSymbolMap { parser.end(el); } - private static final Comparator PARAM_SYMBOL_SLOT_COMPARATOR = - new Comparator() { - @Override - public int compare(HighSymbol sym1, HighSymbol sym2) { - return sym1.getCategoryIndex() - sym2.getCategoryIndex(); - } - }; + private static final Comparator PARAM_SYMBOL_SLOT_COMPARATOR = new Comparator<>() { + @Override + public int compare(HighSymbol sym1, HighSymbol sym2) { + return sym1.getCategoryIndex() - sym2.getCategoryIndex(); + } + }; /** * Add mapped symbols to this LocalVariableMap, by parsing the <symbollist> and <mapsym> tags. @@ -312,7 +311,7 @@ public class LocalSymbolMap { */ public void parseSymbolList(XmlPullParser parser) throws PcodeXMLException { XmlElement el = parser.start("symbollist"); - ArrayList parms = new ArrayList(); + ArrayList parms = new ArrayList<>(); while (parser.peek().isStart()) { HighSymbol sym = parseSymbolXML(parser); if (sym.isParameter()) { @@ -483,7 +482,7 @@ public class LocalSymbolMap { uniqueId = getNextId(); } int conv = EquateSymbol.convertName(nm, val); - if (conv < 0) { + if (conv == EquateSymbol.FORMAT_DEFAULT) { eqSymbol = new EquateSymbol(uniqueId, nm, val, func, addr, hash); eqSymbol.setNameLock(true); } @@ -507,25 +506,37 @@ public class LocalSymbolMap { while (equateAddresses.hasNext()) { Address defAddr = equateAddresses.next(); for (Equate eq : equateTable.getEquates(defAddr)) { - Instruction instr = listing.getInstructionAt(defAddr); - if (instr == null) { - continue; - } - long hash[] = DynamicHash.calcConstantHash(instr, eq.getValue()); - if (hash.length == 0) { - continue; - } - Arrays.sort(hash); // Sort in preparation for deduping + List references = eq.getReferences(defAddr); String displayName = eq.getDisplayName(); long eqValue = eq.getValue(); EquateSymbol eqSymbol; - for (int i = 0; i < hash.length; ++i) { - if (i != 0 && hash[i - 1] == hash[i]) { - continue; // Found a duplicate, skip it + for (EquateReference ref : references) { + long hashVal = ref.getDynamicHashValue(); + if (hashVal == 0) { + // Multiple varnodes of the same constant + Instruction instr = listing.getInstructionAt(defAddr); + if (instr == null) { + continue; + } + long hash[] = DynamicHash.calcConstantHash(instr, eqValue); + if (hash.length == 0) { + continue; + } + Arrays.sort(hash); // Sort in preparation for deduping + for (int i = 0; i < hash.length; ++i) { + if (i != 0 && hash[i - 1] == hash[i]) { + continue; // Found a duplicate, skip it + } + // Emit each varnode copy as a separate EquateSymbol + eqSymbol = newEquateSymbol(0, displayName, eqValue, hash[i], defAddr); + symbolMap.put(eqSymbol.getId(), eqSymbol); + } + } + else { + eqSymbol = newEquateSymbol(0, displayName, eqValue, hashVal, defAddr); + symbolMap.put(eqSymbol.getId(), eqSymbol); } - eqSymbol = newEquateSymbol(0, displayName, eqValue, hash[i], defAddr); - symbolMap.put(eqSymbol.getId(), eqSymbol); } } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/Equate.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/Equate.java index cfd118d5d2..9758defc40 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/Equate.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/Equate.java @@ -15,10 +15,13 @@ */ package ghidra.program.model.symbol; +import java.util.List; + import ghidra.program.model.address.Address; import ghidra.util.UniversalID; import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.InvalidInputException; + /** * An Equate associates a string with a scalar value in the program, * and contains a list of addresses and operand positions that refer @@ -45,7 +48,7 @@ public interface Equate { /** * Get the value of this equate. */ - public long getValue(); + public long getValue(); /** * Gets a more accurate representation of the equate value. Used for rendering as close to the @@ -57,7 +60,7 @@ public interface Equate { /** * Get the number of references to this equate. */ - public int getReferenceCount(); + public int getReferenceCount(); /** * Add a reference (at the given operand position) to this equate. If a reference already @@ -67,9 +70,9 @@ public interface Equate { * @param refAddr the address where the equate is used. * @param opndPosition the operand index where the equate is used. */ - public void addReference(Address refAddr,int opndPosition); - - /** + public void addReference(Address refAddr, int opndPosition); + + /** * Add a reference (at the given dynamic hash position) to this equate. If a reference already * exists for the instruction at this address, then the old reference will be removed * before the new reference is added. @@ -77,39 +80,45 @@ public interface Equate { * @param dynamicHash constant varnode dynamic hash value * @param refAddr the address where the equate is used. */ - public void addReference(long dynamicHash, Address refAddr); + public void addReference(long dynamicHash, Address refAddr); - /** - * Changes the name associated with the equate. - * @param newName the new name for this equate. - * @exception DuplicateNameException thrown if newName is already - * used by another equate. - * @throws InvalidInputException if newName contains blank characters, - * is zero length, or is null - */ - void renameEquate(String newName) throws DuplicateNameException, - InvalidInputException; + /** + * Changes the name associated with the equate. + * @param newName the new name for this equate. + * @exception DuplicateNameException thrown if newName is already + * used by another equate. + * @throws InvalidInputException if newName contains blank characters, + * is zero length, or is null + */ + void renameEquate(String newName) throws DuplicateNameException, InvalidInputException; /** * Get the references for this equate. * @return a array of EquateReferences. */ - public EquateReference[] getReferences(); + public EquateReference[] getReferences(); + + /** + * Get references for this equate attached to a specific address + * @param refAddr is the address + * @return the list of EquateReferences + */ + public List getReferences(Address refAddr); /** * Remove the reference at the given operand position. * @param refAddr the address that was using this equate * @param opndPosition the operand index of the operand that was using this eqate. */ - public void removeReference(Address refAddr, int opndPosition); - - /** + public void removeReference(Address refAddr, int opndPosition); + + /** * Remove the reference at the given address * @param dynamicHash the hash of the reference * @param refAddr the reference's address */ - public void removeReference(long dynamicHash, Address refAddr); - + public void removeReference(long dynamicHash, Address refAddr); + /** * Checks if equate is based off an enum's universal id and checks if the enum still exists. * The equate is still valid if the equate is not based off an enum. @@ -129,10 +138,10 @@ public interface Equate { */ public UniversalID getEnumUUID(); - /** - * Get the name of this equate. - * @see #getName() - */ - @Override + /** + * Get the name of this equate. + * @see #getName() + */ + @Override public String toString(); }