diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/CallFixupAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/CallFixupAnalyzer.java index 36931f31b0..60f8c68024 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/CallFixupAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/CallFixupAnalyzer.java @@ -101,8 +101,8 @@ public class CallFixupAnalyzer extends AbstractAnalyzer { if (mustFix) { PcodeInjectLibrary snippetLibrary = program.getCompilerSpec().getPcodeInjectLibrary(); - InjectPayload callFixup = snippetLibrary.getPayload(InjectPayload.CALLFIXUP_TYPE, - callFixupApplied, program, null); + InjectPayload callFixup = + snippetLibrary.getPayload(InjectPayload.CALLFIXUP_TYPE, callFixupApplied); boolean isfallthru = true; if (callFixup != null) { isfallthru = callFixup.isFallThru(); @@ -405,8 +405,8 @@ public class CallFixupAnalyzer extends AbstractAnalyzer { } } - program.getBookmarkManager().removeBookmarks(repairedCallLocations, BookmarkType.ERROR, - monitor); + program.getBookmarkManager() + .removeBookmarks(repairedCallLocations, BookmarkType.ERROR, monitor); if (!clearInstSet.isEmpty()) { // entries including data flow referenced from instructions will be repaired @@ -449,7 +449,7 @@ public class CallFixupAnalyzer extends AbstractAnalyzer { String[] callFixupNames = snippetLibrary.getCallFixupNames(); for (String fixupName : callFixupNames) { InjectPayload payload = - snippetLibrary.getPayload(InjectPayload.CALLFIXUP_TYPE, fixupName, program, null); + snippetLibrary.getPayload(InjectPayload.CALLFIXUP_TYPE, fixupName); List callFixupTargets = ((InjectPayloadCallfixup) payload).getTargets(); for (String name : callFixupTargets) { cachedTargetFixupMap.put(name, fixupName); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorDialog.java index da0ecfab24..a8d85b1a9e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorDialog.java @@ -359,6 +359,9 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod } private JComponent createCallFixupComboPanel() { + + JPanel panel = new JPanel(); + callFixupComboBox = new GComboBox<>(); String[] callFixupNames = model.getCallFixupNames(); @@ -377,7 +380,8 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod callFixupComboBox.setEnabled(false); } - return callFixupComboBox; + panel.add(callFixupComboBox); + return panel; } private Component buildTable() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java index 1d2c2b5a3f..0ec9498847 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java @@ -405,8 +405,9 @@ public class SymbolicPropogator { canceled = false; // only stop flowing on unknown bad calls when the stack depth could be unknown - boolean callCouldCauseBadStackDepth = - program.getCompilerSpec().getDefaultCallingConvention().getExtrapop() == PrototypeModel.UNKNOWN_EXTRAPOP; + boolean callCouldCauseBadStackDepth = program.getCompilerSpec() + .getDefaultCallingConvention() + .getExtrapop() == PrototypeModel.UNKNOWN_EXTRAPOP; while (!contextStack.isEmpty()) { monitor.checkCanceled(); @@ -834,8 +835,8 @@ public class SymbolicPropogator { try { val1 = vContext.getValue(in[0], evaluator); lval1 = vContext.getConstant(val1, evaluator); - vt = vContext.getVarnode( - minInstrAddress.getAddressSpace().getSpaceID(), lval1, 0); + vt = vContext.getVarnode(minInstrAddress.getAddressSpace().getSpaceID(), + lval1, 0); makeReference(vContext, instruction, ptype, -1, vt, instruction.getFlowType(), monitor); } @@ -873,8 +874,8 @@ public class SymbolicPropogator { if (val1.isConstant()) { // indirect target - assume single code space (same as instruction) - target = instruction.getAddress().getNewTruncatedAddress( - val1.getOffset(), true); + target = instruction.getAddress() + .getNewTruncatedAddress(val1.getOffset(), true); } else if (val1.isAddress()) { // TODO: could this also occur if a memory location was copied ?? @@ -957,8 +958,8 @@ public class SymbolicPropogator { case PcodeOp.CALLOTHER: // HACK ALERT! // if this is a segment op, emulate the segmenting for now. - String opName = this.program.getLanguage().getUserDefinedOpName( - (int) in[0].getOffset()); + String opName = this.program.getLanguage() + .getUserDefinedOpName((int) in[0].getOffset()); if (opName.equals("segment") && in.length > 2) { checkSegmented(out, in[1], in[2], mustClearAll); } @@ -1074,8 +1075,8 @@ public class SymbolicPropogator { } else if (!evaluator.followFalseConditionalBranches()) { // pcode addresses are raw addresses, make sure address is in same instruction space - nextAddr = minInstrAddress.getAddressSpace().getOverlayAddress( - in[0].getAddress()); + nextAddr = minInstrAddress.getAddressSpace() + .getOverlayAddress(in[0].getAddress()); pcodeIndex = ops.length; // break out of the processing } } @@ -1202,8 +1203,8 @@ public class SymbolicPropogator { case PcodeOp.INT_RIGHT: val1 = vContext.getValue(in[0], false, evaluator); val2 = vContext.getValue(in[1], false, evaluator); - lresult = vContext.getConstant(val1, - evaluator) >> vContext.getConstant(val2, evaluator); + lresult = vContext.getConstant(val1, evaluator) >> vContext + .getConstant(val2, evaluator); result = vContext.createConstantVarnode(lresult, val1.getSize()); vContext.putValue(out, result, mustClearAll); break; @@ -1211,8 +1212,8 @@ public class SymbolicPropogator { case PcodeOp.INT_SRIGHT: val1 = vContext.getValue(in[0], true, evaluator); val2 = vContext.getValue(in[1], false, evaluator); - lresult = vContext.getConstant(val1, - evaluator) >>> vContext.getConstant(val2, evaluator); + lresult = vContext.getConstant(val1, evaluator) >>> vContext + .getConstant(val2, evaluator); result = vContext.createConstantVarnode(lresult, val1.getSize()); vContext.putValue(out, result, mustClearAll); break; @@ -1315,8 +1316,8 @@ public class SymbolicPropogator { val2 = vContext.getValue(in[1], true, evaluator); lval1 = vContext.getConstant(val1, evaluator); lval2 = vContext.getConstant(val2, evaluator); - lresult = (vContext.getConstant(val1, - evaluator) < vContext.getConstant(val2, evaluator)) ? 1 : 0; + lresult = (vContext.getConstant(val1, evaluator) < vContext + .getConstant(val2, evaluator)) ? 1 : 0; result = vContext.createConstantVarnode(lresult, val1.getSize()); vContext.putValue(out, result, mustClearAll); break; @@ -1334,8 +1335,8 @@ public class SymbolicPropogator { case PcodeOp.INT_SLESSEQUAL: val1 = vContext.getValue(in[0], true, evaluator); val2 = vContext.getValue(in[1], true, evaluator); - lresult = (vContext.getConstant(val1, - evaluator) <= vContext.getConstant(val2, evaluator)) ? 1 : 0; + lresult = (vContext.getConstant(val1, evaluator) <= vContext + .getConstant(val2, evaluator)) ? 1 : 0; result = vContext.createConstantVarnode(lresult, val1.getSize()); vContext.putValue(out, result, mustClearAll); break; @@ -1344,8 +1345,8 @@ public class SymbolicPropogator { val1 = vContext.getValue(in[0], false, evaluator); val2 = vContext.getValue(in[1], false, evaluator); - lresult = (vContext.getConstant(val1, - evaluator) == vContext.getConstant(val2, evaluator)) ? 1 : 0; + lresult = (vContext.getConstant(val1, evaluator) == vContext + .getConstant(val2, evaluator)) ? 1 : 0; result = vContext.createConstantVarnode(lresult, val1.getSize()); vContext.putValue(out, result, mustClearAll); break; @@ -1353,8 +1354,8 @@ public class SymbolicPropogator { case PcodeOp.INT_NOTEQUAL: val1 = vContext.getValue(in[0], false, evaluator); val2 = vContext.getValue(in[1], false, evaluator); - lresult = (vContext.getConstant(val1, - evaluator) != vContext.getConstant(val2, evaluator)) ? 1 : 0; + lresult = (vContext.getConstant(val1, evaluator) != vContext + .getConstant(val2, evaluator)) ? 1 : 0; result = vContext.createConstantVarnode(lresult, val1.getSize()); vContext.putValue(out, result, mustClearAll); break; @@ -1571,7 +1572,7 @@ public class SymbolicPropogator { PcodeInjectLibrary snippetLibrary = prog.getCompilerSpec().getPcodeInjectLibrary(); InjectPayload payload = - snippetLibrary.getPayload(InjectPayload.CALLFIXUP_TYPE, callFixupName, prog, null); + snippetLibrary.getPayload(InjectPayload.CALLFIXUP_TYPE, callFixupName); if (payload == null) { return null; } @@ -1598,7 +1599,7 @@ public class SymbolicPropogator { PcodeInjectLibrary snippetLibrary = prog.getCompilerSpec().getPcodeInjectLibrary(); InjectPayload payload = - snippetLibrary.getPayload(InjectPayload.CALLMECHANISM_TYPE, injectionName, prog, null); + snippetLibrary.getPayload(InjectPayload.CALLMECHANISM_TYPE, injectionName); if (payload == null) { return null; } @@ -1843,14 +1844,14 @@ public class SymbolicPropogator { // } else if (!vContext.isStackSymbolicSpace(refLocation) && evaluator != null) { - Address constant = program.getAddressFactory().getAddress( - (int) targetSpaceID.getOffset(), offset); + Address constant = program.getAddressFactory() + .getAddress((int) targetSpaceID.getOffset(), offset); Address newTarget = evaluator.evaluateConstant(vContext, instruction, pcodeType, constant, 0, reftype); if (newTarget != null) { makeReference(vContext, instruction, Reference.MNEMONIC, - newTarget.getAddressSpace().getSpaceID(), newTarget.getOffset(), - 0, reftype, pcodeType, false, monitor); + newTarget.getAddressSpace().getSpaceID(), newTarget.getOffset(), 0, + reftype, pcodeType, false, monitor); return; } } diff --git a/Ghidra/Features/Decompiler/build.gradle b/Ghidra/Features/Decompiler/build.gradle index a426377b0a..85a49229f9 100644 --- a/Ghidra/Features/Decompiler/build.gradle +++ b/Ghidra/Features/Decompiler/build.gradle @@ -120,7 +120,7 @@ task buildDecompilerHelpHtml(type: Exec) { rm -f $installHelpPoint/topics/DecompilePlugin/*.html echo '** Building html files **' - xsltproc --output $buildDir/decomp_noscaling.xml --stringparam profile.condition "noscaling" /usr/share/sgml/docbook/xsl-stylesheets/profiling/profile.xsl decompileplugin.xml 2>&1 + xsltproc --output $buildDir/decomp_noscaling.xml --stringparam profile.condition "noscaling" commonprofile.xsl decompileplugin.xml 2>&1 xsltproc --stringparam base.dir ${installHelpPoint}/topics/DecompilePlugin/ --stringparam root.filename Decompiler decompileplugin_html.xsl $buildDir/decomp_noscaling.xml 2>&1 rm ${installHelpPoint}/topics/DecompilePlugin/Decompiler.html sed -i -e '/Frontpage.css/ { p; s/Frontpage.css/languages.css/; }' ${installHelpPoint}/topics/DecompilePlugin/*.html 2>&1 @@ -175,19 +175,19 @@ task buildDecompilerHelpPdf(type: Exec) { echo '** Checking if required executables are installed. **' which fop 2>&1 which xsltproc 2>&1 - rm -f decompileplugin.fo decompileplugin.pdf decompileplugin_withscaling.xml 2>&1 - rm -rf ./images 2>&1 - mkdir -p ./images 2>&1 - cp $installHelpPoint/topics/DecompilePlugin/images/*.png ./images 2>&1 - cp $installHelpPoint/topics/DecompilePlugin/images/*.gif ./images 2>&1 - cp $installHelpPoint/shared/*.png ./images 2>&1 + rm -f $buildDir/decompileplugin.fo $buildDir/decompileplugin.pdf $buildDir/decompileplugin_withscaling.xml 2>&1 + rm -rf $buildDir/images 2>&1 + mkdir -p $buildDir/images 2>&1 + cp $installHelpPoint/topics/DecompilePlugin/images/*.png $buildDir/images 2>&1 + cp $installHelpPoint/topics/DecompilePlugin/images/*.gif $buildDir/images 2>&1 + cp $installHelpPoint/shared/*.png $buildDir/images 2>&1 echo '** Building decompileplugin.fo **' - xsltproc --output ./decompileplugin_withscaling.xml --stringparam profile.condition "withscaling" /usr/share/sgml/docbook/xsl-stylesheets/profiling/profile.xsl decompileplugin.xml 2>&1 - xsltproc --output ./decompileplugin.fo decompileplugin_pdf.xsl decompileplugin_withscaling.xml 2>&1 + xsltproc --output $buildDir/decompileplugin_withscaling.xml --stringparam profile.condition "withscaling" commonprofile.xsl decompileplugin.xml 2>&1 + xsltproc --output $buildDir/decompileplugin.fo decompileplugin_pdf.xsl $buildDir/decompileplugin_withscaling.xml 2>&1 echo '** Building decompileplugin.pdf **' - fop decompileplugin.fo decompileplugin.pdf 2>&1 + fop $buildDir/decompileplugin.fo $buildDir/decompileplugin.pdf 2>&1 echo '** Done. **' """ diff --git a/Ghidra/Features/Decompiler/certification.manifest b/Ghidra/Features/Decompiler/certification.manifest index f965b0d4c3..9a6aa4192b 100644 --- a/Ghidra/Features/Decompiler/certification.manifest +++ b/Ghidra/Features/Decompiler/certification.manifest @@ -30,6 +30,7 @@ src/decompile/datatests/sbyte.xml||GHIDRA||||END| src/decompile/datatests/threedim.xml||GHIDRA||||END| src/decompile/datatests/twodim.xml||GHIDRA||||END| src/decompile/datatests/wayoffarray.xml||GHIDRA||||END| +src/main/doc/commonprofile.xsl||GHIDRA||||END| src/main/doc/cspec.xml||GHIDRA||||END| src/main/doc/cspec_html.xsl||GHIDRA||||END| src/main/doc/decompileplugin.xml||GHIDRA||||END| diff --git a/Ghidra/Features/Decompiler/ghidra_scripts/TurnOnLanguage.java b/Ghidra/Features/Decompiler/ghidra_scripts/TurnOnLanguage.java index 2a86ffda93..0a29834439 100644 --- a/Ghidra/Features/Decompiler/ghidra_scripts/TurnOnLanguage.java +++ b/Ghidra/Features/Decompiler/ghidra_scripts/TurnOnLanguage.java @@ -15,18 +15,17 @@ */ import ghidra.app.script.GhidraScript; import ghidra.framework.options.Options; -import ghidra.program.model.lang.BasicCompilerSpec; +import ghidra.program.database.ProgramCompilerSpec; public class TurnOnLanguage extends GhidraScript { @Override protected void run() throws Exception { - Options decompilerPropertyList = currentProgram.getOptions(BasicCompilerSpec.DECOMPILER_PROPERTY_LIST_NAME); - decompilerPropertyList.registerOption( - BasicCompilerSpec.DECOMPILER_OUTPUT_LANGUAGE, - BasicCompilerSpec.DECOMPILER_OUTPUT_DEF, - null, - BasicCompilerSpec.DECOMPILER_OUTPUT_DESC); + Options decompilerPropertyList = + currentProgram.getOptions(ProgramCompilerSpec.DECOMPILER_PROPERTY_LIST_NAME); + decompilerPropertyList.registerOption(ProgramCompilerSpec.DECOMPILER_OUTPUT_LANGUAGE, + ProgramCompilerSpec.DECOMPILER_OUTPUT_DEF, null, + ProgramCompilerSpec.DECOMPILER_OUTPUT_DESC); } } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc index e04d172dab..56dd7f979f 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc @@ -1218,6 +1218,26 @@ void Architecture::parseCompilerConfig(DocumentStorage &store) else if (elname == "inferptrbounds") parseInferPtrBounds(*iter); } + + el = store.getTag("specextensions"); // Look for any user-defined configuration document + if (el != (const Element *)0) { + const List &userlist(el->getChildren()); + for(iter=userlist.begin();iter!=userlist.end();++iter) { + const string &elname( (*iter)->getName() ); + if (elname == "prototype") + parseProto(*iter); + else if (elname == "callfixup") { + pcodeinjectlib->restoreXmlInject(archid+" : compiler spec", (*iter)->getAttributeValue("name"), + InjectPayload::CALLFIXUP_TYPE, *iter); + } + else if (elname == "callotherfixup") { + userops.parseCallOtherFixup(*iter,this); + } + else if (elname == "global") + globaltags.push_back(*iter); + } + } + // tags instantiate the base symbol table // They need to know about all spaces, so it must come // after parsing of and diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.hh index 9a592cb4a2..974e733e22 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.hh @@ -314,7 +314,7 @@ public: enum { unaffected = 1, ///< The sub-function does not change the value at all killedbycall = 2, ///< The memory is changed and is completely unrelated to its original value - return_address = 3, ///< The memory is being used to pass back a return value from the sub-function + return_address = 3, ///< The memory is being used to store the return address unknown_effect = 4 ///< An unknown effect (indicates the absence of an EffectRecord) }; private: diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_arch.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_arch.cc index d34513ef04..b72a8093db 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_arch.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_arch.cc @@ -284,7 +284,7 @@ void ArchitectureGhidra::buildSpecFile(DocumentStorage &store) istringstream cstream(cspecxml); doc = store.parseDocument(cstream); store.registerTag(doc->getRoot()); - + istringstream tstream(tspecxml); doc = store.parseDocument(tstream); store.registerTag(doc->getRoot()); @@ -293,10 +293,10 @@ void ArchitectureGhidra::buildSpecFile(DocumentStorage &store) doc = store.parseDocument(corestream); store.registerTag(doc->getRoot()); - pspecxml = ""; // Strings aren't used again free memory - cspecxml = ""; - tspecxml = ""; - corespecxml = ""; + pspecxml.clear(); // Strings aren't used again free memory + cspecxml.clear(); + tspecxml.clear(); + corespecxml.clear(); } void ArchitectureGhidra::postSpecFile(void) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_arch.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_arch.hh index f796c7ea76..0953288662 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_arch.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_arch.hh @@ -82,7 +82,8 @@ class ArchitectureGhidra : public Architecture { virtual void postSpecFile(void); virtual void resolveArchitecture(void); public: - ArchitectureGhidra(const string &pspec,const string &cspec,const string &tspec,const string &corespec,istream &i,ostream &o); + ArchitectureGhidra(const string &pspec,const string &cspec,const string &tspec,const string &corespec, + istream &i,ostream &o); const string &getWarnings(void) const { return warnings; } ///< Get warnings produced by the last decompilation void clearWarnings(void) { warnings.clear(); } ///< Clear warnings Document *getRegister(const string ®name); ///< Retrieve a register description given a name diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_process.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_process.cc index e78b0c0ced..46efe6eb0f 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_process.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_process.cc @@ -174,6 +174,10 @@ void RegisterProgram::rawAction(void) } } ghidra = new ArchitectureGhidra(pspec,cspec,tspec,corespec,sin,sout); + pspec.clear(); + cspec.clear(); + tspec.clear(); + corespec.clear(); DocumentStorage store; // temp storage of initialization xml docs ghidra->init(store); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/inject_sleigh.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/inject_sleigh.cc index 4a1e44dbf8..b72d089d2a 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/inject_sleigh.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/inject_sleigh.cc @@ -293,6 +293,23 @@ int4 PcodeInjectLibrarySleigh::registerDynamicInject(InjectPayload *payload) return id; } +/// \brief Force a payload to be dynamic for debug purposes +/// +/// Debug information may include inject information for payloads that aren't dynamic. +/// We substitute a dynamic payload so that analysis uses the debug info to inject, rather +/// than the hard-coded payload information. +/// \param injectid is the id of the payload to treat dynamic +/// \return the new dynamic payload object +InjectPayloadDynamic *PcodeInjectLibrarySleigh::forceDebugDynamic(int4 injectid) + +{ + InjectPayload *oldPayload = injection[injectid]; + InjectPayloadDynamic *newPayload = new InjectPayloadDynamic(glb,oldPayload->getName(),oldPayload->getType()); + delete oldPayload; + injection[injectid] = newPayload; + return newPayload; +} + void PcodeInjectLibrarySleigh::parseInject(InjectPayload *payload) { @@ -399,9 +416,10 @@ void PcodeInjectLibrarySleigh::restoreDebug(const Element *el) s.unsetf(ios::dec | ios::hex | ios::oct); s >> type; int4 id = getPayloadId(type,name); - InjectPayloadDynamic *payload = (InjectPayloadDynamic *)getPayload(id); - if (payload->getSource() != "dynamic") - throw LowlevelError("Mismatch with debug inject XML"); + InjectPayloadDynamic *payload = dynamic_cast(getPayload(id)); + if (payload == (InjectPayloadDynamic *)0) { + payload = forceDebugDynamic(id); + } payload->restoreEntry(subel); } } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/inject_sleigh.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/inject_sleigh.hh index ad6f44bff2..600215c658 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/inject_sleigh.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/inject_sleigh.hh @@ -91,6 +91,7 @@ class PcodeInjectLibrarySleigh : public PcodeInjectLibrary { vector inst; InjectContextSleigh contextCache; int4 registerDynamicInject(InjectPayload *payload); + InjectPayloadDynamic *forceDebugDynamic(int4 injectid); void parseInject(InjectPayload *payload); protected: virtual int4 allocateInject(const string &sourceName,const string &name,int4 type); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/userop.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/userop.cc index e08b43f037..0d13d3a7b6 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/userop.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/userop.cc @@ -216,10 +216,10 @@ void SegmentOp::restoreXml(const Element *el) throw LowlevelError("Bad segment pattern tag: "+subel->getName()); } if (injectId < 0) - throw LowlevelError("Missing child in tag"); + throw LowlevelError("Missing child in tag"); InjectPayload *payload = glb->pcodeinjectlib->getPayload(injectId); if (payload->sizeOutput() != 1) - throw LowlevelError(" child of tag must declare one "); + throw LowlevelError(" child of tag must declare one "); if (payload->sizeInput() == 1) { innerinsize = payload->getInput(0).getSize(); } @@ -228,7 +228,7 @@ void SegmentOp::restoreXml(const Element *el) innerinsize = payload->getInput(1).getSize(); } else - throw LowlevelError(" child of tag must declare one or two tags"); + throw LowlevelError(" child of tag must declare one or two tags"); } /// \param g is the Architecture owning this set of jump assist scripts diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/xml_arch.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/xml_arch.cc index 864a8888a7..e8a184ca2f 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/xml_arch.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/xml_arch.cc @@ -127,6 +127,12 @@ void XmlArchitecture::restoreXml(DocumentStorage &store) ++iter; } } + if (iter != list.end()) { + if ((*iter)->getName() == "specextensions") { + store.registerTag(*iter); + ++iter; + } + } if (iter!=list.end()) { if ((*iter)->getName() == "coretypes") { store.registerTag(*iter); diff --git a/Ghidra/Features/Decompiler/src/main/doc/commonprofile.xsl b/Ghidra/Features/Decompiler/src/main/doc/commonprofile.xsl new file mode 100644 index 0000000000..6e79c8417f --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/doc/commonprofile.xsl @@ -0,0 +1,6 @@ + + + + + diff --git a/Ghidra/Features/Decompiler/src/main/doc/cspec_html.xsl b/Ghidra/Features/Decompiler/src/main/doc/cspec_html.xsl index ad373c0aa1..978d2a6b08 100644 --- a/Ghidra/Features/Decompiler/src/main/doc/cspec_html.xsl +++ b/Ghidra/Features/Decompiler/src/main/doc/cspec_html.xsl @@ -2,7 +2,7 @@ - + article/appendix nop diff --git a/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml b/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml index 928b8a5e26..6c46f58a86 100644 --- a/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml +++ b/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml @@ -834,6 +834,33 @@ + + User-defined P-code Operations - CALLOTHER + + P-code allows for additional, processor specific, operations referred to + as user-defined or CALLOTHER operations. + These may be defined as part of a Ghidra's specification for the processor and + are typically used as placeholders for what is otherwise unmodeled processor behavior. + Each CALLOTHER must have a unique name, and as a p-code operation, it still takes + varnode inputs and may produce a varnode output. But the exact affect of the operation is + not specified. + + + The decompiler treats a CALLOTHER operation as a black box. It will keep track of data + flowing into and out of the operation but won't simplify or transform it. In decompiler + output, a CALLOTHER is usually displayed using its unique name, with functional syntax + showing its inputs and output. + + + Ghidra or a user can provide the behavior details for a named CALLOTHER operation. The + details are provided as a sequence of p-code operations, referred to as a + Callother-Fixup, which is substituted for the + CALLOTHER operation during decompilation, or by other Analyzers that use p-code. + Callother-Fixups are applied by Ghidra for specific processor or compiler variants, + and a user can choose to apply them to an individual Program. (See ) + + + Internal Decompiler Functions @@ -1162,10 +1189,13 @@ use of multiple models. Subsequently, each distinct model has a name like __stdcall or __thiscall. The decompiler makes use of the prototype model, as assigned to the function by the user or discovered in some other way, when performing its analysis of parameters. + It is possible for users to extend the set of prototype models available to a Program, + see . A prototype model is typically used as a whole and is assigned by name to individual functions. But some of - the sub-concepts of the model may be relevant to reverse engineers. + the sub-concepts of the model may be relevant to reverse engineers. Concepts that a prototype + model encapsulates include: Incoming and Outgoing Storage Locations @@ -1211,6 +1241,138 @@ +
+ SLEIGH Specification Files + + SLEIGH is Ghidra's specification language for describing processor instructions. + Specification files are read in for a Program, and once configured, Ghidra's SLEIGH engine can: + + + + Disassemble machine instructions from the underlying bytes and + + + Produce the raw p-code consumed by the decompiler and other analyzers. + + + + + + Specification files are selected based on the Language Id + assigned to the Program at the time it is imported into Ghidra. + (See Import Program) + + + x86:LE:32:default:windows + AARCH64:LE:64:default:v8A:default + MIPS:BE:32:micro:default + + + A Language Id is a label with these 5 formal fields, separated + by a ':' character: + + + Processor family + Endianess + Size of the address bus + Process variant + Compiler producing the Program + + + A field with the value 'default' indicates either the preferred processor variant or the preferred compiler. + + + Within the Ghidra installation, specification files are stored based on the overarching + processor family, such as 'MIPS' or 'x86'. For a specific family, files are located under + + <Root>/Ghidra/Processors/<Family>/data/languages + + where <Root> represents the root directory of the Ghidra installation and + <Family> is the processor family. + + + There are several types of specification files that are distinguishable by their suffix. + These include: + + + + + SLEIGH files - *.slaspec or *.sinc + + + These are the human readable SLEIGH language files. A single specification is + rooted in one of the *.slaspec files, which may recursively include + one or more *.sinc files. The format of these files is described + in the document "SLEIGH: A Language for Rapid Processor Specification". + + + + + Compiled SLEIGH files - *.sla + + + This is a compiled form of a single SLEIGH specification. It is produced + automatically by Ghidra from the corresponding *.slaspec. + + + + + Compiler specification files - *.cspec + + + These files contain configuration for a specific compiler. Analysis of Programs whose + executable content was produced using this compiler benefits from this information. + The file is an XML document with tags describing details of data organization and + other conventions used by the compiler. In particular, the compiler specification + contains tags: + + + + <prototype> - describing a specific calling convention + <callfixup> - describing a Call-fixup + <callotherfixup> - describing a Callother-fixup + + + + + + Processor specification files - *.pspec + + + These files contain configuration information that is specific to a particular + processor variant. + + + + + + + + Modifying Specification Files + + Changing any of the specification files described here is not recommended. + To make additions to either the compiler specification + or the processor specification files, see + , which describes a safe and portable way + to add specific elements. + + + Making modifications to specification files within a Ghidra installation is possible, + but any analysis results obtained will likely not be portable to other installations. + In particular, saving a Program from a modified Ghidra and then reopening it using + an unmodified installation may corrupt the Program database. + + + When Ghidra starts, it checks for changes to *.slaspec + and *.sinc files and will rebuild the corresponding + *.sla file automatically. Also, specification files are read again when + Ghidra restarts. So analysts can and do make changes to these files. + However they need to be prepared to view any results as temporary and + should backup their installation and specific Programs being analyzed. + + +
+ @@ -2085,7 +2247,8 @@ The calling convention used by the function can be specified as part of the function prototype. The convention is specified by name, referring to the formal that describes how storage locations are selected for individual parameters along with other information about how the compiler treats - the function. + the function. Available models are determined by the processor and compiler, but can be extended by the user. + See . In the absence of parameter and return value annotations, the decompiler will use the prototype model as @@ -2152,7 +2315,8 @@ Call-fixups are specified by name. The name and associated p-code chunk are typically defined in the - compiler specification for the Program. + compiler specification for the Program. Users can extend the available set + of call-fixups. See . @@ -2453,12 +2617,15 @@ - Another source of options can be accessed by selecting the Code Browser menu + Options that are specific to the particular Program being analyzed are accessed by + selecting the Code Browser menu Edit -> Options for <Program> - and the picking the Decompiler tab. These - are specific to the particular Program being analyzed. + Picking the Decompiler tab shows + that only affect the decompiler. Picking the tab + shows a table of the available prototype models, call-fixups, and callother-fixups. These + affect more than just the decompiler but are also documented here.
@@ -2961,7 +3128,8 @@
Program Options - Changes to these options affect only the current Program being analyzed. + Changes to these options affect only the decompiler and only for + the current Program being analyzed. @@ -2982,6 +3150,280 @@
+
+ Specification Extensions + + This tab displays elements from the Program's compiler specification and + processor specification and allows the user to add or remove + extensions, including prototype models, call-fixups, and + callother-fixups. + + + Every program has a core set of specification elements, + loaded from the , that cannot + be modified or removed. Extensions, however, can be added to this core specification. Any extension + imported from this dialog is directly associated with the active Program and is stored permanently + with it. + + + Users can change or reimport an extension, if new information points to a better definition. + Users have full control over an extension, and unlike a core element, can tailor it specifically + to the Program. + + + This options tab presents a table of all specification elements. + Each element, whether core or an extension, is displayed on a separate row with three columns: + + + Extension Type - indicating the type of element + Name - showing the formal name of the element + Status - indicating whether the element is core or an extension + + + The core elements of the specification have a blank Status column, and any extension + is labeled either as "extension" or "override". + + + Extension Types + + Each of the element types described here represents an XML tag of the same name, which, if + present in the table, must either be in the compiler specification file, + the processor specification file, or provided to Ghidra as an + import document. + + + + + prototype + + + This element is a that holds a specific named set + of parameter passing details. It + can be applied to individual functions by name, typically via the "Calling Convention" menu + in the Function Editor Dialog. + See the documentation on for how they affect decompilation. + + + The XML tag, <prototype> always has a name attribute + that defines the formal name of the prototype model, which must be unique across all models. + + + + + + + + + ...]]> + + + + + + callfixup + + + This element is a Call-fixup, which can be used to substitute a specific p-code + sequence for CALL instructions during decompilation, as described in + . + + + The <callfixup> tag has a name + attribute listing the formal name, which must be unique across all call-fixups. + + + + ]]> + +]]> + + + + + + callotherfixup + + + This element is a Callother-fixup, which can be used to substitute a specific p-code + sequence for CALLOTHER p-code operations. A CALLOTHER + is a black-box, or unspecified p-code operation, see . + + + The <callotherfixup> tag has a + targetop attribute which lists the + name of the particular CALLOTHER operation it substitutes for. + + + + + ]]> + +]]> + + + + + + + + + + Status + + The Status column labels an element as either a core specification + or an extension; it also gives an indication of whether the element + is about to be installed or removed. + + + With no changes pending, the column will show one of the three main values: + + + + + <blank> + + + A blank Status column indicates that the element is a core part of the + specification, originating from one of the specification files. + These elements cannot be changed or removed. + + + + + extension + + + Indicates that the element is a program specific extension that has been + added to the specification. + + + + + override + + + Indicates that the element, which must be a callotherfixup, + is an extension that overrides a core element with the same target. The extension + effectively replaces the p-code injection of the core element with a user supplied one. + If this type of extension is later removed, the core element becomes active again. + + + + + + + + If the user has either imported additional extensions or selected an extension for removal but + has not yet clicked the Apply button in the Options dialog, the Status column + may show one of the following values, indicating a pending change. + + + + + install + + + Indicates a new extension that will be installed. + + + + + remove + + + Indicates an extension that is about to be removed. + + + + + replace + + + Indicates a new extension that will replace a current + extension with the same name. + + + + + override pending + + + Indicates a new extension that will override a core element when + it is installed. + + + + + + + + + Importing a New Extension + + The Import button at the bottom of the + "Specification Extensions" pane allows the user to import one of the + three element types, prototype, + callfixup, or callotherfixup, + into the program as a new extension. + The user must supply a properly formed XML document, as a file, that fully describes the new + extension. Clicking the Import button brings up a File Chooser dialog, + from which the user must select their prepared XML file. Once Ok is + clicked, the file is read in and validated. If there are any problems with the validation, or if + the new extension's name collides with a core element, the import does not succeed and + an error message will be displayed. Otherwise, the import is accepted, and the table is updated + to indicate the pending change. + + + The final change to the program, installing the new extension, will not happen until the + Apply button, at the bottom of the Options dialog, is clicked. + + + The XML file describing the extension must have one of the tags, + <prototype>, <callfixup>, or <callotherfixup>, + as its single root element. Users can find numerous examples within the compiler + and processor specification files that come as part of Ghidra's installation. + See . + + + In the case of prototype and callfixup + elements, extensions cannot replace existing core elements, so the new extension must not + have a name that matches an existing core element. If a new callotherfixup + extension has a targetop that matches a core element, the extension is automatically treated as an override. + + + Existing extensions can be replaced simply by importing a new extension with the same name or targetop. + + + + Removing an Extension + + The Remove button at the bottom of the "Specification Extensions" pane allows + the user to remove a previously installed extension. A row from the table is selected first, which + must have a Status of extension or override. + Core elements of the specification cannot be removed. + Clicking the Remove button brings up a confirmation dialog, and if + Ok is clicked, the selected extension is marked for removal. The Status of the row + changes to remove, reflecting this. + + + The final change to the program, removing the extension, will not happen until the + Apply button, at the bottom of the Options dialog, is clicked. + + + If a prototype or callfixup is removed, + all functions are checked to see if they have the matching calling convention or call-fixup set. + A function with matching calling convention is changed to have the default convention, which is always a core element. + A function with matching call-fixup is changed to have no call-fixup. + + +
diff --git a/Ghidra/Features/Decompiler/src/main/doc/decompileplugin_html.xsl b/Ghidra/Features/Decompiler/src/main/doc/decompileplugin_html.xsl index b15f1d6dd0..f1744f6669 100644 --- a/Ghidra/Features/Decompiler/src/main/doc/decompileplugin_html.xsl +++ b/Ghidra/Features/Decompiler/src/main/doc/decompileplugin_html.xsl @@ -2,7 +2,7 @@ - + diff --git a/Ghidra/Features/Decompiler/src/main/doc/decompileplugin_pdf.xsl b/Ghidra/Features/Decompiler/src/main/doc/decompileplugin_pdf.xsl index 950cee1977..c9a5c088ac 100644 --- a/Ghidra/Features/Decompiler/src/main/doc/decompileplugin_pdf.xsl +++ b/Ghidra/Features/Decompiler/src/main/doc/decompileplugin_pdf.xsl @@ -2,7 +2,7 @@ - + @@ -16,6 +16,6 @@ - + diff --git a/Ghidra/Features/Decompiler/src/main/doc/main_html.xsl b/Ghidra/Features/Decompiler/src/main/doc/main_html.xsl index b38bdbd68a..802a9939a6 100644 --- a/Ghidra/Features/Decompiler/src/main/doc/main_html.xsl +++ b/Ghidra/Features/Decompiler/src/main/doc/main_html.xsl @@ -2,7 +2,7 @@ - + article/appendix nop diff --git a/Ghidra/Features/Decompiler/src/main/doc/pcoderef_html.xsl b/Ghidra/Features/Decompiler/src/main/doc/pcoderef_html.xsl index 1991be7a6b..931e36975e 100644 --- a/Ghidra/Features/Decompiler/src/main/doc/pcoderef_html.xsl +++ b/Ghidra/Features/Decompiler/src/main/doc/pcoderef_html.xsl @@ -2,7 +2,7 @@ - + diff --git a/Ghidra/Features/Decompiler/src/main/doc/pcoderef_pdf.xsl b/Ghidra/Features/Decompiler/src/main/doc/pcoderef_pdf.xsl index f741208e59..0879af176b 100644 --- a/Ghidra/Features/Decompiler/src/main/doc/pcoderef_pdf.xsl +++ b/Ghidra/Features/Decompiler/src/main/doc/pcoderef_pdf.xsl @@ -2,7 +2,7 @@ - + diff --git a/Ghidra/Features/Decompiler/src/main/doc/sleigh_html.xsl b/Ghidra/Features/Decompiler/src/main/doc/sleigh_html.xsl index b8decd3347..15f1f655cf 100644 --- a/Ghidra/Features/Decompiler/src/main/doc/sleigh_html.xsl +++ b/Ghidra/Features/Decompiler/src/main/doc/sleigh_html.xsl @@ -2,7 +2,7 @@ - + diff --git a/Ghidra/Features/Decompiler/src/main/doc/sleigh_pdf.xsl b/Ghidra/Features/Decompiler/src/main/doc/sleigh_pdf.xsl index 4fc1f0a187..ae015f403d 100644 --- a/Ghidra/Features/Decompiler/src/main/doc/sleigh_pdf.xsl +++ b/Ghidra/Features/Decompiler/src/main/doc/sleigh_pdf.xsl @@ -2,7 +2,7 @@ - + diff --git a/Ghidra/Features/Decompiler/src/main/help/help/TOC_Source.xml b/Ghidra/Features/Decompiler/src/main/help/help/TOC_Source.xml index 2317da834e..a20bcf8a07 100644 --- a/Ghidra/Features/Decompiler/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Features/Decompiler/src/main/help/help/TOC_Source.xml @@ -62,6 +62,7 @@ target="help/topics/DecompilePlugin/DecompilerConcepts.html"> + + - - - + + + + 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 dbf4bd5c7e..3e310b2ac6 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 @@ -4,7 +4,7 @@ Program Annotations Affecting the Decompiler - + @@ -983,7 +983,8 @@ The calling convention used by the function can be specified as part of the function prototype. The convention is specified by name, referring to the formal “Prototype Model” that describes how storage locations are selected for individual parameters along with other information about how the compiler treats - the function. + the function. Available models are determined by the processor and compiler, but can be extended by the user. + See “Specification Extensions”.

In the absence of parameter and return value annotations, the decompiler will use the prototype model as @@ -1042,7 +1043,8 @@

Call-fixups are specified by name. The name and associated p-code chunk are typically defined in the - compiler specification for the Program. + compiler specification for the Program. Users can extend the available set + of call-fixups. See “Specification Extensions”.

diff --git a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerConcepts.html b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerConcepts.html index e40eb14ec3..b7440a1df2 100644 --- a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerConcepts.html +++ b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerConcepts.html @@ -4,7 +4,7 @@ Decompiler Concepts - + @@ -183,7 +183,7 @@

Table . P-code Operations

-
+
@@ -283,7 +283,7 @@

-
+
@@ -725,6 +725,35 @@

+User-defined P-code Operations - CALLOTHER

+ +

+ P-code allows for additional, processor specific, operations referred to + as user-defined or CALLOTHER operations. + These may be defined as part of a Ghidra's specification for the processor and + are typically used as placeholders for what is otherwise unmodeled processor behavior. + Each CALLOTHER must have a unique name, and as a p-code operation, it still takes + varnode inputs and may produce a varnode output. But the exact affect of the operation is + not specified. +

+

+ The decompiler treats a CALLOTHER operation as a black box. It will keep track of data + flowing into and out of the operation but won't simplify or transform it. In decompiler + output, a CALLOTHER is usually displayed using its unique name, with functional syntax + showing its inputs and output. +

+

+ Ghidra or a user can provide the behavior details for a named CALLOTHER operation. The + details are provided as a sequence of p-code operations, referred to as a + Callother-Fixup, which is substituted for the + CALLOTHER operation during decompilation, or by other Analyzers that use p-code. + Callother-Fixups are applied by Ghidra for specific processor or compiler variants, + and a user can choose to apply them to an individual Program. (See “Specification Extensions”) +

+
+ +
+

Internal Decompiler Functions

@@ -1052,10 +1081,13 @@ use of multiple models. Subsequently, each distinct model has a name like __stdcall or __thiscall. The decompiler makes use of the prototype model, as assigned to the function by the user or discovered in some other way, when performing its analysis of parameters. + It is possible for users to extend the set of prototype models available to a Program, + see “Specification Extensions”.

A prototype model is typically used as a whole and is assigned by name to individual functions. But some of - the sub-concepts of the model may be relevant to reverse engineers. + the sub-concepts of the model may be relevant to reverse engineers. Concepts that a prototype + model encapsulates include:

@@ -1107,5 +1139,150 @@

+
+

+SLEIGH Specification Files

+ +

+ SLEIGH is Ghidra's specification language for describing processor instructions. + Specification files are read in for a Program, and once configured, Ghidra's SLEIGH engine can: +

+
+
    +
  • + Disassemble machine instructions from the underlying bytes and +
  • +
  • + Produce the raw p-code consumed by the decompiler and other analyzers. +
  • +
+
+

+

+

+ Specification files are selected based on the Language Id + assigned to the Program at the time it is imported into Ghidra. + (See Import Program) +

+
+
    +
  • x86:LE:32:default:windows
  • +
  • AARCH64:LE:64:default:v8A:default
  • +
  • MIPS:BE:32:micro:default
  • +
+
+

+ A Language Id is a label with these 5 formal fields, separated + by a ':' character: +

+
+
    +
  • Processor family
  • +
  • Endianess
  • +
  • Size of the address bus
  • +
  • Process variant
  • +
  • Compiler producing the Program
  • +
+
+

+ A field with the value 'default' indicates either the preferred processor variant or the preferred compiler. +

+

+ Within the Ghidra installation, specification files are stored based on the overarching + processor family, such as 'MIPS' or 'x86'. For a specific family, files are located under +

+
+ <Root>/Ghidra/Processors/<Family>/data/languages +
+

+ where <Root> represents the root directory of the Ghidra installation and + <Family> is the processor family. +

+

+ There are several types of specification files that are distinguishable by their suffix. + These include: +

+
+
+
SLEIGH files - *.slaspec or *.sinc
+
+

+ These are the human readable SLEIGH language files. A single specification is + rooted in one of the *.slaspec files, which may recursively include + one or more *.sinc files. The format of these files is described + in the document "SLEIGH: A Language for Rapid Processor Specification". +

+
+
Compiled SLEIGH files - *.sla
+
+

+ This is a compiled form of a single SLEIGH specification. It is produced + automatically by Ghidra from the corresponding *.slaspec. +

+
+
Compiler specification files - *.cspec
+
+

+ These files contain configuration for a specific compiler. Analysis of Programs whose + executable content was produced using this compiler benefits from this information. + The file is an XML document with tags describing details of data organization and + other conventions used by the compiler. In particular, the compiler specification + contains tags: +

+

+

+
    +
  • <prototype> - describing a specific calling convention
  • +
  • <callfixup> - describing a Call-fixup
  • +
  • <callotherfixup> - describing a Callother-fixup
  • +
+

+

+
+
Processor specification files - *.pspec
+
+

+ These files contain configuration information that is specific to a particular + processor variant. +

+
+
+
+

+

+
+

+Modifying Specification Files

+ +

+ Changing any of the specification files described here is not recommended. + To make additions to either the compiler specification + or the processor specification files, see + “Specification Extensions”, which describes a safe and portable way + to add specific elements. +

+
+ + + + + +
[Warning]
+ Making modifications to specification files within a Ghidra installation is possible, + but any analysis results obtained will likely not be portable to other installations. + In particular, saving a Program from a modified Ghidra and then reopening it using + an unmodified installation may corrupt the Program database. +
+

+ When Ghidra starts, it checks for changes to *.slaspec + and *.sinc files and will rebuild the corresponding + *.sla file automatically. Also, specification files are read again when + Ghidra restarts. So analysts can and do make changes to these files. + However they need to be prepared to view any results as temporary and + should backup their installation and specific Programs being analyzed. +

+
+
+ diff --git a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerIntro.html b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerIntro.html index da3e82bc06..d7d9961f39 100644 --- a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerIntro.html +++ b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerIntro.html @@ -4,7 +4,7 @@ Decompiler - + diff --git a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerOptions.html b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerOptions.html index 357af8cdcc..9c952516ec 100644 --- a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerOptions.html +++ b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerOptions.html @@ -4,7 +4,7 @@ Decompiler Options - + @@ -42,14 +42,17 @@

- Another source of options can be accessed by selecting the Code Browser menu + Options that are specific to the particular Program being analyzed are accessed by + selecting the Code Browser menu

Edit -> Options for <Program>

- and the picking the Decompiler tab. These “Program Options” - are specific to the particular Program being analyzed. + Picking the Decompiler tab shows “Program Options” + that only affect the decompiler. Picking the “Specification Extensions” tab + shows a table of the available prototype models, call-fixups, and callother-fixups. These + affect more than just the decompiler but are also documented here.

@@ -573,7 +576,8 @@ Program Options

- Changes to these options affect only the current Program being analyzed. + Changes to these options affect only the decompiler and only for + the current Program being analyzed.

@@ -595,6 +599,284 @@

+
+

+Specification Extensions

+ +

+ This tab displays elements from the Program's compiler specification and + processor specification and allows the user to add or remove + extensions, including prototype models, call-fixups, and + callother-fixups. +

+

+ Every program has a core set of specification elements, + loaded from the “SLEIGH Specification Files”, that cannot + be modified or removed. Extensions, however, can be added to this core specification. Any extension + imported from this dialog is directly associated with the active Program and is stored permanently + with it. +

+

+ Users can change or reimport an extension, if new information points to a better definition. + Users have full control over an extension, and unlike a core element, can tailor it specifically + to the Program. +

+

+ This options tab presents a table of all specification elements. + Each element, whether core or an extension, is displayed on a separate row with three columns: +

+
+
    +
  • +Extension Type - indicating the type of element
  • +
  • +Name - showing the formal name of the element
  • +
  • +Status - indicating whether the element is core or an extension
  • +
+
+

+ The core elements of the specification have a blank Status column, and any extension + is labeled either as "extension" or "override". +

+
+

+Extension Types

+ +

+ Each of the element types described here represents an XML tag of the same name, which, if + present in the table, must either be in the compiler specification file, + the processor specification file, or provided to Ghidra as an + import document. +

+
+
+
prototype
+
+

+ This element is a “Prototype Model” that holds a specific named set + of parameter passing details. It + can be applied to individual functions by name, typically via the "Calling Convention" menu + in the Function Editor Dialog. + See the documentation on “Function Prototypes” for how they affect decompilation. +

+

+ The XML tag, <prototype> always has a name attribute + that defines the formal name of the prototype model, which must be unique across all models. +

+
+<prototype name="__stdcall" extrapop="unknown" stackshift="4">
+  <input>
+    <pentry minsize="1" maxsize="500" align="4">
+      <addr offset="4" space="stack"/>
+    </pentry>
+  </input>
+  <output>
+  ...
+	      
+

+

+
+
callfixup
+
+

+ This element is a Call-fixup, which can be used to substitute a specific p-code + sequence for CALL instructions during decompilation, as described in + “Function Prototypes”. +

+

+ The <callfixup> tag has a name + attribute listing the formal name, which must be unique across all call-fixups. +

+
+<callfixup name="EH_prolog3">
+  <pcode>
+    <body><![CDATA<
+      EBP = ESP + 4;
+      tmp = * EBP;
+      ESP = ESP - tmp;
+      ESP = ESP - 24;
+    ]]></body>
+  </pcode>
+</callfixup>
+	      
+

+

+
+
callotherfixup
+
+

+ This element is a Callother-fixup, which can be used to substitute a specific p-code + sequence for CALLOTHER p-code operations. A CALLOTHER + is a black-box, or unspecified p-code operation, see “User-defined P-code Operations - CALLOTHER”. +

+

+ The <callotherfixup> tag has a + targetop attribute which lists the + name of the particular CALLOTHER operation it substitutes for. +

+
+<callotherfixup targetop="dynamicPush">
+  <pcode>
+    <input name="amount"/>
+    <body><![CDATA[
+      RSP = RSP + amount;
+    ]]></body>
+  </pcode>
+</callotherfixup>
+	      
+

+

+
+
+
+

+

+
+
+

+Status

+ +

+ The Status column labels an element as either a core specification + or an extension; it also gives an indication of whether the element + is about to be installed or removed. +

+

+ With no changes pending, the column will show one of the three main values: +

+
+
+
<blank>
+
+

+ A blank Status column indicates that the element is a core part of the + specification, originating from one of the specification files. + These elements cannot be changed or removed. +

+
+
extension
+
+

+ Indicates that the element is a program specific extension that has been + added to the specification. +

+
+
override
+
+

+ Indicates that the element, which must be a callotherfixup, + is an extension that overrides a core element with the same target. The extension + effectively replaces the p-code injection of the core element with a user supplied one. + If this type of extension is later removed, the core element becomes active again. +

+
+
+
+

+

+

+ If the user has either imported additional extensions or selected an extensions for removal but + has not yet clicked the Apply button in the Options dialog, the Status column + may show one of the following values, indicating a pending change. +

+
+
+
install
+
+

+ Indicates a new extension that will be installed. +

+
+
remove
+
+

+ Indicates an extension that is about to be removed. +

+
+
replace
+
+

+ Indicates a new extension that will replace a current + extension with the same name. +

+
+
override pending
+
+

+ Indicates a new extension that will override a core element when + it is installed. +

+
+
+
+

+

+
+
+

+Importing a New Extension

+ +

+ The Import button at the bottom of the + "Specification Extensions" pane allows the user to import one of the + three element types, prototype, + callfixup, or callotherfixup, + into the program as a new extension. + The user must supply a properly formed XML document, as a file, that fully describes the new + extension. Clicking the Import button brings up a File Chooser dialog, + from which the user must select their prepared XML file. Once Ok is + clicked, the file is read in and validated. If there are any problems with the validation, or if + the new extension's name collides with a core element, the import does not succeed and + an error message will be displayed. Otherwise, the import is accepted, and the table is updated + to indicate the pending change. +

+

+ The final change to the program, installing the new extension, will not happen until the + Apply button, at the bottom of the Options dialog, is clicked. +

+

+ The XML file describing the extension must have one of the tags, + <prototype>, <callfixup>, or <callotherfixup>, + as its single root element. Users can find numerous examples within the compiler + and processor specification files that come as part of Ghidra's installation. + See “SLEIGH Specification Files”. +

+

+ In the case of prototype and callfixup + elements, extensions cannot replace existing core elements, so the new extension must not + have a name that matches an existing core element. If a new callotherfixup + extension has a targetop that matches a core element, the extension is automatically treated as an override. +

+

+ Existing extensions can be replaced simply by importing a new extension with the same name or targetop. +

+
+
+

+Removing an Extension

+ +

+ The Remove button at the bottom of the "Specification Extensions" pane allows + the user to remove a previously installed extension. A row from the table is selected first, which + must have a Status of extension or override. + Core elements of the specification cannot be removed. + Clicking the Remove button brings up a confirmation dialog, and if + Ok is clicked, the selected extension is marked for removal. The Status of the row + changes to remove, reflecting this. +

+

+ The final change to the program, removing the extension, will not happen until the + Apply button, at the bottom of the Options dialog, is clicked. +

+

+ If a prototype or callfixup is removed, + all functions are checked to see if they have the matching calling convention or call-fixup set. + A function with matching calling convention is changed to have the default convention, which is always a core element. + A function with matching call-fixup is changed to have no call-fixup. +

+
+
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 06989b7a94..e11f54d743 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 @@ -4,7 +4,7 @@ Decompiler Window - + diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompInterface.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompInterface.java index a4d12379c4..21babd7495 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompInterface.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompInterface.java @@ -222,7 +222,7 @@ public class DecompInterface { (SleighLanguageDescription) pcodelanguage.getLanguageDescription(); ResourceFile pspecfile = sleighdescription.getSpecFile(); String pspecxml = fileToString(pspecfile); - String cspecxml = compilerSpec.getCompilerSpecString(); + String cspecxml = compilerSpec.getXMLString(); decompCallback.setNativeMessage(null); decompProcess.registerProgram(decompCallback, pspecxml, cspecxml, tspec, coretypes); @@ -232,8 +232,9 @@ public class DecompInterface { } if (xmlOptions != null) { decompProcess.setMaxResultSize(xmlOptions.getMaxPayloadMBytes()); - if (!decompProcess.sendCommand1Param("setOptions", - xmlOptions.getXML(this)).toString().equals("t")) { + if (!decompProcess.sendCommand1Param("setOptions", xmlOptions.getXML(this)) + .toString() + .equals("t")) { throw new IOException("Did not accept decompiler options"); } } @@ -241,14 +242,16 @@ public class DecompInterface { throw new IOException("Decompile action not specified"); } if (!actionname.equals("decompile")) { - if (!decompProcess.sendCommand2Params("setAction", actionname, "").toString().equals( - "t")) { + if (!decompProcess.sendCommand2Params("setAction", actionname, "") + .toString() + .equals("t")) { throw new IOException("Could not set decompile action"); } } if (!printSyntaxTree) { - if (!decompProcess.sendCommand2Params("setAction", "", "notree").toString().equals( - "t")) { + if (!decompProcess.sendCommand2Params("setAction", "", "notree") + .toString() + .equals("t")) { throw new IOException("Could not turn off syntax tree"); } } @@ -258,14 +261,16 @@ public class DecompInterface { } } if (sendParamMeasures) { - if (!decompProcess.sendCommand2Params("setAction", "", - "parammeasures").toString().equals("t")) { + if (!decompProcess.sendCommand2Params("setAction", "", "parammeasures") + .toString() + .equals("t")) { throw new IOException("Could not turn on sending of parameter measures"); } } if (jumpLoad) { - if (!decompProcess.sendCommand2Params("setAction", "", "jumpload").toString().equals( - "t")) { + if (!decompProcess.sendCommand2Params("setAction", "", "jumpload") + .toString() + .equals("t")) { throw new IOException("Could not turn on jumptable loads"); } } @@ -409,8 +414,9 @@ public class DecompInterface { } try { verifyProcess(); - return decompProcess.sendCommand2Params("setAction", actionstring, - "").toString().equals("t"); + return decompProcess.sendCommand2Params("setAction", actionstring, "") + .toString() + .equals("t"); } catch (IOException e) { // don't care @@ -446,8 +452,9 @@ public class DecompInterface { String printstring = val ? "tree" : "notree"; try { verifyProcess(); - return decompProcess.sendCommand2Params("setAction", "", printstring).toString().equals( - "t"); + return decompProcess.sendCommand2Params("setAction", "", printstring) + .toString() + .equals("t"); } catch (IOException e) { // don't care @@ -484,8 +491,9 @@ public class DecompInterface { String printstring = val ? "c" : "noc"; try { verifyProcess(); - return decompProcess.sendCommand2Params("setAction", "", printstring).toString().equals( - "t"); + return decompProcess.sendCommand2Params("setAction", "", printstring) + .toString() + .equals("t"); } catch (IOException e) { // don't care @@ -521,8 +529,9 @@ public class DecompInterface { String printstring = val ? "parammeasures" : "noparammeasures"; try { verifyProcess(); - return decompProcess.sendCommand2Params("setAction", "", printstring).toString().equals( - "t"); + return decompProcess.sendCommand2Params("setAction", "", printstring) + .toString() + .equals("t"); } catch (IOException e) { // don't care @@ -551,8 +560,9 @@ public class DecompInterface { String jumpstring = val ? "jumpload" : "nojumpload"; try { verifyProcess(); - return decompProcess.sendCommand2Params("setAction", "", jumpstring).toString().equals( - "t"); + return decompProcess.sendCommand2Params("setAction", "", jumpstring) + .toString() + .equals("t"); } catch (IOException e) { // don't care @@ -588,8 +598,9 @@ public class DecompInterface { try { verifyProcess(); decompProcess.setMaxResultSize(xmlOptions.getMaxPayloadMBytes()); - return decompProcess.sendCommand1Param("setOptions", - xmloptions.getXML(this)).toString().equals("t"); + return decompProcess.sendCommand1Param("setOptions", xmloptions.getXML(this)) + .toString() + .equals("t"); } catch (IOException e) { // don't care @@ -707,9 +718,10 @@ public class DecompInterface { debug.setFunction(func); } decompCallback.setFunction(func, funcEntry, debug); - String addrstring = Varnode.buildXMLAddress(funcEntry); + StringBuilder addrBuf = new StringBuilder(); + AddressXML.buildXML(addrBuf, funcEntry); verifyProcess(); - res = decompProcess.sendCommand1ParamTimeout("decompileAt", addrstring.toString(), + res = decompProcess.sendCommand1ParamTimeout("decompileAt", addrBuf.toString(), timeoutSecs); decompileMessage = decompCallback.getNativeMessage(); } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileCallback.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileCallback.java index f044d942af..1779fd65af 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileCallback.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileCallback.java @@ -55,6 +55,7 @@ import ghidra.util.xml.XmlUtilities; public class DecompileCallback { public final static int MAX_SYMBOL_COUNT = 16; + /** * Data returned for a query about strings */ @@ -70,6 +71,7 @@ public class DecompileCallback { private Function cachedFunction; private AddressSet undefinedBody; private Address funcEntry; + private AddressSpace overlaySpace; // non-null if function being decompiled is in an overlay private int default_extrapop; private Language pcodelanguage; private CompilerSpec pcodecompilerspec; @@ -125,6 +127,8 @@ public class DecompileCallback { undefinedBody = new AddressSet(func.getBody()); } funcEntry = entry; + AddressSpace spc = funcEntry.getAddressSpace(); + overlaySpace = spc.isOverlaySpace() ? spc : null; debug = dbg; if (debug != null) { debug.setPcodeDataTypeManager(dtmanage); @@ -153,19 +157,6 @@ public class DecompileCallback { nativeMessage = msg; } - public synchronized int readXMLSize(String addrxml) { - int attrstart = addrxml.indexOf("size=\""); - if (attrstart >= 4) { - attrstart += 6; - int attrend = addrxml.indexOf('\"', attrstart); - if (attrend > attrstart) { - int size = SpecXmlUtils.decodeInt(addrxml.substring(attrstart, attrend)); - return size; - } - } - return 0; - } - public synchronized ArrayList readXMLNameList(String xml) throws PcodeXMLException { try { NameListHandler nmHandler = new NameListHandler(); @@ -182,9 +173,11 @@ public class DecompileCallback { public byte[] getBytes(String addrxml) { try { - int size = readXMLSize(addrxml); - Address addr; - addr = Varnode.readXMLAddress(addrxml, addrfactory, funcEntry.getAddressSpace()); + Address addr = AddressXML.readXML(addrxml, addrfactory); + int size = AddressXML.readXMLSize(addrxml); + if (overlaySpace != null) { + addr = overlaySpace.getOverlayAddress(addr); + } if (addr == Address.NO_ADDRESS) { throw new PcodeXMLException("Address does not physically map"); } @@ -230,7 +223,10 @@ public class DecompileCallback { Address addr; int flags; try { - addr = Varnode.readXMLAddress(addrstring, addrfactory, funcEntry.getAddressSpace()); + addr = AddressXML.readXML(addrstring, addrfactory); + if (overlaySpace != null) { + addr = overlaySpace.getOverlayAddress(addr); + } } catch (PcodeXMLException e) { Msg.error(this, "Decompiling " + funcEntry + ": " + e.getMessage()); @@ -270,7 +266,10 @@ public class DecompileCallback { public PackedBytes getPcodePacked(String addrstring) { Address addr = null; try { - addr = Varnode.readXMLAddress(addrstring, addrfactory, funcEntry.getAddressSpace()); + addr = AddressXML.readXML(addrstring, addrfactory); + if (overlaySpace != null) { + addr = overlaySpace.getOverlayAddress(addr); + } } catch (PcodeXMLException e) { Msg.error(this, "Decompiling " + funcEntry + ": " + e.getMessage()); @@ -293,8 +292,9 @@ public class DecompileCallback { } } - PackedBytes pcode = instr.getPrototype().getPcodePacked(instr.getInstructionContext(), - new InstructionPcodeOverride(instr), uniqueFactory); + PackedBytes pcode = instr.getPrototype() + .getPcodePacked(instr.getInstructionContext(), + new InstructionPcodeOverride(instr), uniqueFactory); return pcode; } @@ -346,7 +346,7 @@ public class DecompileCallback { public String getPcodeInject(String nm, String context, int type) { PcodeInjectLibrary snippetLibrary = pcodecompilerspec.getPcodeInjectLibrary(); - InjectPayload payload = snippetLibrary.getPayload(type, nm, program, context); + InjectPayload payload = snippetLibrary.getPayload(type, nm); if (payload == null) { Msg.warn(this, "Decompiling " + funcEntry + ", no pcode inject with name: " + nm); return null; // No fixup associated with this name @@ -381,8 +381,8 @@ public class DecompileCallback { con.nextAddr = con.baseAddr.add(fallThruOffset); con.refAddr = null; - for (Reference ref : program.getReferenceManager().getReferencesFrom( - con.baseAddr)) { + for (Reference ref : program.getReferenceManager() + .getReferencesFrom(con.baseAddr)) { if (ref.isPrimary() && ref.getReferenceType().isCall()) { con.refAddr = ref.getToAddress(); break; @@ -487,7 +487,10 @@ public class DecompileCallback { public String getSymbol(String addrstring) { // Return first symbol name at this address Address addr; try { - addr = Varnode.readXMLAddress(addrstring, addrfactory, funcEntry.getAddressSpace()); + addr = AddressXML.readXML(addrstring, addrfactory); + if (overlaySpace != null) { + addr = overlaySpace.getOverlayAddress(addr); + } } catch (PcodeXMLException e) { Msg.error(this, "Decompiling " + funcEntry + ": " + e.getMessage()); @@ -627,8 +630,8 @@ public class DecompileCallback { buf.append("\n"); - buf.append(Varnode.buildXMLAddress(addr)); - buf.append(Varnode.buildXMLAddress(addr)); + AddressXML.buildXML(buf, addr); + AddressXML.buildXML(buf, addr); buf.append("\n"); SpecXmlUtils.xmlEscape(buf, text); buf.append("\n"); @@ -680,8 +683,8 @@ public class DecompileCallback { buf.append("\n"); - buf.append(Varnode.buildXMLAddress(addr)); - buf.append(Varnode.buildXMLAddress(commaddr)); + AddressXML.buildXML(buf, addr); + AddressXML.buildXML(buf, commaddr); buf.append("\n"); SpecXmlUtils.xmlEscape(buf, text); buf.append("\n"); @@ -701,7 +704,10 @@ public class DecompileCallback { public String getMappedSymbolsXML(String addrstring) { // Return XML describing data or functions at addr Address addr; try { - addr = Varnode.readXMLAddress(addrstring, addrfactory, funcEntry.getAddressSpace()); + addr = AddressXML.readXML(addrstring, addrfactory); + if (overlaySpace != null) { + addr = overlaySpace.getOverlayAddress(addr); + } if (addr == Address.NO_ADDRESS) { // Unknown spaces may result from "spacebase" registers defined in cspec return null; @@ -743,7 +749,10 @@ public class DecompileCallback { public String getExternalRefXML(String addrstring) { // Return any external reference at addr Address addr; try { - addr = Varnode.readXMLAddress(addrstring, addrfactory, funcEntry.getAddressSpace()); + addr = AddressXML.readXML(addrstring, addrfactory); + if (overlaySpace != null) { + addr = overlaySpace.getOverlayAddress(addr); + } } catch (PcodeXMLException e) { Msg.error(this, "Decompiling " + funcEntry + ": " + e.getMessage()); @@ -792,6 +801,7 @@ public class DecompileCallback { Namespace namespc = funcSymbol.getNamespace(); if (debug != null) { debug.getFNTypes(hfunc); + debug.addPossiblePrototypeExtension(func); } return buildResult(funcSymbol, namespc); } @@ -829,8 +839,8 @@ public class DecompileCallback { public String getRegisterName(String addrstring) { try { - Address addr = Varnode.readXMLAddress(addrstring, addrfactory, null); - int size = readXMLSize(addrstring); + Address addr = AddressXML.readXML(addrstring, addrfactory); + int size = AddressXML.readXMLSize(addrstring); Register reg = pcodelanguage.getRegister(addr, size); if (reg == null) { return null; @@ -847,7 +857,10 @@ public class DecompileCallback { public String getTrackedRegisters(String addrstring) { Address addr; try { - addr = Varnode.readXMLAddress(addrstring, addrfactory, funcEntry.getAddressSpace()); + addr = AddressXML.readXML(addrstring, addrfactory); + if (overlaySpace != null) { + addr = overlaySpace.getOverlayAddress(addr); + } } catch (PcodeXMLException e) { Msg.error(this, "Decompiling " + funcEntry + ": " + e.getMessage()); @@ -858,7 +871,7 @@ public class DecompileCallback { StringBuilder stringBuf = new StringBuilder(); stringBuf.append("\n"); for (Register reg : context.getRegisters()) { if (reg.isProcessorContext()) { @@ -1020,8 +1033,8 @@ public class DecompileCallback { if (entry.getAddressSpace().equals(addr.getAddressSpace())) { long diff = addr.getOffset() - entry.getOffset(); if ((diff >= 0) && (diff < 8)) { - HighFunction hfunc = new HighFunction(func, pcodelanguage, pcodecompilerspec, - dtmanage); + HighFunction hfunc = + new HighFunction(func, pcodelanguage, pcodecompilerspec, dtmanage); int extrapop = getExtraPopOverride(func, addr); hfunc.grabFromFunction(extrapop, includeDefaultNames, @@ -1031,6 +1044,7 @@ public class DecompileCallback { Namespace namespc = functionSymbol.getNamespace(); if (debug != null) { debug.getFNTypes(hfunc); + debug.addPossiblePrototypeExtension(func); } return buildResult(functionSymbol, namespc); } @@ -1304,8 +1318,11 @@ public class DecompileCallback { Address addr; int maxChars; try { - maxChars = readXMLSize(addrString); - addr = Varnode.readXMLAddress(addrString, addrfactory, funcEntry.getAddressSpace()); + addr = AddressXML.readXML(addrString, addrfactory); + maxChars = AddressXML.readXMLSize(addrString); + if (overlaySpace != null) { + addr = overlaySpace.getOverlayAddress(addr); + } if (addr == Address.NO_ADDRESS) { throw new PcodeXMLException("Address does not physically map"); } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileDebug.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileDebug.java index ae5006a547..e7f1e2ca5c 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileDebug.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileDebug.java @@ -47,6 +47,7 @@ public class DecompileDebug { private Function func; // The function being decompiled private Program program; // The program private File debugFile; // The file to dump the XML document to + private Map specExtensions; // Local extensions to the compiler spec private ArrayList dbscope; // Symbol query: scope private ArrayList database; // description of the symbol private ArrayList dtypes; // Data-types queried @@ -108,16 +109,17 @@ public class DecompileDebug { public DecompileDebug(File debugf) { func = null; debugFile = debugf; - dbscope = new ArrayList(); - database = new ArrayList(); - dtypes = new ArrayList(); - context = new ArrayList(); - cpool = new ArrayList(); - byteset = new TreeSet(); - contextchange = new TreeSet
(); - stringmap = new TreeMap(); - flowoverride = new ArrayList(); - inject = new ArrayList(); + specExtensions = new TreeMap<>(); + dbscope = new ArrayList<>(); + database = new ArrayList<>(); + dtypes = new ArrayList<>(); + context = new ArrayList<>(); + cpool = new ArrayList<>(); + byteset = new TreeSet<>(); + contextchange = new TreeSet<>(); + stringmap = new TreeMap<>(); + flowoverride = new ArrayList<>(); + inject = new ArrayList<>(); contextRegister = null; comments = null; globalnamespace = null; @@ -158,6 +160,7 @@ public class DecompileDebug { buf.append(">\n"); debugStream.write(buf.toString().getBytes()); dumpImage(debugStream, pcodelanguage); + dumpExtensions(debugStream); dumpCoretypes(debugStream); debugStream.write("\n".getBytes()); // dumpTypes(debugStream); @@ -178,7 +181,6 @@ public class DecompileDebug { debugStream.close(); } catch (Exception e) { - // TODO Auto-generated catch block Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); } } @@ -224,24 +226,24 @@ public class DecompileDebug { lastreadonly = readval; } - if (tagstarted && - ((chunk.min != 0) || (lastspace != space) || (lastoffset != chunk.addr.getOffset()))) { + if (tagstarted && ((chunk.min != 0) || (lastspace != space) || + (lastoffset != chunk.addr.getOffset()))) { buf.append("\n\n"); tagstarted = false; } if (!tagstarted) { buf.append("\n"); tagstarted = true; } - for (int i = 0; i < chunk.min; ++i) - { + for (int i = 0; i < chunk.min; ++i) { buf.append(" "); // pad the hex display to 16 bytes } for (int i = chunk.min; i < chunk.max; ++i) { @@ -290,9 +292,9 @@ public class DecompileDebug { StringBuilder buf = new StringBuilder(); buf.append("\n"); for (Map.Entry entry : stringmap.entrySet()) { - buf.append("\n\n\n"); + AddressXML.buildXML(buf, entry.getKey()); + buf.append("\n\n "); int count = 0; @@ -339,12 +341,15 @@ public class DecompileDebug { new DataTypeDependencyOrderer(program.getDataTypeManager(), dtypes); //First output all structures as zero size so to avoid any cyclic dependencies. for (DataType dataType : TypeOrderer.getStructList()) { - debugStream.write((dtmanage.buildStructTypeZeroSizeOveride(dataType) + "\n").toString().getBytes()); + debugStream.write( + (dtmanage.buildStructTypeZeroSizeOveride(dataType) + "\n").toString().getBytes()); } //Next, use the dependency stack to output types. for (DataType dataType : TypeOrderer.getDependencyList()) { if (!(dataType instanceof BuiltIn)) { - debugStream.write((dtmanage.buildType(dataType, dataType.getLength()) + "\n").toString().getBytes()); + debugStream.write( + (dtmanage.buildType(dataType, dataType.getLength()) + "\n").toString() + .getBytes()); } } debugStream.write("\n".getBytes()); @@ -365,7 +370,7 @@ public class DecompileDebug { if (!(lang instanceof SleighLanguage)) { return null; } - ArrayList res = new ArrayList(); + ArrayList res = new ArrayList<>(); ghidra.app.plugin.processors.sleigh.symbol.Symbol[] list = ((SleighLanguage) lang).getSymbolTable().getSymbolList(); for (Symbol element : list) { @@ -426,8 +431,7 @@ public class DecompileDebug { break; } } - if (i == buf.length) - { + if (i == buf.length) { continue; // If all data is identical, then changepoint is not necessary } } @@ -439,7 +443,7 @@ public class DecompileDebug { } stringBuf.append("\n"); for (ContextSymbol sym : ctxsymbols) { int sbit = sym.getInternalLow(); @@ -466,7 +470,7 @@ public class DecompileDebug { return; } debugStream.write("\n".getBytes()); - for(String rec : cpool) { + for (String rec : cpool) { debugStream.write(rec.getBytes()); } debugStream.write("\n".getBytes()); @@ -492,8 +496,8 @@ public class DecompileDebug { for (String element : flowoverride) { debugStream.write(element.getBytes()); } - - debugStream.write("\n".getBytes()); + + debugStream.write("\n".getBytes()); } private void dumpInject(OutputStream debugStream) throws IOException { @@ -508,11 +512,11 @@ public class DecompileDebug { } private ArrayList orderNamespaces() { - TreeMap namespaceMap = new TreeMap(); + TreeMap namespaceMap = new TreeMap<>(); for (Namespace namespace : dbscope) { namespaceMap.put(namespace.getID(), namespace); } - ArrayList res = new ArrayList(); + ArrayList res = new ArrayList<>(); while (!namespaceMap.isEmpty()) { Entry entry = namespaceMap.firstEntry(); Long curKey = entry.getKey(); @@ -592,6 +596,31 @@ public class DecompileDebug { debugStream.write("\n".getBytes()); } + private void dumpExtensions(OutputStream debugStream) throws IOException { + if (specExtensions.isEmpty()) { + return; + } + PcodeInjectLibrary library = program.getCompilerSpec().getPcodeInjectLibrary(); + debugStream.write("\n".getBytes()); + for (Object obj : specExtensions.values()) { + if (obj instanceof PrototypeModel) { + PrototypeModel model = (PrototypeModel) obj; + StringBuilder buffer = new StringBuilder(); + model.saveXml(buffer, library); + String modelString = buffer.toString(); + debugStream.write(modelString.getBytes()); + } + else if (obj instanceof InjectPayload) { + InjectPayload payload = (InjectPayload) obj; + StringBuilder buffer = new StringBuilder(); + payload.saveXml(buffer); + String payloadString = buffer.toString(); + debugStream.write(payloadString.getBytes()); + } + } + debugStream.write("\n".getBytes()); + } + private void dumpCoretypes(OutputStream debugStream) throws IOException { debugStream.write(dtmanage.buildCoreTypes().getBytes()); } @@ -666,11 +695,9 @@ public class DecompileDebug { buf.append(" \n"); - buf.append(" \n"); - buf.append(" \n"); + buf.append("/>\n "); + AddressXML.buildXML(buf, addr); + buf.append("\n \n"); buf.append("\n"); getMapped(namespace, buf.toString()); } @@ -711,7 +738,7 @@ public class DecompileDebug { context.add(doc); } - public void getCPoolRef(String rec,long[] refs) { + public void getCPoolRef(String rec, long[] refs) { StringBuilder buf = new StringBuilder(); buf.append("\n"); + buf.append("\">"); + AddressXML.buildXML(buf, func.getEntryPoint()); + AddressXML.buildXML(buf, addr); + buf.append("\n"); flowoverride.add(buf.toString()); } - public void addInject(Address addr,String name,int injectType,String payload) { + public void addInject(Address addr, String name, int injectType, String payload) { StringBuilder buf = new StringBuilder(); buf.append("\n \n \n "); + AddressXML.buildXML(buf, addr); + buf.append("\n \n\n"); inject.add(buf.toString()); + + PcodeInjectLibrary library = program.getCompilerSpec().getPcodeInjectLibrary(); + if (library.hasProgramPayload(name, injectType)) { + InjectPayload programPayload = library.getPayload(injectType, name); + String title = + (injectType == InjectPayload.CALLFIXUP_TYPE) ? "callfixup_" : "callotherfixup_"; + title = title + name; + specExtensions.put(title, programPayload); + } + } + + public void addPossiblePrototypeExtension(Function testFunc) { + PrototypeModel model = testFunc.getCallingConvention(); + if (model == null) { + return; + } + if (model.isProgramExtension()) { + String title = "prototype_" + model.getName(); + specExtensions.put(title, model); + } } } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java index c71ef52159..4ffff75e39 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java @@ -27,7 +27,9 @@ import ghidra.framework.options.Options; import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.PluginTool; +import ghidra.program.database.ProgramCompilerSpec; import ghidra.program.model.lang.*; +import ghidra.program.model.lang.CompilerSpec.EvaluationModelType; import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; import ghidra.util.SystemUtilities; @@ -404,7 +406,7 @@ public class DecompileOptions { codeViewerBackgroundColor = CODE_VIEWER_BACKGROUND_COLOR; defaultFont = DEFAULT_FONT; displayLineNumbers = LINE_NUMBER_DEF; - displayLanguage = BasicCompilerSpec.DECOMPILER_OUTPUT_DEF; + displayLanguage = ProgramCompilerSpec.DECOMPILER_OUTPUT_DEF; protoEvalModel = "default"; decompileTimeoutSeconds = SUGGESTED_DECOMPILE_TIMEOUT_SECS; payloadLimitMBytes = SUGGESTED_MAX_PAYLOAD_BYTES; @@ -503,21 +505,21 @@ public class DecompileOptions { */ public void grabFromProgram(Program program) { // Default values, even if there is no program - displayLanguage = BasicCompilerSpec.DECOMPILER_OUTPUT_DEF; + displayLanguage = ProgramCompilerSpec.DECOMPILER_OUTPUT_DEF; protoEvalModel = "default"; if (program == null) { return; } CompilerSpec cspec = program.getCompilerSpec(); - PrototypeModel model = (PrototypeModel) cspec.getPrototypeEvaluationModel(program); + PrototypeModel model = cspec.getPrototypeEvaluationModel(EvaluationModelType.EVAL_CURRENT); if (model != null) { String modelname = model.getName(); if (modelname != null) { protoEvalModel = modelname; } } - displayLanguage = cspec.getDecompilerOutputLanguage(program); + displayLanguage = cspec.getDecompilerOutputLanguage(); } public String getProtoEvalModel() { diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileProcess.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileProcess.java index b8f48c0732..1461e77f4e 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileProcess.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileProcess.java @@ -4,19 +4,15 @@ * 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. */ -/* - * Created on Jun 3, 2005 - * - */ package ghidra.app.decompiler; import java.io.*; diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java index 71add0c6f7..6fd33a3913 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java @@ -29,6 +29,7 @@ import ghidra.framework.model.DomainFile; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.program.database.SpecExtension; import ghidra.program.model.address.Address; import ghidra.program.model.listing.*; import ghidra.program.util.ProgramLocation; @@ -231,6 +232,9 @@ public class DecompilePlugin extends Plugin { if (event instanceof ProgramActivatedPluginEvent) { currentProgram = ((ProgramActivatedPluginEvent) event).getActiveProgram(); connectedProvider.doSetProgram(currentProgram); + if (currentProgram != null) { + SpecExtension.registerOptions(currentProgram); + } } else if (event instanceof ProgramLocationPluginEvent) { ProgramLocation location = ((ProgramLocationPluginEvent) event).getLocation(); 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 f062f0c5fc..60adbfba23 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 @@ -36,11 +36,11 @@ import ghidra.app.plugin.core.decompile.actions.*; import ghidra.app.services.*; import ghidra.app.util.HelpTopics; import ghidra.app.util.HighlightProvider; -import ghidra.framework.model.DomainObjectChangedEvent; -import ghidra.framework.model.DomainObjectListener; +import ghidra.framework.model.*; import ghidra.framework.options.*; import ghidra.framework.plugintool.NavigatableComponentProviderAdapter; import ghidra.framework.plugintool.util.ServiceListener; +import ghidra.program.database.SpecExtension; import ghidra.program.model.address.*; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; @@ -307,17 +307,33 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter @Override public void domainObjectChanged(DomainObjectChangedEvent ev) { - if (!isVisible()) { - return; - } - + // Check for events that signal that a decompiler process' data is stale + // and if so force a new process to be spawned if (ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_ADDED) || - ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED)) { + ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED) || + ev.containsEvent(DomainObject.DO_OBJECT_RESTORED)) { controller.resetDecompiler(); } + else if (ev.containsEvent(DomainObject.DO_PROPERTY_CHANGED)) { + Iterator iter = ev.iterator(); + while (iter.hasNext()) { + DomainObjectChangeRecord record = iter.next(); + if (record.getEventType() == DomainObject.DO_PROPERTY_CHANGED) { + if (record.getOldValue() instanceof String) { + String value = (String) record.getOldValue(); + if (value.startsWith(SpecExtension.SPEC_EXTENSION)) { + controller.resetDecompiler(); + break; + } + } + } + } + } - redecompileUpdater.update(); - + // Trigger a redecompile an any program change if the window is active + if (isVisible()) { + redecompileUpdater.update(); + } } private void doRefresh() { diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompiler/validator/DecompilerValidator.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompiler/validator/DecompilerValidator.java index cb0f62640d..da7333e365 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompiler/validator/DecompilerValidator.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompiler/validator/DecompilerValidator.java @@ -26,6 +26,7 @@ import ghidra.app.decompiler.parallel.*; import ghidra.app.plugin.core.analysis.validator.PostAnalysisValidator; import ghidra.program.model.address.Address; import ghidra.program.model.lang.CompilerSpec; +import ghidra.program.model.lang.CompilerSpec.EvaluationModelType; import ghidra.program.model.lang.PrototypeModel; import ghidra.program.model.listing.*; import ghidra.util.Msg; @@ -46,7 +47,7 @@ public class DecompilerValidator extends PostAnalysisValidator { List functions = filterFunctions(program, iter, monitor); DecompilerCallback callback = - new DecompilerCallback(program, new DecompilerValidatorConfigurer()) { + new DecompilerCallback<>(program, new DecompilerValidatorConfigurer()) { @Override public String process(DecompileResults results, TaskMonitor m) throws Exception { @@ -147,7 +148,8 @@ public class DecompilerValidator extends PostAnalysisValidator { private DecompileOptions getDecompilerOptions() { try { CompilerSpec spec = program.getCompilerSpec(); - PrototypeModel model = (PrototypeModel) spec.getPrototypeEvaluationModel(program); + PrototypeModel model = + spec.getPrototypeEvaluationModel(EvaluationModelType.EVAL_CURRENT); options.setProtoEvalModel(model.getName()); } catch (Exception e) { diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerTest.java index f68a579552..485c9acad7 100644 --- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerTest.java +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerTest.java @@ -29,6 +29,7 @@ import docking.widgets.fieldpanel.support.FieldLocation; import ghidra.app.decompiler.ClangToken; import ghidra.app.decompiler.component.ClangTextField; import ghidra.app.decompiler.component.DecompilerPanel; +import ghidra.program.model.pcode.HighFunction; import ghidra.test.AbstractProgramBasedTest; public abstract class AbstractDecompilerTest extends AbstractProgramBasedTest { @@ -171,4 +172,34 @@ public abstract class AbstractDecompilerTest extends AbstractProgramBasedTest { assertEquals(tokenText, text); } } + + protected ClangTextField getLineStarting(String val) { + DecompilerPanel panel = provider.getDecompilerPanel(); + List fields = panel.getFields(); + for (Field field : fields) { + ClangTextField textField = (ClangTextField) field; + String text = textField.getText().trim(); + if (text.startsWith(val)) { + return textField; + } + } + return null; + } + + protected ClangTextField getLineContaining(String val) { + DecompilerPanel panel = provider.getDecompilerPanel(); + List fields = panel.getFields(); + for (Field field : fields) { + ClangTextField textField = (ClangTextField) field; + String text = textField.getText(); + if (text.contains(val)) { + return textField; + } + } + return null; + } + + protected HighFunction getHighFunction() { + return provider.getController().getHighFunction(); + } } diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/HighSymbolTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/HighSymbolTest.java index 65b6e960ea..fa5fd30513 100644 --- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/HighSymbolTest.java +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/HighSymbolTest.java @@ -17,11 +17,8 @@ package ghidra.app.plugin.core.decompile; import static org.junit.Assert.*; -import java.util.List; - import org.junit.Test; -import docking.widgets.fieldpanel.field.Field; import docking.widgets.fieldpanel.support.FieldLocation; import ghidra.app.cmd.equate.SetEquateCmd; import ghidra.app.cmd.function.CreateFunctionCmd; @@ -30,7 +27,6 @@ import ghidra.app.cmd.label.RenameLabelCmd; import ghidra.app.decompiler.ClangToken; import ghidra.app.decompiler.ClangVariableToken; import ghidra.app.decompiler.component.ClangTextField; -import ghidra.app.decompiler.component.DecompilerPanel; import ghidra.app.plugin.core.decompile.actions.IsolateVariableTask; import ghidra.app.plugin.core.decompile.actions.RenameVariableTask; import ghidra.framework.options.Options; @@ -49,36 +45,6 @@ public class HighSymbolTest extends AbstractDecompilerTest { return "Winmine__XP.exe.gzf"; } - protected ClangTextField getLineStarting(String val) { - DecompilerPanel panel = provider.getDecompilerPanel(); - List fields = panel.getFields(); - for (Field field : fields) { - ClangTextField textField = (ClangTextField) field; - String text = textField.getText().trim(); - if (text.startsWith(val)) { - return textField; - } - } - return null; - } - - protected ClangTextField getLineContaining(String val) { - DecompilerPanel panel = provider.getDecompilerPanel(); - List fields = panel.getFields(); - for (Field field : fields) { - ClangTextField textField = (ClangTextField) field; - String text = textField.getText(); - if (text.contains(val)) { - return textField; - } - } - return null; - } - - protected HighFunction getHighFunction() { - return provider.getController().getHighFunction(); - } - private void renameGlobalVariable(HighSymbol highSymbol, ClangToken tokenAtCursor, String newName) { Address addr = highSymbol.getStorage().getMinAddress(); @@ -114,6 +80,7 @@ public class HighSymbolTest extends AbstractDecompilerTest { options.setBoolean("Stack", false); }); } + private void renameVariable(HighSymbol highSymbol, ClangToken tokenAtCursor, String newName) { RenameVariableTask rename = new RenameVariableTask(provider.getTool(), highSymbol.getProgram(), diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/SpecExtensionTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/SpecExtensionTest.java new file mode 100644 index 0000000000..b12aa27f2f --- /dev/null +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/SpecExtensionTest.java @@ -0,0 +1,259 @@ +/* ### + * 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 org.xml.sax.SAXException; + +import ghidra.app.decompiler.component.ClangTextField; +import ghidra.app.plugin.processors.sleigh.SleighException; +import ghidra.framework.options.Options; +import ghidra.framework.store.LockException; +import ghidra.program.database.ProgramCompilerSpec; +import ghidra.program.database.SpecExtension; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.*; +import ghidra.program.model.lang.CompilerSpec.EvaluationModelType; +import ghidra.program.model.listing.Function; +import ghidra.program.model.symbol.SourceType; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.InvalidInputException; +import ghidra.util.task.TaskMonitor; +import ghidra.xml.XmlParseException; + +public class SpecExtensionTest extends AbstractDecompilerTest { + @Override + protected String getProgramName() { + return "Winmine__XP.exe.gzf"; + } + + @Test + public void test_BadCallotherTarget() { + String myfixup = "\n" + " \n" + + " \n" + " \n" + " \n" + "\n"; + String errMessage = null; + try { + SpecExtension specExtension = new SpecExtension(program); + specExtension.addReplaceCompilerSpecExtension(myfixup, TaskMonitor.DUMMY); + } + catch (SleighException | XmlParseException | SAXException | LockException ex) { + errMessage = ex.getMessage(); + } + assertTrue(errMessage.contains("CALLOTHER_FIXUP target does not exist")); + } + + @Test + public void test_BadExtension() { + // Document with a p-code compile error + String myfixup = "\n" + " \n" + + " \n" + " \n" + " \n" + "\n"; + String errMessage = null; + SpecExtension specExtension = new SpecExtension(program); + try { + specExtension.addReplaceCompilerSpecExtension(myfixup, TaskMonitor.DUMMY); + } + catch (SleighException | XmlParseException | SAXException | LockException ex) { + errMessage = ex.getMessage(); + } + assertTrue(errMessage.contains("halting compilation")); + + // Document with an XML parsing problem + myfixup = " "; + errMessage = null; + String subError = null; + try { + specExtension.testExtensionDocument(myfixup); + } + catch (Exception e) { + errMessage = e.getMessage(); + subError = e.getCause().getMessage(); + } + assertTrue(errMessage.contains("Invalid compiler specification")); + assertTrue(subError.contains("must be terminated by the matching")); + + // Document that does not validate against the grammar + myfixup = " "; + errMessage = null; + try { + specExtension.testExtensionDocument(myfixup); + } + catch (Exception e) { + errMessage = e.getMessage(); + } + assertTrue(errMessage.contains("Could not find attribute: name")); + } + + @Test + public void test_ExtensionNameCollision() { + // Legal document that would overwrite a core callfixup + String myfixup = + "ESP = ESP - 4;"; + String errMessage = null; + SpecExtension specExtension = new SpecExtension(program); + try { + specExtension.addReplaceCompilerSpecExtension(myfixup, TaskMonitor.DUMMY); + } + catch (SleighException | XmlParseException | SAXException | LockException ex) { + errMessage = ex.getMessage(); + } + assertTrue(errMessage.contains("Extension cannot replace")); + } + + @Test + public void test_PrototypeExtension() { + decompile("100272e"); + ClangTextField line = getLineContaining("FUN_010026a7(pHVar1);"); + assertNotNull(line); + CompilerSpec cspec = program.getCompilerSpec(); + PrototypeModel defaultModel = cspec.getDefaultCallingConvention(); + StringBuilder buffer = new StringBuilder(); + defaultModel.saveXml(buffer, cspec.getPcodeInjectLibrary()); + String defaultString = buffer.toString(); + // Replace the output register EAX with ECX + defaultString = defaultString.replace("", + ""); + // Change the name + defaultString = defaultString.replace("name=\"__stdcall\"", "name=\"myproto\""); + SpecExtension specExtension = new SpecExtension(program); + int id1 = program.startTransaction("Test prototype install"); + try { + specExtension.addReplaceCompilerSpecExtension(defaultString, TaskMonitor.DUMMY); + } + catch (LockException | SleighException | SAXException | XmlParseException ex) { + throw new AssertionError("Unexpected exception: " + ex.getMessage()); + } + program.endTransaction(id1, true); + PrototypeModel myproto = cspec.getCallingConvention("myproto"); + assertNotNull(myproto); + + int id = program.startTransaction("test extension install"); + Address addr = program.getAddressFactory().getDefaultAddressSpace().getAddress(0x100112c); + Function func = program.getFunctionManager().getReferencedFunction(addr); + boolean changeWorks = true; + try { + func.setCallingConvention("myproto"); + } + catch (InvalidInputException e) { + changeWorks = false; + } + program.endTransaction(id, true); + assertTrue(changeWorks); + + decompile("100272e"); + // Look for the affect of ECX being the output register + line = getLineContaining("FUN_010026a7(extraout_EAX);"); + assertNotNull(line); + + int id3 = program.startTransaction("Change eval model"); + Options options = program.getOptions(ProgramCompilerSpec.DECOMPILER_PROPERTY_LIST_NAME); + options.setString(ProgramCompilerSpec.EVALUATION_MODEL_PROPERTY_NAME, "myproto"); + program.endTransaction(id3, true); + + PrototypeModel evalModel = + program.getCompilerSpec().getPrototypeEvaluationModel(EvaluationModelType.EVAL_CURRENT); + ParamList.WithSlotRec res = new ParamList.WithSlotRec(); + Address ecxAddr = program.getAddressFactory().getRegisterSpace().getAddress(4); + boolean outExists = evalModel.possibleOutputParamWithSlot(ecxAddr, 4, res); + assertTrue(outExists); + + int id2 = program.startTransaction("test extension removal"); + try { + specExtension.removeCompilerSpecExtension("prototype_myproto", TaskMonitor.DUMMY); + } + catch (LockException | CancelledException ex) { + throw new AssertionError("Unexpected exception: " + ex.getMessage()); + } + program.endTransaction(id2, true); + myproto = cspec.getCallingConvention("myproto"); + assertNull(myproto); + assertFalse(func.getCallingConventionName().equals("myproto")); + evalModel = + program.getCompilerSpec().getPrototypeEvaluationModel(EvaluationModelType.EVAL_CURRENT); + assertEquals(evalModel.getName(), "__stdcall"); + } + + @Test + public void test_CallFixupExtension() { + String myfixup = "\n" + " \n" + + " \n" + " \n" + + " \n" + "\n"; + SpecExtension specExtension = new SpecExtension(program); + int id1 = program.startTransaction("test extension install"); + try { + specExtension.addReplaceCompilerSpecExtension(myfixup, TaskMonitor.DUMMY); + } + catch (LockException | SleighException | SAXException | XmlParseException ex) { + throw new AssertionError("Unexpected exception: " + ex.getMessage()); + } + program.endTransaction(id1, true); + PcodeInjectLibrary library = program.getCompilerSpec().getPcodeInjectLibrary(); + InjectPayloadSleigh[] programPayloads = library.getProgramPayloads(); + assertEquals(programPayloads.length, 1); + InjectPayload payload = programPayloads[0]; + assertTrue(programPayloads[0] instanceof InjectPayloadCallfixup); + InjectPayloadCallfixup callfixup = (InjectPayloadCallfixup) payload; + List targets = callfixup.getTargets(); + assertEquals(targets.size(), 1); + assertEquals(targets.get(0), "targ1"); + assertEquals(payload.getName(), "mynewthing"); + assertTrue(payload.isFallThru()); + assertFalse(payload.isIncidentalCopy()); + + int id = program.startTransaction("test extensions"); + Address firstAddr = + program.getAddressFactory().getDefaultAddressSpace().getAddress(0x1002607); + Function func1 = program.getFunctionManager().getFunctionAt(firstAddr); + func1.setCallFixup("mynewthing"); + Address secondAddr = + program.getAddressFactory().getDefaultAddressSpace().getAddress(0x10038d7); + + Function func = program.getFunctionManager().getFunctionAt(secondAddr); + func.setSignatureSource(SourceType.DEFAULT); + program.endTransaction(id, true); + + decompile("100263c"); + ClangTextField line = getLineContaining("injection: mynewthing"); + assertNotNull(line); + // injection causes remaining call to look like it takes 1000 as a parameter + line = getLineStarting("FUN_010038d7(1000);"); + assertNotNull(line); + + // Remove the fixup extension + int id2 = program.startTransaction("test extension removal"); + try { + specExtension.removeCompilerSpecExtension("callfixup_mynewthing", TaskMonitor.DUMMY); + } + catch (LockException | CancelledException ex) { + throw new AssertionError("Unexpected exception: " + ex.getMessage()); + } + program.endTransaction(id2, true); + programPayloads = library.getProgramPayloads(); + assertNull(programPayloads); + + decompile("100263c"); + line = getLineStarting("FUN_01002607();"); + assertNotNull(line); + line = getLineStarting("FUN_010038d7();"); + assertNotNull(line); + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/analyzer/DexHeaderFormatAnalyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/analyzer/DexHeaderFormatAnalyzer.java index 1345b8892c..f7349e2169 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/analyzer/DexHeaderFormatAnalyzer.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/dex/analyzer/DexHeaderFormatAnalyzer.java @@ -28,10 +28,12 @@ import ghidra.app.util.importer.MessageLog; import ghidra.file.analyzers.FileFormatAnalyzer; import ghidra.file.formats.android.dex.format.*; import ghidra.file.formats.android.dex.util.DexUtil; +import ghidra.program.database.ProgramCompilerSpec; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.data.*; -import ghidra.program.model.lang.*; +import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; import ghidra.program.model.listing.*; import ghidra.program.model.listing.Function.FunctionUpdateType; import ghidra.program.model.mem.Memory; @@ -65,7 +67,7 @@ public class DexHeaderFormatAnalyzer extends FileFormatAnalyzer { createInitialFragments(program, header, monitor); - BasicCompilerSpec.enableJavaLanguageDecompilation(program); + ProgramCompilerSpec.enableJavaLanguageDecompilation(program); createNamespaces(program, header, monitor, log); processMap(program, header, monitor, log); processStrings(program, header, monitor, log); @@ -172,11 +174,9 @@ public class DexHeaderFormatAnalyzer extends FileFormatAnalyzer { return; } - for (int i = 0; i < methods.size(); ++i) { + for (EncodedMethod encodedMethod : methods) { monitor.checkCanceled(); - EncodedMethod encodedMethod = methods.get(i); - MethodIDItem methodID = header.getMethods().get(encodedMethod.getMethodIndex()); String methodName = DexUtil.convertToString(header, methodID.getNameIndex()); @@ -213,8 +213,8 @@ public class DexHeaderFormatAnalyzer extends FileFormatAnalyzer { Namespace classNameSpace, MessageLog log) { program.getSymbolTable().addExternalEntryPoint(methodAddress); try { - return program.getSymbolTable().createLabel(methodAddress, methodName, classNameSpace, - SourceType.ANALYSIS); + return program.getSymbolTable() + .createLabel(methodAddress, methodName, classNameSpace, SourceType.ANALYSIS); } catch (InvalidInputException e) { log.appendException(e); @@ -240,8 +240,8 @@ public class DexHeaderFormatAnalyzer extends FileFormatAnalyzer { commentBuilder.append("Method Debug Info Offset: 0x" + Integer.toHexString(codeItem.getDebugInfoOffset()) + "\n"); } - commentBuilder.append( - "Method ID Offset: 0x" + Long.toHexString(methodID.getFileOffset()) + "\n"); + commentBuilder + .append("Method ID Offset: 0x" + Long.toHexString(methodID.getFileOffset()) + "\n"); setPlateComment(program, methodAddress, commentBuilder.toString()); } @@ -522,11 +522,9 @@ public class DexHeaderFormatAnalyzer extends FileFormatAnalyzer { private void processEncodedMethods(Program program, DexHeader header, ClassDefItem item, List methods, TaskMonitor monitor) throws Exception { - for (int i = 0; i < methods.size(); ++i) { + for (EncodedMethod method : methods) { monitor.checkCanceled(); - EncodedMethod method = methods.get(i); - MethodIDItem methodID = header.getMethods().get(method.getMethodIndex()); StringBuilder builder = new StringBuilder(); @@ -662,7 +660,8 @@ public class DexHeaderFormatAnalyzer extends FileFormatAnalyzer { methodAddress.add(setItemDataType.getLength())); processAnnotationSetItem(program, setItem, monitor, log); } - for (ParameterAnnotation parameter : annotationsDirectoryItem.getParameterAnnotations()) { + for (ParameterAnnotation parameter : annotationsDirectoryItem + .getParameterAnnotations()) { monitor.checkCanceled(); Address parameterAddress = toAddr(program, parameter.getAnnotationsOffset()); AnnotationSetReferenceList annotationSetReferenceList = @@ -767,9 +766,9 @@ public class DexHeaderFormatAnalyzer extends FileFormatAnalyzer { classNameSpace, log); if (methodSymbol != null) { String externalName = methodSymbol.getName(true); - program.getReferenceManager().addExternalReference(methodIndexAddress, - "EXTERNAL.dex", externalName, null, SourceType.ANALYSIS, 0, - RefType.DATA); + program.getReferenceManager() + .addExternalReference(methodIndexAddress, "EXTERNAL.dex", + externalName, null, SourceType.ANALYSIS, 0, RefType.DATA); } } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java index 41377314ad..9b28d45eea 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java @@ -481,7 +481,8 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter */ protected boolean propertyChanged(String propertyName, Object oldValue, Object newValue) { setChanged(true); - fireEvent(new DomainObjectChangeRecord(DomainObject.DO_PROPERTY_CHANGED, name, name)); + fireEvent( + new DomainObjectChangeRecord(DomainObject.DO_PROPERTY_CHANGED, propertyName, newValue)); return true; } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/OptionsDB.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/OptionsDB.java index 8156f756cf..64435a785c 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/OptionsDB.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/OptionsDB.java @@ -157,6 +157,8 @@ class OptionsDB extends AbstractOptions { public synchronized void removeOption(String propertyName) { super.removeOption(propertyName); removePropertyFromDB(propertyName); + // NOTE: AbstractOptions does not provide removal notification + notifyOptionChanged(propertyName, null, null); } private void removePropertyFromDB(String propertyName) { @@ -331,7 +333,7 @@ class OptionsDB extends AbstractOptions { @Override protected boolean notifyOptionChanged(String optionName, Object oldValue, Object newValue) { - return domainObj.propertyChanged(name, oldValue, newValue); + return domainObj.propertyChanged(optionName, oldValue, newValue); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java index a049e5a600..3b3ea6a4b2 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java @@ -122,7 +122,8 @@ public class SleighLanguage implements Language { throws SAXException, IOException, UnknownInstructionException { this.defaultSymbols = new ArrayList<>(); this.compilerSpecDescriptions = new LinkedHashMap<>(); - for (CompilerSpecDescription compilerSpecDescription : langDescription.getCompatibleCompilerSpecDescriptions()) { + for (CompilerSpecDescription compilerSpecDescription : langDescription + .getCompatibleCompilerSpecDescriptions()) { this.compilerSpecDescriptions.put(compilerSpecDescription.getCompilerSpecID(), (SleighCompilerSpecDescription) compilerSpecDescription); } @@ -478,8 +479,9 @@ public class SleighLanguage implements Language { String[] args; if (sleighArgsFile != null && sleighArgsFile.isFile()) { - String baseDir = Application.getInstallationDirectory().getAbsolutePath().replace( - File.separatorChar, '/'); + String baseDir = Application.getInstallationDirectory() + .getAbsolutePath() + .replace(File.separatorChar, '/'); if (!baseDir.endsWith("/")) { baseDir += "/"; } @@ -626,63 +628,14 @@ public class SleighLanguage implements Language { return new Pair<>(addrspace.getAddress(first), addrspace.getAddress(last)); } - private void parseJumpAssist(XmlElement el, XmlPullParser parser) { - String name = el.getAttribute("name"); - String source = "pspec: " + getLanguageID().getIdAsString(); - while (parser.peek().isStart()) { - String subName; - XmlElement subel = parser.peek(); - if (subel.getName().charAt(0) == 'c') { - subName = name + "_index2case"; - } - else if (subel.getName().charAt(0) == 'a') { - subName = name + "_index2addr"; - } - else if (subel.getName().charAt(0) == 's') { - subName = name + "_calcsize"; - } - else { - subName = name + "_defaultaddr"; - } - InjectPayloadSleigh payload = - new InjectPayloadSleigh(subName, InjectPayload.EXECUTABLEPCODE_TYPE, source); - payload.restoreXml(parser); - addAdditionInject(payload); - } - } - - public InjectPayloadSleigh parseSegmentOp(XmlElement el, XmlPullParser parser) { - String name = el.getAttribute("userop"); - if (name == null) { - name = "segment"; - } - name = name + "_pcode"; - String source = "pspec: " + getLanguageID().getIdAsString(); - InjectPayloadSleigh payload = null; - if (parser.peek().isStart()) { - if (parser.peek().getName().equals("pcode")) { - payload = new InjectPayloadSleigh(name, InjectPayload.EXECUTABLEPCODE_TYPE, source); - payload.restoreXml(parser); - } - } - while (parser.peek().isStart()) { - parser.discardSubTree(); - } - if (payload == null) { - throw new SleighException("Missing child for tag"); - } - return payload; - } - - private void read(XmlPullParser parser) { + private void read(XmlPullParser parser) throws XmlParseException { Set registerDataSet = new HashSet<>(); - XmlElement element = parser.start("processor_spec"); - while (!parser.peek().isEnd()) { - element = parser.start("properties", "segmented_address", "segmentop", "programcounter", - "data_space", "inferptrbounds", "context_data", "volatile", "jumpassist", - "incidentalcopy", "register_data", "default_symbols", "default_memory_blocks"); - if (element.getName().equals("properties")) { + XmlElement el = parser.start("processor_spec"); + while (parser.peek().isStart()) { + String elName = parser.peek().getName(); + if (elName.equals("properties")) { + XmlElement subel = parser.start(); while (!parser.peek().isEnd()) { XmlElement next = parser.start("property"); String key = next.getAttribute("key"); @@ -690,13 +643,17 @@ public class SleighLanguage implements Language { properties.put(key, value); parser.end(next); } + parser.end(subel); } - else if (element.getName().equals("programcounter")) { - setProgramCounter(element.getAttribute("register")); + else if (elName.equals("programcounter")) { + XmlElement subel = parser.start(); + setProgramCounter(subel.getAttribute("register")); + parser.end(subel); } - else if (element.getName().equals("data_space")) { - setDefaultDataSpace(element.getAttribute("space")); - String overrideString = element.getAttribute("ptr_wordsize"); + else if (elName.equals("data_space")) { + XmlElement subel = parser.start(); + setDefaultDataSpace(subel.getAttribute("space")); + String overrideString = subel.getAttribute("ptr_wordsize"); if (overrideString != null) { int val = SpecXmlUtils.decodeInt(overrideString); if (val <= 0 || val >= 32) { @@ -704,8 +661,10 @@ public class SleighLanguage implements Language { } defaultPointerWordSize = val; } + parser.end(subel); } - else if (element.getName().equals("context_data")) { + else if (elName.equals("context_data")) { + XmlElement subel = parser.start(); while (!parser.peek().isEnd()) { XmlElement next = parser.start(); boolean isContext = next.getName().equals("context_set"); @@ -744,8 +703,10 @@ public class SleighLanguage implements Language { // skip the end tag parser.end(next); } + parser.end(subel); } - else if (element.getName().equals("volatile")) { + else if (elName.equals("volatile")) { + XmlElement subel = parser.start(); while (!parser.peek().getName().equals("volatile")) { XmlElement next = parser.start(); if (next.getName().equals("register")) { @@ -759,11 +720,21 @@ public class SleighLanguage implements Language { // skip the end tag parser.end(next); } + parser.end(subel); } - else if (element.getName().equals("jumpassist")) { - parseJumpAssist(element, parser); + else if (elName.equals("jumpassist")) { + XmlElement subel = parser.start(); + String source = "pspec: " + getLanguageID().getIdAsString(); + String name = subel.getAttribute("name"); + while (parser.peek().isStart()) { + InjectPayloadSleigh payload = new InjectPayloadJumpAssist(name, source); + payload.restoreXml(parser, this); + addAdditionInject(payload); + } + parser.end(subel); } - else if (element.getName().equals("register_data")) { + else if (elName.equals("register_data")) { + XmlElement subel = parser.start(); while (parser.peek().getName().equals("register")) { XmlElement reg = parser.start(); String registerName = reg.getAttribute("name"); @@ -810,8 +781,10 @@ public class SleighLanguage implements Language { // skip the end tag parser.end(reg); } + parser.end(subel); } - else if (element.getName().equals("default_symbols")) { + else if (elName.equals("default_symbols")) { + XmlElement subel = parser.start(); while (parser.peek().getName().equals("symbol")) { XmlElement symbol = parser.start(); String labelName = symbol.getAttribute("name"); @@ -832,8 +805,10 @@ public class SleighLanguage implements Language { // skip the end tag parser.end(symbol); } + parser.end(subel); } - else if (element.getName().equals("default_memory_blocks")) { + else if (elName.equals("default_memory_blocks")) { + XmlElement subel = parser.start(); List list = new ArrayList<>(); while (parser.peek().getName().equals("memory_block")) { XmlElement mblock = parser.start(); @@ -841,27 +816,39 @@ public class SleighLanguage implements Language { // skip the end tag parser.end(mblock); } + parser.end(subel); defaultMemoryBlocks = new MemoryBlockDefinition[list.size()]; list.toArray(defaultMemoryBlocks); } - else if (element.getName().equals("incidentalcopy")) { + else if (elName.equals("incidentalcopy")) { + XmlElement subel = parser.start(); while (parser.peek().isStart()) { parser.discardSubTree(); } + parser.end(subel); } - else if (element.getName().equals("inferptrbounds")) { + else if (elName.equals("inferptrbounds")) { + XmlElement subel = parser.start(); while (parser.peek().isStart()) { parser.discardSubTree(); } + parser.end(subel); } - else if (element.getName().equals("segmentop")) { - InjectPayloadSleigh payload = parseSegmentOp(element, parser); + else if (elName.equals("segmentop")) { + String source = "pspec: " + getLanguageID().getIdAsString(); + InjectPayloadSleigh payload = new InjectPayloadSegment(source); + payload.restoreXml(parser, this); addAdditionInject(payload); } - // get rid of the end tag of whatever we started with at the top of the while - parser.end(element); + else if (elName.equals("segmented_address")) { + XmlElement subel = parser.start(); + parser.end(subel); + } + else { + throw new XmlParseException("Unknown pspec tag: " + elName); + } } - parser.dispose(); + parser.end(el); } private void readRemainingSpecification() throws SAXException, IOException { @@ -870,6 +857,9 @@ public class SleighLanguage implements Language { try { read(parser); } + catch (XmlParseException e) { + Msg.error(this, e.getMessage()); + } finally { parser.dispose(); } @@ -912,9 +902,8 @@ public class SleighLanguage implements Language { if (isBigEndian ^ description.getEndian().isBigEndian()) { if (description.getInstructionEndian().isBigEndian() == description.getEndian() .isBigEndian()) { - throw new SleighException( - ".ldefs says " + getLanguageID() + " is " + description.getEndian() + - " but .sla says " + el.getAttribute("bigendian")); + throw new SleighException(".ldefs says " + getLanguageID() + " is " + + description.getEndian() + " but .sla says " + el.getAttribute("bigendian")); } } uniqueBase = SpecXmlUtils.decodeLong(el.getAttribute("uniqbase")); @@ -1164,7 +1153,9 @@ public class SleighLanguage implements Language { @Override public CompilerSpec getDefaultCompilerSpec() { SleighCompilerSpecDescription compilerSpecDescription = - (SleighCompilerSpecDescription) description.getCompatibleCompilerSpecDescriptions().iterator().next(); + (SleighCompilerSpecDescription) description.getCompatibleCompilerSpecDescriptions() + .iterator() + .next(); try { return getCompilerSpecByID(compilerSpecDescription.getCompilerSpecID()); } @@ -1328,8 +1319,8 @@ public class SleighLanguage implements Language { if (matcher.find()) { if (SystemUtilities.isInDevelopmentMode()) { // Search across repositories in development mode - currentManual = Application.findDataFileInAnyModule( - "manuals/" + matcher.group(1).trim()); + currentManual = Application + .findDataFileInAnyModule("manuals/" + matcher.group(1).trim()); } if (currentManual == null) { currentManual = @@ -1598,7 +1589,8 @@ public class SleighLanguage implements Language { } else { parallelHelper = - (ParallelInstructionLanguageHelper) helperClass.getDeclaredConstructor().newInstance(); + (ParallelInstructionLanguageHelper) helperClass.getDeclaredConstructor() + .newInstance(); } } catch (Exception e) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguageValidator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguageValidator.java index cf15ab7521..36397c7e8d 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguageValidator.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguageValidator.java @@ -16,6 +16,7 @@ package ghidra.app.plugin.processors.sleigh; import java.io.InputStream; +import java.io.StringReader; import org.iso_relax.verifier.*; import org.xml.sax.*; @@ -26,10 +27,32 @@ import ghidra.util.Msg; import utilities.util.FileResolutionResult; import utilities.util.FileUtilities; +/** + * Validate SLEIGH related XML configuration files: .cspec .pspec and .ldefs + * + * A ResourceFile containing an XML document can be verified with one of the + * static methods: + * - validateCspecFile + * - validateLdefsFile + * - validatePspecFile + * + * Alternately the class can be instantiated, which will allocate a single verifier + * that can be run on multiple files. + */ public class SleighLanguageValidator { private static final ResourceFile ldefsRelaxSchemaFile; private static final ResourceFile pspecRelaxSchemaFile; private static final ResourceFile cspecRelaxSchemaFile; + public static final int CSPEC_TYPE = 1; + public static final int PSPEC_TYPE = 2; + public static final int LDEFS_TYPE = 3; + public static final int CSPECTAG_TYPE = 4; + private static final String LANGUAGE_TYPESTRING = "language definitions"; + private static final String COMPILER_TYPESTRING = "compiler specification"; + private static final String PROCESSOR_TYPESTRING = "processor specification"; + + private int verifierType; + private Verifier verifier; static { ResourceFile file = null; @@ -69,16 +92,107 @@ public class SleighLanguageValidator { cspecRelaxSchemaFile = file; } + public SleighLanguageValidator(int type) { + verifierType = type; + ResourceFile schemaFile = null; + switch (type) { + case CSPEC_TYPE: + case CSPECTAG_TYPE: + schemaFile = cspecRelaxSchemaFile; + break; + case PSPEC_TYPE: + schemaFile = pspecRelaxSchemaFile; + break; + case LDEFS_TYPE: + schemaFile = ldefsRelaxSchemaFile; + break; + default: + throw new SleighException("Bad verifier type"); + } + verifier = null; + try { + verifier = getVerifier(schemaFile); + } + catch (Exception e) { + throw new SleighException("Error creating verifier", e); + } + } + + private String getTypeString() { + if (verifierType == PSPEC_TYPE) { + return PROCESSOR_TYPESTRING; + } + if (verifierType == LDEFS_TYPE) { + return LANGUAGE_TYPESTRING; + } + return COMPILER_TYPESTRING; + } + + /** + * Verify the given file against this validator. + * @param specFile is the file + * @throws SleighException with an explanation if the file does not validate + */ + public void verify(ResourceFile specFile) throws SleighException { + FileResolutionResult result = FileUtilities.existsAndIsCaseDependent(specFile); + if (!result.isOk()) { + throw new SleighException( + specFile + " is not properly case dependent: " + result.getMessage()); + } + try { + InputStream in = specFile.getInputStream(); + verifier.setErrorHandler(new VerifierErrorHandler(specFile)); + verifier.verify(new InputSource(in)); + in.close(); + } + catch (Exception e) { + throw new SleighException( + "Invalid " + getTypeString() + " file: " + specFile.getAbsolutePath(), e); + } + } + + /** + * Verify an XML document as a string against this validator. + * Currently this only supports verifierType == CSPECTAG_TYPE. + * @param title is a description of the document + * @param document is the XML document body + * @throws SleighException with an explanation if the document does not validate + */ + public void verify(String title, String document) throws SleighException { + if (verifierType != CSPECTAG_TYPE) { + throw new SleighException("Only cspec tag verification is supported"); + } + StringBuilder buffer = new StringBuilder(); + buffer.append("\n"); + buffer.append("\n"); + buffer.append("\n"); + buffer.append("\n"); + buffer.append("\n"); + buffer.append("\n"); + buffer.append(document); + buffer.append("\n"); + ErrorHandler errorHandler = new VerifierErrorHandler(title, 6); + StringReader reader = new StringReader(buffer.toString()); + + verifier.setErrorHandler(errorHandler); + try { + verifier.verify(new InputSource(reader)); + } + catch (Exception e) { + throw new SleighException("Invalid " + getTypeString() + ": " + title, e); + } + } + public static void validateLdefsFile(ResourceFile ldefsFile) throws SleighException { - validateSleighFile(ldefsRelaxSchemaFile, ldefsFile, "language definitions"); + validateSleighFile(ldefsRelaxSchemaFile, ldefsFile, LANGUAGE_TYPESTRING); } public static void validatePspecFile(ResourceFile pspecFile) throws SleighException { - validateSleighFile(pspecRelaxSchemaFile, pspecFile, "processor specification"); + validateSleighFile(pspecRelaxSchemaFile, pspecFile, PROCESSOR_TYPESTRING); } public static void validateCspecFile(ResourceFile cspecFile) throws SleighException { - validateSleighFile(cspecRelaxSchemaFile, cspecFile, "compiler specification"); + validateSleighFile(cspecRelaxSchemaFile, cspecFile, COMPILER_TYPESTRING); } private static void validateSleighFile(ResourceFile relaxSchemaFile, @@ -86,8 +200,8 @@ public class SleighLanguageValidator { FileResolutionResult result = FileUtilities.existsAndIsCaseDependent(fileToValidate); if (!result.isOk()) { - throw new SleighException(fileToValidate + " is not properly case dependent: " + - result.getMessage()); + throw new SleighException( + fileToValidate + " is not properly case dependent: " + result.getMessage()); } Verifier verifier = null; @@ -99,7 +213,7 @@ public class SleighLanguageValidator { } try { InputStream in = fileToValidate.getInputStream(); - verifier.setErrorHandler(new MyErrorHandler(fileToValidate)); + verifier.setErrorHandler(new VerifierErrorHandler(fileToValidate)); verifier.verify(new InputSource(in)); in.close(); } @@ -116,11 +230,18 @@ public class SleighLanguageValidator { return verifier; } - private static class MyErrorHandler implements ErrorHandler { - final ResourceFile file; + private static class VerifierErrorHandler implements ErrorHandler { + final String documentTitle; + int lineNumberBase; - public MyErrorHandler(ResourceFile file) { - this.file = file; + public VerifierErrorHandler(ResourceFile file) { + documentTitle = file.toString(); + lineNumberBase = 0; + } + + public VerifierErrorHandler(String title, int base) { + documentTitle = title; + lineNumberBase = base; } @Override @@ -130,10 +251,10 @@ public class SleighLanguageValidator { @Override public void error(SAXParseException e) throws SAXException { - Msg.error( - SleighLanguageValidator.class, - "Error validating " + file + " at " + e.getLineNumber() + ":" + - e.getColumnNumber(), e); + int lineno = e.getLineNumber() - lineNumberBase; + Msg.error(SleighLanguageValidator.class, + "Error validating " + documentTitle + " at " + lineno + ":" + e.getColumnNumber(), + e); throw e; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SpecExtensionEditor.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SpecExtensionEditor.java new file mode 100644 index 0000000000..76f2a96714 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SpecExtensionEditor.java @@ -0,0 +1,76 @@ +/* ### + * 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.processors.sleigh; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.JComponent; + +import ghidra.framework.options.*; +import ghidra.program.database.ProgramDB; +import ghidra.util.exception.InvalidInputException; +import ghidra.util.task.TaskMonitor; + +public class SpecExtensionEditor implements OptionsEditor, PropertyChangeListener { + + private ProgramDB program; + private PropertyChangeListener listener; + private SpecExtensionPanel panel; + + public SpecExtensionEditor(ProgramDB program) { + this.program = program; + } + + @Override + public void apply() throws InvalidInputException { + panel.apply(TaskMonitor.DUMMY); + } + + @Override + public void cancel() { + panel.cancel(); + } + + @Override + public void reload() { + // doesn't respond to reload + } + + @Override + public void setOptionsPropertyChangeListener(PropertyChangeListener listener) { + this.listener = listener; + } + + @Override + public JComponent getEditorComponent(Options options, EditorStateFactory editorStateFactory) { + panel = new SpecExtensionPanel(program, this); + return panel; + } + + @Override + public void dispose() { + // stub + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (listener != null) { + listener.propertyChange(evt); + } + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SpecExtensionPanel.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SpecExtensionPanel.java new file mode 100644 index 0000000000..94a5713dd7 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SpecExtensionPanel.java @@ -0,0 +1,709 @@ +/* ### + * 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.processors.sleigh; + +import java.awt.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.*; + +import org.xml.sax.SAXException; + +import docking.widgets.OptionDialog; +import docking.widgets.filechooser.GhidraFileChooser; +import docking.widgets.table.*; +import ghidra.framework.preferences.Preferences; +import ghidra.framework.store.LockException; +import ghidra.program.database.ProgramDB; +import ghidra.program.database.SpecExtension; +import ghidra.program.database.SpecExtension.DocInfo; +import ghidra.program.model.lang.*; +import ghidra.util.Msg; +import ghidra.util.Swing; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.filechooser.GhidraFileChooserModel; +import ghidra.util.filechooser.GhidraFileFilter; +import ghidra.util.task.*; +import ghidra.xml.XmlParseException; + +public class SpecExtensionPanel extends JPanel { + private ProgramDB program; + private PropertyChangeListener listener; + private boolean unappliedChanges; + private SpecExtension specExtension; + private List tableElements; + private ExtensionTableModel tableModel; + private GTable extensionTable; + private JButton exportButton; + private JButton removeButton; + private ListSelectionModel selectionModel; + + private final static int EXTENSION_TYPE_COLUMN = 0; + private final static int NAME_COLUMN = 1; + private final static int STATUS_COLUMN = 2; + + /** + * Status of a particular compiler specification element + */ + public enum Status { + // The order is used to sort the table + CORE("core"), // A core element (cannot be deleted) + EXTENSION("extension"), // An extension thats already present (and won't be changed) + EXTENSION_ERROR("extension(parse error)"), // An extension (already present) that didn't parse + EXTENSION_INSTALL("install"), // A pending extension to be installed + EXTENSION_REPLACE("replace"), // A pending extension replacing existing + EXTENSION_REMOVE("remove"), // An extension to be removed + EXTENSION_OVERRIDE("override"), // An extension overriding a core module + EXTENSION_OVERPENDING("override pending"); // A pending extension which overrides + + private String formalName; + + private Status(String nm) { + formalName = nm; + } + } + + private static final String LAST_EXPORT_DIRECTORY = "LastSpecificationExportDirectory"; + public static final String PREFERENCES_FILE_EXTENSION = ".xml"; + private static final GhidraFileFilter FILE_FILTER = new GhidraFileFilter() { + @Override + public boolean accept(File pathname, GhidraFileChooserModel model) { + return (pathname.isDirectory()) || + (pathname.getName().endsWith(PREFERENCES_FILE_EXTENSION)); + } + + @Override + public String getDescription() { + return "Specification XML Files"; + } + }; + + /** + * A row in the table of compiler spec elements + */ + private static class CompilerElement implements Comparable { + + String name; + String optionName; + SpecExtension.Type type; + Status status; + String xmlString; + + public CompilerElement(String nm, SpecExtension.Type tp, Status st) { + name = nm; + type = tp; + optionName = type.getOptionName(name); + status = st; + xmlString = null; + } + + /** + * Return true if the element is already installed (not pending) + * @return true for an existing extension + */ + public boolean isExisting() { + return (status == Status.CORE || status == Status.EXTENSION || + status == Status.EXTENSION_ERROR || status == Status.EXTENSION_OVERRIDE); + } + + @Override + public int compareTo(CompilerElement o) { + if (type != o.type) { + return type.ordinal() - o.type.ordinal(); + } + if (status != o.status) { + return status.ordinal() - o.status.ordinal(); + } + return name.compareTo(o.name); + } + } + + /** + * Selection listener class for the table model. + */ + private class TableSelectionListener implements ListSelectionListener { + @Override + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting()) { + return; + } + + CompilerElement compilerElement = getSelectedCompilerElement(); + if (compilerElement == null) { + removeButton.setEnabled(false); + exportButton.setEnabled(false); + return; + } + boolean rowExisting = compilerElement.isExisting(); + removeButton.setEnabled(rowExisting && compilerElement.status != Status.CORE); + exportButton.setEnabled(rowExisting); + } + } + + private class ExtensionTableModel extends AbstractGTableModel { + private final String[] columnNames = { "Extension Type", "Name", "Status" }; + + @Override + public String getColumnName(int column) { + return columnNames[column]; + } + + @Override + public int getColumnCount() { + return columnNames.length; + } + + @Override + public String getName() { + return "Compiler Specification Elements"; + } + + @Override + public List getModelData() { + return tableElements; + } + + @Override + public Object getColumnValueForRow(CompilerElement t, int columnIndex) { + switch (columnIndex) { + case EXTENSION_TYPE_COLUMN: + return t.type.getTagName(); + case NAME_COLUMN: + return t.name; + case STATUS_COLUMN: + if (t.status == Status.CORE) { + return ""; + } + return t.status.formalName; + } + return "Unknown column!"; + } + } + + private class CompilerElementTable extends GTable { + private ElementRenderer renderer; + + CompilerElementTable(TableModel model) { + super(model); + renderer = new ElementRenderer(); + } + + @Override + public TableCellRenderer getCellRenderer(int row, int col) { + return renderer; + } + + } + + private class ElementRenderer extends GTableCellRenderer { + @Override + public Component getTableCellRendererComponent(GTableCellRenderingData data) { + + super.getTableCellRendererComponent(data); + + if (data.isSelected()) { + return this; + } + + int row = data.getRowViewIndex(); + + CompilerElement compilerElement = tableModel.getRowObject(row); + + if (compilerElement.status == Status.EXTENSION_ERROR) { + setBackground(Color.pink); + } + + return this; + } + + } + + /** + * Task for applying any accumulated changes in the list of CompilerElements for this Panel to the Program. + */ + public class ChangeExtensionTask extends Task { + + public ChangeExtensionTask() { + super("Committing extension changes", true, true, true); + } + + @Override + public void run(TaskMonitor monitor) { + try { + for (CompilerElement element : tableElements) { + switch (element.status) { + case CORE: + case EXTENSION: + case EXTENSION_ERROR: + case EXTENSION_OVERRIDE: + break; // Unchanged + case EXTENSION_REMOVE: + specExtension.removeCompilerSpecExtension(element.optionName, monitor); + break; + case EXTENSION_INSTALL: + case EXTENSION_REPLACE: + case EXTENSION_OVERPENDING: + specExtension.addReplaceCompilerSpecExtension(element.xmlString, + monitor); + break; + } + } + } + catch (LockException ex) { + Msg.showError(this, null, "Missing Exclusive Access", + "Do not have exclusive acces"); + } + catch (XmlParseException | SAXException ex) { + Msg.showError(this, null, "Failed Committing Extension Changes", ex.getMessage()); + } + catch (CancelledException ex) { + // User cancelled + } + } + } + + private void populateElementTable() { + tableElements = new ArrayList<>(); + CompilerSpec compilerSpec = program.getCompilerSpec(); + PrototypeModel[] models = compilerSpec.getAllModels(); + for (PrototypeModel model : models) { + SpecExtension.Type type = SpecExtension.Type.PROTOTYPE_MODEL; + Status status = Status.CORE; + if (model.isProgramExtension()) { + status = model.isErrorPlaceholder() ? Status.EXTENSION_ERROR : Status.EXTENSION; + } + if (model instanceof PrototypeModelMerged) { + type = SpecExtension.Type.MERGE_MODEL; + } + CompilerElement compEl = new CompilerElement(model.getName(), type, status); + tableElements.add(compEl); + } + PcodeInjectLibrary injectLibrary = compilerSpec.getPcodeInjectLibrary(); + String[] callFixupNames = injectLibrary.getCallFixupNames(); + for (String fixupName : callFixupNames) { + SpecExtension.Type type = SpecExtension.Type.CALL_FIXUP; + Status status = Status.CORE; + if (injectLibrary.hasProgramPayload(fixupName, InjectPayload.CALLFIXUP_TYPE)) { + status = Status.EXTENSION; + if (injectLibrary.getPayload(InjectPayload.CALLFIXUP_TYPE, fixupName) + .isErrorPlaceholder()) { + status = Status.EXTENSION_ERROR; + } + } + CompilerElement compEl = new CompilerElement(fixupName, type, status); + tableElements.add(compEl); + } + String[] callOtherNames = injectLibrary.getCallotherFixupNames(); + for (String fixupName : callOtherNames) { + SpecExtension.Type type = SpecExtension.Type.CALLOTHER_FIXUP; + Status status = Status.CORE; + if (injectLibrary.hasProgramPayload(fixupName, InjectPayload.CALLOTHERFIXUP_TYPE)) { + status = Status.EXTENSION; + if (injectLibrary.isOverride(fixupName, InjectPayload.CALLOTHERFIXUP_TYPE)) { + status = Status.EXTENSION_OVERRIDE; + } + if (injectLibrary.getPayload(InjectPayload.CALLFIXUP_TYPE, fixupName) + .isErrorPlaceholder()) { + status = Status.EXTENSION_ERROR; + } + } + CompilerElement compEl = new CompilerElement(fixupName, type, status); + tableElements.add(compEl); + } + tableElements.sort(null); + } + + private void addListeners() { + selectionModel = extensionTable.getSelectionModel(); + selectionModel.addListSelectionListener(new TableSelectionListener()); + } + + SpecExtensionPanel(ProgramDB program, PropertyChangeListener listener) { + this.program = program; + this.listener = listener; + unappliedChanges = false; + specExtension = new SpecExtension(program); + createPanel(); + populateElementTable(); + addListeners(); + } + + public void apply(TaskMonitor monitor) { + ChangeExtensionTask task = new ChangeExtensionTask(); + new TaskLauncher(task, this); + populateElementTable(); + changesMade(false); + tableModel.fireTableDataChanged(); + } + + /** + * Cancel any pending changes and reload the current table + */ + public void cancel() { + populateElementTable(); + tableModel.fireTableDataChanged(); + } + + /** + * Size the columns. + */ + private void adjustTableColumns() { + extensionTable.doLayout(); + TableColumn column = + extensionTable.getColumn(extensionTable.getColumnName(EXTENSION_TYPE_COLUMN)); + column.setPreferredWidth(100); + column = extensionTable.getColumn(extensionTable.getColumnName(NAME_COLUMN)); + column.setPreferredWidth(250); + column = extensionTable.getColumn(extensionTable.getColumnName(STATUS_COLUMN)); + column.setPreferredWidth(150); + } + + private void createPanel() { + setLayout(new BorderLayout(10, 10)); + tableModel = new ExtensionTableModel(); + extensionTable = new CompilerElementTable(tableModel); + + JScrollPane sp = new JScrollPane(extensionTable); + extensionTable.setPreferredScrollableViewportSize(new Dimension(400, 100)); + extensionTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + adjustTableColumns(); + JPanel centerPanel = new JPanel(new BorderLayout()); + JPanel lowerPanel = createButtonPanel(); + centerPanel.add(sp, BorderLayout.CENTER); + add(centerPanel, BorderLayout.CENTER); + add(lowerPanel, BorderLayout.SOUTH); + } + + private static File getStartingDir() { + String lastDirectoryPath = Preferences.getProperty(LAST_EXPORT_DIRECTORY); + if (lastDirectoryPath != null) { + return new File(lastDirectoryPath); + } + + return new File(System.getProperty("user.home")); + } + + private static File getFileFromUser(String suggestedName) { + KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + Component activeComponent = kfm.getActiveWindow(); + GhidraFileChooser fileChooser = new GhidraFileChooser(activeComponent); + fileChooser.setTitle("Please Select A File"); + fileChooser.setFileFilter(FILE_FILTER); + fileChooser.setApproveButtonText("OK"); + File startDir = getStartingDir(); + if (suggestedName != null) { + fileChooser.setSelectedFile(new File(startDir, suggestedName)); + } + else { + fileChooser.setCurrentDirectory(startDir); + } + + File selectedFile = fileChooser.getSelectedFile(); + + // make sure the file has the correct extension + if ((selectedFile != null) && + !selectedFile.getName().endsWith(PREFERENCES_FILE_EXTENSION)) { + selectedFile = new File(selectedFile.getAbsolutePath() + PREFERENCES_FILE_EXTENSION); + } + + // save off the last location to which the user navigated so we can + // return them to that spot if they use the dialog again. + Preferences.setProperty(LAST_EXPORT_DIRECTORY, + fileChooser.getCurrentDirectory().getAbsolutePath()); + + return selectedFile; + } + + private static String fileToString(File file) throws IOException { + FileReader inputReader = new FileReader(file); + BufferedReader reader = new BufferedReader(inputReader); + try { + StringBuffer buffer = new StringBuffer(); + String line = null; + while ((line = reader.readLine()) != null) { + buffer.append(line); + buffer.append('\n'); + } + return buffer.toString(); + } + finally { + reader.close(); + } + } + + private int findMatch(SpecExtension.Type type, String name) { + for (int i = 0; i < tableElements.size(); ++i) { + CompilerElement el = tableElements.get(i); + if (el.name.equals(name) && el.type == type) { + return i; + } + } + return -1; + } + + // signals that there are unapplied changes + private void changesMade(boolean changes) { + listener.propertyChange( + new PropertyChangeEvent(this, "apply.enabled", unappliedChanges, changes)); + unappliedChanges = changes; + } + + /** + * Present a file chooser, then + * - Load the file as a String + * - Test the validity of the file as an XML document describing an extension + * - Create a new CompilerElement representing the extension OR + * - Mark an existing CompilerElement as being overwritten with the new document + */ + private void importExtension() { + if (!program.hasExclusiveAccess()) { + Msg.showError(this, this, "Import Failure", + "Must have an exclusive checkout to import a new extension"); + return; + } + File file = getFileFromUser(null); + if (file == null) { + return; + } + String document; + DocInfo docInfo = null; + Exception errMessage = null; + try { + document = fileToString(file).trim(); + docInfo = specExtension.testExtensionDocument(document); + int pos = findMatch(docInfo.getType(), docInfo.getFormalName()); + Status status = Status.EXTENSION_INSTALL; + if (pos >= 0) { + CompilerElement previousEl = tableElements.get(pos); + switch (previousEl.status) { + case CORE: + if (!docInfo.isOverride()) { + throw new DuplicateNameException( + "Cannot override core extension: " + previousEl.name); + } + status = Status.EXTENSION_OVERPENDING; + break; + case EXTENSION: + case EXTENSION_ERROR: + case EXTENSION_REMOVE: + case EXTENSION_REPLACE: + status = Status.EXTENSION_REPLACE; + break; + case EXTENSION_OVERRIDE: + case EXTENSION_OVERPENDING: + status = Status.EXTENSION_OVERPENDING; + break; + case EXTENSION_INSTALL: + break; + } + } + CompilerElement newEl = + new CompilerElement(docInfo.getFormalName(), docInfo.getType(), status); + newEl.xmlString = document; + if (pos >= 0) { + tableElements.set(pos, newEl); + } + else { + tableElements.add(newEl); + } + tableElements.sort(null); + changesMade(true); + tableModel.fireTableDataChanged(); + } + catch (Exception e) { + errMessage = e; + } + if (errMessage != null) { + Msg.showError(this, this, "Import Failure", errMessage.getMessage(), errMessage); + return; + } + } + + private String getXmlString(CompilerElement element) { + CompilerSpec compilerSpec = program.getCompilerSpec(); + PcodeInjectLibrary injectLibrary = compilerSpec.getPcodeInjectLibrary(); + InjectPayload payload; + PrototypeModel model; + String resultString = null; + if (element.status == Status.CORE) { + StringBuilder buffer = new StringBuilder(); + switch (element.type) { + case CALL_FIXUP: + payload = injectLibrary.getPayload(InjectPayload.CALLFIXUP_TYPE, element.name); + if (payload != null) { + payload.saveXml(buffer); + } + break; + case CALLOTHER_FIXUP: + payload = + injectLibrary.getPayload(InjectPayload.CALLOTHERFIXUP_TYPE, element.name); + if (payload != null) { + payload.saveXml(buffer); + } + break; + case PROTOTYPE_MODEL: + case MERGE_MODEL: + model = compilerSpec.getCallingConvention(element.name); + if (model != null) { + model.saveXml(buffer, injectLibrary); + } + break; + } + resultString = buffer.toString(); + if (resultString.length() == 0) { + resultString = null; + } + } + else { + resultString = + SpecExtension.getCompilerSpecExtension(program, element.type, element.name); + } + return resultString; + } + + private void exportExtension() { + CompilerElement compilerElement = getSelectedCompilerElement(); + if (compilerElement == null) { + return; + } + if (!compilerElement.isExisting()) { + return; // Only export existing elements + } + String suggestedName = compilerElement.name + PREFERENCES_FILE_EXTENSION; + File outputFile = getFileFromUser(suggestedName); + if (outputFile == null) { + return; + } + if (outputFile.exists()) { + int userChoice = OptionDialog.showYesNoDialog(this, "File exists.", + "Overwrite " + outputFile.getName() + " ?"); + if (userChoice != OptionDialog.OPTION_ONE) { + return; + } + } + String exportString = getXmlString(compilerElement); + String errMessage = null; + if (exportString == null) { + errMessage = "Unable to build document for " + compilerElement.name; + } + else { + FileWriter writer = null; + try { + writer = new FileWriter(outputFile); + writer.write(exportString); + writer.close(); + } + catch (IOException ex) { + errMessage = "Failed to write to file: " + ex.getMessage(); + } + } + if (errMessage != null) { + Msg.showError(this, this, "Export Failure", errMessage); + } + } + + /** + * Present the user with a confirmation dialog. If confirmed, mark + * the selected element for removal. + */ + private void removeExtension() { + if (!program.hasExclusiveAccess()) { + Msg.showError(this, this, "Remove Failure", + "Must have an exclusive checkout to remove an extension"); + return; + } + CompilerElement compilerElement = getSelectedCompilerElement(); + if (compilerElement == null) { + return; + } + if (compilerElement.status == Status.EXTENSION || + compilerElement.status == Status.EXTENSION_ERROR) { + int userChoice = OptionDialog.showYesNoDialog(this, "Remove Extension?", + "Mark the extension " + compilerElement.name + " for removal?"); + if (userChoice != OptionDialog.OPTION_ONE) { + return; + } + } + else if (compilerElement.status == Status.EXTENSION_OVERRIDE) { + int userChoice = OptionDialog.showYesNoDialog(this, "Remove Override?", + "Mark the override " + compilerElement.name + " for removal?"); + if (userChoice != OptionDialog.OPTION_ONE) { + return; + } + } + else { + return; + } + compilerElement.status = Status.EXTENSION_REMOVE; + extensionTable.clearSelection(); + changesMade(true); + tableModel.fireTableDataChanged(); + } + + private CompilerElement getSelectedCompilerElement() { + if (selectionModel.isSelectionEmpty()) { + return null; + } + int selectedRow = extensionTable.getSelectedRow(); + return tableElements.get(selectedRow); + } + + private JPanel createButtonPanel() { + JButton importButton = new JButton("Import..."); + importButton.setToolTipText("Load extension from an XML file"); + importButton.addActionListener(event -> { + // give Swing a chance to repaint + Swing.runLater(() -> { + extensionTable.clearSelection(); + importExtension(); + }); + }); + + exportButton = new JButton("Export..."); + exportButton.setToolTipText("Export extensions to an XML file"); + exportButton.addActionListener(event -> { + // give Swing a chance to repaint + Swing.runLater(() -> { + exportExtension(); + }); + }); + + removeButton = new JButton("Remove"); + removeButton.setToolTipText("Remove an existing extension"); + removeButton.addActionListener(event -> { + // give Swing a chance to repaint + Swing.runLater(() -> { + removeExtension(); + }); + }); + + JPanel containerPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + containerPanel.add(importButton); + containerPanel.add(exportButton); + containerPanel.add(removeButton); + + return containerPanel; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/template/ConstructTpl.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/template/ConstructTpl.java index 2425f10c37..fbb6fd6f38 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/template/ConstructTpl.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/template/ConstructTpl.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,47 +19,90 @@ */ package ghidra.app.plugin.processors.sleigh.template; +import java.util.ArrayList; + import ghidra.program.model.address.AddressFactory; import ghidra.program.model.lang.UnknownInstructionException; import ghidra.util.xml.SpecXmlUtils; import ghidra.xml.XmlElement; import ghidra.xml.XmlPullParser; -import java.util.ArrayList; - /** + * A constructor template, representing the semantic action of a SLEIGH constructor, without + * its final context. The constructor template is made up of a list of p-code op templates, + * which are in turn made up of varnode templates. + * This is one step removed from the final array of PcodeOp objects, but: + * - Constants may still need to incorporate context dependent address resolution and relative offsets. + * - Certain p-code operations may still need expansion to include a dynamic LOAD or STORE operation. + * - The list may hold "build" directives for sub-constructor templates. + * - The list may still hold "label" information for the final resolution of relative jump offsets. * - * - * Placeholder for what resolves to a list of PcodeOps and - * a FixedHandle. It represents the semantic action of a constructor - * and its return value for a particular InstructionContext + * The final PcodeOps are produced by handing this to the build() method of PcodeEmit which has + * the InstructionContext necessary for final resolution. */ public class ConstructTpl { - private int numlabels=0; + private int numlabels = 0; // Number of relative-offset labels in this template private OpTpl[] vec; // The semantic action of constructor private HandleTpl result; // The final semantic value - + + /** + * Constructor for use with restoreXML + */ public ConstructTpl() { } - - public int getNumLabels() { return numlabels; } - public OpTpl[] getOpVec() { return vec; } - public HandleTpl getResult() { return result; } - - public int restoreXml(XmlPullParser parser,AddressFactory factory) throws UnknownInstructionException { + + /** + * Manually build a constructor template. This is useful for building constructor templates + * outside of the normal SLEIGH pipeline, as for an internally created InjectPayload. + * @param opvec is the list of p-code op templates making up the constructor + */ + public ConstructTpl(OpTpl[] opvec) { + vec = opvec; + result = null; + } + + /** + * @return the number of labels needing resolution in this template + */ + public int getNumLabels() { + return numlabels; + } + + /** + * @return the list of p-code op templates making up this constructor template + */ + public OpTpl[] getOpVec() { + return vec; + } + + /** + * @return the (possibly dynamic) location of the final semantic value produced by this constructor + */ + public HandleTpl getResult() { + return result; + } + + /** + * Restore this template from a \ tag in an XML stream. + * @param parser is the XML stream + * @param factory is for manufacturing Address objects + * @return the constructor section id described by the tag + * @throws UnknownInstructionException if the p-code templates contain unknown op-codes + */ + public int restoreXml(XmlPullParser parser, AddressFactory factory) + throws UnknownInstructionException { int sectionid = -1; - XmlElement el = parser.start("construct_tpl"); -// String delaystr = el.getAttribute("delay"); -// if (delaystr != null) -// delayslot = SpecXmlUtils.decodeInt(delaystr); + XmlElement el = parser.start("construct_tpl"); String nmlabelstr = el.getAttribute("labels"); - if (nmlabelstr != null) + if (nmlabelstr != null) { numlabels = SpecXmlUtils.decodeInt(nmlabelstr); + } String sectionidstr = el.getAttribute("section"); - if (sectionidstr != null) + if (sectionidstr != null) { sectionid = SpecXmlUtils.decodeInt(sectionidstr); + } XmlElement handel = parser.peek(); if (handel.getName().equals("null")) { result = null; @@ -68,12 +110,12 @@ public class ConstructTpl { } else { result = new HandleTpl(); - result.restoreXml(parser,factory); + result.restoreXml(parser, factory); } - ArrayList oplist = new ArrayList(); - while(!parser.peek().isEnd()) { + ArrayList oplist = new ArrayList<>(); + while (!parser.peek().isEnd()) { OpTpl op = new OpTpl(); - op.restoreXml(parser,factory); + op.restoreXml(parser, factory); oplist.add(op); } vec = new OpTpl[oplist.size()]; @@ -81,5 +123,5 @@ public class ConstructTpl { parser.end(el); return sectionid; } - + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramCompilerSpec.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramCompilerSpec.java new file mode 100644 index 0000000000..ff77329f2a --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramCompilerSpec.java @@ -0,0 +1,374 @@ +/* ### + * 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.program.database; + +import java.beans.PropertyEditor; +import java.util.*; + +import docking.options.editor.StringWithChoicesEditor; +import generic.stl.Pair; +import ghidra.framework.options.OptionType; +import ghidra.framework.options.Options; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.Program; +import ghidra.util.*; + +/** + * A Program specific version of the CompilerSpec. + * + * Every Program owns a specific ProgramCompilerSpec. It is based on a CompilerSpec + * returned by the Language assigned to the Program, but it may include extensions. + * Extensions are currently either a new form of: + * - PrototypeModel or + * - InjectPayload + * + * Extensions can be installed or removed from a ProgramDB via the Options mechanism (See SpecExtension) + * using SpecExtension.addReplaceCompilerSpecExtension() or SpecExtension.removeCompilerSpecExtension(). + * + * ProgramCompilerSpec allows the static evaluation models, described by the underlying BasicCompilerSpec + * and returned by getPrototypeEvaluationModel(), to be overridden by Program specific options. + * + * getDecompilerOutputLanguage() queries the Program specific language the decompiler should use as output. + * + * installExtensions() is the main entry point for integrating the Program Options + * with the Language's base CompilerSpec and producing a complete in-memory CompilerSpec for the Program. + * + */ +public class ProgramCompilerSpec extends BasicCompilerSpec { + + public static final String DECOMPILER_PROPERTY_LIST_NAME = "Decompiler"; + public static final String DECOMPILER_OUTPUT_LANGUAGE = "Output Language"; + public final static DecompilerLanguage DECOMPILER_OUTPUT_DEF = DecompilerLanguage.C_LANGUAGE; + public final static String DECOMPILER_OUTPUT_DESC = + "Select the source language output by the decompiler."; + + public static final String EVALUATION_MODEL_PROPERTY_NAME = "Prototype Evaluation"; + + private Program program; // Program owning this compiler spec + private Map usermodels = null; + private int versionCounter = 0; // Version number among all cspec variants for the same Program + + /** + * Construct the CompilerSpec for a Program based on a Language CompilerSpec + * @param program is the Program + * @param langSpec is the CompilerSpec from Language to base this on + */ + public ProgramCompilerSpec(Program program, CompilerSpec langSpec) { + super((BasicCompilerSpec) langSpec); + this.program = program; + if (langSpec instanceof ProgramCompilerSpec) { + throw new IllegalArgumentException( + "Cannot instantiate ProgramCompilerSpec from another ProgramCompilerSpec"); + } + } + + /** + * Adds and enables an option to have the decompiler display java. + * @param program to be enabled + */ + public static void enableJavaLanguageDecompilation(Program program) { + Options decompilerPropertyList = program.getOptions(DECOMPILER_PROPERTY_LIST_NAME); + decompilerPropertyList.registerOption(DECOMPILER_OUTPUT_LANGUAGE, DECOMPILER_OUTPUT_DEF, + null, DECOMPILER_OUTPUT_DESC); + decompilerPropertyList.setEnum(DECOMPILER_OUTPUT_LANGUAGE, + DecompilerLanguage.JAVA_LANGUAGE); + } + + @Override + public DecompilerLanguage getDecompilerOutputLanguage() { + Options options = program.getOptions(DECOMPILER_PROPERTY_LIST_NAME); + if (options.contains(DECOMPILER_OUTPUT_LANGUAGE)) { + return options.getEnum(DECOMPILER_OUTPUT_LANGUAGE, DECOMPILER_OUTPUT_DEF); + } + return DECOMPILER_OUTPUT_DEF; + } + + /** + * Install a new set of user-defined (extension) prototype models. + * All the models from the compiler spec are preserved. Any old user-defined + * models are removed or replaced. + * @param extensions is the list of new user-defined models + */ + private void installPrototypeExtensions(List extensions) { + if (usermodels == null) { + if (extensions.isEmpty()) { + return; // No change to prototypes + } + usermodels = new TreeMap<>(); + } + ArrayList finalList = new ArrayList<>(); + TreeSet currentNames = new TreeSet<>(); + for (PrototypeModel model : allmodels) { + currentNames.add(model.getName()); + if (usermodels.containsKey(model.getName())) { + continue; + } + finalList.add(model); // Add original non-userdef models + } + + for (PrototypeModel model : extensions) { + if (currentNames.contains(model.getName())) { + if (!usermodels.containsKey(model.getName())) { + Msg.warn(this, + "Cannot override prototype model " + model.getName() + " with extension"); + continue; + } + } + markPrototypeAsExtension(model); + finalList.add(model); + usermodels.put(model.getName(), model); + } + String defaultName = null; + String evalName = null; + String evalCalledName = null; + if (defaultModel != null) { + defaultName = defaultModel.getName(); + } + if (evalCurrentModel != null) { + evalName = evalCurrentModel.getName(); + } + if (evalCalledModel != null) { + evalCalledName = evalCalledModel.getName(); + } + modelXrefs(finalList, defaultName, evalName, evalCalledName); + if (usermodels.isEmpty()) { + usermodels = null; + } + } + + /** + * Add a new PrototypeModel to the list of extensions with errors + * @param errList is the list of errors + * @param model is the PrototypeModel with errors + * @return the updated list + */ + private static ArrayList addPrototypeError(ArrayList errList, + PrototypeModel model) { + if (model.isErrorPlaceholder()) { + if (errList == null) { + errList = new ArrayList<>(); + } + else if (errList.size() > 4) { // Only accumulate up to 5 + errList.add("..."); + return errList; + } + String message = "prototype: " + model.getName(); + errList.add(message); + } + return errList; + } + + /** + * Add a new InjectPayload to the list of extensions with errors + * @param errList is the list of errors + * @param payload is the InjectPayload with errors + * @return the updated list + */ + private static ArrayList addPayloadError(ArrayList errList, + InjectPayload payload) { + if (payload.isErrorPlaceholder()) { + if (errList == null) { + errList = new ArrayList<>(); + } + else if (errList.size() > 4) { // Only accumulate up to 5 + errList.add("..."); + return errList; + } + String message; + if (payload instanceof InjectPayloadCallfixup) { + message = "callfixup: " + payload.getName(); + } + else { + message = "callotherfixup: " + payload.getName(); + } + errList.add(message); + } + return errList; + } + + /** + * Update the choices presented for evaluation model program option. + */ + private void updateModelChoices() { + Options decompilerPropertyList = program.getOptions(DECOMPILER_PROPERTY_LIST_NAME); + PropertyEditor editor = + decompilerPropertyList.getRegisteredPropertyEditor(EVALUATION_MODEL_PROPERTY_NAME); + if (editor == null) { + return; + } + if (!(editor instanceof StringWithChoicesEditor)) { + return; + } + String[] evalChoices = establishEvaluationModelChoices(evalCurrentModel); + StringWithChoicesEditor choiceEditor = (StringWithChoicesEditor) editor; + choiceEditor.setChoices(evalChoices); + } + + /** + * Report any extensions that have parse errors + * @param errorList is the list of extensions (or null) + */ + private void reportExtensionErrors(ArrayList errorList) { + if (errorList == null) { + return; + } + StringBuilder buffer = new StringBuilder(); + buffer.append("User-defined extensions failed to parse: "); + buffer.append("
    "); + for (String line : errorList) { + buffer.append("
  • ").append(line).append("
  • "); + } + buffer.append("
"); + buffer.append("See Program Options - Specification Extensions"); + Msg.showError(BasicCompilerSpec.class, null, "Specification Extension Errors", + buffer.toString()); + } + + /** + * Update this object with any program specific compiler specification extensions. + */ + protected void installExtensions() { + int storedVersion = SpecExtension.getVersionCounter(program); + if (storedVersion == versionCounter) { + return; // We currently match stored version, nothing to update + } + versionCounter = storedVersion; // Update ourselves to stored version + List> pairList = SpecExtension.getCompilerSpecExtensions(program); + if (pairList.isEmpty() && usermodels == null && pcodeInject.getProgramPayloads() == null) { + return; // No change + } + if (usermodels != null) { + removeProgramMechanismPayloads(usermodels.values()); + } + ArrayList modelExtensions = new ArrayList<>(); + ArrayList injectExtensions = new ArrayList<>(); + ArrayList errorList = null; + for (Pair pair : pairList) { + try { + Object obj = SpecExtension.parseExtension(pair.first, pair.second, this, true); + if (obj instanceof PrototypeModel) { + PrototypeModel prototypeModel = (PrototypeModel) obj; + modelExtensions.add(prototypeModel); + errorList = addPrototypeError(errorList, prototypeModel); + } + else if (obj instanceof InjectPayloadSleigh) { + InjectPayloadSleigh payload = (InjectPayloadSleigh) obj; + injectExtensions.add(payload); + errorList = addPayloadError(errorList, payload); + } + } + catch (Exception e) { + Msg.error(this, + "Bad compiler spec extension: " + pair.first + " - " + e.getMessage()); + } + } + installPrototypeExtensions(modelExtensions); + registerProgramInject(injectExtensions); + updateModelChoices(); + reportExtensionErrors(errorList); + } + + /** + * Build up the choice strings for all the evaluation methods + */ + private String[] establishEvaluationModelChoices(PrototypeModel defaultEval) { + + String[] evalChoices = new String[allmodels.length]; + // Make sure the default evaluation model occurs at the top of the list + int defaultnum = -1; + for (int i = 0; i < allmodels.length; ++i) { + PrototypeModel curModel = allmodels[i]; + evalChoices[i] = curModel.getName(); + if (curModel == defaultEval) { + defaultnum = i; + } + } + + if (defaultnum > 0) { + String tmp = evalChoices[defaultnum]; + for (int i = defaultnum; i > 0; --i) { + // Push everybody down to make room for default at top + evalChoices[i] = evalChoices[i - 1]; + } + evalChoices[0] = tmp; + } + return evalChoices; + } + + @Override + public PrototypeModel getPrototypeEvaluationModel(EvaluationModelType modelType) { + + Options options = program.getOptions(DECOMPILER_PROPERTY_LIST_NAME); + switch (modelType) { + case EVAL_CURRENT: + String name = + options.getString(EVALUATION_MODEL_PROPERTY_NAME, evalCurrentModel.getName()); + for (PrototypeModel model : allmodels) { // Could be a merge model + if (model.getName().equals(name)) { + return model; + } + } + break; + case EVAL_CALLED: + return evalCalledModel; // TODO: Currently no option + } + return null; + } + + /** + * Register program-specific compiler-spec options + */ + protected void registerProgramOptions() { + + // NOTE: Any changes to the option name/path must be handled carefully since + // old property values will remain in the program. There is currently no support + // for upgrading/moving old property values. + + String[] evalChoices = establishEvaluationModelChoices(evalCurrentModel); + Options decompilerPropertyList = program.getOptions(DECOMPILER_PROPERTY_LIST_NAME); + decompilerPropertyList + .setOptionsHelpLocation(new HelpLocation("DecompilePlugin", "ProgramOptions")); + decompilerPropertyList.registerOption(EVALUATION_MODEL_PROPERTY_NAME, + OptionType.STRING_TYPE, evalChoices[0], + new HelpLocation("DecompilePlugin", "OptionProtoEval"), + "Select the default function prototype/evaluation model to be used during Decompiler analysis", + new StringWithChoicesEditor(evalChoices)); + + // TODO: registration of DECOMPILER_OUTPUT_LANGUAGE option should be tied to Processor + // and not presence of stored option. + if (decompilerPropertyList.contains(DECOMPILER_OUTPUT_LANGUAGE)) { + decompilerPropertyList.registerOption(DECOMPILER_OUTPUT_LANGUAGE, DECOMPILER_OUTPUT_DEF, + null, DECOMPILER_OUTPUT_DESC); + } + + Options analysisPropertyList = + program.getOptions(Program.ANALYSIS_PROPERTIES + ".Decompiler Parameter ID"); + analysisPropertyList.createAlias(EVALUATION_MODEL_PROPERTY_NAME, decompilerPropertyList, + EVALUATION_MODEL_PROPERTY_NAME); + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) { + return false; + } + ProgramCompilerSpec op2 = (ProgramCompilerSpec) obj; + if (!SystemUtilities.isEqual(usermodels, op2.usermodels)) { + return false; + } + return true; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java index 237f44302d..0492f9254b 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java @@ -98,8 +98,9 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM * created tables. * 18-Feb-2021 - version 23 Added support for Big Reflist for tracking FROM references. * Primarily used for large numbers of Entry Point references. + * 31-Mar-2021 - version 24 Added support for CompilerSpec extensions */ - static final int DB_VERSION = 23; + static final int DB_VERSION = 24; /** * UPGRADE_REQUIRED_BFORE_VERSION should be changed to DB_VERSION anytime the @@ -189,7 +190,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM private ProgramUserDataDB programUserData; private Table table; private Language language; - private CompilerSpec compilerSpec; + private ProgramCompilerSpec compilerSpec; private LanguageID languageID; private CompilerSpecID compilerSpecID; @@ -219,7 +220,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM super(new DBHandle(), name, 500, 1000, consumer); this.language = language; - this.compilerSpec = compilerSpec; + this.compilerSpec = new ProgramCompilerSpec(this, compilerSpec); languageID = language.getLanguageID(); compilerSpecID = compilerSpec.getCompilerSpecID(); @@ -244,7 +245,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM programUserData = new ProgramUserDataDB(this); endTransaction(id, true); clearUndo(false); - compilerSpec.registerProgramOptions(this); + this.compilerSpec.registerProgramOptions(); getCodeManager().activateContextLocking(); success = true; } @@ -353,7 +354,9 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM recordChanges = true; endTransaction(id, true); clearUndo(false); - compilerSpec.registerProgramOptions(this); + SpecExtension.checkFormatVersion(this); + compilerSpec.installExtensions(); + compilerSpec.registerProgramOptions(); getCodeManager().activateContextLocking(); success = true; } @@ -374,12 +377,13 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM * @throws CompilerSpecNotFoundException if the compiler spec cannot be found */ private void initCompilerSpec() throws CompilerSpecNotFoundException { + CompilerSpec langSpec; try { if (languageUpgradeTranslator != null) { - compilerSpec = languageUpgradeTranslator.getOldCompilerSpec(compilerSpecID); + langSpec = languageUpgradeTranslator.getOldCompilerSpec(compilerSpecID); } else { - compilerSpec = language.getCompilerSpecByID(compilerSpecID); + langSpec = language.getCompilerSpecByID(compilerSpecID); } } catch (CompilerSpecNotFoundException e) { @@ -387,12 +391,13 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM "Compiler Spec " + compilerSpecID + " for Language " + language.getLanguageDescription().getDescription() + " Not Found, using default: " + e); - compilerSpec = language.getDefaultCompilerSpec(); + langSpec = language.getDefaultCompilerSpec(); if (compilerSpec == null) { throw e; } compilerSpecID = compilerSpec.getCompilerSpecID(); } + compilerSpec = new ProgramCompilerSpec(this, langSpec); } /** @@ -412,8 +417,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM Language newLanguage = language; Language oldLanguage = OldLanguageFactory.getOldLanguageFactory() - .getOldLanguage( - languageID, languageVersion); + .getOldLanguage(languageID, languageVersion); if (oldLanguage == null) { // Assume minor version behavior - old language does not exist for current major version Msg.error(this, "Old language specification not found: " + languageID + @@ -422,10 +426,8 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM } // Ensure that we can upgrade the language - languageUpgradeTranslator = - LanguageTranslatorFactory.getLanguageTranslatorFactory() - .getLanguageTranslator( - oldLanguage, newLanguage); + languageUpgradeTranslator = LanguageTranslatorFactory.getLanguageTranslatorFactory() + .getLanguageTranslator(oldLanguage, newLanguage); if (languageUpgradeTranslator == null) { // TODO: This is a bad situation!! Most language revisions should be supportable, if not we have no choice but to throw @@ -467,10 +469,8 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM private VersionException checkForLanguageChange(LanguageNotFoundException e, int openMode) throws LanguageNotFoundException { - languageUpgradeTranslator = - LanguageTranslatorFactory.getLanguageTranslatorFactory() - .getLanguageTranslator( - languageID, languageVersion); + languageUpgradeTranslator = LanguageTranslatorFactory.getLanguageTranslatorFactory() + .getLanguageTranslator(languageID, languageVersion); if (languageUpgradeTranslator == null) { throw e; } @@ -1201,8 +1201,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM * @throws MemoryConflictException if image base override is active */ public AddressSpace addOverlaySpace(String blockName, AddressSpace originalSpace, - long minOffset, long maxOffset) - throws LockException, MemoryConflictException { + long minOffset, long maxOffset) throws LockException, MemoryConflictException { checkExclusiveAccess(); if (imageBaseOverride) { @@ -1838,6 +1837,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM for (int i = 0; i < NUM_MANAGERS; i++) { managers[i].invalidateCache(all); } + compilerSpec.installExtensions(); // Reload any extensions } catch (IOException e) { dbError(e); @@ -2002,8 +2002,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM } LanguageTranslator languageTranslator = LanguageTranslatorFactory.getLanguageTranslatorFactory() - .getLanguageTranslator(language, - newLanguage); + .getLanguageTranslator(language, newLanguage); if (languageTranslator == null) { throw new IncompatibleLanguageException("Language translation not supported"); } @@ -2054,7 +2053,8 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM } if (newCompilerSpecID != null) { - compilerSpec = language.getCompilerSpecByID(newCompilerSpecID); + compilerSpec = new ProgramCompilerSpec(this, + language.getCompilerSpecByID(newCompilerSpecID)); } compilerSpecID = compilerSpec.getCompilerSpecID(); languageVersion = language.getVersion(); @@ -2459,4 +2459,18 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM contextMgr.flushProcessorContextWriteCache(); super.flushWriteCache(); } + + /** + * Install updated compiler spec extension options. + * See {@link SpecExtension}. + */ + protected void installExtensions() { + lock.acquire(); + try { + compilerSpec.installExtensions(); + } + finally { + lock.release(); + } + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/SpecExtension.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/SpecExtension.java new file mode 100644 index 0000000000..8361fb7586 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/SpecExtension.java @@ -0,0 +1,785 @@ +/* ### + * 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.program.database; + +import java.util.ArrayList; +import java.util.List; + +import org.xml.sax.*; + +import generic.stl.Pair; +import ghidra.app.plugin.processors.sleigh.*; +import ghidra.framework.options.Options; +import ghidra.framework.store.LockException; +import ghidra.program.model.lang.*; +import ghidra.program.model.lang.CompilerSpec.EvaluationModelType; +import ghidra.program.model.listing.*; +import ghidra.util.*; +import ghidra.util.exception.*; +import ghidra.util.task.TaskMonitor; +import ghidra.xml.*; + +/** + * Utility class for installing/removing "specification extensions" to a Program. + * A specification extension is a program specific version of either a: + * - Prototype Model + * - Call Fixup or + * - Callother Fixup + * Normally these objects are provided by the language specific configuration files (.cspec or .pspec), + * but this class allows additional objects to be added that are specific to the program. + * + * Internally, each spec extension is stored as an XML document as a formal Program Option. Each type of + * extension is described by a specific XML tag and is parsed as it would be in a .cspec or .pspec file. + * The XML tags are: + * - \ - describing a Call Fixup + * - \ - describing a Callother Fixup + * - \ - describing a typical Prototype Model + * - \ - describing a Prototype Model merged from other models + * + * Each type of object has a unique name or target, which must be specified as part of the XML tag, + * which is referred to in this class as the extension's "formal name". In the \ tag, + * the formal name is given by the "targetop" attribute; for all the other tags, the formal name is + * given by the "name" attribute". + * + * The parent option for all extensions is given by the static field SPEC_EXTENSION. Under the parent + * option, each extension is stored as a string with an option name, constructed by + * concatenating the extension's formal name with a prefix corresponding to the extension's XML tag name. + * + * testExtensionDocument() is used independently to extensively test whether a document + * describes a valid extension. + * + * Extensions are installed on a program via addReplaceCompilerSpecExtension(). + * Extensions are removed from a program via removeCompilerSpecExtension(). + */ +public class SpecExtension { + + public final static String SPEC_EXTENSION = "Specification Extensions"; + public final static String FORMAT_VERSION_OPTIONNAME = "FormatVersion"; + public final static String VERSION_COUNTER_OPTIONNAME = "VersionCounter"; + public final static int FORMAT_VERSION = 1; // Current version of specification XML format + private ProgramDB program; + private SleighLanguageValidator cspecValidator = null; + + /** + * The possible types of spec extensions. + */ + public enum Type { + // The order is used to sort tables of extensions + PROTOTYPE_MODEL("prototype"), + MERGE_MODEL("resolve"), + CALL_FIXUP("callfixup"), + CALLOTHER_FIXUP("callotherfixup"); + + private String tagName; + + private Type(String nm) { + tagName = nm; + } + + /** + * Get the XML tag name associated with the specific extension type. + * @return the tag name + */ + public String getTagName() { + return tagName; + } + + /** + * For a given extension's formal name, generate the option name used to store the extension. + * The option name is the tag name concatenated with the formal name, separated by '_' + * @param formalName is the formal name of the extension + * @return the option name + */ + public String getOptionName(String formalName) { + return tagName + '_' + formalName; + } + } + + /** + * Helper class for collecting information about an extension XML document + * and constructing its option name for storage + */ + public static class DocInfo { + private Type type; // Type of extension + private String formalName; // Formal name extracted from the document + private String optionName; // Option name used to store document + private boolean override; // true if the extension overrides a core document + + private static String generateOptionNameFromDocument(Type type, String document) + throws SleighException { + int startPos, endPos; + String tagAttribute; + switch (type) { + case PROTOTYPE_MODEL: + case MERGE_MODEL: + case CALL_FIXUP: + tagAttribute = "name=\""; + break; + case CALLOTHER_FIXUP: + tagAttribute = "targetop=\""; + break; + default: + throw new SleighException("Unsupported extension type"); + } + startPos = document.indexOf(tagAttribute, 0); + if (startPos < 0) { + throw new SleighException("Could not find attribute: " + tagAttribute); + } + startPos += tagAttribute.length(); + endPos = document.indexOf('\"', startPos); + if (endPos < 0) { + throw new SleighException("Bad XML document"); + } + String formalName = document.substring(startPos, endPos); + if (!isValidFormalName(formalName)) { + throw new SleighException("Name of extension uses invalid characters"); + } + return type.getOptionName(formalName); + } + + /** + * Construct by directly pulling information from the XML document + * @param document is the entire XML document as a String + */ + public DocInfo(String document) { + type = getExtensionType(document, true); + optionName = generateOptionNameFromDocument(type, document); + formalName = SpecExtension.getFormalName(optionName); + override = false; + } + + /** + * @return the Type of the extension + */ + public final Type getType() { + return type; + } + + /** + * @return the formal name of the extension + */ + public final String getFormalName() { + return formalName; + } + + /** + * @return the option name associated with the extension + */ + public final String getOptionName() { + return optionName; + } + + /** + * @return true if the extension overrides a core object + */ + public final boolean isOverride() { + return override; + } + } + + /** + * Construct an extension manager attached to a specific program. + * Multiple add/remove/test actions can be performed. Validator state is cached between calls. + * @param program is the specific Program + */ + public SpecExtension(Program program) { + if (!(program instanceof ProgramDB)) { + throw new IllegalArgumentException("only normal program supported"); + } + this.program = (ProgramDB) program; + } + + /** + * Get the extension type either from the XML tag name or the option name + * @param nm is the XML tag or option name + * @param isXML is true for an XML tag, false for an option name + * @return the extension type + * @throws SleighException if no type matches the name + */ + public static Type getExtensionType(String nm, boolean isXML) throws SleighException { + int pos = 0; + if (isXML) { + while (pos + 1 < nm.length() && (nm.charAt(pos) != '<' || nm.charAt(pos + 1) == '?') || + nm.charAt(pos + 1) == '!') { + pos += 1; + } + pos += 1; + } + if (nm.length() <= pos) { + throw new SleighException("Unrecognized extension"); + } + switch (nm.charAt(pos)) { + case 'c': + if (nm.startsWith(Type.CALL_FIXUP.getTagName(), pos)) { + return Type.CALL_FIXUP; + } + else if (nm.startsWith(Type.CALLOTHER_FIXUP.getTagName(), pos)) { + return Type.CALLOTHER_FIXUP; + } + break; + case 'p': + if (nm.startsWith(Type.PROTOTYPE_MODEL.getTagName(), pos)) { + return Type.PROTOTYPE_MODEL; + } + break; + case 'r': + if (nm.startsWith(Type.MERGE_MODEL.getTagName(), pos)) { + return Type.MERGE_MODEL; + } + break; + } + throw new SleighException("Unrecognized extension"); + } + + /** + * Check if the given option name corresponds to an extension + * @param nm is the given option name + * @return true if the name labels a spec extension + */ + private static boolean isCompilerProperty(String nm) { + try { + getExtensionType(nm, false); + return true; + } + catch (SleighException ex) { + return false; + } + } + + /** + * Get version of CompilerSpec extensions stored with the Program + * @param program is the given Program + * @return the version number + */ + public static int getVersionCounter(Program program) { + Options options = program.getOptions(SPEC_EXTENSION); + return options.getInt(VERSION_COUNTER_OPTIONNAME, 0); + } + + /** + * Get all compiler spec extensions for the program. The extensions are XML documents + * strings, with an associated "option name" string. + * Return a list of (optionname,document) pairs, which may be empty + * @param program is the Program to get extensions for + * @return the list of (optionname,document) pairs + */ + public static List> getCompilerSpecExtensions(Program program) { + Options options = program.getOptions(SPEC_EXTENSION); + List optionNames = options.getOptionNames(); + ArrayList> pairList = new ArrayList<>(); + for (String optionName : optionNames) { + if (isCompilerProperty(optionName)) { + String value = options.getString(optionName, null); + if (value != null) { + pairList.add(new Pair<>(optionName, value)); + } + } + } + return pairList; + } + + /** + * Get the raw string making up an extension, given its type and name + * @param program is the program to extract the extension from + * @param type is the type of extension + * @param name is the formal name of the extension + * @return the extension string or null + */ + public static String getCompilerSpecExtension(Program program, Type type, String name) { + String optionName = type.getOptionName(name); + Options options = program.getOptions(SPEC_EXTENSION); + return options.getString(optionName, null); + } + + /** + * Check the format version for spec extensions for a given program. + * If the program reports a version that does not match the current + * number attached to the running tool (FORMAT_VERSION), a VersionException is thrown + * @param program is the given Program + * @throws VersionException the reported version does not match the tool + */ + public static void checkFormatVersion(Program program) throws VersionException { + Options options = program.getOptions(SPEC_EXTENSION); + int formatVersion = options.getInt(FORMAT_VERSION_OPTIONNAME, 0); + if (formatVersion > FORMAT_VERSION) { + throw new VersionException("Program contains spec extensions with newer/unknown format", + VersionException.NEWER_VERSION, false); + } + } + + /** + * Register the options system allowing spec extensions with the given Program + * @param program is the given Program + */ + public static void registerOptions(Program program) { + if (!(program instanceof ProgramDB)) { + Msg.error(SpecExtension.class, "Can only add extensions on a normal program"); + return; + } + if (!SystemUtilities.isInHeadlessMode()) { + Options options = program.getOptions(SPEC_EXTENSION); + options.setOptionsHelpLocation(new HelpLocation("DecompilePlugin", "ExtensionOptions")); + options.registerOptionsEditor(new SpecExtensionEditor((ProgramDB) program)); + } + } + + /** + * Get the formal name of an extension from its option name. + * @param optionName is the option name + * @return the formal name + */ + public static String getFormalName(String optionName) { + return optionName.substring(optionName.indexOf('_') + 1); + } + + /** + * Determine if the desired formal name is a valid identifier + * @param formalName is the formal name to check + * @return true if the name is valid + */ + public static boolean isValidFormalName(String formalName) { + if (formalName.length() == 0) { + return false; + } + for (int i = 0; i < formalName.length(); ++i) { + char c = formalName.charAt(i); + if (!Character.isLetterOrDigit(c) && c != '_' && c != '.' && c != '-') { + return false; + } + } + return true; + } + + /** + * Generate an XML error handler suitable for parsing a specification document. + * - Warnings are logged. + * - Errors cause a SAXParseException + * + * @param docTitle is the title of the document + * @return the error handler object + */ + private static ErrorHandler getErrorHandler(String docTitle) { + ErrorHandler errHandler = new ErrorHandler() { + @Override + public void error(SAXParseException exception) throws SAXException { + throw exception; + } + + @Override + public void fatalError(SAXParseException exception) throws SAXException { + throw exception; + } + + @Override + public void warning(SAXParseException exception) throws SAXException { + Msg.warn(this, "Warning parsing '" + docTitle + "'", exception); + } + }; + return errHandler; + } + + /** + * Parse an XML string and build the corresponding compiler spec extension object. + * Currently this can either be a + * - PrototypeModel or + * - InjectPayload + * + * For InjectPayloadCallfixup or InjectPayloadCallother, the p-code \ tag + * is also parsed, and the caller can control whether any parse errors + * cause an exception or whether a dummy payload is provided instead. + * @param optionName is the option name the extension is attached to + * @param extension is the XML document as a String + * @param cspec is the compiler spec the new extension is for + * @param provideDummy if true, provide a dummy payload if necessary + * @return the extension object + * @throws SAXException is there are XML format errors + * @throws XmlParseException if the XML document is badly formed + * @throws SleighException if internal p-code does not parse + */ + public static Object parseExtension(String optionName, String extension, CompilerSpec cspec, + boolean provideDummy) throws SAXException, XmlParseException, SleighException { + ErrorHandler errHandler = getErrorHandler("extensions"); + XmlPullParser parser = + XmlPullParserFactory.create(extension, optionName, errHandler, false); + String elName = parser.peek().getName(); + if (elName.endsWith("prototype")) { + PrototypeModel model; + if (parser.peek().getName().equals("resolveprototype")) { + PrototypeModelMerged mergemodel = new PrototypeModelMerged(); + ArrayList curModels = + new ArrayList<>(cspec.getCallingConventions().length); + for (PrototypeModel curModel : cspec.getCallingConventions()) { + curModels.add(curModel); + } + try { + mergemodel.restoreXml(parser, curModels); + model = mergemodel; + } + catch (XmlParseException ex) { + if (!provideDummy) { + throw ex; + } + // XML failed to parse, associate default model as a placeholder + model = new PrototypeModelError(getFormalName(optionName), + cspec.getDefaultCallingConvention()); + } + } + else { + model = new PrototypeModel(); + try { + model.restoreXml(parser, cspec); + } + catch (XmlParseException ex) { + if (!provideDummy) { + throw ex; + } + // XML failed to parse, associate default model as a placeholder + model = new PrototypeModelError(getFormalName(optionName), + cspec.getDefaultCallingConvention()); + } + } + return model; + } + else if (elName.equals("callfixup")) { + String nm = parser.peek().getAttribute("name"); + PcodeInjectLibrary injectLibrary = cspec.getPcodeInjectLibrary(); + InjectPayload payload = + injectLibrary.allocateInject(optionName, nm, InjectPayload.CALLFIXUP_TYPE); + if (!(payload instanceof InjectPayloadSleigh)) { + throw new XmlParseException("Cannot use attached name: " + nm); + } + try { + payload.restoreXml(parser, (SleighLanguage) cspec.getLanguage()); + injectLibrary.parseInject(payload); // Try to parse the pcode body + } + catch (XmlParseException ex) { + if (!provideDummy) { + throw ex; + } + // The XML parse itself failed, provide a generic placeholder + payload = new InjectPayloadCallfixupError(cspec.getLanguage().getAddressFactory(), + getFormalName(optionName)); + } + catch (SleighException ex) { + if (!provideDummy) { + throw ex; + } + // The pcode body failed to parse, payload metadata, but provide dummy p-code + payload = new InjectPayloadCallfixupError(cspec.getLanguage().getAddressFactory(), + (InjectPayloadCallfixup) payload); + } + return payload; + } + else if (elName.equals("callotherfixup")) { + String nm = parser.peek().getAttribute("name"); + PcodeInjectLibrary injectLibrary = cspec.getPcodeInjectLibrary(); + InjectPayload payload = + injectLibrary.allocateInject(optionName, nm, InjectPayload.CALLOTHERFIXUP_TYPE); + if (!(payload instanceof InjectPayloadSleigh)) { + throw new XmlParseException("Cannot use attached name: " + nm); + } + try { + payload.restoreXml(parser, (SleighLanguage) cspec.getLanguage()); + injectLibrary.parseInject(payload); + } + catch (XmlParseException ex) { + if (!provideDummy) { + throw ex; + } + // The XML parse itself failed, provide a generic placeholder + payload = new InjectPayloadCallotherError(cspec.getLanguage().getAddressFactory(), + getFormalName(optionName)); + } + catch (SleighException ex) { + // The p-code parse failed, keep the metadata, but provide dummy p-code + payload = new InjectPayloadCallotherError(cspec.getLanguage().getAddressFactory(), + (InjectPayloadCallother) payload); + } + return payload; + } + throw new XmlParseException("Unknown compiler spec extension: " + elName); + } + + /** + * Check that the proposed callfixup extension does not collide with built-in fixups + * @param doc is info about the proposed extension + * @throws SleighException is there is a collision + */ + private void checkCallFixup(DocInfo doc) throws SleighException { + CompilerSpec cspec = program.getCompilerSpec(); + PcodeInjectLibrary injectLibrary = cspec.getPcodeInjectLibrary(); + InjectPayload payload = + injectLibrary.getPayload(InjectPayload.CALLFIXUP_TYPE, doc.formalName); + if (payload == null) { + return; + } + if (injectLibrary.hasProgramPayload(doc.formalName, InjectPayload.CALLFIXUP_TYPE)) { + return; + } + throw new SleighException("Extension cannot replace callfixup: " + doc.formalName); + } + + /** + * Check that the proposed callotherfixup extension targets a user-defined op + * that exists. Check if the extension would override a built-in fixup. + * @param doc is info on the proposed extension + * @throws SleighException if the targeted op does not exist + */ + private void checkCallotherFixup(DocInfo doc) throws SleighException { + CompilerSpec cspec = program.getCompilerSpec(); + PcodeInjectLibrary injectLibrary = cspec.getPcodeInjectLibrary(); + if (!injectLibrary.hasUserDefinedOp(doc.formalName)) { + throw new SleighException("CALLOTHER_FIXUP target does not exist: " + doc.formalName); + } + InjectPayload payload = + injectLibrary.getPayload(InjectPayload.CALLOTHERFIXUP_TYPE, doc.formalName); + if (payload == null) { + return; + } + if (injectLibrary.hasProgramPayload(doc.formalName, InjectPayload.CALLOTHERFIXUP_TYPE)) { + return; + } + // A callother payload is allowed to override an existing core payload + // So this check never fails, but we mark that the override is occurring + doc.override = true; + } + + /** + * Check that the proposed prototype extension does not collide with a + * built-in prototype. + * @param doc is info on the proposed prototype + * @throws SleighException if there is a collision + */ + private void checkPrototype(DocInfo doc) throws SleighException { + CompilerSpec cspec = program.getCompilerSpec(); + PrototypeModel[] allModels = cspec.getAllModels(); + for (PrototypeModel model : allModels) { + if (model.getName().equals(doc.formalName)) { + if (!model.isProgramExtension()) { + throw new SleighException( + "Extension cannot replace prototype: " + doc.formalName); + } + } + } + } + + /** + * Check the given document information against existing objects already in the compiler spec. + * Any problem (like name collisions) causes an exception to get thrown. + * Checks may populate additional document information + * @param doc is the document information: name, type + * @throws SleighException if there is a problem + */ + private void checkExtension(DocInfo doc) throws SleighException { + switch (doc.type) { + case CALL_FIXUP: + checkCallFixup(doc); + break; + case CALLOTHER_FIXUP: + checkCallotherFixup(doc); + break; + case MERGE_MODEL: + checkPrototype(doc); + break; + case PROTOTYPE_MODEL: + checkPrototype(doc); + break; + } + } + + /** + * Test if the given XML document describes a suitable spec extension. + * The document must fully parse and validate and must not conflict with the existing spec; + * otherwise an exception is thrown. If all tests pass, an object describing basic properties + * of the document is returned. + * @param document is the given XML document + * @return info about the document + * @throws SleighException if validity checks fail + * @throws XmlParseException if the XML is badly formed + * @throws SAXException if there are parse errors + */ + public DocInfo testExtensionDocument(String document) + throws SleighException, SAXException, XmlParseException { + DocInfo res = new DocInfo(document); + if (cspecValidator == null) { + cspecValidator = new SleighLanguageValidator(SleighLanguageValidator.CSPECTAG_TYPE); + } + cspecValidator.verify(res.optionName, document); + checkExtension(res); + parseExtension(res.optionName, document, program.getCompilerSpec(), false); + return res; + } + + /** + * Clean up references to a callfixup that is going to be removed + * @param fixupName is the name of the fixup + * @param monitor is a task monitor + * @throws CancelledException if the task is cancelled + */ + private void removeCallFixup(String fixupName, TaskMonitor monitor) throws CancelledException { + FunctionManager manager = program.getFunctionManager(); + monitor.setMessage("Searching for references to " + fixupName); + monitor.setMaximum(manager.getFunctionCount()); + FunctionIterator iter = manager.getFunctions(true); + for (int i = 0; i < 2; ++i) { + while (iter.hasNext()) { + monitor.checkCanceled(); + monitor.incrementProgress(1); + Function function = iter.next(); + String currentFixup = function.getCallFixup(); + if (currentFixup != null && currentFixup.equals(fixupName)) { + function.setCallFixup(null); + } + } + if (i == 1) { + break; + } + iter = manager.getExternalFunctions(); + } + } + + /** + * Clean up any references to a callotherfixup that is going to be removed + * @param fixupName is the name of the callother fixup + * @param monitor is a task monitor + */ + private void removeCallotherFixup(String fixupName, TaskMonitor monitor) { + // Nothing to clean up currently + } + + /** + * If the indicated evaluation model matches the given name, + * clear the evaluation model to the default + * @param modelType is the indicated evaluation model + * @param modelName is the given name needing to be cleared + */ + private void clearPrototypeEvaluationModel(EvaluationModelType modelType, String modelName) { + CompilerSpec compilerSpec = program.getCompilerSpec(); + PrototypeModel evalModel = compilerSpec.getPrototypeEvaluationModel(modelType); + if (!evalModel.getName().equals(modelName)) { + return; + } + String newName = compilerSpec.getDefaultCallingConvention().getName(); + Options options = program.getOptions(ProgramCompilerSpec.DECOMPILER_PROPERTY_LIST_NAME); + options.setString(ProgramCompilerSpec.EVALUATION_MODEL_PROPERTY_NAME, newName); + } + + /** + * Clean up references to a prototype extension that is about to be removed. + * Functions that use this prototype are changed to have an "unknown" prototype. + * @param modelName is the name of the prototype being removed + * @param monitor is a task monitor + * @throws CancelledException if the task is cancelled + */ + private void removePrototype(String modelName, TaskMonitor monitor) throws CancelledException { + FunctionManager manager = program.getFunctionManager(); + monitor.setMessage("Searching for references to " + modelName); + monitor.setMaximum(manager.getFunctionCount()); + FunctionIterator iter = manager.getFunctions(true); + for (int i = 0; i < 2; i += 1) { + while (iter.hasNext()) { + monitor.checkCanceled(); + monitor.incrementProgress(1); + Function function = iter.next(); + PrototypeModel currentModel = function.getCallingConvention(); + if (currentModel != null && currentModel.getName().equals(modelName)) { + try { + function.setCallingConvention("unknown"); + } + catch (InvalidInputException e) { + // shouldn't reach here + } + } + } + if (i == 1) { + break; + } + iter = manager.getExternalFunctions(); + } + // Clear any evaluation model that matches the prototype being removed + clearPrototypeEvaluationModel(EvaluationModelType.EVAL_CURRENT, modelName); + } + + /** + * Install or replace a spec extension to the program. The extension is presented as + * an XML document, from which a name is extracted. If an extension previously existed + * with the same name, it is overwritten. Otherwise the document is treated as a new + * extension. Testing is performed before installation: + * - Document is parsed as XML and is verified against spec grammars + * - Internal p-code tags from InjectPayloads are compiled + * - Name collisions are checked for + * @param document is the XML document describing the extension + * @param monitor is a task monitor + * @throws LockException if the caller does not exclusive access to the program + * @throws XmlParseException for a badly formed extension document + * @throws SAXException for parse errors in the extension document + * @throws SleighException for a document that fails verification + */ + public void addReplaceCompilerSpecExtension(String document, TaskMonitor monitor) + throws LockException, SleighException, SAXException, XmlParseException { + program.checkExclusiveAccess(); + monitor.setMessage("Testing validity of new document"); + DocInfo info = testExtensionDocument(document); + monitor.setMessage("Installing " + info.getFormalName()); + Options specOptions = program.getOptions(SpecExtension.SPEC_EXTENSION); + int progVersion = specOptions.getInt(SpecExtension.VERSION_COUNTER_OPTIONNAME, 0); + progVersion = (progVersion + 1) % 0x40000000; // Change the version number associated with the CompilerSpec + specOptions.setString(info.getOptionName(), document); + specOptions.setInt(SpecExtension.VERSION_COUNTER_OPTIONNAME, progVersion); + specOptions.setInt(SpecExtension.FORMAT_VERSION_OPTIONNAME, SpecExtension.FORMAT_VERSION); + program.installExtensions(); + } + + /** + * Remove the indicated spec extension from the program. + * Depending on the type, references to the extension are removed or altered + * first, to facilitate final removal of the extension. + * All changes are made in a single transaction that can be cancelled. + * @param optionName is the option name where the extension is stored + * @param monitor is a provided monitor that can trigger cancellation + * @throws LockException if the caller does not have exclusive access to the program + * @throws CancelledException if the caller cancels the operation via the task monitor + */ + public void removeCompilerSpecExtension(String optionName, TaskMonitor monitor) + throws LockException, CancelledException { + program.checkExclusiveAccess(); + Type type = SpecExtension.getExtensionType(optionName, false); + Options specOptions = program.getOptions(SpecExtension.SPEC_EXTENSION); + if (!specOptions.contains(optionName)) { + throw new SleighException("Extension does not exist: " + optionName); + } + int progVersion = specOptions.getInt(SpecExtension.VERSION_COUNTER_OPTIONNAME, 0); + progVersion = (progVersion + 1) % 0x40000000; // Change version number associated with the CompilerSpec + String extName = SpecExtension.getFormalName(optionName); + switch (type) { + case CALL_FIXUP: + removeCallFixup(extName, monitor); + break; + case CALLOTHER_FIXUP: + removeCallotherFixup(extName, monitor); + break; + case MERGE_MODEL: + removePrototype(extName, monitor); + break; + case PROTOTYPE_MODEL: + removePrototype(extName, monitor); + break; + } + specOptions.removeOption(optionName); + specOptions.setInt(SpecExtension.VERSION_COUNTER_OPTIONNAME, progVersion); + program.installExtensions(); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionDB.java index ac3559ac57..9eaffd17e2 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionDB.java @@ -295,8 +295,7 @@ public class FunctionDB extends DatabaseObject implements Function { try { checkIsValid(); return manager.getCodeManager() - .getComment(CodeUnit.REPEATABLE_COMMENT, - getEntryPoint()); + .getComment(CodeUnit.REPEATABLE_COMMENT, getEntryPoint()); } finally { manager.lock.release(); @@ -314,8 +313,7 @@ public class FunctionDB extends DatabaseObject implements Function { try { checkDeleted(); manager.getCodeManager() - .setComment(getEntryPoint(), CodeUnit.REPEATABLE_COMMENT, - comment); + .setComment(getEntryPoint(), CodeUnit.REPEATABLE_COMMENT, comment); } finally { manager.lock.release(); @@ -809,9 +807,9 @@ public class FunctionDB extends DatabaseObject implements Function { if (baseType instanceof TypeDef) { baseType = ((TypeDef) baseType).getBaseDataType(); } - returnParam.setDynamicStorage( - (baseType instanceof VoidDataType) ? VariableStorage.VOID_STORAGE - : VariableStorage.UNASSIGNED_STORAGE); + returnParam + .setDynamicStorage((baseType instanceof VoidDataType) ? VariableStorage.VOID_STORAGE + : VariableStorage.UNASSIGNED_STORAGE); PrototypeModel callingConvention = getCallingConvention(); if (callingConvention == null) { @@ -898,8 +896,8 @@ public class FunctionDB extends DatabaseObject implements Function { } } program.getBookmarkManager() - .setBookmark(getEntryPoint(), BookmarkType.ERROR, - "Bad Variables Removed", "Removed " + badSymbols.size() + " bad variables"); + .setBookmark(getEntryPoint(), BookmarkType.ERROR, "Bad Variables Removed", + "Removed " + badSymbols.size() + " bad variables"); for (Symbol s : badSymbols) { s.delete(); } @@ -2711,8 +2709,7 @@ public class FunctionDB extends DatabaseObject implements Function { else { if (program.getCompilerSpec() .getPcodeInjectLibrary() - .getPayload( - InjectPayload.CALLFIXUP_TYPE, name, null, null) == null) { + .getPayload(InjectPayload.CALLFIXUP_TYPE, name) == null) { Msg.warn(this, "Undefined CallFixup set at " + entryPoint + ": " + name); } callFixupMap.add(entryPoint, name); @@ -2829,8 +2826,7 @@ public class FunctionDB extends DatabaseObject implements Function { tagManager.applyFunctionTag(getID(), tag.getId()); Address addr = getEntryPoint(); - program.setChanged(ChangeManager.DOCR_TAG_ADDED_TO_FUNCTION, addr, addr, tag, - tag); + program.setChanged(ChangeManager.DOCR_TAG_ADDED_TO_FUNCTION, addr, addr, tag, tag); } // Add to local cache diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionManagerDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionManagerDB.java index 172d26b91e..6a4da88407 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionManagerDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionManagerDB.java @@ -211,7 +211,7 @@ public class FunctionManagerDB implements FunctionManager { @Override public List getCallingConventionNames() { CompilerSpec compilerSpec = program.getCompilerSpec(); - PrototypeModel[] namedCallingConventions = compilerSpec.getNamedCallingConventions(); + PrototypeModel[] namedCallingConventions = compilerSpec.getCallingConventions(); List names = new ArrayList<>(namedCallingConventions.length + 2); names.add(Function.UNKNOWN_CALLING_CONVENTION_STRING); names.add(Function.DEFAULT_CALLING_CONVENTION_STRING); @@ -1006,8 +1006,7 @@ public class FunctionManagerDB implements FunctionManager { } else { it = program.getSymbolTable() - .getSymbols(program.getMemory(), SymbolType.FUNCTION, - forward); + .getSymbols(program.getMemory(), SymbolType.FUNCTION, forward); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java index a724d43cc6..7a754f89a8 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java @@ -922,7 +922,8 @@ public class Disassembler implements DisassemblerConflictHandler { disassemblerContext.flowToAddress(addr); - MemBuffer instrMemBuffer = new WrappedMemBuffer(blockMemBuffer, DISASSEMBLE_MEMORY_CACHE_SIZE, + MemBuffer instrMemBuffer = + new WrappedMemBuffer(blockMemBuffer, DISASSEMBLE_MEMORY_CACHE_SIZE, (int) addr.subtract(blockMemBuffer.getAddress())); adjustPreParseContext(instrMemBuffer); @@ -971,8 +972,8 @@ public class Disassembler implements DisassemblerConflictHandler { // delay slots assumed to always fall-through - queue next addr disassemblerContext.copyToFutureFlowState(addr); if (disassemblerQueue != null) { - disassemblerQueue.queueDelaySlotFallthrough( - existingBlockStartInstr); + disassemblerQueue + .queueDelaySlotFallthrough(existingBlockStartInstr); } return; } @@ -1356,8 +1357,8 @@ public class Disassembler implements DisassemblerConflictHandler { return false; } PcodeInjectLibrary pcodeInjectLibrary = program.getCompilerSpec().getPcodeInjectLibrary(); - InjectPayload callFixup = pcodeInjectLibrary.getPayload(InjectPayload.CALLFIXUP_TYPE, - callFixupStr, program, null); + InjectPayload callFixup = + pcodeInjectLibrary.getPayload(InjectPayload.CALLFIXUP_TYPE, callFixupStr); if (callFixup == null) { return false; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldPacking.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldPacking.java index df8490beb1..b6e3981220 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldPacking.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldPacking.java @@ -42,5 +42,4 @@ public interface BitFieldPacking { * a zero-length bit-field */ int getZeroLengthBoundary(); - } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldPackingImpl.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldPackingImpl.java index cc5cab0935..3c1cff9a55 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldPackingImpl.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldPackingImpl.java @@ -15,6 +15,10 @@ */ package ghidra.program.model.data; +import ghidra.util.xml.SpecXmlUtils; +import ghidra.xml.XmlElement; +import ghidra.xml.XmlPullParser; + public class BitFieldPackingImpl implements BitFieldPacking { private boolean useMSConvention = false; @@ -65,4 +69,73 @@ public class BitFieldPackingImpl implements BitFieldPacking { this.zeroLengthBoundary = zeroLengthBoundary; } + /** + * Write configuration to a stream as an XML \ tag + * @param buffer is the stream to write to + */ + public void saveXml(StringBuilder buffer) { + if (!useMSConvention && typeAlignmentEnabled && zeroLengthBoundary == 0) { + return; // All defaults + } + buffer.append("\n"); + if (useMSConvention) { + buffer.append("\n"); + } + if (!typeAlignmentEnabled) { + buffer.append("\n"); + } + if (zeroLengthBoundary != 0) { + buffer.append("\n"); + } + buffer.append("\n"); + } + + /** + * Restore settings from a \ tag in an XML stream. + * The XML is designed to override existing settings from the default constructor + * @param parser is the XML stream + */ + protected void restoreXml(XmlPullParser parser) { + parser.start(); + while (parser.peek().isStart()) { + XmlElement subel = parser.start(); + String name = subel.getName(); + String value = subel.getAttribute("value"); + + if (name.equals("use_MS_convention")) { + useMSConvention = SpecXmlUtils.decodeBoolean(value); + } + else if (name.equals("type_alignment_enabled")) { + typeAlignmentEnabled = SpecXmlUtils.decodeBoolean(value); + } + else if (name.equals("zero_length_boundary")) { + zeroLengthBoundary = SpecXmlUtils.decodeInt(value); + } + + parser.end(subel); + } + parser.end(); + } + + @Override + public boolean equals(Object obj) { + BitFieldPackingImpl op2 = (BitFieldPackingImpl) obj; + if (typeAlignmentEnabled != op2.typeAlignmentEnabled) { + return false; + } + if (useMSConvention != op2.useMSConvention) { + return false; + } + if (zeroLengthBoundary != op2.zeroLengthBoundary) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return (typeAlignmentEnabled ? 1 : 13) + (useMSConvention ? 5 : 27) + zeroLengthBoundary; + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataOrganizationImpl.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataOrganizationImpl.java index 9e93dfacbb..c4543a9c75 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataOrganizationImpl.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataOrganizationImpl.java @@ -20,6 +20,9 @@ import java.util.Arrays; import ghidra.program.model.lang.Language; import ghidra.util.datastruct.IntIntHashtable; import ghidra.util.exception.NoValueException; +import ghidra.util.xml.SpecXmlUtils; +import ghidra.xml.XmlElement; +import ghidra.xml.XmlPullParser; /** * DataOrganization provides a single place for determining size and alignment information @@ -48,7 +51,7 @@ public class DataOrganizationImpl implements DataOrganization { private boolean bigEndian = false; private boolean isSignedChar = true; - private BitFieldPacking bitFieldPacking = new BitFieldPackingImpl(); + private BitFieldPackingImpl bitFieldPacking = new BitFieldPackingImpl(); /* * Map for determining the alignment of a data type based upon its size. @@ -163,7 +166,7 @@ public class DataOrganizationImpl implements DataOrganization { /** * Set data endianess - * @param bigEndian + * @param bigEndian is true to set big endian */ public void setBigEndian(boolean bigEndian) { this.bigEndian = bigEndian; @@ -401,7 +404,7 @@ public class DataOrganizationImpl implements DataOrganization { * Set the bitfield packing information associated with this data organization. * @param bitFieldPacking bitfield packing information */ - public void setBitFieldPacking(BitFieldPacking bitFieldPacking) { + public void setBitFieldPacking(BitFieldPackingImpl bitFieldPacking) { this.bitFieldPacking = bitFieldPacking; } @@ -647,4 +650,287 @@ public class DataOrganizationImpl implements DataOrganization { return (value2 != 0) ? getGreatestCommonDenominator(value2, value1 % value2) : value1; } + public void saveXml(StringBuilder buffer) { + buffer.append("\n"); + if (absoluteMaxAlignment != NO_MAXIMUM_ALIGNMENT) { + buffer.append("\n"); + } + if (machineAlignment != 8) { + buffer.append("\n"); + } + if (defaultAlignment != 1) { + buffer.append("\n"); + } + if (defaultPointerAlignment != 4) { + buffer.append("\n"); + } + if (pointerSize != 0) { + buffer.append("\n"); + } + if (pointerShift != 0) { + buffer.append("\n"); + } + if (!isSignedChar) { + buffer.append("\n"); + } + if (charSize != 1) { + buffer.append("\n"); + } + if (wideCharSize != 2) { + buffer.append("\n"); + } + if (shortSize != 2) { + buffer.append("\n"); + } + if (integerSize != 4) { + buffer.append("\n"); + } + if (longSize != 4) { + buffer.append("\n"); + } + if (longLongSize != 8) { + buffer.append("\n"); + } + if (floatSize != 4) { + buffer.append("\n"); + } + if (doubleSize != 8) { + buffer.append("\n"); + } + if (longDoubleSize != 8) { + buffer.append("\n"); + } + if (sizeAlignmentMap.size() != 0) { + int[] keys = sizeAlignmentMap.getKeys(); + buffer.append("\n"); + for (int key : keys) { + buffer.append("\n"); + } + buffer.append("\n"); + } + bitFieldPacking.saveXml(buffer); + buffer.append("\n"); + } + + /** + * Restore settings from an XML stream. This expects to see a \ tag. + * The XML is designed to override existing default settings. So this object needs to + * be pre-populated with defaults, typically via getDefaultOrganization(). + * @param parser is the XML stream + */ + public void restoreXml(XmlPullParser parser) { + parser.start(); + while (parser.peek().isStart()) { + String name = parser.peek().getName(); + + if (name.equals("char_type")) { + XmlElement subel = parser.start(); + String boolStr = subel.getAttribute("signed"); + isSignedChar = SpecXmlUtils.decodeBoolean(boolStr); + parser.end(subel); + continue; + } + else if (name.equals("bitfield_packing")) { + bitFieldPacking.restoreXml(parser); + continue; + } + else if (name.equals("size_alignment_map")) { + XmlElement subel = parser.start(); + while (parser.peek().isStart()) { + XmlElement subsubel = parser.start(); + int size = SpecXmlUtils.decodeInt(subsubel.getAttribute("size")); + int alignment = SpecXmlUtils.decodeInt(subsubel.getAttribute("alignment")); + sizeAlignmentMap.put(size, alignment); + parser.end(subsubel); + } + parser.end(subel); + continue; + } + + XmlElement subel = parser.start(); + String value = subel.getAttribute("value"); + + if (name.equals("absolute_max_alignment")) { + absoluteMaxAlignment = SpecXmlUtils.decodeInt(value); + } + else if (name.equals("machine_alignment")) { + machineAlignment = SpecXmlUtils.decodeInt(value); + } + else if (name.equals("default_alignment")) { + defaultAlignment = SpecXmlUtils.decodeInt(value); + } + else if (name.equals("default_pointer_alignment")) { + defaultPointerAlignment = SpecXmlUtils.decodeInt(value); + } + else if (name.equals("pointer_size")) { + pointerSize = SpecXmlUtils.decodeInt(value); + } + else if (name.equals("pointer_shift")) { + pointerShift = SpecXmlUtils.decodeInt(value); + } + else if (name.equals("char_size")) { + charSize = SpecXmlUtils.decodeInt(value); + } + else if (name.equals("wchar_size")) { + wideCharSize = SpecXmlUtils.decodeInt(value); + } + else if (name.equals("short_size")) { + shortSize = SpecXmlUtils.decodeInt(value); + } + else if (name.equals("integer_size")) { + integerSize = SpecXmlUtils.decodeInt(value); + } + else if (name.equals("long_size")) { + longSize = SpecXmlUtils.decodeInt(value); + } + else if (name.equals("long_long_size")) { + longLongSize = SpecXmlUtils.decodeInt(value); + } + else if (name.equals("float_size")) { + floatSize = SpecXmlUtils.decodeInt(value); + } + else if (name.equals("double_size")) { + doubleSize = SpecXmlUtils.decodeInt(value); + } + else if (name.equals("long_double_size")) { + longDoubleSize = SpecXmlUtils.decodeInt(value); + } + parser.end(subel); + } + + parser.end(); + + } + + @Override + public boolean equals(Object obj) { + DataOrganizationImpl op2 = (DataOrganizationImpl) obj; + if (absoluteMaxAlignment != op2.absoluteMaxAlignment) { + return false; + } + if (bigEndian != op2.bigEndian) { + return false; + } + if (!bitFieldPacking.equals(op2.bitFieldPacking)) { + return false; + } + if (charSize != op2.charSize || wideCharSize != op2.wideCharSize) { + return false; + } + if (defaultAlignment != op2.defaultAlignment) { + return false; + } + if (defaultPointerAlignment != op2.defaultPointerAlignment) { + return false; + } + if (doubleSize != op2.doubleSize || floatSize != op2.floatSize) { + return false; + } + if (integerSize != op2.integerSize || longLongSize != op2.longLongSize) { + return false; + } + if (shortSize != op2.shortSize) { + return false; + } + if (longSize != op2.longSize || longDoubleSize != op2.longDoubleSize) { + return false; + } + if (isSignedChar != op2.isSignedChar) { + return false; + } + if (machineAlignment != op2.machineAlignment) { + return false; + } + if (pointerSize != op2.pointerSize || pointerShift != op2.pointerShift) { + return false; + } + int[] keys = sizeAlignmentMap.getKeys(); + int[] op2keys = op2.sizeAlignmentMap.getKeys(); + if (keys.length != op2keys.length) { + return false; + } + try { + for (int i = 0; i < keys.length; ++i) { + if (sizeAlignmentMap.get(keys[i]) != op2.sizeAlignmentMap.get(op2keys[i])) { + return false; + } + } + } + catch (NoValueException ex) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = bitFieldPacking.hashCode(); + hash = 79 * hash + absoluteMaxAlignment; + hash = 79 * hash + (bigEndian ? 27 : 13); + hash = 79 * hash + charSize; + hash = 79 * hash + defaultAlignment; + hash = 79 * hash + defaultPointerAlignment; + hash = 79 * hash + doubleSize; + hash = 79 * hash + floatSize; + hash = 79 * hash + integerSize; + hash = 79 * hash + (isSignedChar ? 1 : 3); + hash = 79 * hash + longDoubleSize; + hash = 79 * hash + longLongSize; + hash = 79 * hash + longSize; + hash = 79 * hash + machineAlignment; + hash = 79 * hash + pointerShift; + hash = 79 * hash + pointerSize; + hash = 79 * hash + shortSize; + hash = 79 * hash + wideCharSize; + int[] keys = sizeAlignmentMap.getKeys(); + try { + for (int key : keys) { + hash = 79 * hash + sizeAlignmentMap.get(key); + } + } + catch (NoValueException ex) { + hash = 0; + } + return hash; + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java index 40308e21b4..c17ef05542 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java @@ -16,35 +16,38 @@ */ package ghidra.program.model.lang; -import java.io.*; +import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.Constructor; import java.math.BigInteger; import java.util.*; +import java.util.Map.Entry; import org.xml.sax.*; -import docking.options.editor.StringWithChoicesEditor; import generic.jar.ResourceFile; +import generic.stl.Pair; import ghidra.app.plugin.processors.sleigh.*; -import ghidra.framework.options.OptionType; -import ghidra.framework.options.Options; import ghidra.program.model.address.*; import ghidra.program.model.data.*; -import ghidra.program.model.listing.*; -import ghidra.util.HelpLocation; +import ghidra.program.model.listing.DefaultProgramContext; +import ghidra.program.model.listing.Parameter; +import ghidra.program.model.pcode.AddressXML; +import ghidra.program.model.pcode.Varnode; import ghidra.util.Msg; +import ghidra.util.SystemUtilities; import ghidra.util.xml.SpecXmlUtils; import ghidra.xml.*; +/** + * BasicCompilerSpec implements the CompilerSpec interface based on static information + * from a particular .cspec file. Typically the .cspec file is read in once by a Language + * object whenever a new or opened Program indicates a particular language and compiler. + * The BasicCompilerSpec is owned by the Language and (parts of it) may be reused by + * multiple Programs. + */ public class BasicCompilerSpec implements CompilerSpec { - public static final String DECOMPILER_PROPERTY_LIST_NAME = "Decompiler"; - public static final String DECOMPILER_OUTPUT_LANGUAGE = "Output Language"; - public final static DecompilerLanguage DECOMPILER_OUTPUT_DEF = DecompilerLanguage.C_LANGUAGE; - public final static String DECOMPILER_OUTPUT_DESC = - "Select the source language output by the decompiler."; - private static final String EVALUATION_MODEL_PROPERTY_NAME = "Prototype Evaluation"; - public static final String STACK_SPACE_NAME = "stack"; public static final String JOIN_SPACE_NAME = "join"; public static final String OTHER_SPACE_NAME = "OTHER"; @@ -58,53 +61,70 @@ public class BasicCompilerSpec implements CompilerSpec { private final SleighLanguage language; private DataOrganizationImpl dataOrganization; private List ctxsetting = new ArrayList<>(); - private PrototypeModel defaultModel; - private PrototypeModel defaultEvaluationModel; - private PrototypeModel[] models; - private PrototypeModel[] evalmodels; - private Register stackPointer; + protected PrototypeModel defaultModel; + protected PrototypeModel evalCurrentModel; // Default model used to evaluate current function + protected PrototypeModel evalCalledModel; // Default model used to evaluate a called function + protected PrototypeModel[] allmodels; // All models + protected PrototypeModel[] models; // All models excluding merge models + private boolean copiedThisModel; // true if __thiscall is copied from default model + private Register stackPointer; // Register holding the stack pointer private AddressSpace stackSpace; private AddressSpace stackBaseSpace; private AddressSpace joinSpace; private boolean stackGrowsNegative = true; private boolean reverseJustifyStack = false; - private Map spaceBases = new HashMap<>(); - private PcodeInjectLibrary pcodeInject; - private AddressSet globalSet; + private Map> spaceBases; + private List>> extraRanges; + protected PcodeInjectLibrary pcodeInject; + private AddressSet globalSet; // Set of addresses the decompiler considers "global" in scope private LinkedHashMap properties = new LinkedHashMap<>(); - private Map callingConventionMap = new HashMap<>(); - private String[] evaluationModelChoices; - private String specString; - private ResourceFile specFile; + private Map callingConventionMap = null; + private boolean aggressiveTrim; // Does decompiler aggressively trim sign extensions + private List preferSplit; // List of registers the decompiler prefers to split + private AddressSet noHighPtr; // Memory regions the decompiler treats as not addressable + private AddressSet readOnlySet; // (Additional) memory ranges the decompiler treats as read-only + private Varnode returnAddress; // Register/memory where decompiler expects return address to be stored + private int funcPtrAlign; // Alignment of function pointers, 0=no alignment (default) + private List> deadCodeDelay; + private List inferPtrBounds; // Restrictions on where decompiler can infer pointers - private Exception parseException; + /** + * Construct the specification from an XML stream. This is currently only used for testing. + * @param description is the .ldefs description matching this specification + * @param language is the language that owns the specification + * @param stream is the XML stream + * @throws XmlParseException for badly formed XML + * @throws SAXException for syntax errors in the XML + * @throws IOException for errors accessing the stream + */ + public BasicCompilerSpec(CompilerSpecDescription description, SleighLanguage language, + InputStream stream) throws XmlParseException, SAXException, IOException { + this.description = description; + this.language = language; + buildInjectLibrary(); + this.dataOrganization = DataOrganizationImpl.getDefaultOrganization(language); + ErrorHandler errHandler = getErrorHandler("test"); + XmlPullParser parser = XmlPullParserFactory.create(stream, "testpath", errHandler, false); + initialize("testpath", parser); + } + + /** + * Read in the specification from an XML file. + * @param description is the .ldefs description associated with the specification + * @param language is the language owning the specification + * @param cspecFile is the XML file + * @throws CompilerSpecNotFoundException for any form of error preventing the specification from being loaded. + */ public BasicCompilerSpec(CompilerSpecDescription description, SleighLanguage language, final ResourceFile cspecFile) throws CompilerSpecNotFoundException { this.description = description; this.language = language; buildInjectLibrary(); this.dataOrganization = DataOrganizationImpl.getDefaultOrganization(language); - specString = null; - specFile = cspecFile; - - ErrorHandler errHandler = new ErrorHandler() { - @Override - public void error(SAXParseException exception) throws SAXException { - parseException = exception; - } - - @Override - public void fatalError(SAXParseException exception) throws SAXException { - parseException = exception; - } - - @Override - public void warning(SAXParseException exception) throws SAXException { - Msg.warn(this, "Warning parsing '" + cspecFile + "'", exception); - } - }; + Exception parseException = null; + ErrorHandler errHandler = getErrorHandler(cspecFile.toString()); InputStream stream; try { SleighLanguageValidator.validateCspecFile(cspecFile); @@ -128,16 +148,7 @@ public class BasicCompilerSpec implements CompilerSpec { } } } - catch (FileNotFoundException e) { - parseException = e; - } - catch (IOException e) { - parseException = e; - } - catch (SAXException e) { - parseException = e; - } - catch (XmlParseException e) { + catch (IOException | SAXException | XmlParseException e) { parseException = e; } @@ -147,15 +158,95 @@ public class BasicCompilerSpec implements CompilerSpec { } } - private void initialize(String sourceName, XmlPullParser parser) throws XmlParseException { - this.sourceName = sourceName; + /** + * Clone the spec so that program can safely extend it without affecting the base + * spec from Language. + * @param op2 is the spec to clone + */ + protected BasicCompilerSpec(BasicCompilerSpec op2) { + language = op2.language; + description = op2.description; + // PrototypeModel is immutable but the map may change, so callingConventionMap + // should only be added to through addThisCallingConvention() and modelXrefs() + callingConventionMap = op2.callingConventionMap; + ctxsetting = op2.ctxsetting; // ContextSetting can be considered immutable + dataOrganization = op2.dataOrganization; // DataOrganizationImpl can be considered immutable + evalCurrentModel = op2.evalCurrentModel; // PrototypeModel is immutable + evalCalledModel = op2.evalCalledModel; + defaultModel = op2.defaultModel; + allmodels = op2.allmodels; + copiedThisModel = op2.copiedThisModel; + globalSet = op2.globalSet; // May need to clone if \ tag becomes user extendable + joinSpace = op2.joinSpace; // AddressSpace is immutable + models = op2.models; + pcodeInject = op2.pcodeInject.clone(); + properties = op2.properties; // Currently an immutable map + reverseJustifyStack = op2.reverseJustifyStack; + sourceName = op2.sourceName; + spaceBases = op2.spaceBases; // Currently an immutable map + extraRanges = op2.extraRanges; // Currently an immutable map + stackBaseSpace = op2.stackBaseSpace; + stackGrowsNegative = op2.stackGrowsNegative; + stackPointer = op2.stackPointer; // Register is immutable + stackSpace = op2.stackSpace; + aggressiveTrim = op2.aggressiveTrim; + preferSplit = op2.preferSplit; // immutable set + noHighPtr = op2.noHighPtr; // immutable set + readOnlySet = op2.readOnlySet; // immutable set + returnAddress = op2.returnAddress; + funcPtrAlign = op2.funcPtrAlign; + deadCodeDelay = op2.deadCodeDelay; + inferPtrBounds = op2.inferPtrBounds; + } + + /** + * Generate an XML error handler suitable for parsing a specification document. + * - Warnings are logged. + * - Errors cause a SAXParseException + * + * @param docTitle is the title of the document + * @return the error handler object + */ + protected static ErrorHandler getErrorHandler(String docTitle) { + ErrorHandler errHandler = new ErrorHandler() { + @Override + public void error(SAXParseException exception) throws SAXException { + throw exception; + } + + @Override + public void fatalError(SAXParseException exception) throws SAXException { + throw exception; + } + + @Override + public void warning(SAXParseException exception) throws SAXException { + Msg.warn(this, "Warning parsing '" + docTitle + "'", exception); + } + }; + return errHandler; + } + + private void initialize(String srcName, XmlPullParser parser) throws XmlParseException { + this.sourceName = srcName; + spaceBases = null; + extraRanges = null; globalSet = new AddressSet(); + preferSplit = null; + noHighPtr = null; + readOnlySet = null; defaultModel = null; - models = new PrototypeModel[0]; + allmodels = null; + models = null; + stackPointer = null; + aggressiveTrim = false; + returnAddress = null; + funcPtrAlign = 0; + deadCodeDelay = null; + inferPtrBounds = null; + copiedThisModel = false; restoreXml(parser); - - addThisCallConventionIfMissing(); } @SuppressWarnings("unchecked") @@ -192,46 +283,41 @@ public class BasicCompilerSpec implements CompilerSpec { List additionalInject = language.getAdditionalInject(); if (additionalInject != null) { for (InjectPayloadSleigh payload : additionalInject) { - pcodeInject.registerInject(payload.clone()); + pcodeInject.registerInject(payload); } } } - private void addThisCallConventionIfMissing() { + private void addThisCallConventionIfMissing(List modelList, + String defaultName) { + if (defaultName == null) { + return; + } boolean foundThisCall = false; - for (PrototypeModel model : models) { - if (CALLING_CONVENTION_thiscall.equals(model.getName())) { + PrototypeModel defModel = null; + for (PrototypeModel model : modelList) { + if (CALLING_CONVENTION_thiscall.equals(model.name)) { foundThisCall = true; - break; + } + if (defaultName.equals(model.name)) { + defModel = model; } } - if (defaultModel != null && !foundThisCall) { - PrototypeModel[] newModels = new PrototypeModel[models.length + 1]; - System.arraycopy(models, 0, newModels, 0, models.length); - PrototypeModel thisModel = - new PrototypeModel(CALLING_CONVENTION_thiscall, defaultModel); - callingConventionMap.put(CALLING_CONVENTION_thiscall, thisModel); - newModels[models.length] = thisModel; - models = newModels; + if (defModel != null && !foundThisCall) { + PrototypeModel thisModel = new PrototypeModel(CALLING_CONVENTION_thiscall, defModel); + modelList.add(thisModel); + copiedThisModel = true; } } - public String getCompilerSpecString() throws FileNotFoundException, IOException { - if (specString != null) { - return specString; - } - InputStreamReader reader = new InputStreamReader(specFile.getInputStream()); - char[] cbuf = new char[1024]; - - StringBuffer buf = new StringBuffer(); - int curlen = reader.read(cbuf); - while (curlen > 0) { - buf.append(cbuf, 0, curlen); - curlen = reader.read(cbuf); - } - reader.close(); - specString = buf.toString(); - return specString; + /** + * Convenience method to marshal this entire object, via saveXml, into a String object. + * @return a String containing this entire spec as an XML document. + */ + public String getXMLString() { + StringBuilder buffer = new StringBuilder(); + saveXml(buffer); + return buffer.toString(); } @Override @@ -266,11 +352,32 @@ public class BasicCompilerSpec implements CompilerSpec { return callingConventionMap.get(name); } + @Override + public PrototypeModel[] getAllModels() { + return allmodels; + } + @Override public PrototypeModel getDefaultCallingConvention() { return defaultModel; } + @Override + public DecompilerLanguage getDecompilerOutputLanguage() { + return DecompilerLanguage.C_LANGUAGE; + } + + @Override + public PrototypeModel getPrototypeEvaluationModel(EvaluationModelType modelType) { + switch (modelType) { + case EVAL_CURRENT: + return evalCurrentModel; + case EVAL_CALLED: + return evalCalledModel; + } + return null; + } + @Override public Register getStackPointer() { return stackPointer; @@ -292,20 +399,6 @@ public class BasicCompilerSpec implements CompilerSpec { return stackBaseSpace; } - @Override - public PrototypeModel[] getNamedCallingConventions() { - PrototypeModel[] tmpNamed = new PrototypeModel[models.length]; - int current = 0; - for (int i = 0; i < tmpNamed.length; i++) { - if (models[i].getName() != null) { - tmpNamed[current++] = models[i]; - } - } - PrototypeModel[] named = new PrototypeModel[current]; - System.arraycopy(tmpNamed, 0, named, 0, current); - return named; - } - @Override public boolean stackGrowsNegative() { return stackGrowsNegative; @@ -330,18 +423,6 @@ public class BasicCompilerSpec implements CompilerSpec { return description; } - // - // .cspec parsing (only those portions of the spec needed are actually processed) - // - - private Register getRegister(String registerName) { - Register reg = language.getRegister(registerName); - if (reg == null) { - throw new SleighException("Unknown register: " + registerName); - } - return reg; - } - @Override public AddressSpace getAddressSpace(String spaceName) { AddressSpace space; @@ -369,43 +450,12 @@ public class BasicCompilerSpec implements CompilerSpec { } /** - * Build up the choice strings for all the evaluation methods + * Build the model arrays given a complete list of models. + * The array -models- contains all normal PrototypeModel objects + * The array -allmodels- contains all models, including merge models. + * + * @param modelList is the complete list of models */ - private void establishEvaluationModelChoices(PrototypeModel defaultEvaluationModel) { - - // Make sure the default evaluation model occurs at the top of the evalmodels list - int defaultnum = -1; - for (int i = 0; i < evalmodels.length; ++i) { - if (evalmodels[i] == defaultEvaluationModel) { - defaultnum = i; - } - } - - if (defaultnum > 0) { - PrototypeModel tmp = evalmodels[defaultnum]; - for (int i = defaultnum; i > 0; --i) { - // Push everybody down to make room for default at top - evalmodels[i] = evalmodels[i - 1]; - } - evalmodels[0] = tmp; - } - - // Now build a list of menu strings with 1-1 correspondence to models in evalmodels - evaluationModelChoices = new String[evalmodels.length]; - for (int i = 0; i < evalmodels.length; ++i) { - String name = evalmodels[i].getName(); - if (name == null) { - if (i == 0) { - name = "default"; - } - else { - name = "spec" + Integer.toString(i); - } - } - evaluationModelChoices[i] = name; - } - } - private void buildModelArrays(List modelList) { int fullcount = 0; int resolvecount = 0; @@ -416,36 +466,149 @@ public class BasicCompilerSpec implements CompilerSpec { } } models = new PrototypeModel[fullcount - resolvecount]; - evalmodels = new PrototypeModel[fullcount]; + allmodels = new PrototypeModel[fullcount]; int i = 0; int j = 0; for (PrototypeModel model : modelList) { if (model.isMerged()) { - evalmodels[fullcount - resolvecount + j] = model; + allmodels[fullcount - resolvecount + j] = model; j += 1; } else { models[i] = model; - evalmodels[i] = model; + allmodels[i] = model; i += 1; } } } + /** + * Establish cross referencing to prototype models. + * All xrefs are regenerated from a single complete list of PrototypeModels + * + * @param modelList is the complete list of models + * @param defaultName is the name to use for the default model (or null) + * @param evalCurrent is the name to use for evaluating the current function (or null) + * @param evalCalled is the name to use for evaluating called functions (or null) + */ + protected void modelXrefs(List modelList, String defaultName, + String evalCurrent, String evalCalled) { + buildModelArrays(modelList); + callingConventionMap = new HashMap<>(); + for (PrototypeModel model : models) { + String name = model.getName(); + if (name != null) { + callingConventionMap.put(name, model); + } + } + + defaultModel = null; + if (defaultName != null) { + defaultModel = callingConventionMap.get(defaultName); + } + evalCurrentModel = defaultModel; // The default evaluation is to assume default model + evalCalledModel = defaultModel; + + for (PrototypeModel evalmodel : allmodels) { + if (evalCurrent != null && evalmodel.getName().equals(evalCurrent)) { + evalCurrentModel = evalmodel; + } + if (evalCalled != null && evalmodel.getName().equals(evalCalled)) { + evalCalledModel = evalmodel; + } + } + } + + /** + * Marshal this entire specification to an XML stream. An XML document is written with + * root tag \. + * @param buffer is the XML stream + */ + public void saveXml(StringBuilder buffer) { + buffer.append("\n"); + saveProperties(buffer); + dataOrganization.saveXml(buffer); + ContextSetting.buildContextDataXml(buffer, ctxsetting); + if (aggressiveTrim) { + buffer.append("\n"); + } + if (stackPointer != null) { + buffer.append("\n"); + } + saveSpaceBases(buffer); + saveMemoryTags(buffer, "global", globalSet); + saveReturnAddress(buffer); // Must come before PrototypeModels + pcodeInject.saveCompilerSpecXml(buffer); + if (defaultModel != null) { + buffer.append("\n"); + defaultModel.saveXml(buffer, pcodeInject); + buffer.append("\n"); + } + for (PrototypeModel model : allmodels) { + if (model == defaultModel) { + continue; // Already emitted + } + if (copiedThisModel && model.hasThisPointer() && + model.name.equals(CALLING_CONVENTION_thiscall)) { + continue; // Don't need to emit the copy + } + model.saveXml(buffer, pcodeInject); + } + if (evalCurrentModel != null && evalCurrentModel != defaultModel) { + buffer.append("\n"); + } + if (evalCalledModel != null && evalCalledModel != defaultModel) { + buffer.append("\n"); + } + savePreferSplit(buffer); + saveMemoryTags(buffer, "nohighptr", noHighPtr); + saveMemoryTags(buffer, "readonly", readOnlySet); + if (funcPtrAlign != 0) { + buffer.append("\n"); + } + saveDeadCodeDelay(buffer); + saveInferPtrBounds(buffer); + buffer.append(""); + } + + /** + * Initialize this object from an XML stream. A single \ tag is expected. + * @param parser is the XML stream + * @throws XmlParseException for badly formed XML + */ private void restoreXml(XmlPullParser parser) throws XmlParseException { - stackPointer = null; List modelList = new ArrayList<>(); + boolean seenDefault = false; + String defaultName = null; String evalCurrentPrototype = null; + String evalCalledPrototype = null; parser.start("compiler_spec"); while (parser.peek().isStart()) { String name = parser.peek().getName(); if (name.equals("properties")) { - readProperties(parser); + restoreProperties(parser); } else if (name.equals("data_organization")) { - restoreDataOrganization(parser); + dataOrganization.restoreXml(parser); } else if (name.equals("callfixup")) { String nm = parser.peek().getAttribute("name"); @@ -457,7 +620,7 @@ public class BasicCompilerSpec implements CompilerSpec { parser); } else if (name.equals("context_data")) { - restoreContextData(parser); + ContextSetting.parseContextData(ctxsetting, parser, this); } else if (name.equals("stackpointer")) { setStackPointer(parser); @@ -466,65 +629,101 @@ public class BasicCompilerSpec implements CompilerSpec { restoreSpaceBase(parser); } else if (name.equals("global")) { - restoreGlobal(parser); + restoreMemoryTags("global", parser, globalSet); } else if (name.equals("default_proto")) { parser.start(); - addPrototypeModel(modelList, parser, true); + PrototypeModel model = addPrototypeModel(modelList, parser); parser.end(); + if (!seenDefault) { + defaultName = model.name; + seenDefault = true; + } } else if (name.equals("prototype")) { - addPrototypeModel(modelList, parser, false); + PrototypeModel model = addPrototypeModel(modelList, parser); + if (defaultName == null) { + defaultName = model.name; + } } else if (name.equals("resolveprototype")) { - addPrototypeModel(modelList, parser, false); + addPrototypeModel(modelList, parser); } else if (name.equals("eval_current_prototype")) { evalCurrentPrototype = parser.start().getAttribute("name"); parser.end(); } - else if (name.equals("segmentop")) { - XmlElement el = parser.start(); - InjectPayloadSleigh payload = language.parseSegmentOp(el, parser); + else if (name.equals("eval_called_prototype")) { + evalCalledPrototype = parser.start().getAttribute("name"); parser.end(); + } + else if (name.equals("segmentop")) { + String source = "cspec: " + language.getLanguageID().getIdAsString(); + InjectPayloadSleigh payload = new InjectPayloadSegment(source); + payload.restoreXml(parser, language); pcodeInject.registerInject(payload); } + else if (name.equals("aggressivetrim")) { + XmlElement el = parser.start(); + aggressiveTrim = SpecXmlUtils.decodeBoolean(el.getAttribute("signext")); + parser.end(el); + } + else if (name.equals("prefersplit")) { + restorePreferSplit(parser); + } + else if (name.equals("nohighptr")) { + noHighPtr = new AddressSet(); + restoreMemoryTags("nohighptr", parser, noHighPtr); + } + else if (name.equals("readonly")) { + readOnlySet = new AddressSet(); + restoreMemoryTags("readonly", parser, readOnlySet); + } + else if (name.equals("returnaddress")) { + restoreReturnAddress(parser); + } + else if (name.equals("funcptr")) { + XmlElement subel = parser.start(); + funcPtrAlign = SpecXmlUtils.decodeInt(subel.getAttribute("align")); + parser.end(subel); + } + else if (name.equals("deadcodedelay")) { + restoreDeadCodeDelay(parser); + } + else if (name.equals("inferptrbounds")) { + restoreInferPtrBounds(parser); + } else { XmlElement el = parser.start(); parser.discardSubTree(el); } } + parser.end(); if (stackPointer == null) { stackSpace = new GenericAddressSpace(STACK_SPACE_NAME, language.getDefaultSpace().getSize(), language.getDefaultSpace().getAddressableUnitSize(), AddressSpace.TYPE_STACK, 0); } - - buildModelArrays(modelList); - // populate nameToModelMap - for (PrototypeModel model : models) { - String name = model.getName(); - if (name != null) { - callingConventionMap.put(name, model); - } - } - - defaultEvaluationModel = defaultModel; // The default evaluation is to assume default model - - if (evalCurrentPrototype != null) { // Look for an explicit default evaluation - for (PrototypeModel evalmodel : evalmodels) { - if (evalmodel.getName().equals(evalCurrentPrototype)) { - defaultEvaluationModel = evalmodel; - break; - } - } - } - establishEvaluationModelChoices(defaultEvaluationModel); - parser.end(); + addThisCallConventionIfMissing(modelList, defaultName); + modelXrefs(modelList, defaultName, evalCurrentPrototype, evalCalledPrototype); } - private void readProperties(XmlPullParser parser) { + private void saveProperties(StringBuilder buffer) { + if (properties.isEmpty()) { + return; + } + buffer.append("\n"); + for (Entry property : properties.entrySet()) { + buffer.append("\n"); + } + buffer.append("\n"); + } + + private void restoreProperties(XmlPullParser parser) { parser.start(); while (parser.peek().isStart()) { XmlElement el = parser.start(); @@ -541,226 +740,239 @@ public class BasicCompilerSpec implements CompilerSpec { parser.end(); } - private void restoreDataOrganization(XmlPullParser parser) throws XmlParseException { - - parser.start(); - while (parser.peek().isStart()) { - XmlElement subel = parser.start(); - String name = subel.getName(); - - if (name.equals("char_type")) { - String boolStr = subel.getAttribute("signed"); - dataOrganization.setCharIsSigned(SpecXmlUtils.decodeBoolean(boolStr)); - parser.end(subel); - continue; - } - - String value = subel.getAttribute("value"); - - if (name.equals("absolute_max_alignment")) { - dataOrganization.setAbsoluteMaxAlignment(SpecXmlUtils.decodeInt(value)); - } - else if (name.equals("machine_alignment")) { - dataOrganization.setMachineAlignment(SpecXmlUtils.decodeInt(value)); - } - else if (name.equals("default_alignment")) { - dataOrganization.setDefaultAlignment(SpecXmlUtils.decodeInt(value)); - } - else if (name.equals("default_pointer_alignment")) { - dataOrganization.setDefaultPointerAlignment(SpecXmlUtils.decodeInt(value)); - } - else if (name.equals("pointer_size")) { - dataOrganization.setPointerSize(SpecXmlUtils.decodeInt(value)); - } - else if (name.equals("pointer_shift")) { - dataOrganization.setPointerShift(SpecXmlUtils.decodeInt(value)); - } - else if (name.equals("char_size")) { - dataOrganization.setCharSize(SpecXmlUtils.decodeInt(value)); - } - else if (name.equals("wchar_size")) { - dataOrganization.setWideCharSize(SpecXmlUtils.decodeInt(value)); - } - else if (name.equals("short_size")) { - dataOrganization.setShortSize(SpecXmlUtils.decodeInt(value)); - } - else if (name.equals("integer_size")) { - dataOrganization.setIntegerSize(SpecXmlUtils.decodeInt(value)); - } - else if (name.equals("long_size")) { - dataOrganization.setLongSize(SpecXmlUtils.decodeInt(value)); - } - else if (name.equals("long_long_size")) { - dataOrganization.setLongLongSize(SpecXmlUtils.decodeInt(value)); - } - else if (name.equals("float_size")) { - dataOrganization.setFloatSize(SpecXmlUtils.decodeInt(value)); - } - else if (name.equals("double_size")) { - dataOrganization.setDoubleSize(SpecXmlUtils.decodeInt(value)); - } - else if (name.equals("long_double_size")) { - dataOrganization.setLongDoubleSize(SpecXmlUtils.decodeInt(value)); - } - else if (name.equals("size_alignment_map")) { - dataOrganization.clearSizeAlignmentMap(); - while (parser.peek().isStart()) { - XmlElement subsubel = parser.start(); - int size = SpecXmlUtils.decodeInt(subsubel.getAttribute("size")); - int alignment = SpecXmlUtils.decodeInt(subsubel.getAttribute("alignment")); - dataOrganization.setSizeAlignment(size, alignment); - parser.end(subsubel); - } - } - else if (name.equals("bitfield_packing")) { - dataOrganization.setBitFieldPacking(parseBitFieldPacking(parser)); - } - parser.end(subel); + private void saveSpaceBases(StringBuilder buffer) { + if (spaceBases == null) { + return; } - - parser.end(); - } - - private BitFieldPacking parseBitFieldPacking(XmlPullParser parser) { - BitFieldPackingImpl bitFieldPacking = new BitFieldPackingImpl(); - while (parser.peek().isStart()) { - XmlElement subel = parser.start(); - String name = subel.getName(); - String value = subel.getAttribute("value"); - - if (name.equals("use_MS_convention")) { - bitFieldPacking.setUseMSConvention(SpecXmlUtils.decodeBoolean(value)); - } - else if (name.equals("type_alignment_enabled")) { - bitFieldPacking.setTypeAlignmentEnabled(SpecXmlUtils.decodeBoolean(value)); - } - else if (name.equals("zero_length_boundary")) { - bitFieldPacking.setZeroLengthBoundary(SpecXmlUtils.decodeInt(value)); - } - - parser.end(subel); + for (Entry> entry : spaceBases.entrySet()) { + buffer.append("\n"); } - return bitFieldPacking; } private void restoreSpaceBase(XmlPullParser parser) { + if (spaceBases == null) { + spaceBases = new TreeMap<>(); + } XmlElement el = parser.start(); String name = el.getAttribute("name"); - getRegister(el.getAttribute("register")); + Register reg = language.getRegister(el.getAttribute("register")); + if (reg == null) { + throw new SleighException("Unknown register: " + name); + } String spaceName = el.getAttribute("space"); if (language.getAddressFactory().getAddressSpace(name) != null || spaceBases.containsKey(name)) { throw new SleighException("Duplicate space name: " + name); } AddressSpace space = getAddressSpace(spaceName); - spaceBases.put(name, space); + spaceBases.put(name, new Pair<>(space, reg.getName())); parser.end(el); } - private void restoreGlobal(XmlPullParser parser) { - parser.start(); + private void saveReturnAddress(StringBuilder buffer) { + if (returnAddress == null) { + return; + } + buffer.append("\n"); + buffer.append("\n"); + buffer.append("\n"); + } + + private void restoreReturnAddress(XmlPullParser parser) throws XmlParseException { + XmlElement el = parser.start(); + XmlElement subel = parser.start(); + AddressXML addrSized = AddressXML.restoreXml(subel, this); + returnAddress = addrSized.getVarnode(); + parser.end(subel); + parser.end(el); + } + + private void readExtraRange(XmlElement el, String spcName, String tagName) { + AddressSpace addressSpace = spaceBases.get(spcName).first; + long first = 0; + long last = -1; + boolean seenLast = false; + String attrvalue = el.getAttribute("first"); + if (attrvalue != null) { + first = SpecXmlUtils.decodeLong(attrvalue); + } + attrvalue = el.getAttribute("last"); + if (attrvalue != null) { + last = SpecXmlUtils.decodeLong(attrvalue); + seenLast = true; + } + if (!seenLast) { + last = addressSpace.getMaxAddress().getOffset(); + } + if (extraRanges == null) { + extraRanges = new ArrayList<>(); + } + extraRanges.add(new Pair<>(tagName + '_' + spcName, new Pair<>(first, last))); + } + + private void saveExtraRanges(StringBuilder buffer, String tagName) { + if (extraRanges == null) { + return; + } + for (Pair> entry : extraRanges) { + if (!entry.first.startsWith(tagName)) { + continue; + } + String spcName = entry.first.substring(entry.first.indexOf('_') + 1); + long first = entry.second.first; + long last = entry.second.second; + boolean useFirst = (first != 0); + boolean useLast = (last != -1); + buffer.append("\n"); + } + } + + private void saveMemoryTags(StringBuilder buffer, String tagName, AddressSet addrSet) { + if (addrSet == null) { + return; + } + buffer.append('<').append(tagName).append(">\n"); + AddressRangeIterator iter = addrSet.getAddressRanges(); + while (iter.hasNext()) { + AddressRange range = iter.next(); + buffer.append("\n"); + } + saveExtraRanges(buffer, tagName); + buffer.append("\n"); + } + + private void restoreMemoryTags(String tagName, XmlPullParser parser, AddressSet addrSet) + throws XmlParseException { + parser.start(tagName); while (parser.peek().isStart()) { XmlElement subel = parser.start(); String name = subel.getName(); - if (name.equals("range")) { - AddressRange range = getAddressRange(subel); - if (range != null) { - globalSet.add(range); + if (name.equals("range") || name.equals("register")) { + String spcName = subel.getAttribute("space"); + if (spcName != null && spaceBases != null && spaceBases.containsKey(spcName)) { + readExtraRange(subel, spcName, tagName); + } + else { + AddressXML range = AddressXML.restoreRangeXml(subel, this); + Address firstAddress = range.getFirstAddress(); + Address lastAddress = range.getLastAddress(); + AddressRange addrRange = new AddressRangeImpl(firstAddress, lastAddress); + addrSet.add(addrRange); } } - else if (name.equals("register")) { - String regName = subel.getAttribute("name"); - Register reg = getRegister(regName); - globalSet.addRange(reg.getAddress(), - reg.getAddress().add(reg.getMinimumByteSize() - 1)); + else { + throw new XmlParseException("Unexpected <" + tagName + "> sub-tag: " + name); } parser.end(subel); } parser.end(); } - private void restoreContextData(XmlPullParser parser) { - parser.start(); - while (parser.peek().isStart()) { - String name = parser.peek().getName(); - if (name.equals("context_set")) { - addContextSet(parser); - } - else if (name.equals("tracked_set")) { - addTrackedSet(parser); - } - } - parser.end(); - } - - private void addTrackedSet(XmlPullParser parser) { + private void restorePreferSplit(XmlPullParser parser) throws XmlParseException { XmlElement el = parser.start(); - AddressRange range = getAddressRange(el); + String styleString = el.getAttribute("style"); + if (styleString == null || !styleString.equals("inhalf")) { + throw new XmlParseException("Unknown prefersplit strategy"); + } + preferSplit = new ArrayList<>(); while (parser.peek().isStart()) { XmlElement subel = parser.start(); - ctxsetting.add(getContextSetting(subel, range, false)); + AddressXML addrSized = AddressXML.restoreXml(subel, this); + parser.end(subel); + preferSplit.add(addrSized.getVarnode()); + } + parser.end(el); + } + + private void savePreferSplit(StringBuilder buffer) { + if (preferSplit == null || preferSplit.isEmpty()) { + return; + } + buffer.append("\n"); + for (Varnode varnode : preferSplit) { + buffer.append("\n"); + } + buffer.append("\n"); + } + + private void restoreDeadCodeDelay(XmlPullParser parser) { + if (deadCodeDelay == null) { + deadCodeDelay = new ArrayList<>(); + } + XmlElement el = parser.start(); + AddressSpace space = getAddressSpace(el.getAttribute("space")); + int delay = SpecXmlUtils.decodeInt(el.getAttribute("delay")); + deadCodeDelay.add(new Pair<>(space, delay)); + parser.end(el); + } + + private void saveDeadCodeDelay(StringBuilder buffer) { + if (deadCodeDelay == null) { + return; + } + for (Pair pair : deadCodeDelay) { + buffer.append("\n"); + } + } + + private void restoreInferPtrBounds(XmlPullParser parser) throws XmlParseException { + if (inferPtrBounds == null) { + inferPtrBounds = new ArrayList<>(); + } + XmlElement el = parser.start(); + while (parser.peek().isStart()) { + XmlElement subel = parser.start(); + AddressXML addrSized = AddressXML.restoreRangeXml(subel, this); + AddressRange addrRange = + new AddressRangeImpl(addrSized.getFirstAddress(), addrSized.getLastAddress()); + inferPtrBounds.add(addrRange); parser.end(subel); } parser.end(el); } - private void addContextSet(XmlPullParser parser) { - XmlElement el = parser.start(); - - AddressRange range = getAddressRange(el); - while (parser.peek().isStart()) { - XmlElement subel = parser.start(); - ctxsetting.add(getContextSetting(subel, range, true)); - parser.end(subel); + private void saveInferPtrBounds(StringBuilder buffer) { + if (inferPtrBounds == null) { + return; } - parser.end(el); - } - - private ContextSetting getContextSetting(XmlElement setElement, AddressRange range, - boolean isContextReg) { - String name = setElement.getAttribute("name"); - BigInteger val = getBigInteger(setElement.getAttribute("val"), 0); - Register reg = getRegister(name); - if (isContextReg) { - if (!reg.isProcessorContext()) { - throw new SleighException("Register " + name + " is not a context register"); - } + buffer.append("\n"); + for (AddressRange addrRange : inferPtrBounds) { + buffer.append("\n"); } - else if (reg.isProcessorContext()) { - throw new SleighException("Unexpected context register " + name); - } - return new ContextSetting(reg, val, range.getMinAddress(), range.getMaxAddress()); - } - - /** - * Returns address range defined by spacified set element - * or null if range corresponds to a virtual space (i.e., spacebase). - * @param setParentElement - */ - private AddressRange getAddressRange(XmlElement setParentElement) { - String spaceName = setParentElement.getAttribute("space"); - if (spaceBases.containsKey(spaceName)) { - return null; - } - AddressSpace addrspace = getAddressSpace(spaceName); - long first = addrspace.getMinAddress().getOffset(); - long last = addrspace.getMaxAddress().getOffset(); - String valstring = setParentElement.getAttribute("first"); - if (valstring != null) { - first = SpecXmlUtils.decodeLong(valstring); - } - valstring = setParentElement.getAttribute("last"); - if (valstring != null) { - last = SpecXmlUtils.decodeLong(valstring); - } - Address firstAddress = addrspace.getAddress(first); - Address lastAddress = addrspace.getAddress(last); - return new AddressRangeImpl(firstAddress, lastAddress); + buffer.append("\n"); } private void setStackPointer(XmlPullParser parser) { XmlElement el = parser.start(); - stackPointer = getRegister(el.getAttribute("register")); + String regName = el.getAttribute("register"); + stackPointer = language.getRegister(regName); + if (stackPointer == null) { + throw new SleighException("Unknown register: " + regName); + } String baseSpaceName = el.getAttribute("space"); stackBaseSpace = getAddressSpace(baseSpaceName); if (stackBaseSpace == null) { @@ -805,36 +1017,20 @@ public class BasicCompilerSpec implements CompilerSpec { // } // } - private BigInteger getBigInteger(String valStr, long defaultValue) { - int radix = 10; - if (valStr.startsWith("0x") || valStr.startsWith("0X")) { - valStr = valStr.substring(2); - radix = 16; - } - try { - return new BigInteger(valStr, radix); - } - catch (Exception e) { - return BigInteger.valueOf(defaultValue); - } - } - - private void addPrototypeModel(List modelList, XmlPullParser parser, - boolean isDefault) throws XmlParseException { + private PrototypeModel addPrototypeModel(List modelList, XmlPullParser parser) + throws XmlParseException { PrototypeModel model; if (parser.peek().getName().equals("resolveprototype")) { PrototypeModelMerged mergemodel = new PrototypeModelMerged(); - mergemodel.restoreXml(parser, modelList, stackGrowsNegative); + mergemodel.restoreXml(parser, modelList); model = mergemodel; } else { model = new PrototypeModel(); - model.restoreXml(parser, this, stackGrowsNegative); - } - if (defaultModel == null || isDefault) { - defaultModel = model; + model.restoreXml(parser, this); } modelList.add(model); + return model; } @Override @@ -842,62 +1038,6 @@ public class BasicCompilerSpec implements CompilerSpec { return dataOrganization; } - private String getPrototypeEvaluationModelChoice(Program program) { - Options options = program.getOptions(DECOMPILER_PROPERTY_LIST_NAME); - return options.getString(EVALUATION_MODEL_PROPERTY_NAME, (String) null); - } - - @Override - public Object getPrototypeEvaluationModel(Program program) { - - String modelName = getPrototypeEvaluationModelChoice(program); - - // Names in evaluationModelChoices must directly correspond to PrototypeModel in evalmodels - for (int i = 0; i < evaluationModelChoices.length; ++i) { - if (evaluationModelChoices[i].equals(modelName)) { - return evalmodels[i]; - } - } - return null; - } - - @Override - public DecompilerLanguage getDecompilerOutputLanguage(Program program) { - Options options = program.getOptions(DECOMPILER_PROPERTY_LIST_NAME); - if (options.contains(DECOMPILER_OUTPUT_LANGUAGE)) { - return options.getEnum(DECOMPILER_OUTPUT_LANGUAGE, DECOMPILER_OUTPUT_DEF); - } - return DECOMPILER_OUTPUT_DEF; - } - - @Override - public void registerProgramOptions(Program program) { - - // NOTE: Any changes to the option name/path must be handled carefully since - // old property values will remain in the program. There is currently no support - // for upgrading/moving old property values. - - Options decompilerPropertyList = program.getOptions(DECOMPILER_PROPERTY_LIST_NAME); - decompilerPropertyList - .setOptionsHelpLocation(new HelpLocation("DecompilePlugin", "ProgramOptions")); - decompilerPropertyList.registerOption(EVALUATION_MODEL_PROPERTY_NAME, - OptionType.STRING_TYPE, evaluationModelChoices[0], - new HelpLocation("DecompilePlugin", "OptionProtoEval"), - "Select the default function prototype/evaluation model to be used during Decompiler analysis", - new StringWithChoicesEditor(evaluationModelChoices)); - - if (decompilerPropertyList.contains(DECOMPILER_OUTPUT_LANGUAGE)) { - decompilerPropertyList.registerOption(DECOMPILER_OUTPUT_LANGUAGE, DECOMPILER_OUTPUT_DEF, - null, DECOMPILER_OUTPUT_DESC); - - } - - Options analysisPropertyList = - program.getOptions(Program.ANALYSIS_PROPERTIES + ".Decompiler Parameter ID"); - analysisPropertyList.createAlias(EVALUATION_MODEL_PROPERTY_NAME, decompilerPropertyList, - EVALUATION_MODEL_PROPERTY_NAME); - } - @Override public PrototypeModel matchConvention(GenericCallingConvention genericCallingConvention) { if (genericCallingConvention == GenericCallingConvention.unknown) { @@ -913,10 +1053,10 @@ public class BasicCompilerSpec implements CompilerSpec { @Override public PrototypeModel findBestCallingConvention(Parameter[] params) { - if (!defaultEvaluationModel.isMerged()) { - return defaultEvaluationModel; + if (!evalCurrentModel.isMerged()) { + return evalCurrentModel; } - return ((PrototypeModelMerged) defaultEvaluationModel).selectModel(params); + return ((PrototypeModelMerged) evalCurrentModel).selectModel(params); } @Override @@ -964,16 +1104,140 @@ public class BasicCompilerSpec implements CompilerSpec { } /** - * Adds and enables an option to have the decompiler display java. - * @param program to be enabled + * Remove any call mechanism injections associated with the given list of PrototypeModels + * @param modelList is the given list */ - public static void enableJavaLanguageDecompilation(Program program) { - Options decompilerPropertyList = - program.getOptions(BasicCompilerSpec.DECOMPILER_PROPERTY_LIST_NAME); - decompilerPropertyList.registerOption(BasicCompilerSpec.DECOMPILER_OUTPUT_LANGUAGE, - BasicCompilerSpec.DECOMPILER_OUTPUT_DEF, null, - BasicCompilerSpec.DECOMPILER_OUTPUT_DESC); - decompilerPropertyList.setEnum(BasicCompilerSpec.DECOMPILER_OUTPUT_LANGUAGE, - DecompilerLanguage.JAVA_LANGUAGE); + protected void removeProgramMechanismPayloads(Collection modelList) { + for (PrototypeModel model : modelList) { + if (model.hasInjection()) { + pcodeInject.removeMechanismPayload(model.getInjectName()); + } + } + } + + /** + * Register Program based InjectPayloads with the p-code library. + * This allows derived classes to extend the library + * @param injectExtensions is the list of payloads to register + */ + protected void registerProgramInject(List injectExtensions) { + pcodeInject.registerProgramInject(injectExtensions); + } + + /** + * Mark a given PrototypeModel as a Program specific extension + * @param model is the given PrototypeModel + */ + protected static void markPrototypeAsExtension(PrototypeModel model) { + model.isExtension = true; + } + + @Override + public boolean equals(Object obj) { + BasicCompilerSpec op2 = (BasicCompilerSpec) obj; + if (aggressiveTrim != op2.aggressiveTrim || copiedThisModel != op2.copiedThisModel) { + return false; + } + if (!dataOrganization.equals(op2.dataOrganization)) { + return false; + } + if (!ctxsetting.equals(op2.ctxsetting)) { + return false; + } + if (!SystemUtilities.isEqual(deadCodeDelay, op2.deadCodeDelay)) { + return false; + } + if (defaultModel != null) { + if (op2.defaultModel == null) { + return false; + } + if (!defaultModel.name.equals(op2.defaultModel.name)) { + return false; + } + } + else if (op2.defaultModel != null) { + return false; + } + if (evalCalledModel != null) { + if (op2.evalCalledModel == null) { + return false; + } + if (!evalCalledModel.name.equals(op2.evalCalledModel.name)) { + return false; + } + } + else if (op2.evalCalledModel != null) { + return false; + } + if (evalCurrentModel != null) { + if (op2.evalCurrentModel == null) { + return false; + } + if (!evalCurrentModel.name.equals(op2.evalCurrentModel.name)) { + return false; + } + } + else if (op2.evalCurrentModel != null) { + return false; + } + if (allmodels.length != op2.allmodels.length) { + return false; + } + if (!SystemUtilities.isArrayEqual(allmodels, op2.allmodels)) { + return false; + } + if (!SystemUtilities.isEqual(extraRanges, op2.extraRanges)) { + return false; + } + if (funcPtrAlign != op2.funcPtrAlign) { + return false; + } + if (!globalSet.equals(op2.globalSet)) { + return false; + } + if (!SystemUtilities.isEqual(inferPtrBounds, op2.inferPtrBounds)) { + return false; + } + if (!SystemUtilities.isEqual(noHighPtr, op2.noHighPtr)) { + return false; + } + if (!pcodeInject.equals(op2.pcodeInject)) { + return false; + } + if (!SystemUtilities.isEqual(preferSplit, op2.preferSplit)) { + return false; + } + if (!properties.equals(op2.properties)) { + return false; + } + if (!SystemUtilities.isEqual(readOnlySet, op2.readOnlySet)) { + return false; + } + if (!SystemUtilities.isEqual(returnAddress, op2.returnAddress)) { + return false; + } + if (reverseJustifyStack != op2.reverseJustifyStack) { + return false; + } + if (!SystemUtilities.isEqual(spaceBases, op2.spaceBases)) { + return false; + } + if (!SystemUtilities.isEqual(stackBaseSpace, op2.stackBaseSpace)) { + return false; + } + if (stackGrowsNegative != op2.stackGrowsNegative) { + return false; + } + if (!SystemUtilities.isEqual(stackPointer, op2.stackPointer)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = language.getLanguageID().hashCode(); + hash = 79 * description.getCompilerSpecID().hashCode() + hash; + return hash; } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/CompilerSpec.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/CompilerSpec.java index 3ad0a8b151..8a7aa14ae9 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/CompilerSpec.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/CompilerSpec.java @@ -21,10 +21,20 @@ import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.data.DataOrganization; import ghidra.program.model.data.GenericCallingConvention; -import ghidra.program.model.listing.*; +import ghidra.program.model.listing.DefaultProgramContext; +import ghidra.program.model.listing.Parameter; /** - * Interface for classes that hold compiler option information + * Interface for requesting specific information about the compiler used to + * build a Program being analyzed. Major elements that can be queried include: + * - AddressSpaces from the Language plus compiler specific ones like "stack" + * - DataOrganization describing size and alignment of primitive data-types: int, long, pointers, etc. + * - PrototypeModels describing calling conventions used by the compiler: __stdcall, __thiscall, etc. + * - InjectPayloads or p-code that can used for + * - Call-fixups, substituting p-code for compiler bookkeeping functions during analysis. + * - Callother-fixups, substituting p-code for user-defined p-code operations. + * - Memory ranges that the compiler treats as global + * - Context and register values known to the compiler over specific memory ranges */ public interface CompilerSpec { @@ -35,6 +45,16 @@ public interface CompilerSpec { public final static String CALLING_CONVENTION_fastcall = "__fastcall"; public final static String CALLING_CONVENTION_vectorcall = "__vectorcall"; + /** + * Labels for PrototypeModels that are used by default for various analysis/evaluation + * use-cases, when the true model isn't known. The CompilerSpec maintains a specific + * default PrototypeModel to be used for each use-case label. + */ + public enum EvaluationModelType { + EVAL_CURRENT, // A PrototypeModel used to evaluate the "current" function + EVAL_CALLED // A PrototypeModel used to evaluate a "called" function + } + /** * Get the Language this compiler spec is based on. Note that * compiler specs may be reused across multiple languages in the @@ -45,12 +65,11 @@ public interface CompilerSpec { public Language getLanguage(); /** - * Returns a brief description of the compiler spec + * @return a brief description of the compiler spec */ public CompilerSpecDescription getCompilerSpecDescription(); /** - * Returns the id string associated with this compiler spec; * @return the id string associated with this compiler spec; */ public CompilerSpecID getCompilerSpecID(); @@ -72,11 +91,11 @@ public interface CompilerSpec { /** * Get an address space by name. This can be value added over the normal AddressFactory.getAddressSpace * routine because the compiler spec can refer to special internal spaces like the stack space - * @param spaceName + * @param spaceName is the name of the address space * @return the corresponding AddressSpace object */ public AddressSpace getAddressSpace(String spaceName); - + /** * Get the stack address space defined by this specification * @return stack address space @@ -90,7 +109,7 @@ public interface CompilerSpec { public AddressSpace getStackBaseSpace(); /** - * Returns true if stack grows with negative offsets + * @return true if the stack grows with negative offsets */ public boolean stackGrowsNegative(); @@ -102,7 +121,7 @@ public interface CompilerSpec { public void applyContextSettings(DefaultProgramContext ctx); /** - * Returns an array of the prototype models. Each prototype model specifies a calling convention. + * @return an array of the prototype models. Each prototype model specifies a calling convention. */ public PrototypeModel[] getCallingConventions(); @@ -114,9 +133,9 @@ public interface CompilerSpec { public PrototypeModel getCallingConvention(String name); /** - * Returns an array of the named prototype models. Each prototype model specifies a calling convention. + * @return all possible PrototypeModels, including calling conventions and merge models */ - public PrototypeModel[] getNamedCallingConventions(); + public PrototypeModel[] getAllModels(); /** * Returns the prototype model that is the default calling convention or else null. @@ -125,61 +144,60 @@ public interface CompilerSpec { public PrototypeModel getDefaultCallingConvention(); /** - * Returns true if specified address location has been designated global - * @param addr address location + * Get the language that the decompiler produces + * @return an enum specifying the language + */ + public DecompilerLanguage getDecompilerOutputLanguage(); + + /** + * Get the evaluation model matching the given type. + * If analysis needs to apply a PrototypeModel to a function but a specific model + * is not known, then this method can be used to select a putative PrototypeModel + * based on the analysis use-case: + * - EVAL_CURRENT indicates the model to use for the "current function" being analyzed + * - EVAL_CALLED indicates the model to use for a function called by the current function + * @param modelType is the type of evaluation model + * @return prototype evaluation model + */ + public PrototypeModel getPrototypeEvaluationModel(EvaluationModelType modelType); + + /** + * @param addr is the (start of the) storage location + * @return true if the specified storage location has been designated "global" in scope */ public boolean isGlobal(Address addr); public DataOrganization getDataOrganization(); - + public PcodeInjectLibrary getPcodeInjectLibrary(); /** - * Register program-specific compiler-spec options - * @param program - */ - public void registerProgramOptions(Program program); - - /** - * Get the program-specific prototype evaluation model. - * @param program - * @return prototype evaluation model - */ - public Object getPrototypeEvaluationModel(Program program); - - /** - * Get the language that the decompiler produces - * @param program - * @return an enum specifying the language - */ - public DecompilerLanguage getDecompilerOutputLanguage(Program program); - - /** - * Get the PrototypeModel based on the genericCallingConvention - * @param genericCallingConvention + * Get the PrototypeModel corresponding to the given generic calling convention + * @param genericCallingConvention is the given generic calling convention * @return the matching model or the defaultModel if nothing matches */ public PrototypeModel matchConvention(GenericCallingConvention genericCallingConvention); - + /** * Find the best guess at a calling convention model from this compiler spec * given an ordered list of (potential) parameters. + * @param params is the ordered list of parameters * @return prototype model corresponding to the specified function signature */ public PrototypeModel findBestCallingConvention(Parameter[] params); /** - * Returns whether this lanugage has a property defined. + * Returns whether this language has a property defined. * @param key the property key * @return if the property is defined */ public boolean hasProperty(String key); - + /** - * Return true if function prototypes respect the C-language datatype conversion conventions. - * This amounts to converting array datatypes to pointer-to-element datatypes. + * Return true if function prototypes respect the C-language data-type conversion conventions. + * This amounts to converting array data-types to pointer-to-element data-types. * In C, arrays are passed by reference (structures are still passed by value) - * @return + * @return if the prototype does C-language data-type conversions */ public boolean doesCDataTypeConversions(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/ContextSetting.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/ContextSetting.java index 75eb40742a..f2cf712ffb 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/ContextSetting.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/ContextSetting.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +15,15 @@ */ package ghidra.program.model.lang; -import ghidra.program.model.address.Address; - import java.math.BigInteger; +import java.util.Iterator; +import java.util.List; + +import ghidra.app.plugin.processors.sleigh.SleighException; +import ghidra.program.model.address.Address; +import ghidra.program.model.pcode.AddressXML; +import ghidra.util.xml.SpecXmlUtils; +import ghidra.xml.*; /** * Class for context configuration information as @@ -37,6 +42,35 @@ public class ContextSetting { this.endAddr = endAddr; } + /** + * Construct from an XML \ tag. The tag is a child of either \ or \ + * which provides details of the memory range affected. + * @param el is the XML tag + * @param cspec is used to lookup register names present in the tag + * @param isContextReg is true for a \ parent, false for a \ parent + * @param first is the first Address in the affected memory range + * @param last is the last Address in the affected memory range + */ + private ContextSetting(XmlElement el, CompilerSpec cspec, boolean isContextReg, Address first, + Address last) throws SleighException { + startAddr = first; + endAddr = last; + String name = el.getAttribute("name"); + value = getBigInteger(el.getAttribute("val"), 0); + register = cspec.getLanguage().getRegister(name); + if (register == null) { + throw new SleighException("Unknown register: " + name); + } + if (isContextReg) { + if (!register.isProcessorContext()) { + throw new SleighException("Register " + name + " is not a context register"); + } + } + else if (register.isProcessorContext()) { + throw new SleighException("Unexpected context register " + name); + } + } + public Register getRegister() { return register; } @@ -52,4 +86,128 @@ public class ContextSetting { public Address getEndAddress() { return endAddr; } + + private BigInteger getBigInteger(String valStr, long defaultValue) { + int radix = 10; + if (valStr.startsWith("0x") || valStr.startsWith("0X")) { + valStr = valStr.substring(2); + radix = 16; + } + try { + return new BigInteger(valStr, radix); + } + catch (Exception e) { + return BigInteger.valueOf(defaultValue); + } + } + + public void saveXml(StringBuilder buffer) { + buffer.append("\n"); + } + + @Override + public boolean equals(Object obj) { + ContextSetting op2 = (ContextSetting) obj; + if (!startAddr.equals(op2.startAddr)) { + return false; + } + if (!endAddr.equals(op2.endAddr)) { + return false; + } + if (!register.equals(op2.register)) { + return false; + } + if (!value.equals(op2.value)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = startAddr.hashCode(); + hash = 79 * hash + endAddr.hashCode(); + hash = 79 * hash + register.hashCode(); + hash = 79 * hash + value.hashCode(); + return hash; + } + + public static void parseContextSet(List resList, XmlPullParser parser, + CompilerSpec cspec) throws XmlParseException { + XmlElement el = parser.start(); + boolean isContextReg; + if (el.getName().equals("context_set")) { + isContextReg = true; + } + else if (el.getName().equals("tracked_set")) { + isContextReg = false; + } + else { + throw new XmlParseException("Unknown context setting tag: " + el.getName()); + } + AddressXML range = AddressXML.restoreRangeXml(el, cspec); + Address firstAddr = range.getFirstAddress(); + Address lastAddr = range.getLastAddress(); + while (parser.peek().isStart()) { + XmlElement subel = parser.start(); + ContextSetting ctxSetting = + new ContextSetting(subel, cspec, isContextReg, firstAddr, lastAddr); + parser.end(subel); + resList.add(ctxSetting); + } + parser.end(el); + } + + public static void parseContextData(List resList, XmlPullParser parser, + CompilerSpec cspec) throws XmlParseException { + parser.start(); + while (parser.peek().isStart()) { + parseContextSet(resList, parser, cspec); + } + parser.end(); + } + + public static void buildContextDataXml(StringBuilder buffer, List ctxList) { + if (ctxList.isEmpty()) { + return; + } + buffer.append("\n"); + Iterator iter = ctxList.iterator(); + ContextSetting startContext = iter.next(); + boolean isContextReg = startContext.register.isProcessorContext(); + Address firstAddr = startContext.startAddr; + Address lastAddr = startContext.endAddr; + while (iter.hasNext()) { + buffer.append(isContextReg ? "\n"); + startContext.saveXml(buffer); + while (iter.hasNext()) { + startContext = iter.next(); + boolean nextIsContext = startContext.register.isProcessorContext(); + boolean shouldBreak = false; + if (isContextReg != nextIsContext) { + isContextReg = nextIsContext; + shouldBreak = true; + } + if (!firstAddr.equals(startContext.startAddr)) { + firstAddr = startContext.startAddr; + shouldBreak = true; + } + if (!lastAddr.equals(startContext.endAddr)) { + lastAddr = startContext.endAddr; + shouldBreak = true; + } + if (shouldBreak) { + break; + } + startContext.saveXml(buffer); + } + buffer.append(isContextReg ? "\n" : "\n"); + } + buffer.append("\n"); + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectContext.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectContext.java index 43eff65d62..ee55dcd7c4 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectContext.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectContext.java @@ -15,24 +15,21 @@ */ package ghidra.program.model.lang; -import ghidra.app.plugin.processors.sleigh.SleighLanguage; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressFactory; -import ghidra.program.model.pcode.PcodeXMLException; -import ghidra.program.model.pcode.Varnode; -import ghidra.util.xml.SpecXmlUtils; - import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import javax.xml.parsers.SAXParser; -import org.xml.sax.Attributes; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; +import org.xml.sax.*; import org.xml.sax.helpers.DefaultHandler; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressFactory; +import ghidra.program.model.pcode.*; +import ghidra.util.xml.SpecXmlUtils; + public class InjectContext { private class Handler extends DefaultHandler { private AddressFactory addrFactory; @@ -48,18 +45,19 @@ public class InjectContext { @Override public void startElement(String uri, String localName, String rawName, Attributes attr) throws SAXException { - if (rawName.equals("context")) + if (rawName.equals("context")) { state = 1; + } else if (rawName.equals("input")) { - inputlist = new ArrayList(); + inputlist = new ArrayList<>(); state = 3; } else if (rawName.equals("output")) { - output = new ArrayList(); + output = new ArrayList<>(); state = 4; } else if (rawName.equals("addr")) { - curaddr = Varnode.readXMLAddress(rawName, attr, addrFactory); + curaddr = AddressXML.readXML(rawName, attr, addrFactory); if (state == 1) { baseAddr = curaddr; state = 2; @@ -69,20 +67,22 @@ public class InjectContext { } else if (state == 3) { int size = SpecXmlUtils.decodeInt(attr.getValue("size")); - Varnode vn = new Varnode(curaddr,size); + Varnode vn = new Varnode(curaddr, size); inputlist.add(vn); } else if (state == 4) { int size = SpecXmlUtils.decodeInt(attr.getValue("size")); - Varnode vn = new Varnode(curaddr,size); + Varnode vn = new Varnode(curaddr, size); output.add(vn); } } - else - throw new SAXException("Unrecognized inject tag: "+rawName); - + else { + throw new SAXException("Unrecognized inject tag: " + rawName); + } + } } + public SleighLanguage language; public Address baseAddr; // Base address of op (call,userop) causing the inject public Address nextAddr; // Address of next instruction following the injecting instruction @@ -93,16 +93,19 @@ public class InjectContext { public InjectContext() { } - - public void restoreXml(SAXParser parser,String xml,AddressFactory addrFactory) throws PcodeXMLException { + + public void restoreXml(SAXParser parser, String xml, AddressFactory addrFactory) + throws PcodeXMLException { Handler handler = new Handler(addrFactory); try { parser.parse(new InputSource(new StringReader(xml)), handler); - } catch (SAXException e) { - throw new PcodeXMLException("Problem parsing inject context: "+e.getMessage()); - } catch (IOException e) { - throw new PcodeXMLException("Problem parsing inject context: "+e.getMessage()); } - + catch (SAXException e) { + throw new PcodeXMLException("Problem parsing inject context: " + e.getMessage()); + } + catch (IOException e) { + throw new PcodeXMLException("Problem parsing inject context: " + e.getMessage()); + } + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayload.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayload.java index 05d65cf65f..805a182e2c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayload.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayload.java @@ -16,8 +16,11 @@ package ghidra.program.model.lang; import ghidra.app.plugin.processors.sleigh.PcodeEmit; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.program.model.listing.Program; import ghidra.program.model.pcode.PcodeOp; +import ghidra.xml.XmlParseException; +import ghidra.xml.XmlPullParser; /** * InjectPayload encapsulates a semantic (p-code) override which can be injected @@ -26,7 +29,7 @@ import ghidra.program.model.pcode.PcodeOp; * */ public interface InjectPayload { - + public static final int CALLFIXUP_TYPE = 1; public static final int CALLOTHERFIXUP_TYPE = 2; public static final int CALLMECHANISM_TYPE = 3; @@ -36,13 +39,13 @@ public interface InjectPayload { private String name; private int index; private int size; - - public InjectParameter(String nm,int sz) { + + public InjectParameter(String nm, int sz) { name = nm; index = 0; size = sz; } - + public String getName() { return name; } @@ -50,14 +53,34 @@ public interface InjectPayload { public int getIndex() { return index; } - + public int getSize() { return size; } - + void setIndex(int i) { index = i; } + + @Override + public boolean equals(Object obj) { + InjectParameter op2 = (InjectParameter) obj; + if (index != op2.index || size != op2.size) { + return false; + } + if (!name.equals(op2.name)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = name.hashCode(); + hash = 79 * hash + index; + hash = 79 * hash + size; + return hash; + } } /** @@ -84,19 +107,26 @@ public interface InjectPayload { * @return array of any input parameters for this inject */ public InjectParameter[] getInput(); - + /** * @return array of any output parameters for this inject */ public InjectParameter[] getOutput(); - + + /** + * If parsing a payload (from XML) fails, a placeholder payload may be substituted and + * this method returns true for the substitute. In all other cases, this returns false. + * @return true if this is a placeholder for a payload with parse errors. + */ + public boolean isErrorPlaceholder(); + /** * Given a context, send the p-code payload to the emitter * @param context is the context for injection * @param emit is the object accumulating the final p-code */ - public void inject(InjectContext context,PcodeEmit emit); - + public void inject(InjectContext context, PcodeEmit emit); + /** * A convenience function wrapping the inject method, to produce the final set * of PcodeOp objects in an array @@ -105,9 +135,29 @@ public interface InjectPayload { * @return the array of PcodeOps */ public PcodeOp[] getPcode(Program program, InjectContext con); - + /** * @return true if the injected p-code falls thru */ public boolean isFallThru(); + + /** + * @return true if this inject's COPY operations should be treated as incidental + */ + public boolean isIncidentalCopy(); + + /** + * Write out configuration parameters as a \ XML tag + * @param buffer is the stream to write to + */ + public void saveXml(StringBuilder buffer); + + /** + * Restore the payload from an XML stream. The root expected document is + * the \ tag, which may be wrapped with another tag by the derived class. + * @param parser is the XML stream + * @param language is used to resolve registers and address spaces + * @throws XmlParseException for badly formed XML + */ + public void restoreXml(XmlPullParser parser, SleighLanguage language) throws XmlParseException; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadCallfixup.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadCallfixup.java index 60d87161e1..ed03cca12a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadCallfixup.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadCallfixup.java @@ -18,18 +18,39 @@ package ghidra.program.model.lang; import java.util.ArrayList; import java.util.List; -import ghidra.app.plugin.processors.sleigh.SleighException; -import ghidra.xml.XmlElement; -import ghidra.xml.XmlPullParser; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.app.plugin.processors.sleigh.template.ConstructTpl; +import ghidra.util.xml.SpecXmlUtils; +import ghidra.xml.*; public class InjectPayloadCallfixup extends InjectPayloadSleigh { - private List targetSymbolNames; + protected List targetSymbolNames; + + /** + * Constructor for a partial clone of a payload whose p-code failed to parse. + * @param pcode is the p-code to substitute + * @param failedPayload is the failed callfixup + */ + protected InjectPayloadCallfixup(ConstructTpl pcode, InjectPayloadCallfixup failedPayload) { + super(pcode, failedPayload); + targetSymbolNames = failedPayload.targetSymbolNames; + } + + /** + * Construct a dummy payload + * @param pcode is the dummy p-code sequence to use + * @param nm is the name of the payload + */ + protected InjectPayloadCallfixup(ConstructTpl pcode, String nm) { + super(pcode, CALLFIXUP_TYPE, nm); + targetSymbolNames = new ArrayList<>(); + } public InjectPayloadCallfixup(String sourceName) { super(sourceName); type = CALLFIXUP_TYPE; - targetSymbolNames = new ArrayList(); + targetSymbolNames = new ArrayList<>(); } public List getTargets() { @@ -37,22 +58,21 @@ public class InjectPayloadCallfixup extends InjectPayloadSleigh { } @Override - public InjectPayloadSleigh clone() { - InjectPayloadSleigh res = new InjectPayloadCallfixup(source); - res.copy(this); - return res; + public void saveXml(StringBuilder buffer) { + buffer.append("\n"); + for (String nm : targetSymbolNames) { + buffer.append("\n"); + } + super.saveXml(buffer); + buffer.append("\n"); } @Override - protected void copy(InjectPayloadSleigh op2) { - super.copy(op2); - InjectPayloadCallfixup fixup = (InjectPayloadCallfixup) op2; - for (String target : fixup.targetSymbolNames) - targetSymbolNames.add(target); - } - - @Override - public void restoreXml(XmlPullParser parser) { + public void restoreXml(XmlPullParser parser, SleighLanguage language) throws XmlParseException { XmlElement fixupEl = parser.start("callfixup"); name = fixupEl.getAttribute("name"); boolean pcodeSubtag = false; @@ -62,21 +82,41 @@ public class InjectPayloadCallfixup extends InjectPayloadSleigh { XmlElement subel = parser.start(); String targetName = subel.getAttribute("name"); if (targetName == null) { - throw new SleighException("Invalid callfixup target, missing target name"); + throw new XmlParseException("Invalid callfixup target, missing target name"); } targetSymbolNames.add(targetName); parser.end(subel); } else if (elname.equals("pcode")) { - super.restoreXml(parser); + super.restoreXml(parser, language); pcodeSubtag = true; } else { - throw new SleighException("Unknown callfixup tag: " + elname); + throw new XmlParseException("Unknown callfixup tag: " + elname); } } - if (!pcodeSubtag) - throw new SleighException(" missing subtag: " + name); + if (!pcodeSubtag) { + throw new XmlParseException(" missing subtag: " + name); + } parser.end(fixupEl); } + + @Override + public boolean equals(Object obj) { + InjectPayloadCallfixup op2 = (InjectPayloadCallfixup) obj; + if (!targetSymbolNames.equals(op2.targetSymbolNames)) { + return false; + } + return super.equals(obj); + } + + @Override + public int hashCode() { + int hash = 13; + for (String target : targetSymbolNames) { + hash = 79 * hash + target.hashCode(); + } + hash = 79 * hash + super.hashCode(); + return hash; + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadCallfixupError.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadCallfixupError.java new file mode 100644 index 0000000000..6f95d2c667 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadCallfixupError.java @@ -0,0 +1,39 @@ +/* ### + * 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.program.model.lang; + +import ghidra.program.model.address.AddressFactory; + +/** + * A substitute for a callfixup that did not successfully parse. + */ +public class InjectPayloadCallfixupError extends InjectPayloadCallfixup { + + public InjectPayloadCallfixupError(AddressFactory addrFactory, + InjectPayloadCallfixup failedPayload) { + // Make a partial clone + super(InjectPayloadSleigh.getDummyPcode(addrFactory), failedPayload); + } + + public InjectPayloadCallfixupError(AddressFactory addrFactory, String nm) { + super(InjectPayloadSleigh.getDummyPcode(addrFactory), nm); + } + + @Override + public boolean isErrorPlaceholder() { + return true; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadCallother.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadCallother.java index f362648646..46cd16b70c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadCallother.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadCallother.java @@ -15,31 +15,53 @@ */ package ghidra.program.model.lang; -import ghidra.app.plugin.processors.sleigh.SleighException; -import ghidra.xml.XmlElement; -import ghidra.xml.XmlPullParser; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.app.plugin.processors.sleigh.template.ConstructTpl; +import ghidra.util.xml.SpecXmlUtils; +import ghidra.xml.*; public class InjectPayloadCallother extends InjectPayloadSleigh { + /** + * Constructor for a partial clone of a payload whose p-code failed to parse. + * @param pcode is the p-code to substitute + * @param failedPayload is the failed callfixup + */ + protected InjectPayloadCallother(ConstructTpl pcode, InjectPayloadCallother failedPayload) { + super(pcode, failedPayload); + } + + /** + * Constructor for a dummy payload + * @param pcode is the dummy p-code to use + * @param nm is the name of the payload + */ + protected InjectPayloadCallother(ConstructTpl pcode, String nm) { + super(pcode, CALLOTHERFIXUP_TYPE, nm); + } + public InjectPayloadCallother(String sourceName) { super(sourceName); type = CALLOTHERFIXUP_TYPE; } @Override - public InjectPayloadSleigh clone() { - InjectPayloadSleigh res = new InjectPayloadCallother(source); - res.copy(this); - return res; + public void saveXml(StringBuilder buffer) { + buffer.append("\n"); + super.saveXml(buffer); + buffer.append("\n"); } @Override - public void restoreXml(XmlPullParser parser) { + public void restoreXml(XmlPullParser parser, SleighLanguage language) throws XmlParseException { XmlElement fixupEl = parser.start("callotherfixup"); name = fixupEl.getAttribute("targetop"); - if (!parser.peek().isStart() || !parser.peek().getName().equals("pcode")) - throw new SleighException(" does not contain a tag"); - super.restoreXml(parser); + if (!parser.peek().isStart() || !parser.peek().getName().equals("pcode")) { + throw new XmlParseException(" does not contain a tag"); + } + super.restoreXml(parser, language); parser.end(fixupEl); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadCallotherError.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadCallotherError.java new file mode 100644 index 0000000000..c2b1e8d2fe --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadCallotherError.java @@ -0,0 +1,43 @@ +/* ### + * 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.program.model.lang; + +import ghidra.program.model.address.AddressFactory; + +/** + * A substitute for a callother fixup that did not fully parse + */ +public class InjectPayloadCallotherError extends InjectPayloadCallother { + + /** + * Constructor for use if the p-code template did not parse + * @param addrFactory is the address factory to use constructing dummy p-code + * @param failedPayload is the object with the failed template + */ + public InjectPayloadCallotherError(AddressFactory addrFactory, + InjectPayloadCallother failedPayload) { + super(InjectPayloadSleigh.getDummyPcode(addrFactory), failedPayload); + } + + public InjectPayloadCallotherError(AddressFactory addrFactory, String nm) { + super(InjectPayloadSleigh.getDummyPcode(addrFactory), nm); + } + + @Override + public boolean isErrorPlaceholder() { + return true; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadJumpAssist.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadJumpAssist.java new file mode 100644 index 0000000000..d438d84dc3 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadJumpAssist.java @@ -0,0 +1,48 @@ +/* ### + * 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.program.model.lang; + +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.xml.*; + +public class InjectPayloadJumpAssist extends InjectPayloadSleigh { + + private String baseName; + + public InjectPayloadJumpAssist(String bName, String sourceName) { + super(sourceName); + baseName = bName; + type = EXECUTABLEPCODE_TYPE; + } + + @Override + public void restoreXml(XmlPullParser parser, SleighLanguage language) throws XmlParseException { + XmlElement subel = parser.peek(); + if (subel.getName().charAt(0) == 'c') { + name = baseName + "_index2case"; + } + else if (subel.getName().charAt(0) == 'a') { + name = baseName + "_index2addr"; + } + else if (subel.getName().charAt(0) == 's') { + name = baseName + "_calcsize"; + } + else { + name = baseName + "_defaultaddr"; + } + super.restoreXml(parser, language); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadSegment.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadSegment.java new file mode 100644 index 0000000000..84b3b73d81 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadSegment.java @@ -0,0 +1,137 @@ +/* ### + * 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.program.model.lang; + +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.pcode.AddressXML; +import ghidra.util.SystemUtilities; +import ghidra.util.xml.SpecXmlUtils; +import ghidra.xml.*; + +public class InjectPayloadSegment extends InjectPayloadSleigh { + + private AddressSpace space; + private boolean supportsFarPointer; + private AddressSpace constResolveSpace; + private long constResolveOffset; + private int constResolveSize; + + public InjectPayloadSegment(String source) { + super(source); + type = EXECUTABLEPCODE_TYPE; + space = null; + supportsFarPointer = false; + constResolveSpace = null; + constResolveOffset = 0; + constResolveSize = 0; + } + + @Override + public void saveXml(StringBuilder buffer) { + buffer.append(" 0 ? name.substring(0, pos) : name; + if (!subName.equals("segment")) { + SpecXmlUtils.encodeStringAttribute(buffer, "userop", subName); + } + SpecXmlUtils.encodeStringAttribute(buffer, "space", space.getName()); + if (supportsFarPointer) { + SpecXmlUtils.encodeBooleanAttribute(buffer, "farpointer", supportsFarPointer); + } + buffer.append(">\n"); + super.saveXml(buffer); + if (constResolveSpace != null) { + buffer.append("\n"); + buffer.append("\n"); + buffer.append("\n"); + } + buffer.append("\n"); + } + + @Override + public void restoreXml(XmlPullParser parser, SleighLanguage language) throws XmlParseException { + XmlElement el = parser.start(); + name = el.getAttribute("userop"); + if (name == null) { + name = "segment"; + } + name = name + "_pcode"; + String spaceString = el.getAttribute("space"); + space = language.getAddressFactory().getAddressSpace(spaceString); + if (space == null) { + throw new XmlParseException("Unknown address space: " + spaceString); + } + supportsFarPointer = SpecXmlUtils.decodeBoolean(el.getAttribute("farpointer")); + if (parser.peek().isStart()) { + if (parser.peek().getName().equals("pcode")) { + super.restoreXml(parser, language); + } + else { + throw new XmlParseException("Missing child for tag"); + } + } + if (parser.peek().isStart()) { + XmlElement subel = parser.start("constresolve"); + XmlElement subsubel = parser.start(); + AddressXML addrSize = AddressXML.restoreXml(subsubel, language); + addrSize.getFirstAddress(); // Fail fast. Throws AddressOutOfBoundsException if offset is invalid + constResolveSpace = addrSize.getAddressSpace(); + constResolveOffset = addrSize.getOffset(); + constResolveSize = (int) addrSize.getSize(); + parser.end(subsubel); + parser.end(subel); + } + parser.end(el); + } + + @Override + public boolean equals(Object obj) { + InjectPayloadSegment op2 = (InjectPayloadSegment) obj; + if (constResolveOffset != op2.constResolveOffset) { + return false; + } + if (constResolveSize != op2.constResolveSize) { + return false; + } + if (!SystemUtilities.isEqual(constResolveSpace, op2.constResolveSpace)) { + return false; + } + if (!space.equals(op2.space)) { + return false; + } + if (supportsFarPointer != op2.supportsFarPointer) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = space.hashCode(); + if (constResolveSpace != null) { + hash = 79 * hash + constResolveSpace.hashCode(); + } + hash = 79 * hash + Long.hashCode(constResolveOffset); + hash = 79 * hash + constResolveSize; + hash = 79 * hash + (supportsFarPointer ? 1 : 13); + return hash; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadSleigh.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadSleigh.java index b9cb571bff..776ed48509 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadSleigh.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/InjectPayloadSleigh.java @@ -16,18 +16,19 @@ package ghidra.program.model.lang; import java.util.ArrayList; +import java.util.List; import ghidra.app.plugin.processors.sleigh.*; -import ghidra.app.plugin.processors.sleigh.template.ConstructTpl; -import ghidra.app.plugin.processors.sleigh.template.OpTpl; +import ghidra.app.plugin.processors.sleigh.template.*; import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressFactory; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; +import ghidra.util.SystemUtilities; import ghidra.util.xml.SpecXmlUtils; -import ghidra.xml.XmlElement; -import ghidra.xml.XmlPullParser; +import ghidra.xml.*; /** * InjectPayloadSleigh defines an InjectPayload of p-code which is defined via @@ -38,66 +39,78 @@ public class InjectPayloadSleigh implements InjectPayload { private ConstructTpl pcodeTemplate; private int paramShift; private boolean isfallthru; // Precomputed fallthru of inject + private boolean incidentalCopy; // Treat COPY operations as incidental private InjectParameter[] inputlist; private InjectParameter[] output; + private int subType; // 0=uponentry 1=uponreturn protected String name; // Formal name of this inject protected int type; // type of this payload CALLFIXUP_TYPE, CALLOTHERFIXUP_TYPE, etc. protected String source; // Source of this payload private String parseString; // String to be parsed for pcode + /** + * Constructor for partial clone of another payload whose p-code failed to parse + * @param pcode is substitute p-code to replace the failed parse + * @param failedPayload is the failed payload + */ + protected InjectPayloadSleigh(ConstructTpl pcode, InjectPayloadSleigh failedPayload) { + pcodeTemplate = pcode; + paramShift = failedPayload.paramShift; + incidentalCopy = failedPayload.incidentalCopy; + inputlist = failedPayload.inputlist; + output = failedPayload.output; + subType = failedPayload.subType; + name = failedPayload.name; + type = failedPayload.type; + source = failedPayload.source + "_FAILED"; + parseString = null; + isfallthru = computeFallThru(); + } + + /** + * Constructor for a dummy payload, given just a name + * @param pcode is the dummy p-code sequence + * @param tp is the type of injection + * @param nm is the name of the injection + */ + protected InjectPayloadSleigh(ConstructTpl pcode, int tp, String nm) { + pcodeTemplate = pcode; + paramShift = 0; + incidentalCopy = false; + inputlist = new InjectParameter[0]; + output = new InjectParameter[0]; + subType = -1; + name = nm; + type = tp; + source = "FAILED"; + parseString = null; + isfallthru = computeFallThru(); + } + /** * Constructor for use where restoreXml is overridden and provides name and type - * @param sourceName + * @param sourceName is string describing the source of this payload */ protected InjectPayloadSleigh(String sourceName) { name = null; type = -1; + subType = -1; + incidentalCopy = false; inputlist = null; output = null; source = sourceName; } - public InjectPayloadSleigh clone() { - InjectPayloadSleigh res = new InjectPayloadSleigh(source); - res.copy(this); - return res; - } - - protected void copy(InjectPayloadSleigh op2) { - inputlist = null; - output = null; - paramShift = op2.paramShift; - isfallthru = op2.isfallthru; - name = op2.name; - type = op2.type; - source = op2.source; - parseString = op2.parseString; - if (op2.inputlist != null) { - inputlist = new InjectParameter[op2.inputlist.length]; - for (int i = 0; i < inputlist.length; ++i) { - inputlist[i] = - new InjectParameter(op2.inputlist[i].getName(), op2.inputlist[i].getSize()); - inputlist[i].setIndex(op2.inputlist[i].getIndex()); - } - } - if (op2.output != null) { - output = new InjectParameter[op2.output.length]; - for (int i = 0; i < output.length; ++i) { - output[i] = new InjectParameter(op2.output[i].getName(), op2.output[i].getSize()); - output[i].setIndex(op2.output[i].getIndex()); - } - } - } - /** * Provide basic form, restoreXml fills in the rest * @param nm must provide formal name * @param tp must provide type - * @param sourceName + * @param sourceName is a description of the source of this payload */ public InjectPayloadSleigh(String nm, int tp, String sourceName) { name = nm; type = tp; + subType = -1; inputlist = null; output = null; source = sourceName; @@ -117,18 +130,18 @@ public class InjectPayloadSleigh implements InjectPayload { public String getSource() { return source; } - + @Override public int getParamShift() { return paramShift; } - protected void setInputParameters(ArrayList in) { + protected void setInputParameters(List in) { inputlist = new InjectParameter[in.size()]; in.toArray(inputlist); } - protected void setOutputParameters(ArrayList out) { + protected void setOutputParameters(List out) { output = new InjectParameter[out.size()]; out.toArray(output); } @@ -142,14 +155,19 @@ public class InjectPayloadSleigh implements InjectPayload { public InjectParameter[] getOutput() { return output; } - + @Override - public void inject(InjectContext context,PcodeEmit emit) { + public boolean isErrorPlaceholder() { + return false; + } + + @Override + public void inject(InjectContext context, PcodeEmit emit) { ParserWalker walker = emit.getWalker(); try { walker.snippetState(); setupParameters(context, walker); - emit.build(pcodeTemplate,-1); + emit.build(pcodeTemplate, -1); } catch (UnknownInstructionException e) { // Should not be happening in a CallFixup e.printStackTrace(); @@ -159,7 +177,7 @@ public class InjectPayloadSleigh implements InjectPayload { e.printStackTrace(); return; } - emit.resolveRelatives(); + emit.resolveRelatives(); } @Override @@ -168,7 +186,7 @@ public class InjectPayloadSleigh implements InjectPayload { new SleighParserContext(con.baseAddr, con.nextAddr, con.refAddr, con.callAddr); ParserWalker walker = new ParserWalker(protoContext); PcodeEmitObjects emit = new PcodeEmitObjects(walker); - inject(con,emit); + inject(con, emit); return emit.getPcodeOp(); } @@ -177,6 +195,11 @@ public class InjectPayloadSleigh implements InjectPayload { return isfallthru; } + @Override + public boolean isIncidentalCopy() { + return incidentalCopy; + } + private boolean computeFallThru() { OpTpl[] opVec = pcodeTemplate.getOpVec(); if (opVec.length <= 0) { @@ -188,7 +211,7 @@ public class InjectPayloadSleigh implements InjectPayload { case PcodeOp.RETURN: return false; } - return true; + return true; } /** @@ -204,21 +227,68 @@ public class InjectPayloadSleigh implements InjectPayload { for (InjectParameter element : output) { element.setIndex(id); id += 1; - } + } } - public void restoreXml(XmlPullParser parser) { - ArrayList inlist = new ArrayList(); - ArrayList outlist = new ArrayList(); - XmlElement el = parser.start(); + @Override + public void saveXml(StringBuilder buffer) { + buffer.append("= 0) { + SpecXmlUtils.encodeStringAttribute(buffer, "inject", + (subType == 0) ? "uponentry" : "uponreturn"); + } + if (paramShift != 0) { + SpecXmlUtils.encodeSignedIntegerAttribute(buffer, "paramshift", paramShift); + } + if (pcodeTemplate == null) { + SpecXmlUtils.encodeBooleanAttribute(buffer, "dynamic", true); + } + if (incidentalCopy) { + SpecXmlUtils.encodeBooleanAttribute(buffer, "incidentalcopy", incidentalCopy); + } + buffer.append(">\n"); + for (InjectParameter param : inputlist) { + buffer.append("\n"); + } + for (InjectParameter param : output) { + buffer.append("\n"); + } + if (pcodeTemplate != null) { + // Decompiler will not read the tag + buffer.append(" local tmp:1 = 0; \n"); + } + buffer.append("\n"); + } + + @Override + public void restoreXml(XmlPullParser parser, SleighLanguage language) throws XmlParseException { + ArrayList inlist = new ArrayList<>(); + ArrayList outlist = new ArrayList<>(); + XmlElement el = parser.start(); // The tag + String injectstr = el.getAttribute("inject"); + if (injectstr != null) { + if (injectstr.equals("uponentry")) { + subType = 0; + } + else if (injectstr.equals("uponreturn")) { + subType = 1; + } + else { + throw new XmlParseException("Unknown \"inject\" attribute value: " + injectstr); + } + } String pshiftstr = el.getAttribute("paramshift"); paramShift = SpecXmlUtils.decodeInt(pshiftstr); - boolean isDynamic = false; - String dynstr = el.getAttribute("dynamic"); - if (dynstr != null) - isDynamic = SpecXmlUtils.decodeBoolean(dynstr); + boolean isDynamic = SpecXmlUtils.decodeBoolean(el.getAttribute("dynamic")); + incidentalCopy = SpecXmlUtils.decodeBoolean(el.getAttribute("incidentalcopy")); XmlElement subel = parser.peek(); - while(subel.isStart()) { + while (subel.isStart()) { subel = parser.start(); if (subel.getName().equals("body")) { parseString = parser.end(subel).getText(); @@ -227,35 +297,38 @@ public class InjectPayloadSleigh implements InjectPayload { String paramName = subel.getAttribute("name"); int size = SpecXmlUtils.decodeInt(subel.getAttribute("size")); InjectParameter param = new InjectParameter(paramName, size); - if (subel.getName().equals("input")) + if (subel.getName().equals("input")) { inlist.add(param); - else + } + else { outlist.add(param); + } parser.end(subel); subel = parser.peek(); } parser.end(el); if (parseString != null) { parseString = parseString.trim(); - if (parseString.length()==0) + if (parseString.length() == 0) { parseString = null; + } + } + if (parseString == null && (!isDynamic)) { + throw new XmlParseException("Missing pcode in injection: " + source); } - if (parseString == null && (!isDynamic)) - throw new SleighException("Missing pcode in injection: "+source); setInputParameters(inlist); setOutputParameters(outlist); orderParameters(); } - + String releaseParseString() { String res = parseString; parseString = null; // Don't hold on to a reference return res; } - - //changed to public for PcodeInjectLibraryJava - public void setTemplate(ConstructTpl ctl) { + + protected void setTemplate(ConstructTpl ctl) { pcodeTemplate = ctl; isfallthru = computeFallThru(); } @@ -264,34 +337,42 @@ public class InjectPayloadSleigh implements InjectPayload { * Verify that the storage locations passed -con- match the restrictions for this payload * @param con is InjectContext containing parameter storage locations */ - private void checkParameterRestrictions(InjectContext con,Address addr) { + private void checkParameterRestrictions(InjectContext con, Address addr) { int insize = (con.inputlist == null) ? 0 : con.inputlist.size(); - if (inputlist.length != insize) - throw new SleighException("Input parameters do not match injection specification: "+source); - for(int i=0;i=0;--i) { // Move from least significant to most + for (int i = joinrec.length - 1; i >= 0; --i) { // Move from least significant to most Varnode vdata = joinrec[i]; - int cur = justifiedContainAddress(vdata.getAddress().getAddressSpace(),vdata.getOffset(),vdata.getSize(), - addr.getAddressSpace(),addr.getOffset(),sz,false,((flags & IS_BIG_ENDIAN)!=0)); - if (cur<0) { + int cur = justifiedContainAddress(vdata.getAddress().getAddressSpace(), + vdata.getOffset(), vdata.getSize(), addr.getAddressSpace(), addr.getOffset(), + sz, false, ((flags & IS_BIG_ENDIAN) != 0)); + if (cur < 0) { res += vdata.getSize(); // We skipped this many less significant bytes } else { @@ -150,9 +152,9 @@ public class ParamEntry { return -1; // Not contained at all } if (alignment == 0) { // Ordinary endian containment - return justifiedContainAddress(spaceid,addressbase,size, - addr.getAddressSpace(),addr.getOffset(),sz, - ((flags & FORCE_LEFT_JUSTIFY)!=0),((flags & IS_BIG_ENDIAN)!=0)); + return justifiedContainAddress(spaceid, addressbase, size, addr.getAddressSpace(), + addr.getOffset(), sz, ((flags & FORCE_LEFT_JUSTIFY) != 0), + ((flags & IS_BIG_ENDIAN) != 0)); } if (spaceid != addr.getAddressSpace()) { return -1; @@ -165,21 +167,21 @@ public class ParamEntry { if (unsignedCompare(endaddr, startaddr)) { return -1; // Don't allow wrap around } - if (unsignedCompare(addressbase + size-1,endaddr)) { + if (unsignedCompare(addressbase + size - 1, endaddr)) { return -1; } startaddr -= addressbase; endaddr -= addressbase; if (!isLeftJustified()) { // For right justified (big endian), endaddr must be aligned - int res = (int)((endaddr+1) % alignment); - if (res==0) { + int res = (int) ((endaddr + 1) % alignment); + if (res == 0) { return 0; } - return (alignment-res); + return (alignment - res); } return (int) (startaddr % alignment); } - + /** * Assuming the address is contained in this entry and we -skip- to a certain byte * return the slot associated with that byte @@ -187,24 +189,24 @@ public class ParamEntry { * @param skip is the number of bytes to skip * @return the slot index */ - public int getSlot(Address addr,int skip) { + public int getSlot(Address addr, int skip) { int res = group; if (alignment != 0) { long diff = addr.getOffset() + skip - addressbase; - int baseslot = (int)diff / alignment; + int baseslot = (int) diff / alignment; if (isReverseStack()) { - res += (numslots-1) - baseslot; + res += (numslots - 1) - baseslot; } else { res += baseslot; } } else if (skip != 0) { - res += (groupsize -1); + res += (groupsize - 1); } return res; } - + /** * Return the storage address assigned when allocating something of size -sz- assuming -slotnum- slots * have already been assigned. Set res.space to null if the -sz- is too small or if @@ -214,7 +216,7 @@ public class ParamEntry { * @param res the final storage address * @return slotnum plus the number of slots used */ - public int getAddrBySlot(int slotnum,int sz,VarnodeData res) { + public int getAddrBySlot(int slotnum, int sz, VarnodeData res) { res.space = null; // Start with an invalid result int spaceused; if (sz < minsize) { @@ -230,13 +232,13 @@ public class ParamEntry { res.space = spaceid; res.offset = addressbase; // Get base address of the slot spaceused = size; - if ((flags & SMALLSIZE_FLOAT)!=0) { + if ((flags & SMALLSIZE_FLOAT) != 0) { return slotnum; } } else { int slotsused = sz / alignment; // How many slots does a -sz- byte object need - if ( (sz %alignment) != 0) { + if ((sz % alignment) != 0) { slotsused += 1; } if (slotnum + slotsused > numslots) { @@ -262,95 +264,60 @@ public class ParamEntry { return slotnum; } - /** - * Create a join record from an XML tag. Pieces of the join are encoded as a sequence of tag attributes - * @param el - * @throws XmlParseException - */ - private void readJoinXML(XmlElement el,CompilerSpec cspec) throws XmlParseException { - ArrayList pieces = new ArrayList(); - int sizesum = 0; - int pos = 0; - for(;;) { - String attrName = "piece" + Integer.toString(pos+1); - String attrVal = el.getAttribute(attrName); - if (attrVal == null) { - break; - } - int offpos = attrVal.indexOf(':'); - Varnode newvn; - if (offpos == -1) { - Register register = cspec.getLanguage().getRegister(attrVal); - if (register == null) { - throw new XmlParseException("Unknown pentry register: " + attrVal); - } - newvn = new Varnode(register.getAddress(),register.getBitLength()/8); - } - else { - int szpos = attrVal.indexOf(':', offpos+1); - if (szpos == -1) { - throw new XmlParseException("join address piece attribute is malformed"); - } - String spcname = attrVal.substring(0, offpos); - AddressSpace spc = cspec.getAddressSpace(spcname); - long offset = SpecXmlUtils.decodeLong(attrVal.substring(offpos+1,szpos)); - long sz = SpecXmlUtils.decodeLong(attrVal.substring(szpos+1)); - newvn = new Varnode(spc.getAddress(offset),(int)sz); - } - pieces.add(newvn); - sizesum += newvn.getSize(); - pos += 1; + public void saveXml(StringBuilder buffer) { + buffer.append(" regSize) { - throw new XmlParseException( - "Invalid pentry size specified for " + regSize + "-byte register: " + regName); - } - spaceid = register.getAddressSpace(); - addressbase = register.getOffset(); + if (type == TYPE_FLOAT || type == TYPE_PTR) { + String tok = (type == TYPE_FLOAT) ? "float" : "ptr"; + SpecXmlUtils.encodeStringAttribute(buffer, "metatype", tok); + } + if (groupsize != 1) { + SpecXmlUtils.encodeSignedIntegerAttribute(buffer, "groupsize", groupsize); + } + String extString = null; + if ((flags & SMALLSIZE_SEXT) != 0) { + extString = "sign"; + } + else if ((flags & SMALLSIZE_ZEXT) != 0) { + extString = "zero"; + } + else if ((flags & SMALLSIZE_INTTYPE) != 0) { + extString = "inttype"; + } + else if ((flags & SMALLSIZE_FLOAT) != 0) { + extString = "float"; + } + if (extString != null) { + SpecXmlUtils.encodeStringAttribute(buffer, "extension", extString); + } + buffer.append(">\n"); + AddressXML addressSize; + if (joinrec == null) { + // Treat as unsized address with no size + addressSize = new AddressXML(spaceid, addressbase, 0); } else { - spaceid = cspec.getAddressSpace(subel.getAttribute("space")); - if (spaceid.getType() == AddressSpace.TYPE_JOIN) { - readJoinXML(subel,cspec); - } - else { - addressbase = SpecXmlUtils.decodeLong(subel.getAttribute("offset")); - } + addressSize = new AddressXML(spaceid, addressbase, size, joinrec); } - parser.end(subel); - + addressSize.saveXml(buffer); + buffer.append(""); } - - public void restoreXml(XmlPullParser parser,CompilerSpec cspec,boolean normalstack) throws XmlParseException { + + public void restoreXml(XmlPullParser parser, CompilerSpec cspec) throws XmlParseException { flags = 0; type = TYPE_UNKNOWN; size = minsize = -1; // Must be filled in alignment = 0; // default numslots = 1; groupsize = 1; // default - + XmlElement el = parser.start("pentry"); Iterator> iter = el.getAttributes().entrySet().iterator(); - while(iter.hasNext()) { + while (iter.hasNext()) { Entry entry = iter.next(); String name = entry.getKey(); if (name.equals("minsize")) { @@ -384,7 +351,7 @@ public class ParamEntry { groupsize = SpecXmlUtils.decodeInt(entry.getValue()); } else if (name.equals("extension")) { - flags &= ~(SMALLSIZE_ZEXT | SMALLSIZE_SEXT | SMALLSIZE_INTTYPE); + flags &= ~(SMALLSIZE_ZEXT | SMALLSIZE_SEXT | SMALLSIZE_INTTYPE | SMALLSIZE_FLOAT); String value = entry.getValue(); if (value.equals("sign")) { flags |= SMALLSIZE_SEXT; @@ -399,11 +366,11 @@ public class ParamEntry { flags |= SMALLSIZE_FLOAT; } else if (!value.equals("none")) { - throw new XmlParseException("Bad extension attribute: "+value); + throw new XmlParseException("Bad extension attribute: " + value); } } else { - throw new XmlParseException("Unknown paramentry attribute: "+name); + throw new XmlParseException("Unknown paramentry attribute: " + name); } } if (minsize < 1 || size < minsize) { @@ -413,9 +380,18 @@ public class ParamEntry { if (alignment == size) { alignment = 0; } - - readXMLAddress(parser, cspec, size); - + + XmlElement subel = parser.start(); + AddressXML addressSized = AddressXML.restoreXml(subel, cspec); + parser.end(subel); + if (addressSized.getSize() != 0 && size > addressSized.getSize()) { + throw new XmlParseException(" maxsize is bigger than memory range"); + } + addressSized.getFirstAddress(); // Fail fast. Throws AddressOutOfBounds exception if offset is invalid + spaceid = addressSized.getAddressSpace(); + addressbase = addressSized.getOffset(); + joinrec = addressSized.getJoinRecord(); + boolean isbigendian = cspec.getLanguage().isBigEndian(); if (isbigendian) { flags |= IS_BIG_ENDIAN; @@ -428,28 +404,73 @@ public class ParamEntry { if (spaceid.isStackSpace() && (!cspec.isStackRightJustified()) && isbigendian) { flags |= FORCE_LEFT_JUSTIFY; } - if (!normalstack) { + if (!cspec.stackGrowsNegative()) { flags |= REVERSE_STACK; if (alignment != 0) { if ((size % alignment) != 0) { - throw new XmlParseException("For positive stack growth, size must match alignment"); + throw new XmlParseException( + "For positive stack growth, size must match alignment"); } } } // resolveJoin parser.end(el); } - + + @Override + public boolean equals(Object obj) { + ParamEntry op2 = (ParamEntry) obj; + if (!spaceid.equals(op2.spaceid) || addressbase != op2.addressbase) { + return false; + } + if (size != op2.size || minsize != op2.minsize || alignment != op2.alignment) { + return false; + } + if (type != op2.type || flags != op2.flags) { + return false; + } + if (numslots != op2.numslots) { + return false; + } + if (group != op2.group || groupsize != op2.groupsize) { + return false; + } + if (!SystemUtilities.isArrayEqual(joinrec, op2.joinrec)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = spaceid.hashCode(); + hash = 79 * hash + Long.hashCode(addressbase); + hash = 79 * hash + alignment; + hash = 79 * hash + flags; + hash = 79 * hash + group; + hash = 79 * hash + groupsize; + hash = 79 * hash + minsize; + hash = 79 * hash + numslots; + hash = 79 * hash + size; + hash = 79 * hash + type; + if (joinrec != null) { + for (Varnode vn : joinrec) { + hash = 79 * hash + vn.hashCode(); + } + } + return hash; + } + /** * Unsigned less-than operation - * @param a - * @param b + * @param a is the first operand + * @param b is the second operand * @return return true is a is less than b, where a and b are interpreted as unsigned integers */ - public static boolean unsignedCompare(long a,long b) { - return (a+0x8000000000000000L < b + 0x8000000000000000L); + public static boolean unsignedCompare(long a, long b) { + return (a + 0x8000000000000000L < b + 0x8000000000000000L); } - + /** * Return -1 if (op2,sz2) is not properly contained in (op1,sz1) * If it is contained, return the endian aware offset of (op2,sz2) @@ -459,33 +480,35 @@ public class ParamEntry { * @param offset1 the first offset * @param sz1 size of first space * @param spc2 the second address space + * @param offset2 is the second offset * @param sz2 size of second space * @param forceleft is true if containment is forced to be on the left even for big endian * @param isBigEndian true if big endian * @return the endian aware offset or -1 */ - public static int justifiedContainAddress(AddressSpace spc1,long offset1,int sz1,AddressSpace spc2,long offset2,int sz2,boolean forceleft,boolean isBigEndian) { + public static int justifiedContainAddress(AddressSpace spc1, long offset1, int sz1, + AddressSpace spc2, long offset2, int sz2, boolean forceleft, boolean isBigEndian) { if (spc1 != spc2) { return -1; } - if (unsignedCompare(offset2,offset1)) { + if (unsignedCompare(offset2, offset1)) { return -1; } long off1 = offset1 + (sz1 - 1); long off2 = offset2 + (sz2 - 1); - if (unsignedCompare(off1,off2)) { + if (unsignedCompare(off1, off2)) { return -1; } if (isBigEndian && (!forceleft)) { - return (int)(off1 - off2); + return (int) (off1 - off2); } - return (int)(offset2 - offset1); + return (int) (offset2 - offset1); } - + public static int getMetatype(DataType tp) { // TODO: A complete metatype implementation if (tp instanceof TypeDef) { - tp = ((TypeDef)tp).getBaseDataType(); + tp = ((TypeDef) tp).getBaseDataType(); } if (tp instanceof AbstractFloatDataType) { return TYPE_FLOAT; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/ParamList.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/ParamList.java index 0cb239fdd2..441738aef8 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/ParamList.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/ParamList.java @@ -33,7 +33,7 @@ public interface ParamList { int slot; int slotsize; } - + /** * Given a list of datatypes, calculate the storage locations used for passing those datatypes * @param prog is the active progra @@ -42,30 +42,34 @@ public interface ParamList { * @param res is the vector for holding the VariableStorage corresponding to datatypes * @param addAutoParams if true add/process auto-parameters */ - public void assignMap(Program prog,DataType[] proto,boolean isinput,ArrayList res, boolean addAutoParams); - - public void restoreXml(XmlPullParser parser,CompilerSpec cspec,boolean normalstack) throws XmlParseException; + public void assignMap(Program prog, DataType[] proto, boolean isinput, + ArrayList res, boolean addAutoParams); + + public void saveXml(StringBuilder buffer, boolean isInput); + + public void restoreXml(XmlPullParser parser, CompilerSpec cspec) throws XmlParseException; /** * Get a list of all parameter storage locations consisting of a single register - * @return + * @param prog is the controlling program + * @return an array of VariableStorage */ public VariableStorage[] getPotentialRegisterStorage(Program prog); - + /** * Return the amount of alignment used for parameters passed on the stack, or -1 if there are no stack params * @return the alignment */ public int getStackParameterAlignment(); - + /** * Find the boundary offset that separates parameters on the stack from other local variables * This is usually the address of the first stack parameter, but if the stack grows positive, this is * the first address AFTER the parameters on the stack - * @return + * @return the boundary offset */ public Long getStackParameterOffset(); - + /** * Determine if a particular address range is a possible parameter, and if so what slot(s) it occupies * @param loc is the starting address of the range @@ -73,7 +77,7 @@ public interface ParamList { * @param res holds the resulting slot and slotsize * @return true if the range is a possible parameter */ - public boolean possibleParamWithSlot(Address loc,int size,WithSlotRec res); - + public boolean possibleParamWithSlot(Address loc, int size, WithSlotRec res); + public boolean isThisBeforeRetPointer(); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/ParamListStandard.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/ParamListStandard.java index b9b3d09d7a..8d9099d24b 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/ParamListStandard.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/ParamListStandard.java @@ -23,6 +23,7 @@ import ghidra.program.model.address.AddressSpace; import ghidra.program.model.data.*; import ghidra.program.model.listing.*; import ghidra.program.model.pcode.Varnode; +import ghidra.util.SystemUtilities; import ghidra.util.exception.InvalidInputException; import ghidra.util.xml.SpecXmlUtils; import ghidra.xml.*; @@ -48,51 +49,65 @@ public class ParamListStandard implements ParamList { */ private int findEntry(Address loc, int size) { for (int i = 0; i < entry.length; ++i) { - if (entry[i].getMinSize() > size) + if (entry[i].getMinSize() > size) { continue; - if (entry[i].justifiedContain(loc, size) == 0) // Make sure the range is properly justified in entry + } + if (entry[i].justifiedContain(loc, size) == 0) { return i; + } } return -1; } /** * Assign next available memory chunk to type + * @param program is the Program * @param tp type being assigned storage * @param status status from previous assignments + * @param ishiddenret is true if the parameter is a hidden return value + * @param isindirect is true if parameter is really a pointer to the real parameter value * @return Address of assigned memory chunk */ protected VariableStorage assignAddress(Program program, DataType tp, int[] status, boolean ishiddenret, boolean isindirect) { - if (tp == null) + if (tp == null) { tp = DataType.DEFAULT; + } DataType baseType = tp; - if (baseType instanceof TypeDef) + if (baseType instanceof TypeDef) { baseType = ((TypeDef) baseType).getBaseDataType(); - if (baseType instanceof VoidDataType) + } + if (baseType instanceof VoidDataType) { return VariableStorage.VOID_STORAGE; + } int sz = tp.getLength(); - if (sz == 0) + if (sz == 0) { return VariableStorage.UNASSIGNED_STORAGE; + } for (ParamEntry element : entry) { int grp = element.getGroup(); - if (status[grp] < 0) + if (status[grp] < 0) { continue; + } if ((element.getType() != ParamEntry.TYPE_UNKNOWN) && - (ParamEntry.getMetatype(tp) != element.getType())) + (ParamEntry.getMetatype(tp) != element.getType())) { continue; // Wrong type + } VarnodeData res = new VarnodeData(); status[grp] = element.getAddrBySlot(status[grp], tp.getLength(), res); - if (res.space == null) + if (res.space == null) { continue; // -tp- does not fit in this entry + } if (element.isExclusion()) { int maxgrp = grp + element.getGroupSize(); - for (int j = grp; j < maxgrp; ++j) + for (int j = grp; j < maxgrp; ++j) { // For an exclusion entry status[j] = -1; // some number of groups are taken up - if (element.isFloatExtended()) // If this is a small float datatype in a bigger container + } + if (element.isFloatExtended()) { sz = element.getSize(); // Still use the entire container size, when assigning storage + } } VariableStorage store; try { @@ -120,8 +135,8 @@ public class ParamListStandard implements ParamList { return store; } if (ishiddenret) { - return DynamicVariableStorage.getUnassignedDynamicStorage( - AutoParameterType.RETURN_STORAGE_PTR); + return DynamicVariableStorage + .getUnassignedDynamicStorage(AutoParameterType.RETURN_STORAGE_PTR); } return DynamicVariableStorage.getUnassignedDynamicStorage(isindirect); } @@ -130,8 +145,9 @@ public class ParamListStandard implements ParamList { public void assignMap(Program prog, DataType[] proto, boolean isinput, ArrayList res, boolean addAutoParams) { int[] status = new int[numgroup]; - for (int i = 0; i < numgroup; ++i) + for (int i = 0; i < numgroup; ++i) { status[i] = 0; + } if (isinput) { if (addAutoParams && res.size() == 2) { // Check for hidden parameters defined by the output list @@ -149,8 +165,9 @@ public class ParamListStandard implements ParamList { Pointer pointer = dtm.getPointer(proto[i]); store = assignAddress(prog, pointer, status, false, true); } - else + else { store = assignAddress(prog, proto[i], status, false, false); + } res.add(store); } } @@ -165,8 +182,9 @@ public class ParamListStandard implements ParamList { ArrayList res = new ArrayList<>(); for (ParamEntry element : entry) { ParamEntry pe = element; - if (!pe.isExclusion()) + if (!pe.isExclusion()) { continue; + } if (pe.getSpace().isRegisterSpace()) { VariableStorage var = null; try { @@ -176,8 +194,9 @@ public class ParamListStandard implements ParamList { catch (InvalidInputException e) { // Skip this particular storage location } - if (var != null) + if (var != null) { res.add(var); + } } } VariableStorage[] arres = new VariableStorage[res.size()]; @@ -186,8 +205,24 @@ public class ParamListStandard implements ParamList { } @Override - public void restoreXml(XmlPullParser parser, CompilerSpec cspec, boolean normalstack) - throws XmlParseException { + public void saveXml(StringBuilder buffer, boolean isInput) { + buffer.append(isInput ? "\n"); + for (ParamEntry el : entry) { + el.saveXml(buffer); + buffer.append('\n'); + } + buffer.append(isInput ? "" : ""); + } + + @Override + public void restoreXml(XmlPullParser parser, CompilerSpec cspec) throws XmlParseException { ArrayList pe = new ArrayList<>(); int lastgroup = -1; numgroup = 0; @@ -200,30 +235,37 @@ public class ParamListStandard implements ParamList { pointermax = SpecXmlUtils.decodeInt(attribute); } attribute = mainel.getAttribute("thisbeforeretpointer"); - if (attribute != null) + if (attribute != null) { thisbeforeret = SpecXmlUtils.decodeBoolean(attribute); + } boolean seennonfloat = false; // Have we seen any integer slots yet for (;;) { XmlElement el = parser.peek(); - if (!el.isStart()) + if (!el.isStart()) { break; + } ParamEntry pentry = new ParamEntry(numgroup); - pentry.restoreXml(parser, cspec, normalstack); + pentry.restoreXml(parser, cspec); pe.add(pentry); if (pentry.getType() == ParamEntry.TYPE_FLOAT) { - if (seennonfloat) + if (seennonfloat) { throw new XmlParseException( "parameter list floating-point entries must come first"); + } } - else + else { seennonfloat = true; - if (pentry.getSpace().isStackSpace()) + } + if (pentry.getSpace().isStackSpace()) { spacebase = pentry.getSpace(); + } int maxgroup = pentry.getGroup() + pentry.getGroupSize(); - if (maxgroup > numgroup) + if (maxgroup > numgroup) { numgroup = maxgroup; - if (pentry.getGroup() < lastgroup) + } + if (pentry.getGroup() < lastgroup) { throw new XmlParseException("pentrys must come in group order"); + } lastgroup = pentry.getGroup(); } parser.end(mainel); @@ -245,13 +287,16 @@ public class ParamListStandard implements ParamList { public Long getStackParameterOffset() { for (ParamEntry element : entry) { ParamEntry pentry = element; - if (pentry.isExclusion()) + if (pentry.isExclusion()) { continue; - if (!pentry.getSpace().isStackSpace()) + } + if (!pentry.getSpace().isStackSpace()) { continue; + } long res = pentry.getAddressBase(); - if (pentry.isReverseStack()) + if (pentry.isReverseStack()) { res += pentry.getSize(); + } res = pentry.getSpace().truncateOffset(res); return res; } @@ -260,20 +305,56 @@ public class ParamListStandard implements ParamList { @Override public boolean possibleParamWithSlot(Address loc, int size, WithSlotRec res) { - if (loc == null) + if (loc == null) { return false; + } int num = findEntry(loc, size); - if (num == -1) + if (num == -1) { return false; + } ParamEntry curentry = entry[num]; res.slot = curentry.getSlot(loc, 0); - if (curentry.isExclusion()) + if (curentry.isExclusion()) { res.slotsize = curentry.getGroupSize(); - else + } + else { res.slotsize = ((size - 1) / curentry.getAlign()) + 1; + } return true; } + @Override + public boolean equals(Object obj) { + ParamListStandard op2 = (ParamListStandard) obj; + if (!SystemUtilities.isArrayEqual(entry, op2.entry)) { + return false; + } + if (numgroup != op2.numgroup || pointermax != op2.pointermax) { + return false; + } + if (!SystemUtilities.isEqual(spacebase, op2.spacebase)) { + return false; + } + if (thisbeforeret != op2.thisbeforeret) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = numgroup; + hash = 79 * hash + pointermax; + hash = 79 * hash + (thisbeforeret ? 27 : 19); + for (ParamEntry param : entry) { + hash = 79 * hash + param.hashCode(); + } + if (spacebase == null) { + hash = 79 * hash + spacebase.hashCode(); + } + return hash; + } + @Override public boolean isThisBeforeRetPointer() { return thisbeforeret; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PcodeInjectLibrary.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PcodeInjectLibrary.java index d7b6429b10..edff669c70 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PcodeInjectLibrary.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PcodeInjectLibrary.java @@ -17,6 +17,7 @@ package ghidra.program.model.lang; import java.io.IOException; import java.util.*; +import java.util.Map.Entry; import org.jdom.JDOMException; import org.xml.sax.*; @@ -30,30 +31,107 @@ import ghidra.program.model.lang.InjectPayload.InjectParameter; import ghidra.program.model.listing.Program; import ghidra.sleigh.grammar.Location; import ghidra.util.Msg; -import ghidra.xml.XmlPullParser; -import ghidra.xml.XmlPullParserFactory; +import ghidra.util.SystemUtilities; +import ghidra.xml.*; public class PcodeInjectLibrary { - private SleighLanguage language; - private long uniqueBase; // Current base address for new temporary registers + protected SleighLanguage language; + protected long uniqueBase; // Current base address for new temporary registers - private Map callFixupMap; // Map of names to registered callfixups - private Map callOtherFixupMap; // Map of registered callotherfixups names to injection id + private Map callFixupMap; // Map of names to registered callfixups + private Map callOtherFixupMap; // Map of registered callotherfixups names to injection id + private InjectPayload[] callOtherOverride; // List of overridden callotherfixups private Map callMechFixupMap; // Map of registered injectUponEntry/Return ids private Map exePcodeMap; // Map of registered p-code scripts + private InjectPayloadSleigh[] programPayload; // List of Program specific payloads public PcodeInjectLibrary(SleighLanguage l) { language = l; uniqueBase = language.getUniqueBase(); - callFixupMap = new TreeMap(); - callOtherFixupMap = new TreeMap(); - callMechFixupMap = new TreeMap(); - exePcodeMap = new TreeMap(); + callFixupMap = new TreeMap<>(); + callOtherFixupMap = new TreeMap<>(); + callOtherOverride = null; + callMechFixupMap = new TreeMap<>(); + exePcodeMap = new TreeMap<>(); + programPayload = null; } - public InjectPayload getPayload(int type, String name, Program program, String context) { - if (name == null) + /** + * Clone a library so that a Program can extend the library without + * modifying the base library from Language. InjectPayloads can be considered + * immutable and don't need to be cloned. + * @param op2 is the library to clone + */ + public PcodeInjectLibrary(PcodeInjectLibrary op2) { + language = op2.language; + uniqueBase = op2.uniqueBase; + callFixupMap = new TreeMap<>(op2.callFixupMap); + callOtherFixupMap = new TreeMap<>(op2.callOtherFixupMap); + callOtherOverride = op2.callOtherOverride; + callMechFixupMap = new TreeMap<>(op2.callMechFixupMap); + exePcodeMap = new TreeMap<>(op2.exePcodeMap); + programPayload = op2.programPayload; + } + + /** + * @return A clone of this library + */ + @Override + public PcodeInjectLibrary clone() { + return new PcodeInjectLibrary(this); + } + + /** + * @return an array of all the program specific payloads (or null) + */ + public InjectPayloadSleigh[] getProgramPayloads() { + return programPayload; + } + + /** + * Determine if the given payload name and type exists and is an extension + * of the program. + * @param nm is the payload name + * @param type is the payload type + * @return true if the program extension exists + */ + public boolean hasProgramPayload(String nm, int type) { + if (programPayload == null) { + return false; + } + for (InjectPayload payload : programPayload) { + if (payload.getType() != type) { + continue; + } + if (payload.getName().equals(nm)) { + return true; + } + } + return false; + } + + /** + * Check if a specific payload has been overridden by a user extension + * @param nm is the name of the payload + * @param type is the type of payload + * @return true if the payload is overridden + */ + public boolean isOverride(String nm, int type) { + if (callOtherOverride == null || type != InjectPayload.CALLOTHERFIXUP_TYPE) { + return false; + } + for (InjectPayload payload : callOtherOverride) { + if (payload.getName().equals(nm)) { + return true; + } + } + return false; + } + + public InjectPayload getPayload(int type, String name) { + if (name == null) { return null; + } if (type == InjectPayload.CALLFIXUP_TYPE) { return callFixupMap.get(name); } @@ -69,30 +147,41 @@ public class PcodeInjectLibrary { return null; } - private void parseInject(InjectPayload payload) throws SleighException { + /** + * Convert the XML string representation of the given payload to a ConstructTpl + * The payload should be unattached (not already installed in the library) + * @param payload is the given payload whose XML should be converted + * @throws SleighException if there is any parsing issue + */ + public void parseInject(InjectPayload payload) throws SleighException { String sourceName = payload.getSource(); - if (sourceName == null) + if (sourceName == null) { sourceName = "unknown"; - if (!(payload instanceof InjectPayloadSleigh)) + } + if (!(payload instanceof InjectPayloadSleigh)) { return; + } InjectPayloadSleigh payloadSleigh = (InjectPayloadSleigh) payload; - String translateSpec = - language.buildTranslatorTag(language.getAddressFactory(), uniqueBase, - language.getSymbolTable()); - String pcodeText = payloadSleigh.releaseParseString(); - if (pcodeText == null) - return; // Dynamic p-code generation + if (pcodeText == null) { + return; // Dynamic p-code generation, or already parsed + } + + String translateSpec = language.buildTranslatorTag(language.getAddressFactory(), uniqueBase, + language.getSymbolTable()); + try { PcodeParser parser = new PcodeParser(translateSpec); Location loc = new Location(sourceName, 1); InjectParameter[] input = payload.getInput(); - for (InjectParameter element : input) + for (InjectParameter element : input) { parser.addOperand(loc, element.getName(), element.getIndex()); + } InjectParameter[] output = payload.getOutput(); - for (InjectParameter element : output) + for (InjectParameter element : output) { parser.addOperand(loc, element.getName(), element.getIndex()); + } String constructTplXml = PcodeParser.stringifyTemplate(parser.compilePcode(pcodeText, sourceName, 1)); if (constructTplXml == null) { @@ -132,8 +221,8 @@ public class PcodeInjectLibrary { throw new SleighException("compiled pcode contains invalid opcode " + sourceName, e); } catch (JDOMException e) { - throw new SleighException("pcode compile failed due to invalid translator tag " + - sourceName, e); + throw new SleighException( + "pcode compile failed due to invalid translator tag " + sourceName, e); } catch (SAXException e) { throw new SleighException("pcode compiler returned invalid xml " + sourceName, e); @@ -165,6 +254,9 @@ public class PcodeInjectLibrary { } } + /** + * @return a list of names for all installed call-fixups + */ public String[] getCallFixupNames() { Set keySet = callFixupMap.keySet(); String[] names = new String[keySet.size()]; @@ -172,13 +264,46 @@ public class PcodeInjectLibrary { return names; } + /** + * @return a list of names for all installed callother-fixups + */ + public String[] getCallotherFixupNames() { + ArrayList list = new ArrayList<>(); + for (Entry entry : callOtherFixupMap.entrySet()) { + if (entry.getValue() != null) { + list.add(entry.getKey()); + } + } + String[] res = new String[list.size()]; + list.toArray(res); + return res; + } + public InjectContext buildInjectContext() { InjectContext res = new InjectContext(); res.language = language; return res; } + /** + * Determine if the language has a given user-defined op. + * In which case, a CALLOTHER_FIXUP can be installed for it. + * @param name is the putative name of the user-defined op + * @return true if the user-defined op exists + */ + public boolean hasUserDefinedOp(String name) { + if (callOtherFixupMap.size() == 0) { + int max = language.getNumberOfUserDefinedOpNames(); + for (int i = 0; i < max; ++i) { + String opname = language.getUserDefinedOpName(i); + callOtherFixupMap.put(opname, null); // Initialize with null pcodeinjection + } + } + return callOtherFixupMap.containsKey(name); + } + protected void registerInject(InjectPayload payload) { + parseInject(payload); switch (payload.getType()) { case InjectPayload.CALLFIXUP_TYPE: if (callFixupMap.containsKey(payload.getName())) { @@ -188,19 +313,14 @@ public class PcodeInjectLibrary { callFixupMap.put(payload.getName(), payload); break; case InjectPayload.CALLOTHERFIXUP_TYPE: - if (callOtherFixupMap.size() == 0) { - int max = language.getNumberOfUserDefinedOpNames(); - for (int i = 0; i < max; ++i) { - String opname = language.getUserDefinedOpName(i); - callOtherFixupMap.put(opname, null); // Initialize with null pcodeinjection - } - } - if (!callOtherFixupMap.containsKey(payload.getName())) + if (!hasUserDefinedOp(payload.getName())) { throw new SleighException( "Unknown callother name in : " + payload.getName()); - if (callOtherFixupMap.get(payload.getName()) != null) + } + if (callOtherFixupMap.get(payload.getName()) != null) { throw new SleighException( "Duplicate tag: " + payload.getName()); + } callOtherFixupMap.put(payload.getName(), payload); break; case InjectPayload.CALLMECHANISM_TYPE: @@ -220,25 +340,154 @@ public class PcodeInjectLibrary { default: throw new SleighException("Unknown p-code inject type"); } - parseInject(payload); } - protected InjectPayloadSleigh allocateInject(String sourceName, String name, int tp) { - if (tp == InjectPayload.CALLFIXUP_TYPE) + /** + * Remove a specific call mechanism payload. + * @param nm is the name of the payload + * @return true if a payload was successfully removed + */ + protected boolean removeMechanismPayload(String nm) { + InjectPayload payload = callMechFixupMap.remove(nm); + return (payload != null); + } + + protected void uninstallProgramPayloads() { + if (programPayload != null) { + for (InjectPayloadSleigh payload : programPayload) { + if (payload.type == InjectPayload.CALLFIXUP_TYPE) { + callFixupMap.remove(payload.name); + } + else if (payload.type == InjectPayload.CALLOTHERFIXUP_TYPE) { + callOtherFixupMap.put(payload.name, null); + } + } + programPayload = null; + if (callOtherOverride != null) { + // Undo callother overrides, reinstalling the overridden payloads + for (InjectPayload payload : callOtherOverride) { + callOtherFixupMap.put(payload.getName(), payload); + } + callOtherOverride = null; + } + } + } + + /** + * Look for user callother payloads that override an existing core fixup. + * Move these out of the map into the override list. Don't install user payload yet. + * @param userPayloads is the list of user payloads + */ + private void setupOverrides(List userPayloads) { + int count = 0; + for (InjectPayloadSleigh payload : userPayloads) { + if (payload.getType() == InjectPayload.CALLOTHERFIXUP_TYPE) { + InjectPayload origPayload = callOtherFixupMap.get(payload.name); + if (origPayload != null) { + count += 1; + } + } + } + if (count == 0) { + return; + } + callOtherOverride = new InjectPayload[count]; + count = 0; + for (InjectPayloadSleigh payload : userPayloads) { + if (payload.getType() == InjectPayload.CALLOTHERFIXUP_TYPE) { + InjectPayload origPayload = callOtherFixupMap.get(payload.name); + if (origPayload != null) { + callOtherFixupMap.put(payload.name, null); + callOtherOverride[count] = origPayload; + count += 1; + } + } + } + } + + protected void registerProgramInject(List userPayloads) { + uninstallProgramPayloads(); + if (userPayloads.isEmpty()) { + return; // Leave programPayload null if there are no program payloads + } + setupOverrides(userPayloads); + programPayload = new InjectPayloadSleigh[userPayloads.size()]; + int count = 0; + for (InjectPayloadSleigh payload : userPayloads) { + try { + registerInject(payload); + programPayload[count] = payload; + count += 1; + } + catch (SleighException ex) { + Msg.warn(this, + "Error installing fixup extension: " + payload.name + ": " + ex.getMessage()); + } + } + if (count != programPayload.length) { + InjectPayloadSleigh[] finalPayloads = new InjectPayloadSleigh[count]; + System.arraycopy(programPayload, 0, finalPayloads, 0, count); + programPayload = finalPayloads; + } + } + + /** + * The main InjectPayload factory interface. This can be overloaded by derived libraries + * to produce custom dynamic payloads. + * @param sourceName is a description of the source of the payload + * @param name is the formal name of the payload + * @param tp is the type of payload: CALLFIXUP_TYPE, CALLOTHERFIXUP_TYPE, etc. + * @return the newly minted InjectPayload + */ + public InjectPayload allocateInject(String sourceName, String name, int tp) { + if (tp == InjectPayload.CALLFIXUP_TYPE) { return new InjectPayloadCallfixup(sourceName); - else if (tp == InjectPayload.CALLOTHERFIXUP_TYPE) + } + else if (tp == InjectPayload.CALLOTHERFIXUP_TYPE) { return new InjectPayloadCallother(sourceName); + } return new InjectPayloadSleigh(name, tp, sourceName); } - public InjectPayload restoreXmlInject(String source, String name, int tp, - XmlPullParser parser) { - InjectPayloadSleigh payload = allocateInject(source, name, tp); - payload.restoreXml(parser); + /** + * Save the parts of the inject library that come from the compiler spec + * to the output stream as XML tags + * @param buffer is the output stream + */ + public void saveCompilerSpecXml(StringBuilder buffer) { + for (InjectPayload injectPayload : callFixupMap.values()) { + if (injectPayload instanceof InjectPayloadSleigh) { + ((InjectPayloadSleigh) injectPayload).saveXml(buffer); + } + } + for (InjectPayload injectPayload : callOtherFixupMap.values()) { + if (injectPayload instanceof InjectPayloadSleigh) { + ((InjectPayloadSleigh) injectPayload).saveXml(buffer); + } + } + for (InjectPayload injectPayload : exePcodeMap.values()) { + if (injectPayload instanceof InjectPayloadSegment) { + if (injectPayload.getSource().startsWith("cspec")) { + ((InjectPayloadSleigh) injectPayload).saveXml(buffer); + } + } + } + } + + public InjectPayload restoreXmlInject(String source, String name, int tp, XmlPullParser parser) + throws XmlParseException { + InjectPayload payload = allocateInject(source, name, tp); + payload.restoreXml(parser, language); registerInject(payload); return payload; } + /** + * Get the constant pool associated with the given Program + * @param program is the given Program + * @return the ConstantPool associated with the Program + * @throws IOException for issues constructing the object + */ public ConstantPool getConstantPool(Program program) throws IOException { return null; } @@ -247,4 +496,60 @@ public class PcodeInjectLibrary { protected long getUniqueBase() { return uniqueBase; } + + @Override + public boolean equals(Object obj) { + PcodeInjectLibrary op2 = (PcodeInjectLibrary) obj; + // Cannot compare uniqueBase as one side may not have parsed p-code +// if (uniqueBase != op2.uniqueBase) { +// return false; +// } + if (!callFixupMap.equals(op2.callFixupMap)) { + return false; + } + if (!callMechFixupMap.equals(op2.callMechFixupMap)) { + return false; + } + if (!callOtherFixupMap.equals(op2.callOtherFixupMap)) { + return false; + } + if (!SystemUtilities.isArrayEqual(callOtherOverride, op2.callOtherOverride)) { + return false; + } + if (!exePcodeMap.equals(op2.exePcodeMap)) { + return false; + } + if (!SystemUtilities.isArrayEqual(programPayload, op2.programPayload)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 111; + for (InjectPayload payload : callFixupMap.values()) { + hash = 79 * hash + payload.hashCode(); + } + for (InjectPayload payload : callMechFixupMap.values()) { + hash = 79 * hash + payload.hashCode(); + } + for (InjectPayload payload : callOtherFixupMap.values()) { + hash = 79 * hash + payload.hashCode(); + } + for (InjectPayload payload : exePcodeMap.values()) { + hash = 79 * hash + payload.hashCode(); + } + if (programPayload != null) { + for (InjectPayloadSleigh payload : programPayload) { + hash = 79 * hash + payload.hashCode(); + } + } + if (callOtherOverride != null) { + for (InjectPayload payload : callOtherOverride) { + hash = 79 * hash + payload.hashCode(); + } + } + return hash; + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PrototypeModel.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PrototypeModel.java index 52daf4adb2..a6be60647b 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PrototypeModel.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PrototypeModel.java @@ -17,10 +17,12 @@ package ghidra.program.model.lang; import java.util.ArrayList; -import ghidra.program.model.address.Address; +import ghidra.program.model.address.*; import ghidra.program.model.data.*; import ghidra.program.model.listing.*; +import ghidra.program.model.pcode.AddressXML; import ghidra.program.model.pcode.Varnode; +import ghidra.util.SystemUtilities; import ghidra.util.exception.InvalidInputException; import ghidra.util.xml.SpecXmlUtils; import ghidra.xml.*; @@ -37,48 +39,122 @@ public class PrototypeModel { public static final int UNKNOWN_EXTRAPOP = 0x8000; protected String name; // Name of model + protected boolean isExtension; // True if this model is a Program specific extension private int extrapop; // change in stackpointer // across function calls private int stackshift; // change in stackpointer // due to call mechanism private ParamList inputParams; // (possible) parameter locations private ParamList outputParams; + private Varnode[] unaffected; // Memory ranges unaffected by calls + private Varnode[] killedbycall; // Memory ranges definitely affected by calls + private Varnode[] returnaddress; // Memory used to store the return address + private Varnode[] likelytrash; // Memory likely to be meaningless on input + private AddressSet localRange; // Range on the stack considered for local storage + private AddressSet paramRange; // Range on the stack considered for parameter storage private InputListType inputListType = InputListType.STANDARD; private GenericCallingConvention genericCallingConvention; private boolean hasThis; // Convention has a this (auto-parameter) private boolean isConstruct; // Convention is used for object construction + private boolean hasUponEntry; // Does this have an uponentry injection + private boolean hasUponReturn; // Does this have an uponreturn injection public PrototypeModel(String name, PrototypeModel model) { this.name = name; + isExtension = false; extrapop = model.extrapop; stackshift = model.stackshift; inputListType = model.inputListType; inputParams = model.inputParams; outputParams = model.outputParams; + unaffected = model.unaffected; + killedbycall = model.killedbycall; + returnaddress = model.returnaddress; + likelytrash = model.likelytrash; + localRange = new AddressSet(model.localRange); + paramRange = new AddressSet(model.paramRange); hasThis = model.hasThis || name.equals(CompilerSpec.CALLING_CONVENTION_thiscall); isConstruct = model.isConstruct; genericCallingConvention = GenericCallingConvention.getGenericCallingConvention(name); + hasUponEntry = model.hasUponEntry; + hasUponReturn = model.hasUponReturn; } public PrototypeModel() { name = null; + isExtension = false; extrapop = PrototypeModel.UNKNOWN_EXTRAPOP; stackshift = -1; inputParams = null; outputParams = null; + unaffected = null; + killedbycall = null; + returnaddress = null; + likelytrash = null; + localRange = null; + paramRange = null; genericCallingConvention = GenericCallingConvention.unknown; hasThis = false; isConstruct = false; + hasUponEntry = false; + hasUponReturn = false; } public GenericCallingConvention getGenericCallingConvention() { return genericCallingConvention; } + /** + * @return list of registers unaffected by called functions + */ + public Varnode[] getUnaffectedList() { + if (unaffected == null) { + unaffected = new Varnode[0]; + } + return unaffected; + } + + /** + * @return list of registers definitely affected by called functions + */ + public Varnode[] getKilledByCallList() { + if (killedbycall == null) { + killedbycall = new Varnode[0]; + } + return killedbycall; + } + + /** + * @return list of registers whose input value is likely meaningless + */ + public Varnode[] getLikelyTrash() { + if (likelytrash == null) { + likelytrash = new Varnode[0]; + } + return likelytrash; + } + + /** + * @return list of registers/memory used to store the return address + */ + public Varnode[] getReturnAddress() { + if (returnaddress == null) { + returnaddress = new Varnode[0]; + } + return returnaddress; + } + public boolean isMerged() { return false; } + /** + * @return true if this model is a Program specific extension to the CompilerSpec + */ + public boolean isProgramExtension() { + return isExtension; + } + public String getName() { return name; } @@ -103,14 +179,17 @@ public class PrototypeModel { return inputListType; } + public boolean hasInjection() { + return hasUponEntry || hasUponReturn; + } + /** * @deprecated * Get the preferred return location given the specified dataType. * In truth, there is no one location. The routines that use this method tend * to want the default storage location for integer or pointer return values. - * @param dataType first parameter dataType or null for a default - * undefined type. - * @param program + * @param dataType first parameter dataType or null for an undefined type. + * @param program is the Program * @return return location or {@link VariableStorage#UNASSIGNED_STORAGE} if * unable to determine suitable location */ @@ -119,7 +198,7 @@ public class PrototypeModel { DataType clone = dataType.clone(program.getDataTypeManager()); DataType[] arr = new DataType[1]; arr[0] = clone; - ArrayList res = new ArrayList(); + ArrayList res = new ArrayList<>(); outputParams.assignMap(program, arr, false, res, false); if (res.size() > 0) { return res.get(0); @@ -135,42 +214,47 @@ public class PrototypeModel { * be appended. (may be null) * @param dataType dataType associated with next parameter location or null * for a default undefined type. - * @param program + * @param program is the Program * @return next parameter location or {@link VariableStorage#UNASSIGNED_STORAGE} if * unable to determine suitable location */ - public VariableStorage getNextArgLocation(Parameter[] params, DataType dataType, Program program) { + public VariableStorage getNextArgLocation(Parameter[] params, DataType dataType, + Program program) { return getArgLocation(params != null ? params.length : 0, params, dataType, program); } /** - * Get the preferred parameter location for a specified parameter specified by argIndex + * Get the preferred parameter location for a specified index, * which will be added/inserted within the set of existing function params. * If existing parameters use custom storage, this method should not be used. + * @param argIndex is the index * @param params existing set parameters to which the parameter specified by * argIndex will be added/inserted be appended (may be null). * @param dataType dataType associated with next parameter location or null * for a default undefined type. - * @param program + * @param program is the Program * @return parameter location or {@link VariableStorage#UNASSIGNED_STORAGE} if * unable to determine suitable location */ public VariableStorage getArgLocation(int argIndex, Parameter[] params, DataType dataType, Program program) { - if (dataType != null) + if (dataType != null) { dataType = dataType.clone(program.getDataTypeManager()); - // Identify next arg index based upon number of storage varnodes - // already assigned to parameters - this may not work well if - // customized storage has been used + // Identify next arg index based upon number of storage varnodes + // already assigned to parameters - this may not work well if + // customized storage has been used + } DataType arr[] = new DataType[argIndex + 2]; arr[0] = DataType.VOID; // Assume the return type is void for (int i = 0; i < argIndex; ++i) { - if (params != null && i < params.length) + if (params != null && i < params.length) { arr[i + 1] = params[i].getDataType(); // Copy in current types if we have them - else + } + else { arr[i + 1] = DataType.DEFAULT; // Otherwise assume default (integer) type + } } arr[argIndex + 1] = dataType; @@ -181,7 +265,7 @@ public class PrototypeModel { /** * Compute the variable storage for a given function and set of return/parameter datatypes * defined by an array of data types. - * @param program + * @param program is the Program * @param dataTypes return/parameter datatypes (first element is always the return datatype, * i.e., minimum array length is 1) * @param addAutoParams TODO @@ -206,7 +290,7 @@ public class PrototypeModel { dataTypes = ammendedTypes; } - ArrayList res = new ArrayList(); + ArrayList res = new ArrayList<>(); outputParams.assignMap(program, dataTypes, false, res, addAutoParams); inputParams.assignMap(program, dataTypes, true, res, addAutoParams); VariableStorage[] finalres = new VariableStorage[res.size()]; @@ -222,9 +306,8 @@ public class PrototypeModel { if (inputParams.isThisBeforeRetPointer()) { // pointer has been bumped by auto-return-storage // must swap storage and position for slots 1 and 2 - finalres[2] = - new DynamicVariableStorage(program, finalres[1].getAutoParameterType(), - finalres[2].getVarnodes()); + finalres[2] = new DynamicVariableStorage(program, + finalres[1].getAutoParameterType(), finalres[2].getVarnodes()); } else { thisIndex = 2; @@ -238,11 +321,12 @@ public class PrototypeModel { } else { finalres[thisIndex] = - DynamicVariableStorage.getUnassignedDynamicStorage(AutoParameterType.THIS); + DynamicVariableStorage.getUnassignedDynamicStorage(AutoParameterType.THIS); } } catch (InvalidInputException e) { - finalres[thisIndex] = DynamicVariableStorage.getUnassignedDynamicStorage(AutoParameterType.THIS); + finalres[thisIndex] = + DynamicVariableStorage.getUnassignedDynamicStorage(AutoParameterType.THIS); } } @@ -250,6 +334,15 @@ public class PrototypeModel { return finalres; } + /** + * If a PrototypeModel fails to parse (from XML) a substitute model may be provided, in which + * case this method returns true. In all other cases this method returns false; + * @return true if this object is a substitute for a model that didn't parse + */ + public boolean isErrorPlaceholder() { + return false; + } + private void buildParamList(String strategy) throws XmlParseException { if (strategy == null || strategy.equals("standard")) { inputParams = new ParamListStandard(); @@ -261,20 +354,178 @@ public class PrototypeModel { outputParams = new ParamListStandard(); inputListType = InputListType.REGISTER; } - else + else { throw new XmlParseException("Unknown assign strategy: " + strategy); + } } - public void restoreXml(XmlPullParser parser, CompilerSpec cspec, boolean normalstack) + public void saveXml(StringBuilder buffer, PcodeInjectLibrary injectLibrary) { + buffer.append("\n"); + inputParams.saveXml(buffer, true); + buffer.append('\n'); + outputParams.saveXml(buffer, false); + buffer.append('\n'); + if (hasUponEntry || hasUponReturn) { + InjectPayload payload = + injectLibrary.getPayload(InjectPayload.CALLMECHANISM_TYPE, getInjectName()); + payload.saveXml(buffer); + } + if (unaffected != null) { + buffer.append("\n"); + writeVarnodes(buffer, unaffected); + buffer.append("\n"); + } + if (killedbycall != null) { + buffer.append("\n"); + writeVarnodes(buffer, killedbycall); + buffer.append("\n"); + } + if (likelytrash != null) { + buffer.append("\n"); + writeVarnodes(buffer, likelytrash); + buffer.append("\n"); + } + if (returnaddress != null) { + buffer.append("\n"); + writeVarnodes(buffer, returnaddress); + buffer.append("\n"); + } + if (localRange != null && !localRange.isEmpty()) { + buffer.append("\n"); + writeAddressSet(buffer, localRange); + buffer.append("\n"); + } + if (paramRange != null && !paramRange.isEmpty()) { + buffer.append("\n"); + writeAddressSet(buffer, paramRange); + buffer.append("\n"); + } + buffer.append("\n"); + } + + private void writeVarnodes(StringBuilder buffer, Varnode[] varnodes) { + for (Varnode vn : varnodes) { + buffer.append("\n"); + } + } + + private Varnode[] readVarnodes(XmlPullParser parser, CompilerSpec cspec) throws XmlParseException { + parser.start(); + ArrayList varnodeList = new ArrayList<>(); + while (parser.peek().isStart()) { + XmlElement el = parser.start(); + AddressXML ourAddress = AddressXML.restoreXml(el, cspec); + if (ourAddress.getJoinRecord() != null) { + throw new XmlParseException( + "No \"join\" in , , or "); + } + varnodeList.add(ourAddress.getVarnode()); + parser.end(el); + } + parser.end(); + Varnode[] res = new Varnode[varnodeList.size()]; + varnodeList.toArray(res); + return res; + } + + private void writeAddressSet(StringBuilder buffer, AddressSet addressSet) { + AddressRangeIterator iter = addressSet.getAddressRanges(); + while (iter.hasNext()) { + AddressRange addrRange = iter.next(); + AddressSpace space = addrRange.getAddressSpace(); + long first = addrRange.getMinAddress().getOffset(); + long last = addrRange.getMaxAddress().getOffset(); + if (space.hasSignedOffset()) { + long mask; + if (space.getSize() < 64) { + mask = 1; + mask <<= space.getSize(); + } + else { + mask = 0; + } + mask -= 1; + if (first < 0 && last >= 0) { // Range crosses 0 + first &= mask; + // Split out the piece coming before 0 + buffer.append("\n"); + // Reset first,last to be the piece coming after 0 + first = 0; + } + first &= mask; + last &= mask; + } + buffer.append("\n"); + } + } + + private AddressSet readAddressSet(XmlPullParser parser, CompilerSpec cspec) + throws XmlParseException { + AddressSet addressSet = new AddressSet(); + parser.start(); + while (parser.peek().isStart()) { + XmlElement el = parser.start(); + AddressXML range = AddressXML.restoreRangeXml(el, cspec); + parser.end(el); + Address firstAddr = range.getFirstAddress(); + Address lastAddr = range.getLastAddress(); + addressSet.add(firstAddr, lastAddr); + } + parser.end(); + return addressSet; + } + + protected String getInjectName() { + if (hasUponEntry) { + return name + "@@inject_uponentry"; + } + return name + "@@inject_uponreturn"; + } + + public void restoreXml(XmlPullParser parser, CompilerSpec cspec) throws XmlParseException { inputParams = null; outputParams = null; XmlElement protoElement = parser.start(); name = protoElement.getAttribute("name"); extrapop = PrototypeModel.UNKNOWN_EXTRAPOP; String extpopStr = protoElement.getAttribute("extrapop"); - if (!extpopStr.equals("unknown")) + if (!extpopStr.equals("unknown")) { extrapop = SpecXmlUtils.decodeInt(extpopStr); + } stackshift = SpecXmlUtils.decodeInt(protoElement.getAttribute("stackshift")); String type = protoElement.getAttribute("type"); if (type != null) { @@ -286,33 +537,57 @@ public class PrototypeModel { hasThis = false; isConstruct = false; String thisString = protoElement.getAttribute("hasthis"); - if (thisString != null) + if (thisString != null) { hasThis = SpecXmlUtils.decodeBoolean(thisString); - else + } + else { hasThis = name.equals(CompilerSpec.CALLING_CONVENTION_thiscall); + } String constructString = protoElement.getAttribute("constructor"); - if (constructString != null) + if (constructString != null) { isConstruct = SpecXmlUtils.decodeBoolean(constructString); + } buildParamList(protoElement.getAttribute("strategy")); while (parser.peek().isStart()) { XmlElement subel = parser.peek(); - if (subel.getName().equals("input")) { - inputParams.restoreXml(parser, cspec, normalstack); + String elName = subel.getName(); + if (elName.equals("input")) { + inputParams.restoreXml(parser, cspec); } - else if (subel.getName().equals("output")) { - outputParams.restoreXml(parser, cspec, normalstack); + else if (elName.equals("output")) { + outputParams.restoreXml(parser, cspec); } - else if (subel.getName().equals("pcode")) { + else if (elName.equals("pcode")) { XmlElement el = parser.peek(); - String nm; String source = "Compiler spec=" + cspec.getCompilerSpecID().getIdAsString(); - if (el.getAttribute("inject").equals("uponentry")) - nm = name + "@@inject_uponentry"; - else - nm = name + "@@inject_uponreturn"; - cspec.getPcodeInjectLibrary().restoreXmlInject(source, nm, - InjectPayload.CALLMECHANISM_TYPE, parser); + if (el.getAttribute("inject").equals("uponentry")) { + hasUponEntry = true; + } + else { + hasUponReturn = true; + } + cspec.getPcodeInjectLibrary() + .restoreXmlInject(source, getInjectName(), InjectPayload.CALLMECHANISM_TYPE, + parser); + } + else if (elName.equals("unaffected")) { + unaffected = readVarnodes(parser, cspec); + } + else if (elName.equals("killedbycall")) { + killedbycall = readVarnodes(parser, cspec); + } + else if (elName.equals("returnaddress")) { + returnaddress = readVarnodes(parser, cspec); + } + else if (elName.equals("likelytrash")) { + likelytrash = readVarnodes(parser, cspec); + } + else if (elName.equals("localrange")) { + localRange = readAddressSet(parser, cspec); + } + else if (elName.equals("paramrange")) { + paramRange = readAddressSet(parser, cspec); } else { subel = parser.start(); @@ -342,6 +617,59 @@ public class PrototypeModel { return inputParams.getPotentialRegisterStorage(prog); } + @Override + public boolean equals(Object obj) { + PrototypeModel op2 = (PrototypeModel) obj; + if (!name.equals(op2.name)) { + return false; + } + if (extrapop != op2.extrapop || stackshift != op2.stackshift) { + return false; + } + if (genericCallingConvention != op2.genericCallingConvention) { + return false; + } + if (hasThis != op2.hasThis || isConstruct != op2.isConstruct) { + return false; + } + if (hasUponEntry != op2.hasUponEntry || hasUponReturn != op2.hasUponReturn) { + return false; + } + if (inputListType != op2.inputListType) { + return false; + } + if (!inputParams.equals(op2.inputParams)) { + return false; + } + if (!outputParams.equals(op2.outputParams)) { + return false; + } + if (!SystemUtilities.isArrayEqual(unaffected, op2.unaffected)) { + return false; + } + if (!SystemUtilities.isArrayEqual(killedbycall, op2.killedbycall)) { + return false; + } + if (!SystemUtilities.isArrayEqual(likelytrash, op2.likelytrash)) { + return false; + } + if (!SystemUtilities.isEqual(localRange, op2.localRange)) { + return false; + } + if (!SystemUtilities.isEqual(paramRange, op2.paramRange)) { + return false; + } + if (!SystemUtilities.isArrayEqual(returnaddress, op2.returnaddress)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return name.hashCode(); + } + @Override public String toString() { return getName(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PrototypeModelError.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PrototypeModelError.java new file mode 100644 index 0000000000..2abbf9db1a --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PrototypeModelError.java @@ -0,0 +1,31 @@ +/* ### + * 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.program.model.lang; + +/** + * A PrototypeModel cloned from another, but marked as an error placeholder + */ +public class PrototypeModelError extends PrototypeModel { + + public PrototypeModelError(String name, PrototypeModel copyModel) { + super(name, copyModel); + } + + @Override + public boolean isErrorPlaceholder() { + return true; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PrototypeModelMerged.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PrototypeModelMerged.java index b56cc3588a..93642bf7e0 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PrototypeModelMerged.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PrototypeModelMerged.java @@ -20,6 +20,8 @@ import java.util.*; import ghidra.app.plugin.processors.sleigh.SleighException; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Parameter; +import ghidra.util.SystemUtilities; +import ghidra.util.xml.SpecXmlUtils; import ghidra.xml.*; /** @@ -30,56 +32,72 @@ import ghidra.xml.*; public class PrototypeModelMerged extends PrototypeModel { private PrototypeModel[] modellist; // models we are trying to distinguish between - + public PrototypeModelMerged() { super(); modellist = null; } - + /* (non-Javadoc) * @see ghidra.program.model.lang.PrototypeModel#isMerged() */ + @Override public boolean isMerged() { return true; } - + public int numModels() { return modellist.length; } - + public PrototypeModel getModel(int i) { return modellist[i]; } - - public void restoreXml(XmlPullParser parser,List modelList,boolean normalstack) throws XmlParseException { - ArrayList mylist = new ArrayList(); + + @Override + public void saveXml(StringBuilder buffer, PcodeInjectLibrary injectLibrary) { + buffer.append("\n"); + for (PrototypeModel model : modellist) { + buffer.append("\n"); + } + buffer.append("\n"); + } + + public void restoreXml(XmlPullParser parser, List modelList) + throws XmlParseException { + ArrayList mylist = new ArrayList<>(); XmlElement el = parser.start(); name = el.getAttribute("name"); - while(parser.peek().isStart()) { + while (parser.peek().isStart()) { XmlElement subel = parser.start(); String modelName = subel.getAttribute("name"); PrototypeModel foundModel = null; - for(PrototypeModel model : modelList) { + for (PrototypeModel model : modelList) { if (model.name.equals(modelName)) { foundModel = model; break; } } - if (foundModel == null) - throw new XmlParseException("Missing prototype model: "+modelName); + if (foundModel == null) { + throw new XmlParseException("Missing prototype model: " + modelName); + } mylist.add(foundModel); parser.end(subel); } parser.end(el); - modellist = new PrototypeModel[ mylist.size() ]; + modellist = new PrototypeModel[mylist.size()]; mylist.toArray(modellist); } - + public PrototypeModel selectModel(Parameter[] params) throws SleighException { int bestscore = 500; int bestindex = -1; - for(int i=0;i= 0) + if (bestindex >= 0) { return modellist[bestindex]; + } throw new SleighException("No model matches : missing default"); } - + private static class PEntry implements Comparable { - public int origIndex; // Original index of parameter +// public int origIndex; // Original index of parameter public int slot; // slot within the list public int size; // number of slots occupied - + @Override public int compareTo(PEntry o) { - if (slot != o.slot) + if (slot != o.slot) { return (slot < o.slot) ? -1 : 1; + } return 0; } } - + private static class ScoreProtoModel { private boolean isinputscore; // true for prototype inputs, false for prototype outputs private ArrayList entry; private PrototypeModel model; private int finalscore; private int mismatch; - - public ScoreProtoModel(boolean isinput,PrototypeModel mod,int numparam) { + + public ScoreProtoModel(boolean isinput, PrototypeModel mod, int numparam) { isinputscore = isinput; model = mod; - entry = new ArrayList(numparam); + entry = new ArrayList<>(numparam); finalscore = -1; mismatch = 0; } - + public int getScore() { return finalscore; } - - public int getNumMismatch() { - return mismatch; - } - - public void addParameter(Address addr,int sz) { - int orig = entry.size(); + + public void addParameter(Address addr, int sz) { +// int orig = entry.size(); ParamList.WithSlotRec rec = new ParamList.WithSlotRec(); boolean isparam; - - if (isinputscore) + + if (isinputscore) { isparam = model.possibleInputParamWithSlot(addr, sz, rec); - else + } + else { isparam = model.possibleOutputParamWithSlot(addr, sz, rec); - + } + if (isparam) { PEntry pe = new PEntry(); - pe.origIndex = orig; +// pe.origIndex = orig; pe.slot = rec.slot; - pe.size = rec.slotsize; - entry.add(pe); + pe.size = rec.slotsize; + entry.add(pe); } - else + else { mismatch += 1; + } } - + public void doScore() { Collections.sort(entry); // Sort our entries via slot - + int nextfree = 0; // Next slot we expect to see int basescore = 0; int[] penalty = new int[4]; @@ -166,23 +186,25 @@ public class PrototypeModelMerged extends PrototypeModel { penalty[3] = 5; int penaltyfinal = 3; int mismatchpenalty = 20; - - for(int i=0;i nextfree) { // We have some kind of hole in our slot coverage - while(nextfree < p.slot) { - if (nextfree < 4) + while (nextfree < p.slot) { + if (nextfree < 4) { basescore += penalty[nextfree]; - else + } + else { basescore += penaltyfinal; + } nextfree += 1; } nextfree += p.size; } else if (nextfree > p.slot) { // Some kind of slot duplication basescore += mismatchpenalty; - if (p.slot + p.size > nextfree) + if (p.slot + p.size > nextfree) { nextfree = p.slot + p.size; + } } else { nextfree = p.slot + p.size; @@ -191,4 +213,22 @@ public class PrototypeModelMerged extends PrototypeModel { finalscore = basescore + mismatchpenalty * mismatch; } } + + @Override + public boolean equals(Object obj) { + PrototypeModelMerged op2 = (PrototypeModelMerged) obj; + if (!name.equals(op2.name)) { + return false; + } + return SystemUtilities.isArrayEqual(modellist, op2.modellist); + } + + @Override + public int hashCode() { + int hash = name.hashCode(); + for (PrototypeModel model : modellist) { + hash = 79 * hash + model.hashCode(); + } + return hash; + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/InstructionPcodeOverride.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/InstructionPcodeOverride.java index 5d5b60e8d1..d2647b5ed3 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/InstructionPcodeOverride.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/InstructionPcodeOverride.java @@ -125,8 +125,9 @@ public class InstructionPcodeOverride implements PcodeOverride { if (fixupName == null) { return null; } - InjectPayload fixup = program.getCompilerSpec().getPcodeInjectLibrary().getPayload( - InjectPayload.CALLFIXUP_TYPE, fixupName, program, null); + InjectPayload fixup = program.getCompilerSpec() + .getPcodeInjectLibrary() + .getPayload(InjectPayload.CALLFIXUP_TYPE, fixupName); if (fixup == null) { Msg.warn(this, "Undefined call-fixup at " + callDestAddr + ": " + fixupName); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/AddressXML.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/AddressXML.java new file mode 100644 index 0000000000..d9b684f831 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/AddressXML.java @@ -0,0 +1,664 @@ +/* ### + * 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.program.model.pcode; + +import java.util.ArrayList; + +import org.xml.sax.Attributes; + +import ghidra.program.model.address.*; +import ghidra.program.model.lang.*; +import ghidra.util.xml.SpecXmlUtils; +import ghidra.xml.XmlElement; +import ghidra.xml.XmlParseException; + +/** + * Utility class for the myriad ways of marshaling/unmarshaling an address and an optional size, + * to/from XML for the various configuration files. + * + * An object of the class itself is the most general form, where the specified address + * - MAY have an associated size given in bytes + * - MAY be in the JOIN address space, with physical pieces making up the logical value explicitly provided. + * + * The static buildXML methods write out an \ tag given component elements without allocating an object. + * The static readXML methods read XML tags (presented in different forms) and returns an Address object. + * The static appendAttributes methods write out attributes of an address to an arbitrary XML tag. + * The static restoreXML methods read an \ tag and produce a general AddressXML object. + */ +public class AddressXML { + private AddressSpace space; // Address space containing the memory range + private long offset; // Starting offset of the range + private long size; // Number of bytes in the size + private Varnode[] joinRecord; // If non-null, separate address ranges being bonded in the "join" space + + /** + * Internal constructor for incremental initialization + */ + private AddressXML() { + space = null; + joinRecord = null; + } + + /** + * Construct an Address range as a space/offset/size + * @param spc is the address space containing the range + * @param off is the starting byte offset of the range + * @param sz is the size of the range in bytes + */ + public AddressXML(AddressSpace spc, long off, int sz) { + space = spc; + offset = off; + size = sz; + joinRecord = null; + } + + /** + * Construct a logical memory range, representing multiple ranges pieced together. + * The logical range is assigned an address in the JOIN address space. + * The physical pieces making up the logical range are passed in as a sequence of + * Varnodes representing, in order, the most significant through the least significant + * portions of the value. + * @param spc is the JOIN address space (must have a type of AddressSpace.TYPE_JOIN) + * @param off is the offset of the logical value within the JOIN space + * @param sz is the number of bytes in the logical value + * @param pieces is the array of 1 or more physical pieces + */ + public AddressXML(AddressSpace spc, long off, int sz, Varnode[] pieces) { + if (spc.getType() != AddressSpace.TYPE_JOIN) { + throw new IllegalArgumentException( + "JOIN address space required to represent an Address with pieces"); + } + space = spc; + offset = off; + size = sz; + joinRecord = pieces; + } + + private void readJoinXML(XmlElement el, CompilerSpec cspec) throws XmlParseException { + ArrayList pieces = new ArrayList<>(); + int sizesum = 0; + int pos = 0; + for (;;) { + String attrName = "piece" + Integer.toString(pos + 1); + String attrVal = el.getAttribute(attrName); + if (attrVal == null) { + break; + } + int offpos = attrVal.indexOf(':'); + Varnode newvn; + if (offpos == -1) { + Register register = cspec.getLanguage().getRegister(attrVal); + if (register == null) { + throw new XmlParseException("Unknown pentry register: " + attrVal); + } + newvn = new Varnode(register.getAddress(), register.getBitLength() / 8); + } + else { + int szpos = attrVal.indexOf(':', offpos + 1); + if (szpos == -1) { + throw new XmlParseException("join address piece attribute is malformed"); + } + String spcname = attrVal.substring(0, offpos); + AddressSpace spc = cspec.getAddressSpace(spcname); + long off = SpecXmlUtils.decodeLong(attrVal.substring(offpos + 1, szpos)); + long sz = SpecXmlUtils.decodeLong(attrVal.substring(szpos + 1)); + newvn = new Varnode(spc.getAddress(off), (int) sz); + } + pieces.add(newvn); + sizesum += newvn.getSize(); + pos += 1; + } + offset = 0; // This should be the offset assigned by the join space + size = sizesum; // Size is sum unless explicit attribute overwrites this with logical size + joinRecord = new Varnode[pieces.size()]; + pieces.toArray(joinRecord); + } + + /** + * Write this sized address as an \ XML tag. + * @param buffer is the buffer to write to + */ + public void saveXml(StringBuilder buffer) { + if (joinRecord != null) { + long logicalSize = size; + long sizeSum = 0; + for (Varnode vn : joinRecord) { + sizeSum += vn.getSize(); + } + if (sizeSum == size) { + logicalSize = 0; + } + buildXML(buffer, joinRecord, logicalSize); + return; + } + buffer.append(""); + } + + /** + * Restore an Address (as an AddressSpace and an offset) and an optional size from XML tag. + * The tag can have any name, but it must either have: + * - A "name" attribute, indicating a register name OR + * - A "space" and "offset" attribute, indicating the address space and offset + * + * If a register name is given, size is obtained from the register. If an offset is + * given, the size can optionally be specified using a "size" attribute. + * If not explicitly described, the size is set to zero. + * + * This method supports the "join" address space attached to the compiler specification + * @param el is the XML tag + * @param cspec is the compiler spec for looking up registers + * @return an AddressXML object containing the recovered space,offset,size + * @throws XmlParseException for problems parsing + */ + public static AddressXML restoreXml(XmlElement el, CompilerSpec cspec) + throws XmlParseException { + AddressXML result; + if (el.getName().equals("register")) { + String regName = el.getAttribute("name"); + if (regName == null) { + throw new XmlParseException("Missing pentry register name"); + } + Register register = cspec.getLanguage().getRegister(regName); + if (register == null) { + throw new XmlParseException("Unknown pentry register: " + regName); + } + result = new AddressXML(register.getAddressSpace(), register.getOffset(), + register.getMinimumByteSize()); + } + else { + result = new AddressXML(); + result.size = 0; + String spaceName = el.getAttribute("space"); + result.space = cspec.getAddressSpace(spaceName); + if (result.space == null) { + throw new XmlParseException("Unknown address space: " + spaceName); + } + if (result.space.getType() == AddressSpace.TYPE_JOIN) { + result.readJoinXML(el, cspec); + } + else { + result.offset = SpecXmlUtils.decodeLong(el.getAttribute("offset")); + } + String sizeString = el.getAttribute("size"); + if (sizeString != null) { + result.size = SpecXmlUtils.decodeInt(sizeString); + } + } + return result; + } + + /** + * Restore an Address (as an AddressSpace and an offset) and an optional size from XML tag. + * The tag can have any name, but it must either have: + * - A "name" attribute, indicating a register name OR + * - A "space" and "offset" attribute, indicating the address space and offset + * + * If a register name is given, size is obtained from the register. If an offset is + * given, the size can optionally be specified using a "size" attribute. + * If not explicitly described, the size is set to zero. + * @param el is the XML tag + * @param language is the processor language for looking up registers and address spaces + * @return an AddressXML object containing the recovered space,offset,size + * @throws XmlParseException for problems parsing + */ + public static AddressXML restoreXml(XmlElement el, Language language) throws XmlParseException { + AddressXML result; + if (el.getName().equals("register")) { + String regName = el.getAttribute("name"); + if (regName == null) { + throw new XmlParseException("Missing register name"); + } + Register register = language.getRegister(regName); + if (register == null) { + throw new XmlParseException("Unknown register: " + regName); + } + result = new AddressXML(register.getAddressSpace(), register.getOffset(), + register.getMinimumByteSize()); + } + else { + result = new AddressXML(); + result.size = 0; + String spaceName = el.getAttribute("space"); + result.space = language.getAddressFactory().getAddressSpace(spaceName); + if (result.space == null) { + throw new XmlParseException("Unknown address space: " + spaceName); + } + result.offset = SpecXmlUtils.decodeLong(el.getAttribute("offset")); + String sizeString = el.getAttribute("size"); + if (sizeString != null) { + result.size = SpecXmlUtils.decodeInt(sizeString); + } + } + return result; + } + + /** + * A memory range is read from attributes of an XML tag. The tag must either have: + * - "name" attribute - indicating a register + * - "space" attribute - with optional "first" and "last" attributes + * + * With the "space" attribute, "first" defaults to 0 and "last" defaults to the last offset in the space. + * @param el is the XML element + * @param cspec is a compiler spec to resolve address spaces and registers + * @return an AddressXML object representing the range + * @throws XmlParseException if the XML is badly formed + */ + public static AddressXML restoreRangeXml(XmlElement el, CompilerSpec cspec) + throws XmlParseException { + AddressXML result = new AddressXML(); + result.offset = 0; + long last = -1; + boolean seenLast = false; + String attrvalue = el.getAttribute("space"); + if (attrvalue != null) { + result.space = cspec.getAddressSpace(attrvalue); + if (result.space == null) { + throw new XmlParseException("Undefined space: " + attrvalue); + } + } + attrvalue = el.getAttribute("first"); + if (attrvalue != null) { + result.offset = SpecXmlUtils.decodeLong(attrvalue); + } + attrvalue = el.getAttribute("last"); + if (attrvalue != null) { + last = SpecXmlUtils.decodeLong(attrvalue); + seenLast = true; + } + attrvalue = el.getAttribute("name"); + if (attrvalue != null) { + Register register = cspec.getLanguage().getRegister(attrvalue); + result.space = register.getAddressSpace(); + result.offset = register.getOffset(); + last = (result.offset - 1) + register.getMinimumByteSize(); + seenLast = true; + } + if (result.space == null) { + throw new XmlParseException("No address space indicated in range tag"); + } + if (!seenLast) { + last = result.space.getMaxAddress().getOffset(); + } + result.size = (last - result.offset) + 1; + return result; + } + + /** + * @return the space associated of this address + */ + public final AddressSpace getAddressSpace() { + return space; + } + + /** + * @return the byte offset of this address + */ + public final long getOffset() { + return offset; + } + + /** + * @return the size in bytes associated with this address + */ + public final long getSize() { + return size; + } + + /** + * Get the array of physical pieces making up this logical address range, if + * the range is in the JOIN address space. Otherwise return null. + * @return the physical pieces or null + */ + public final Varnode[] getJoinRecord() { + return joinRecord; + } + + /** + * Build a raw Varnode from the Address and size + * @return the new Varnode + */ + public Varnode getVarnode() { + Address addr = space.getAddress(offset); + return new Varnode(addr, (int) size); + } + + /** + * @return the first address in the range + */ + public Address getFirstAddress() { + return space.getAddress(offset); + } + + /** + * @return the last address in the range + */ + public Address getLastAddress() { + return space.getAddress(offset + size - 1); + } + + /** + * Parse String containing an XML tag representing an Address. + * The format options are simple enough that we don't try to invoke + * an actual XML parser but just walk the string. This recognizes + * - \ + * - \ or + * - any tag with a "space" and "offset" attribute + * + * @param addrstring is the string containing the XML tag + * @param addrfactory is the factory that can produce addresses + * @return the created Address or Address.NO_ADDRESS in some special cases + * @throws PcodeXMLException for a badly formed Address + */ + public static Address readXML(String addrstring, AddressFactory addrfactory) + throws PcodeXMLException { + + int tagstart = addrstring.indexOf('<'); + if (tagstart >= 0) { + tagstart += 1; + if (addrstring.startsWith("spaceid", tagstart)) { + tagstart += 8; + int attrstart = addrstring.indexOf("name=\"", tagstart); + if (attrstart >= 0) { + attrstart += 6; + int nameend = addrstring.indexOf('\"', attrstart); + if (nameend >= 0) { + AddressSpace spc = + addrfactory.getAddressSpace(addrstring.substring(attrstart, nameend)); + int spaceid = spc.getSpaceID(); + spc = addrfactory.getConstantSpace(); + return spc.getAddress(spaceid); + } + } + + } + // There are several tag forms where we essentially want to just look for 'space' and 'offset' attributes + // don't explicitly check the tag name + int spacestart = addrstring.indexOf("space=\""); + if (spacestart >= 4) { + spacestart += 7; + int spaceend = addrstring.indexOf('"', spacestart); + if (spaceend >= spacestart) { + String spcname = addrstring.substring(spacestart, spaceend); + int offstart = addrstring.indexOf("offset=\""); + if (offstart >= 4) { + offstart += 8; + int offend = addrstring.indexOf('"', offstart); + if (offend >= offstart) { + String offstr = addrstring.substring(offstart, offend); + AddressSpace spc = addrfactory.getAddressSpace(spcname); + // Unknown spaces may result from "spacebase" registers defined in cspec + if (spc == null) { + return Address.NO_ADDRESS; + } + long offset = SpecXmlUtils.decodeLong(offstr); + return spc.getAddress(offset); + } + } + } + } + } + throw new PcodeXMLException("Badly formed address: " + addrstring); + } + + /** + * Read the (first) size attribute from an XML tag string as an integer + * @param addrxml is the XML string + * @return the decoded integer or zero if the attribute doesn't exist + */ + public static int readXMLSize(String addrxml) { + int attrstart = addrxml.indexOf("size=\""); + if (attrstart >= 4) { + attrstart += 6; + int attrend = addrxml.indexOf('\"', attrstart); + if (attrend > attrstart) { + int size = SpecXmlUtils.decodeInt(addrxml.substring(attrstart, attrend)); + return size; + } + } + return 0; + } + + /** + * Create an address from an XML parse tree node. This recognizes XML tags + * - \ + * - \ + * - \ or + * - any tag with "space" and "offset" attributes + * + * An empty \ tag, with no attributes, results in Address.NO_ADDRESS being returned. + * @param el is the parse tree element + * @param addrFactory address factory used to create valid addresses + * @return Address created from XML info + */ + public static Address readXML(XmlElement el, AddressFactory addrFactory) { + String localName = el.getName(); + if (localName.equals("spaceid")) { + AddressSpace spc = addrFactory.getAddressSpace(el.getAttribute("name")); + int spaceid = spc.getSpaceID(); + spc = addrFactory.getConstantSpace(); + return spc.getAddress(spaceid); + } + else if (localName.equals("iop")) { + int ref = SpecXmlUtils.decodeInt(el.getAttribute("value")); + AddressSpace spc = addrFactory.getConstantSpace(); + return spc.getAddress(ref); + } + String space = el.getAttribute("space"); + if (space == null) { + return Address.NO_ADDRESS; + } + long offset = SpecXmlUtils.decodeLong(el.getAttribute("offset")); + AddressSpace spc = addrFactory.getAddressSpace(space); + if (spc == null) { + return null; + } + return spc.getAddress(offset); + } + + /** + * Read an Address given an XML tag name and its attributes. This recognizes XML tags + * - \ + * - \ + * - \ + * - any tag with "space" or "offset" attributes + * + * An empty \ tag, with no attributes, results in Address.NO_ADDRESS being returned. + * @param localName is the name of the tag + * @param attr is the collection of attributes for the tag + * @param addrFactory is an Address factory + * @return the scanned address + */ + public static Address readXML(String localName, Attributes attr, AddressFactory addrFactory) { + if (localName.equals("spaceid")) { + AddressSpace spc = addrFactory.getAddressSpace(attr.getValue("name")); + int spaceid = spc.getSpaceID(); + spc = addrFactory.getConstantSpace(); + return spc.getAddress(spaceid); + } + else if (localName.equals("iop")) { + int ref = SpecXmlUtils.decodeInt(attr.getValue("value")); + AddressSpace spc = addrFactory.getConstantSpace(); + return spc.getAddress(ref); + } + String space = attr.getValue("space"); + if (space == null) { + return Address.NO_ADDRESS; + } + long offset = SpecXmlUtils.decodeLong(attr.getValue("offset")); + AddressSpace spc = addrFactory.getAddressSpace(space); + if (spc == null) { + return Address.NO_ADDRESS; + } + return spc.getAddress(offset); + } + + /** + * Append "space" and "offset" attributes describing the given Address to the XML stream. + * This assumes the XML tag name has already been emitted. + * @param buf is the XML stream + * @param addr is the given Address + */ + public static void appendAttributes(StringBuilder buf, Address addr) { + AddressSpace space = addr.getAddressSpace(); + if (space.isOverlaySpace()) { + if (space.getType() != AddressSpace.TYPE_OTHER) { + space = space.getPhysicalSpace(); + addr = space.getAddress(addr.getOffset()); + } + } + SpecXmlUtils.encodeStringAttribute(buf, "space", space.getName()); + SpecXmlUtils.encodeUnsignedIntegerAttribute(buf, "offset", addr.getUnsignedOffset()); + } + + /** + * Append "space" "offset" and "size" attributes describing the given memory range to the XML stream. + * This assumes the XML tag name has already been emitted. + * @param buf is the XML stream + * @param addr is the starting Address of the memory range + * @param size is the size of the memory range + */ + public static void appendAttributes(StringBuilder buf, Address addr, int size) { + AddressSpace space = addr.getAddressSpace(); + if (space.isOverlaySpace()) { + if (space.getType() != AddressSpace.TYPE_OTHER) { + space = space.getPhysicalSpace(); + addr = space.getAddress(addr.getOffset()); + } + } + SpecXmlUtils.encodeStringAttribute(buf, "space", space.getName()); + SpecXmlUtils.encodeUnsignedIntegerAttribute(buf, "offset", addr.getUnsignedOffset()); + SpecXmlUtils.encodeSignedIntegerAttribute(buf, "size", size); + } + + /** + * Append a memory range, as "space", "first", and "last" attributes, to the XML stream. + * This assumes the XML tag name has already been emitted. + * @param buffer is the XML stream + * @param startAddr is the first address in the range + * @param endAddr is the last address in the range + */ + public static void appendAttributes(StringBuilder buffer, Address startAddr, Address endAddr) { + AddressSpace space = startAddr.getAddressSpace(); + long offset = startAddr.getOffset(); + long size = endAddr.getOffset() - offset + 1; + + if (space != endAddr.getAddressSpace()) { + throw new IllegalArgumentException( + "Range boundaries are not in the same address space"); + } + if (size < 0) { + throw new IllegalArgumentException("Start of range comes after end of range"); + } + + long last = offset + size - 1; + boolean useFirst = (offset != 0); + boolean useLast = (last != -1); + SpecXmlUtils.encodeStringAttribute(buffer, "space", space.getName()); + if (useFirst) { + SpecXmlUtils.encodeUnsignedIntegerAttribute(buffer, "first", offset); + } + if (useLast) { + SpecXmlUtils.encodeUnsignedIntegerAttribute(buffer, "last", last); + } + } + + /** + * Write out the given Address as an \ tag to the XML stream + * + * @param buf is the XML stream + * @param addr -- Address to convert to XML + */ + public static void buildXML(StringBuilder buf, Address addr) { + + if ((addr == null) || (addr == Address.NO_ADDRESS)) { + buf.append(""); + return; + } + buf.append(""); + } + + /** + * Write out the given Address and a size as an \ tag to the XML stream + * + * @param buf is the XML stream + * @param addr is the given Address + * @param size is the given size + */ + public static void buildXML(StringBuilder buf, Address addr, int size) { + buf.append(""); + } + + private static void buildVarnodePiece(StringBuilder buf, Address addr, int size) { + AddressSpace space = addr.getAddressSpace(); + if (space.isOverlaySpace()) { + space = space.getPhysicalSpace(); + addr = space.getAddress(addr.getOffset()); + } + buf.append(space.getName()); + buf.append(":0x"); + long off = addr.getUnsignedOffset(); + buf.append(Long.toHexString(off)); + buf.append(':'); + buf.append(Integer.toString(size)); + } + + /** + * Write out a sequence of Varnodes as a single \ tag to an XML stream. + * If there is more than one Varnode, or if the logical size is non-zero, + * the \ tag will specify the address space as "join" and will have + * additional "piece" attributes. + * + * @param buf is the XML stream + * @param varnodes is the sequence of storage varnodes + * @param logicalsize is the logical size value of the varnode + */ + public static void buildXML(StringBuilder buf, Varnode[] varnodes, long logicalsize) { + + if (varnodes == null) { + buf.append(""); + return; + } + if ((varnodes.length == 1) && (logicalsize == 0)) { + AddressXML.buildXML(buf, varnodes[0].getAddress(), varnodes[0].getSize()); + return; + } + buf.append(""); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/FunctionPrototype.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/FunctionPrototype.java index ffbc88094b..3c04a24556 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/FunctionPrototype.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/FunctionPrototype.java @@ -190,8 +190,9 @@ public class FunctionPrototype { } // if the callfixup has no fallthru, set the noreturn property too Program program = f.getProgram(); - InjectPayload callFixup = program.getCompilerSpec().getPcodeInjectLibrary().getPayload( - InjectPayload.CALLFIXUP_TYPE, fixupname, program, null); + InjectPayload callFixup = program.getCompilerSpec() + .getPcodeInjectLibrary() + .getPayload(InjectPayload.CALLFIXUP_TYPE, fixupname); if (callFixup == null) { return false; } @@ -369,8 +370,8 @@ public class FunctionPrototype { if (sz != returnstorage.size()) { // If the sizes do no match logicalsize = sz; // force the logical size on the varnode } - String addrstring = Varnode.buildXMLAddress(returnstorage.getVarnodes(), logicalsize); - res.append(addrstring).append("\n "); + AddressXML.buildXML(res, returnstorage.getVarnodes(), logicalsize); + res.append("\n "); } else { // Decompiler will use model for storage diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighExternalSymbol.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighExternalSymbol.java index 99ff223616..a2c9e1f974 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighExternalSymbol.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighExternalSymbol.java @@ -61,7 +61,7 @@ public class HighExternalSymbol extends HighSymbol { SpecXmlUtils.xmlEscapeAttribute(buf, "name", name + "_exref"); } buf.append(">\n"); - buf.append(Varnode.buildXMLAddress(resolveAddress)); + AddressXML.buildXML(buf, resolveAddress); buf.append("\n"); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighFunction.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighFunction.java index de9571acb1..bf40974021 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighFunction.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighFunction.java @@ -183,7 +183,7 @@ public class HighFunction extends PcodeSyntaxTree { JumpTable jumpTab = JumpTable.readOverride((Namespace) obj, symtab); if (jumpTab != null) { if (jumpTables == null) { - jumpTables = new ArrayList(); + jumpTables = new ArrayList<>(); } jumpTables.add(jumpTab); } @@ -194,7 +194,7 @@ public class HighFunction extends PcodeSyntaxTree { DataTypeSymbol protover = HighFunctionDBUtil.readOverride(sym); if (protover != null) { if (protoOverrides == null) { - protoOverrides = new ArrayList(); + protoOverrides = new ArrayList<>(); } protoOverrides.add(protover); } @@ -259,14 +259,13 @@ public class HighFunction extends PcodeSyntaxTree { XmlElement start = parser.start("function"); String name = start.getAttribute("name"); if (!func.getName().equals(name)) { - throw new PcodeXMLException( - "Function name mismatch: " + func.getName() + " + " + name); + throw new PcodeXMLException("Function name mismatch: " + func.getName() + " + " + name); } while (!parser.peek().isEnd()) { XmlElement subel = parser.peek(); if (subel.getName().equals("addr")) { subel = parser.start("addr"); - Address addr = Varnode.readXMLAddress(subel, getAddressFactory()); + Address addr = AddressXML.readXML(subel, getAddressFactory()); parser.end(subel); addr = func.getEntryPoint().getAddressSpace().getOverlayAddress(addr); if (!func.getEntryPoint().equals(addr)) { @@ -318,7 +317,7 @@ public class HighFunction extends PcodeSyntaxTree { table.restoreXml(parser, getAddressFactory()); if (!table.isEmpty()) { if (jumpTables == null) { - jumpTables = new ArrayList(); + jumpTables = new ArrayList<>(); } jumpTables.add(table); } @@ -352,8 +351,8 @@ public class HighFunction extends PcodeSyntaxTree { */ public HighVariable splitOutMergeGroup(HighVariable high, Varnode vn) throws PcodeException { try { - ArrayList newinst = new ArrayList(); - ArrayList oldinst = new ArrayList(); + ArrayList newinst = new ArrayList<>(); + ArrayList oldinst = new ArrayList<>(); short ourgroup = vn.getMergeGroup(); Varnode[] curinst = high.getInstances(); for (Varnode curvn : curinst) { @@ -457,10 +456,10 @@ public class HighFunction extends PcodeSyntaxTree { } resBuf.append(">\n"); if (entryPoint == null) { - resBuf.append(Varnode.buildXMLAddress(func.getEntryPoint())); + AddressXML.buildXML(resBuf, func.getEntryPoint()); } else { - resBuf.append(Varnode.buildXMLAddress(entryPoint)); // Address is forced on XML + AddressXML.buildXML(resBuf, entryPoint); // Address is forced on XML } localSymbols.buildLocalDbXML(resBuf, namespace); proto.buildPrototypeXML(resBuf, getDataTypeManager()); @@ -482,9 +481,7 @@ public class HighFunction extends PcodeSyntaxTree { FunctionPrototype fproto = new FunctionPrototype( (FunctionSignature) sym.getDataType(), compilerSpec, false); resBuf.append("\n"); - resBuf.append("\n"); + AddressXML.buildXML(resBuf, addr); fproto.buildPrototypeXML(resBuf, dtmanage); resBuf.append("\n"); } @@ -532,7 +529,7 @@ public class HighFunction extends PcodeSyntaxTree { public static void createLabelSymbol(SymbolTable symtab, Address addr, String name, Namespace namespace, SourceType source, boolean useLocalNamespace) - throws InvalidInputException { + throws InvalidInputException { if (namespace == null && useLocalNamespace) { namespace = symtab.getNamespace(addr); } @@ -560,8 +557,8 @@ public class HighFunction extends PcodeSyntaxTree { public static boolean clearNamespace(SymbolTable symtab, Namespace space) throws InvalidInputException { SymbolIterator iter = symtab.getSymbols(space); - ArrayList
addrlist = new ArrayList
(); - ArrayList namelist = new ArrayList(); + ArrayList
addrlist = new ArrayList<>(); + ArrayList namelist = new ArrayList<>(); while (iter.hasNext()) { Symbol sym = iter.next(); if (!(sym instanceof CodeSymbol)) { @@ -608,7 +605,7 @@ public class HighFunction extends PcodeSyntaxTree { throws PcodeXMLException { try { XmlPullParser parser = - XmlPullParserFactory.create(xml, "Decompiler Result Parser", handler, false); + XmlPullParserFactory.create(xml, "Decompiler Result Parser", handler, false); return parser; } catch (Exception e) { @@ -638,7 +635,7 @@ public class HighFunction extends PcodeSyntaxTree { static public void createNamespaceTag(StringBuilder buf, Namespace namespace) { buf.append("\n"); if (namespace != null) { - ArrayList arr = new ArrayList(); + ArrayList arr = new ArrayList<>(); Namespace curspc = namespace; while (curspc != null) { arr.add(0, curspc); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighFunctionShellSymbol.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighFunctionShellSymbol.java index ec8782fb5b..c8300d1903 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighFunctionShellSymbol.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighFunctionShellSymbol.java @@ -59,7 +59,7 @@ public class HighFunctionShellSymbol extends HighSymbol { SpecXmlUtils.xmlEscapeAttribute(buf, "name", name); SpecXmlUtils.encodeSignedIntegerAttribute(buf, "size", 1); buf.append(">\n"); - buf.append(Varnode.buildXMLAddress(getStorage().getMinAddress())); + AddressXML.buildXML(buf, getStorage().getMinAddress()); buf.append("\n"); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighParamID.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighParamID.java index e565cf089a..5c3df67093 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighParamID.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighParamID.java @@ -46,8 +46,8 @@ public class HighParamID extends PcodeSyntaxTree { private Address functionaddress; private String modelname; // Name of prototype model private Integer protoextrapop; - private List inputlist = new ArrayList(); - private List outputlist = new ArrayList(); + private List inputlist = new ArrayList<>(); + private List outputlist = new ArrayList<>(); /** * @param function function associated with the higher level function abstraction. @@ -107,7 +107,8 @@ public class HighParamID extends PcodeSyntaxTree { } /** - * @return the specific of input for functionparams + * @param i is the specific index to return + * @return the specific input for functionparams */ public ParamMeasure getInput(int i) { return inputlist.get(i); @@ -121,6 +122,7 @@ public class HighParamID extends PcodeSyntaxTree { } /** + * @param i is the index of the specific output * @return the specific of output for functionparams */ public ParamMeasure getOutput(int i) { @@ -135,14 +137,14 @@ public class HighParamID extends PcodeSyntaxTree { XmlElement start = parser.start("parammeasures"); functionname = start.getAttribute("name"); if (!func.getName().equals(functionname)) { - throw new PcodeXMLException("Function name mismatch: " + func.getName() + - " + " + functionname); + throw new PcodeXMLException( + "Function name mismatch: " + func.getName() + " + " + functionname); } while (!parser.peek().isEnd()) { XmlElement subel = parser.peek(); if (subel.getName().equals("addr")) { subel = parser.start("addr"); - functionaddress = Varnode.readXMLAddress(subel, getAddressFactory()); + functionaddress = AddressXML.readXML(subel, getAddressFactory()); parser.end(subel); functionaddress = func.getEntryPoint().getAddressSpace().getOverlayAddress(functionaddress); @@ -177,8 +179,10 @@ public class HighParamID extends PcodeSyntaxTree { /** * Read in the inputs or outputs list for this function from an XML rep - * @param el - * @throws PcodeXMLException + * @param parser is the XML parser + * @param pmlist is populated with the resulting list + * @param tag is the name of the tag + * @throws PcodeXMLException for improperly formed XML */ private void parseParamMeasureXML(XmlPullParser parser, List pmlist, String tag) throws PcodeXMLException { @@ -191,7 +195,8 @@ public class HighParamID extends PcodeSyntaxTree { parser.end(el); } - public static ErrorHandler getErrorHandler(final Object errOriginator, final String targetName) { + public static ErrorHandler getErrorHandler(final Object errOriginator, + final String targetName) { return new ErrorHandler() { @Override public void error(SAXParseException exception) throws SAXException { @@ -216,9 +221,10 @@ public class HighParamID extends PcodeSyntaxTree { * TODO: this probably doesn't belong here. * * @param xml string to parse + * @param handler is the error handler * @return an XML tree element * - * @throws PcodeXMLException + * @throws PcodeXMLException for improper XML */ static public XmlPullParser stringTree(String xml, ErrorHandler handler) throws PcodeXMLException { @@ -235,6 +241,7 @@ public class HighParamID extends PcodeSyntaxTree { /** * Update any parameters for this Function from parameters defined in this map. * + * @param storeDataTypes is true if data-types are getting stored * @param srctype function signature source */ public void storeReturnToDatabase(boolean storeDataTypes, SourceType srctype) { @@ -275,12 +282,13 @@ public class HighParamID extends PcodeSyntaxTree { * Update any parameters for this Function from parameters defined in this map. * Originally from LocalSymbolMap, but being modified. * + * @param storeDataTypes is true if data-types are being stored * @param srctype function signature source */ public void storeParametersToDatabase(boolean storeDataTypes, SourceType srctype) { PcodeDataTypeManager dtManage = getDataTypeManager(); try { - List params = new ArrayList(); + List params = new ArrayList<>(); for (ParamMeasure pm : inputlist) { Varnode vn = pm.getVarnode(); DataType dataType; @@ -292,9 +300,7 @@ public class HighParamID extends PcodeSyntaxTree { else { dataType = dtManage.findUndefined(vn.getSize()); } - Variable v = - new ParameterImpl(null, dataType, buildStorage(vn), - func.getProgram()); + Variable v = new ParameterImpl(null, dataType, buildStorage(vn), func.getProgram()); //Msg.debug(this, "function(" + func.getName() + ")--param: " + v.toString() + // " -- type: " + dataType.getName()); params.add(v); @@ -302,7 +308,7 @@ public class HighParamID extends PcodeSyntaxTree { func.updateFunction(modelname, null, params, FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, srctype); - if ( !paramStorageMatches(func, params)) { + if (!paramStorageMatches(func, params)) { // try again if dynamic storage assignment does not match decompiler's // force into custom storage mode func.updateFunction(modelname, null, params, FunctionUpdateType.CUSTOM_STORAGE, diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/JumpTable.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/JumpTable.java index f86ab5802a..adcdd29ca9 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/JumpTable.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/JumpTable.java @@ -38,8 +38,7 @@ public class JumpTable { /** * Translate address into preferred memory space (JumpTable.preferredSpace) - * @param addr - * @param preferredSpace + * @param addr is the given Address * @return preferred address or original addr */ private Address translateOverlayAddress(Address addr) { @@ -84,7 +83,7 @@ public class JumpTable { size = SpecXmlUtils.decodeInt(el.getAttribute("size")); num = SpecXmlUtils.decodeInt(el.getAttribute("num")); XmlElement subel = parser.start("addr"); - addr = translateOverlayAddress(Varnode.readXMLAddress(subel, addrFactory)); + addr = translateOverlayAddress(AddressXML.readXML(subel, addrFactory)); parser.end(subel); parser.end(el); } @@ -106,7 +105,7 @@ public class JumpTable { buf.append("\n"); for (Address element : destlist) { buf.append("\n"); } // We could add and tags to specify switch variable @@ -150,41 +149,42 @@ public class JumpTable { } public boolean isEmpty() { - if (addressTable == null) + if (addressTable == null) { return true; - if (addressTable.length == 0) + } + if (addressTable.length == 0) { return true; + } return false; } /** * Create a JumpTable object by parsing the XML elements - * @param parser - * @param addrFactory - * @throws PcodeXMLException + * @param parser is the XML parser + * @param addrFactory is used to look-up address spaces + * @throws PcodeXMLException for improperly formed XML */ public void restoreXml(XmlPullParser parser, AddressFactory addrFactory) throws PcodeXMLException { XmlElement el = parser.start("jumptable"); try { - ArrayList
aTable = new ArrayList
(); - ArrayList lTable = new ArrayList(); - ArrayList ldTable = new ArrayList(); + ArrayList
aTable = new ArrayList<>(); + ArrayList lTable = new ArrayList<>(); + ArrayList ldTable = new ArrayList<>(); if (!parser.peek().isStart()) { // Empty jumptable return; } XmlElement addrel = parser.start("addr"); - Address switchAddr = - translateOverlayAddress(Varnode.readXMLAddress(addrel, addrFactory)); + Address switchAddr = translateOverlayAddress(AddressXML.readXML(addrel, addrFactory)); parser.end(addrel); while (parser.peek().isStart()) { if (parser.peek().getName().equals("dest")) { XmlElement subel = parser.start("dest"); Address caseAddr = - translateOverlayAddress(Varnode.readXMLAddress(subel, addrFactory)); + translateOverlayAddress(AddressXML.readXML(subel, addrFactory)); aTable.add(caseAddr); String slabel = subel.getAttribute("label"); if (slabel != null) { @@ -198,8 +198,9 @@ public class JumpTable { loadtable.restoreXml(parser, addrFactory); ldTable.add(loadtable); } - else + else { parser.discardSubTree(); + } } opAddress = switchAddr; @@ -217,18 +218,18 @@ public class JumpTable { public void buildXml(StringBuilder buf) { buf.append("\n"); - buf.append("\n"); + AddressXML.buildXML(buf, opAddress); + buf.append('\n'); if (addressTable != null) { for (Address element : addressTable) { buf.append("\n"); } } - if (override != null) + if (override != null) { override.buildXml(buf); + } buf.append("\n"); } @@ -249,24 +250,29 @@ public class JumpTable { } public void writeOverride(Function func) throws InvalidInputException { - if (override == null) + if (override == null) { throw new InvalidInputException("Jumptable is not an override"); + } Address[] destlist = override.getDestinations(); - if (destlist.length == 0) + if (destlist.length == 0) { throw new InvalidInputException("Jumptable has no destinations"); - if (!func.getBody().contains(opAddress)) + } + if (!func.getBody().contains(opAddress)) { throw new InvalidInputException("Switch is not in function body"); + } Program program = func.getProgram(); SymbolTable symtab = program.getSymbolTable(); Namespace space = HighFunction.findCreateOverrideSpace(func); - if (space == null) + if (space == null) { throw new InvalidInputException("Could not create \"override\" namespace"); + } space = HighFunction.findCreateNamespace(symtab, space, "jmp_" + opAddress.toString()); - if (!HighFunction.clearNamespace(symtab, space)) + if (!HighFunction.clearNamespace(symtab, space)) { throw new InvalidInputException( "Jumptable override namespace contains non-label symbols."); + } HighFunction.createLabelSymbol(symtab, opAddress, "switch", space, SourceType.USER_DEFINED, false); @@ -279,20 +285,24 @@ public class JumpTable { public static JumpTable readOverride(Namespace space, SymbolTable symtab) { Address branchind = null; - ArrayList
destlist = new ArrayList
(); + ArrayList
destlist = new ArrayList<>(); SymbolIterator iter = symtab.getSymbols(space); while (iter.hasNext()) { Symbol sym = iter.next(); - if (!(sym instanceof CodeSymbol)) + if (!(sym instanceof CodeSymbol)) { continue; + } Address addr = sym.getAddress(); - if (sym.getName().equals("switch")) + if (sym.getName().equals("switch")) { branchind = addr; - else if (sym.getName().startsWith("case")) + } + else if (sym.getName().startsWith("case")) { destlist.add(addr); + } } - if ((branchind != null) && (destlist.size() > 0)) + if ((branchind != null) && (destlist.size() > 0)) { return new JumpTable(branchind, destlist, true); + } return null; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/MappedEntry.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/MappedEntry.java index 0827ed6e3e..4628f48218 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/MappedEntry.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/MappedEntry.java @@ -65,7 +65,7 @@ public class MappedEntry extends SymbolEntry { "Invalid symbol 0-sized data-type: " + symbol.type.getName()); } try { - Address varAddr = Varnode.readXMLAddress(addrel, addrFactory); + Address varAddr = AddressXML.readXML(addrel, addrFactory); AddressSpace spc = varAddr.getAddressSpace(); if ((spc == null) || (spc.getType() != AddressSpace.TYPE_VARIABLE)) { storage = new VariableStorage(program, varAddr, sz); @@ -89,8 +89,7 @@ public class MappedEntry extends SymbolEntry { if (typeLength != storage.size() && symbol.type instanceof AbstractFloatDataType) { logicalsize = typeLength; // Force a logicalsize } - String addrRes = Varnode.buildXMLAddress(storage.getVarnodes(), logicalsize); - buf.append(addrRes); + AddressXML.buildXML(buf, storage.getVarnodes(), logicalsize); buildRangelistXML(buf); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java index 8a5f340318..7ff838cad0 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java @@ -100,7 +100,7 @@ public class PcodeDataTypeManager { progDataTypes = prog.getDataTypeManager(); dataOrganization = progDataTypes.getDataOrganization(); voidInputIsVarargs = true; // By default, do not lock-in void parameter lists - displayLanguage = prog.getCompilerSpec().getDecompilerOutputLanguage(prog); + displayLanguage = prog.getCompilerSpec().getDecompilerOutputLanguage(); if (displayLanguage != DecompilerLanguage.C_LANGUAGE) { voidInputIsVarargs = false; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Varnode.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Varnode.java index 4e6f867f4b..2474a653a8 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Varnode.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Varnode.java @@ -17,8 +17,6 @@ package ghidra.program.model.pcode; import java.util.Iterator; -import org.xml.sax.Attributes; - import ghidra.program.model.address.*; import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Register; @@ -123,22 +121,22 @@ public class Varnode { /** * Determine if this varnode contains the specified address - * @param address the address for which to check + * @param addr the address for which to check * @return true if this varnode contains the specified address */ - public boolean contains(Address address) { - if (spaceID != address.getAddressSpace().getSpaceID()) { + public boolean contains(Address addr) { + if (spaceID != addr.getAddressSpace().getSpaceID()) { return false; } if (isConstant() || isUnique() || isHash()) { // this is not really a valid use case - return offset == address.getOffset(); + return offset == addr.getOffset(); } long endOffset = offset; if (size > 0) { endOffset = offset + size - 1; } - long addrOffset = address.getOffset(); + long addrOffset = addr.getOffset(); if (offset > endOffset) { // handle long-wrap condition return offset <= addrOffset; } @@ -320,110 +318,16 @@ public class Varnode { * @param buf is the builder to which to append XML */ public void buildXML(StringBuilder buf) { - buildXMLAddress(buf, address, size); + AddressXML.buildXML(buf, address, size); } /** - * Build an XML document representation of a varnode with the given address and size. - * - * @param resBuf is the builder to which to append the XML - * @param addr location varnode is defined at - * @param size size of the varnode. - */ - public static void buildXMLAddress(StringBuilder resBuf, Address addr, int size) { - resBuf.append(""); - } - - /** - * Convert an address into an XML document. - * - * @param addr -- Address to convert to XML - * @return XML string - */ - public static String buildXMLAddress(Address addr) { - - if ((addr == null) || (addr == Address.NO_ADDRESS)) { - return ""; - } - StringBuilder resBuf = new StringBuilder(); - resBuf.append(""); - return resBuf.toString(); - } - - /** - * Convert a varnode array into an XML document. - * - * @param varnodes sequence of storage varnodes - * @param logicalsize the logical size value of the varnode - * @return XML string - */ - public static String buildXMLAddress(Varnode[] varnodes, int logicalsize) { - - if (varnodes == null) { - return ""; - } - if ((varnodes.length == 1) && (logicalsize == 0)) { - StringBuilder buf = new StringBuilder(); - buildXMLAddress(buf, varnodes[0].address, varnodes[0].size); - return buf.toString(); - } - StringBuilder resBuf = new StringBuilder(); - resBuf.append(""); - return resBuf.toString(); - } - - private static void buildVarnodePiece(StringBuilder buf, Address addr, int size) { - AddressSpace space = addr.getAddressSpace(); - if (space.isOverlaySpace()) { - space = space.getPhysicalSpace(); - addr = space.getAddress(addr.getOffset()); - } - buf.append(space.getName()); - buf.append(":0x"); - long off = addr.getUnsignedOffset(); - buf.append(Long.toHexString(off)); - buf.append(':'); - buf.append(Integer.toString(size)); - } - - public static void appendSpaceOffset(StringBuilder buf, Address addr) { - AddressSpace space = addr.getAddressSpace(); - if (space.isOverlaySpace()) { - if (space.getType() != AddressSpace.TYPE_OTHER) { - space = space.getPhysicalSpace(); - addr = space.getAddress(addr.getOffset()); - } - } - SpecXmlUtils.encodeStringAttribute(buf, "space", space.getName()); - SpecXmlUtils.encodeUnsignedIntegerAttribute(buf, "offset", addr.getUnsignedOffset()); - } - - /** - * Build a varnode from a SAX parse tree node + * Build a Varnode from an XML stream * * @param parser the parser * @param factory pcode factory used to create valid pcode - * * @return new varnode element based on info in the XML. - * - * @throws PcodeXMLException + * @throws PcodeXMLException if XML is improperly formed */ public static Varnode readXML(XmlPullParser parser, PcodeFactory factory) throws PcodeXMLException { @@ -442,7 +346,7 @@ public class Varnode { return vn; } } - Address addr = readXMLAddress(el, factory.getAddressFactory()); + Address addr = AddressXML.readXML(el, factory.getAddressFactory()); if (addr == null) { return null; } @@ -525,7 +429,7 @@ public class Varnode { /** * Convert this varnode to an alternate String representation based on a specified language. - * @param language + * @param language is the specified Language * @return string representation */ public String toString(Language language) { @@ -576,125 +480,4 @@ public class Varnode { result = prime * result + spaceID; return result; } - - /** - * Create an address from a SAX parse tree node. - * - * @param el SAX parse tree element - * @param addrFactory address factory used to create valid addresses - * @return Address created from XML info - */ - public static Address readXMLAddress(XmlElement el, AddressFactory addrFactory) { - String localName = el.getName(); - if (localName.equals("spaceid")) { - AddressSpace spc = addrFactory.getAddressSpace(el.getAttribute("name")); - int spaceid = spc.getSpaceID(); - spc = addrFactory.getConstantSpace(); - return spc.getAddress(spaceid); - } - else if (localName.equals("iop")) { - int ref = SpecXmlUtils.decodeInt(el.getAttribute("value")); - AddressSpace spc = addrFactory.getConstantSpace(); - return spc.getAddress(ref); - } - String space = el.getAttribute("space"); - if (space == null) { - return Address.NO_ADDRESS; - } - long offset = SpecXmlUtils.decodeLong(el.getAttribute("offset")); - AddressSpace spc = addrFactory.getAddressSpace(space); - if (spc == null) { - return null; - } - return spc.getAddress(offset); - } - - public static Address readXMLAddress(String localName, Attributes attr, - AddressFactory addrFactory) { - if (localName.equals("spaceid")) { - AddressSpace spc = addrFactory.getAddressSpace(attr.getValue("name")); - int spaceid = spc.getSpaceID(); - spc = addrFactory.getConstantSpace(); - return spc.getAddress(spaceid); - } - else if (localName.equals("iop")) { - int ref = SpecXmlUtils.decodeInt(attr.getValue("value")); - AddressSpace spc = addrFactory.getConstantSpace(); - return spc.getAddress(ref); - } - String space = attr.getValue("space"); - if (space == null) { - return Address.NO_ADDRESS; - } - long offset = SpecXmlUtils.decodeLong(attr.getValue("offset")); - AddressSpace spc = addrFactory.getAddressSpace(space); - if (spc == null) { - return Address.NO_ADDRESS; - } - return spc.getAddress(offset); - } - - /** - * Parse an XML containing an address. The format options are simple enough that we don't try to invoke - * an actual XML parser but just walk the string - * @param addrstring is the string containing the XML tag - * @param addrfactory is the factory that can produce addresses - * @param refSpace can be null but is otherwise the reference AddressSpace from which the request is sent. - * @return the created Address or Address.NO_ADDRESS in some special cases - * @throws PcodeXMLException - */ - public static Address readXMLAddress(String addrstring, AddressFactory addrfactory, - AddressSpace refSpace) throws PcodeXMLException { - - int tagstart = addrstring.indexOf('<'); - if (tagstart >= 0) { - tagstart += 1; - if (addrstring.startsWith("spaceid", tagstart)) { - tagstart += 8; - int attrstart = addrstring.indexOf("name=\"", tagstart); - if (attrstart >= 0) { - attrstart += 6; - int nameend = addrstring.indexOf('\"', attrstart); - if (nameend >= 0) { - AddressSpace spc = - addrfactory.getAddressSpace(addrstring.substring(attrstart, nameend)); - int spaceid = spc.getSpaceID(); - spc = addrfactory.getConstantSpace(); - return spc.getAddress(spaceid); - } - } - - } - // There are several tag forms where we essentially want to just look for 'space' and 'offset' attributes - // don't explicitly check the tag name - int spacestart = addrstring.indexOf("space=\""); - if (spacestart >= 4) { - spacestart += 7; - int spaceend = addrstring.indexOf('"', spacestart); - if (spaceend >= spacestart) { - String spcname = addrstring.substring(spacestart, spaceend); - int offstart = addrstring.indexOf("offset=\""); - if (offstart >= 4) { - offstart += 8; - int offend = addrstring.indexOf('"', offstart); - if (offend >= offstart) { - String offstr = addrstring.substring(offstart, offend); - AddressSpace spc = addrfactory.getAddressSpace(spcname); - // Unknown spaces may result from "spacebase" registers defined in cspec - if (spc == null) { - return Address.NO_ADDRESS; - } - long offset = SpecXmlUtils.decodeLong(offstr); - Address addr = spc.getAddress(offset); - if (refSpace != null && refSpace.isOverlaySpace()) { - return refSpace.getOverlayAddress(addr); - } - return addr; - } - } - } - } - } - throw new PcodeXMLException("Badly formed address: " + addrstring); - } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/LanguageTranslatorAdapter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/LanguageTranslatorAdapter.java index 22259a13c3..8ed01f71b2 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/LanguageTranslatorAdapter.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/LanguageTranslatorAdapter.java @@ -215,8 +215,8 @@ public abstract class LanguageTranslatorAdapter implements LanguageTranslator { if (!oldSpaces.isEmpty()) { // spaceMap = null; - throw new IncompatibleLanguageException("Failed to map one or more address spaces: " + - oldSpaces); + throw new IncompatibleLanguageException( + "Failed to map one or more address spaces: " + oldSpaces); } } @@ -333,7 +333,8 @@ public abstract class LanguageTranslatorAdapter implements LanguageTranslator { } protected boolean isSameRegisterConstruction(Register oldReg, Register newReg) { - if (oldReg.getLeastSignificatBitInBaseRegister() != newReg.getLeastSignificatBitInBaseRegister() || + if (oldReg.getLeastSignificatBitInBaseRegister() != newReg + .getLeastSignificatBitInBaseRegister() || oldReg.getBitLength() != newReg.getBitLength()) { return false; } @@ -522,12 +523,10 @@ class TemporaryCompilerSpec implements CompilerSpec { throws CompilerSpecNotFoundException { this.translator = translator; this.oldCompilerSpecID = oldCompilerSpecID; - newCompilerSpec = - translator.getNewLanguage().getCompilerSpecByID( - translator.getNewCompilerSpecID(oldCompilerSpecID)); - description = - new BasicCompilerSpecDescription(oldCompilerSpecID, - newCompilerSpec.getCompilerSpecDescription().getCompilerSpecName()); + newCompilerSpec = translator.getNewLanguage() + .getCompilerSpecByID(translator.getNewCompilerSpecID(oldCompilerSpecID)); + description = new BasicCompilerSpecDescription(oldCompilerSpecID, + newCompilerSpec.getCompilerSpecDescription().getCompilerSpecName()); } @Override @@ -549,6 +548,11 @@ class TemporaryCompilerSpec implements CompilerSpec { return null; } + @Override + public PrototypeModel[] getAllModels() { + return new PrototypeModel[0]; + } + @Override public CompilerSpecDescription getCompilerSpecDescription() { return description; @@ -565,13 +569,18 @@ class TemporaryCompilerSpec implements CompilerSpec { } @Override - public Language getLanguage() { - return translator.getOldLanguage(); + public DecompilerLanguage getDecompilerOutputLanguage() { + return DecompilerLanguage.C_LANGUAGE; } @Override - public PrototypeModel[] getNamedCallingConventions() { - return new PrototypeModel[0]; + public PrototypeModel getPrototypeEvaluationModel(EvaluationModelType modelType) { + return newCompilerSpec.getPrototypeEvaluationModel(modelType); + } + + @Override + public Language getLanguage() { + return translator.getOldLanguage(); } @Override @@ -614,16 +623,6 @@ class TemporaryCompilerSpec implements CompilerSpec { return newCompilerSpec.getDataOrganization(); } - @Override - public Object getPrototypeEvaluationModel(Program program) { - throw new UnsupportedOperationException( - "Language for upgrade use only (getPrototypeEvaluationModel)"); - } - - @Override - public void registerProgramOptions(Program program) { - } - @Override public PrototypeModel matchConvention(GenericCallingConvention genericCallingConvention) { throw new UnsupportedOperationException("Language for upgrade use only (matchConvention)"); @@ -680,9 +679,4 @@ class TemporaryCompilerSpec implements CompilerSpec { public PcodeInjectLibrary getPcodeInjectLibrary() { return newCompilerSpec.getPcodeInjectLibrary(); } - - @Override - public DecompilerLanguage getDecompilerOutputLanguage(Program program) { - return newCompilerSpec.getDecompilerOutputLanguage(program); - } } diff --git a/Ghidra/Processors/Dalvik/src/main/java/ghidra/dalvik/dex/inject/InjectPayloadDexParameters.java b/Ghidra/Processors/Dalvik/src/main/java/ghidra/dalvik/dex/inject/InjectPayloadDexParameters.java index a4f9a1da55..16574b2801 100644 --- a/Ghidra/Processors/Dalvik/src/main/java/ghidra/dalvik/dex/inject/InjectPayloadDexParameters.java +++ b/Ghidra/Processors/Dalvik/src/main/java/ghidra/dalvik/dex/inject/InjectPayloadDexParameters.java @@ -18,6 +18,7 @@ package ghidra.dalvik.dex.inject; import java.io.IOException; import ghidra.app.plugin.processors.sleigh.PcodeEmit; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.file.formats.android.dex.analyzer.DexAnalysisState; import ghidra.file.formats.android.dex.format.*; import ghidra.file.formats.android.dex.util.DexUtil; @@ -30,6 +31,8 @@ import ghidra.program.model.listing.Program; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; import ghidra.util.Msg; +import ghidra.util.xml.SpecXmlUtils; +import ghidra.xml.*; /** * The "uponentry" injection for a DEX method. We simulate DEX's register stack by copying values from @@ -39,17 +42,21 @@ import ghidra.util.Msg; public class InjectPayloadDexParameters implements InjectPayload { public final static int INPUT_REGISTER_START = 0x100; public final static int REGISTER_START = 0x1000; + private String name; + private String sourceName; private InjectParameter[] noParams; private boolean analysisStateRecoverable; - public InjectPayloadDexParameters() { + public InjectPayloadDexParameters(String nm, String srcName) { + name = nm; + sourceName = srcName; noParams = new InjectParameter[0]; analysisStateRecoverable = true; } @Override public String getName() { - return "dexparameters"; + return name; } @Override @@ -57,10 +64,9 @@ public class InjectPayloadDexParameters implements InjectPayload { return CALLMECHANISM_TYPE; } - @Override public String getSource() { - return "dexparameters"; + return sourceName; } @Override @@ -78,6 +84,11 @@ public class InjectPayloadDexParameters implements InjectPayload { return noParams; } + @Override + public boolean isErrorPlaceholder() { + return false; + } + @Override public void inject(InjectContext context, PcodeEmit emit) { // not used @@ -101,23 +112,27 @@ public class InjectPayloadDexParameters implements InjectPayload { PcodeOp[] resOps; Function func = program.getFunctionManager().getFunctionContaining(con.baseAddr); EncodedMethod encodedMethod = null; - if (func != null) + if (func != null) { encodedMethod = analysisState.getEncodedMethod(func.getEntryPoint()); - if (encodedMethod == null) + } + if (encodedMethod == null) { return new PcodeOp[0]; + } int paramCount = 0; - if (!encodedMethod.isStatic()) + if (!encodedMethod.isStatic()) { paramCount += 1; // A this pointer at least + } CodeItem codeItem = encodedMethod.getCodeItem(); int registerIndex = codeItem.getRegistersSize() - codeItem.getIncomingSize(); - MethodIDItem methodIDItem = header.getMethods().get( encodedMethod.getMethodIndex() ); + MethodIDItem methodIDItem = header.getMethods().get(encodedMethod.getMethodIndex()); int prototypeIndex = methodIDItem.getProtoIndex() & 0xffff; PrototypesIDItem prototype = header.getPrototypes().get(prototypeIndex); TypeList parameters = prototype.getParameters(); - if (parameters != null) + if (parameters != null) { paramCount += parameters.getItems().size(); + } AddressSpace registerSpace = program.getAddressFactory().getAddressSpace("register"); - resOps = new PcodeOp[ paramCount ]; + resOps = new PcodeOp[paramCount]; long fromOffset = INPUT_REGISTER_START; // Base of designated input registers long toOffset = REGISTER_START + 4 * registerIndex; // Base of registers in method's frame int i = 0; @@ -126,16 +141,16 @@ public class InjectPayloadDexParameters implements InjectPayload { Address toAddr = registerSpace.getAddress(toOffset); fromOffset += 4; toOffset += 4; - PcodeOp op = new PcodeOp(con.baseAddr,i,PcodeOp.COPY); - op.setInput(new Varnode(fromAddr,4), 0); - op.setOutput(new Varnode(toAddr,4)); + PcodeOp op = new PcodeOp(con.baseAddr, i, PcodeOp.COPY); + op.setInput(new Varnode(fromAddr, 4), 0); + op.setOutput(new Varnode(toAddr, 4)); resOps[i] = op; i += 1; } if (parameters != null) { for (TypeItem parameterTypeItem : parameters.getItems()) { - String parameterTypeString = DexUtil.convertTypeIndexToString( - header, parameterTypeItem.getType()); + String parameterTypeString = + DexUtil.convertTypeIndexToString(header, parameterTypeItem.getType()); int size; char firstChar = parameterTypeString.charAt(0); Address fromAddr = registerSpace.getAddress(fromOffset); @@ -156,4 +171,42 @@ public class InjectPayloadDexParameters implements InjectPayload { public boolean isFallThru() { return true; } + + @Override + public boolean isIncidentalCopy() { + return false; + } + + @Override + public void saveXml(StringBuilder buffer) { + // Provide a minimal tag so decompiler can call-back + buffer.append("\n"); + } + + @Override + public void restoreXml(XmlPullParser parser, SleighLanguage language) throws XmlParseException { + XmlElement el = parser.start(); + String injectString = el.getAttribute("inject"); + if (injectString == null || !injectString.equals("uponentry")) { + throw new XmlParseException("Expecting inject=\"uponentry\" attribute"); + } + boolean isDynamic = SpecXmlUtils.decodeBoolean(el.getAttribute("dynamic")); + if (!isDynamic) { + throw new XmlParseException("Expecting dynamic attribute"); + } + parser.end(el); + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof InjectPayloadDexParameters); // All instances are equal + } + + @Override + public int hashCode() { + return 123474219; // All instances are equal + } } diff --git a/Ghidra/Processors/Dalvik/src/main/java/ghidra/dalvik/dex/inject/InjectPayloadDexRange.java b/Ghidra/Processors/Dalvik/src/main/java/ghidra/dalvik/dex/inject/InjectPayloadDexRange.java index 03fbdba0c0..49587fda7e 100644 --- a/Ghidra/Processors/Dalvik/src/main/java/ghidra/dalvik/dex/inject/InjectPayloadDexRange.java +++ b/Ghidra/Processors/Dalvik/src/main/java/ghidra/dalvik/dex/inject/InjectPayloadDexRange.java @@ -15,11 +15,10 @@ */ package ghidra.dalvik.dex.inject; -import ghidra.app.plugin.processors.sleigh.PcodeEmit; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.InjectContext; -import ghidra.program.model.lang.InjectPayload; +import ghidra.program.model.lang.InjectPayloadCallother; import ghidra.program.model.listing.Program; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; @@ -31,44 +30,10 @@ import ghidra.program.model.pcode.Varnode; * The registers are moved to the specially designated input registers iv0, iv1, iv2, ... * */ -public class InjectPayloadDexRange implements InjectPayload { +public class InjectPayloadDexRange extends InjectPayloadCallother { public InjectPayloadDexRange() { - } - - @Override - public String getName() { - return "dexrange"; - } - - @Override - public int getType() { - return CALLOTHERFIXUP_TYPE; - } - - @Override - public String getSource() { - return "dexrange"; - } - - @Override - public int getParamShift() { - return 0; - } - - @Override - public InjectParameter[] getInput() { - return null; // Not used - } - - @Override - public InjectParameter[] getOutput() { - return null; // Not used - } - - @Override - public void inject(InjectContext context, PcodeEmit emit) { - // not used + super("dexrange"); } @Override @@ -96,10 +61,4 @@ public class InjectPayloadDexRange implements InjectPayload { } return resOps; } - - @Override - public boolean isFallThru() { - return true; - } - } diff --git a/Ghidra/Processors/Dalvik/src/main/java/ghidra/dalvik/dex/inject/PcodeInjectLibraryDex.java b/Ghidra/Processors/Dalvik/src/main/java/ghidra/dalvik/dex/inject/PcodeInjectLibraryDex.java index 697891fee6..18b557186e 100644 --- a/Ghidra/Processors/Dalvik/src/main/java/ghidra/dalvik/dex/inject/PcodeInjectLibraryDex.java +++ b/Ghidra/Processors/Dalvik/src/main/java/ghidra/dalvik/dex/inject/PcodeInjectLibraryDex.java @@ -23,35 +23,32 @@ import ghidra.program.model.listing.Program; public class PcodeInjectLibraryDex extends PcodeInjectLibrary { - private InjectPayloadDexParameters paramPayload = null; - private InjectPayloadDexRange rangePayload = null; - public PcodeInjectLibraryDex(SleighLanguage l) { super(l); } - @Override - public InjectPayload getPayload(int type, String name, Program program, - String context) { - if (type == InjectPayload.CALLMECHANISM_TYPE) { - if (paramPayload == null) { - paramPayload = new InjectPayloadDexParameters(); - } - return paramPayload; - } - else if (type == InjectPayload.CALLOTHERFIXUP_TYPE && name.equals("moveRangeToIV")) { - if (rangePayload == null) { - rangePayload = new InjectPayloadDexRange(); - } - return rangePayload; - } + public PcodeInjectLibraryDex(PcodeInjectLibraryDex op2) { + super(op2); + } - return super.getPayload(type, name, program, context); + @Override + public PcodeInjectLibrary clone() { + return new PcodeInjectLibraryDex(this); + } + + @Override + public InjectPayload allocateInject(String sourceName, String name, int tp) { + if (tp == InjectPayload.CALLMECHANISM_TYPE) { + return new InjectPayloadDexParameters(name, sourceName); + } + else if (tp == InjectPayload.CALLOTHERFIXUP_TYPE && name.equals("moveRangeToIV")) { + return new InjectPayloadDexRange(); + } + return super.allocateInject(sourceName, name, tp); } @Override public ConstantPool getConstantPool(Program program) throws IOException { return new ConstantPoolDex(program); } - } diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectGetField.java b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectGetField.java index ef9c0d2ad2..a40649e98f 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectGetField.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectGetField.java @@ -27,11 +27,6 @@ public class InjectGetField extends InjectPayloadJava { super(sourceName, language, uniqBase); } - @Override - public String getName() { - return PcodeInjectLibraryJava.GETFIELD; - } - @Override public PcodeOp[] getPcode(Program program, InjectContext con) { AbstractConstantPoolInfoJava[] constantPool = getConstantPool(program); diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectGetStatic.java b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectGetStatic.java index 3047cd4471..74696944f3 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectGetStatic.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectGetStatic.java @@ -27,11 +27,6 @@ public class InjectGetStatic extends InjectPayloadJava { super(sourceName, language, uniqBase); } - @Override - public String getName() { - return PcodeInjectLibraryJava.GETSTATIC; - } - @Override public PcodeOp[] getPcode(Program program, InjectContext con) { AbstractConstantPoolInfoJava[] constantPool = getConstantPool(program); diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeDynamic.java b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeDynamic.java index 7120229539..b2526704fa 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeDynamic.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeDynamic.java @@ -27,11 +27,6 @@ public class InjectInvokeDynamic extends InjectPayloadJava { super(sourceName, language, uniqBase); } - @Override - public String getName() { - return PcodeInjectLibraryJava.INVOKE_DYNAMIC; - } - @Override public PcodeOp[] getPcode(Program program, InjectContext con) { AbstractConstantPoolInfoJava[] constantPool = getConstantPool(program); diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeInterface.java b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeInterface.java index 4f37fa80ca..6088109ee8 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeInterface.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeInterface.java @@ -27,11 +27,6 @@ public class InjectInvokeInterface extends InjectPayloadJava { super(sourceName, language, uniqBase); } - @Override - public String getName() { - return PcodeInjectLibraryJava.INVOKE_INTERFACE; - } - @Override public PcodeOp[] getPcode(Program program, InjectContext con) { AbstractConstantPoolInfoJava[] constantPool = getConstantPool(program); diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeSpecial.java b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeSpecial.java index 6ab5b4a49d..0ed5d1f322 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeSpecial.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeSpecial.java @@ -27,11 +27,6 @@ public class InjectInvokeSpecial extends InjectPayloadJava { super(sourceName, language, uniqBase); } - @Override - public String getName() { - return PcodeInjectLibraryJava.INVOKE_SPECIAL; - } - @Override public PcodeOp[] getPcode(Program program, InjectContext con) { AbstractConstantPoolInfoJava[] constantPool = getConstantPool(program); diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeStatic.java b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeStatic.java index 1e94e94ade..f2fa1acd91 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeStatic.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeStatic.java @@ -27,11 +27,6 @@ public class InjectInvokeStatic extends InjectPayloadJava { super(sourceName, language, uniqBase); } - @Override - public String getName() { - return PcodeInjectLibraryJava.INVOKE_STATIC; - } - @Override public PcodeOp[] getPcode(Program program, InjectContext con) { AbstractConstantPoolInfoJava[] constantPool = getConstantPool(program); diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeVirtual.java b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeVirtual.java index c4d9058312..82bc350dbc 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeVirtual.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectInvokeVirtual.java @@ -27,11 +27,6 @@ public class InjectInvokeVirtual extends InjectPayloadJava { super(sourceName, language, uniqBase); } - @Override - public String getName() { - return PcodeInjectLibraryJava.INVOKE_VIRTUAL; - } - @Override public PcodeOp[] getPcode(Program program, InjectContext con) { AbstractConstantPoolInfoJava[] constantPool = getConstantPool(program); diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectLdc.java b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectLdc.java index 1bae3ba83a..3e231c9087 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectLdc.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectLdc.java @@ -27,11 +27,6 @@ public class InjectLdc extends InjectPayloadJava { super(sourceName, language, uniqBase); } - @Override - public String getName() { - return PcodeInjectLibraryJava.LDC; - } - @Override public PcodeOp[] getPcode(Program program, InjectContext con) { AbstractConstantPoolInfoJava[] constantPool = getConstantPool(program); diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectMultiANewArray.java b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectMultiANewArray.java index 506ffe41e3..0e6f9f6712 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectMultiANewArray.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectMultiANewArray.java @@ -27,11 +27,6 @@ public class InjectMultiANewArray extends InjectPayloadJava { super(sourceName, language, uniqBase); } - @Override - public String getName() { - return PcodeInjectLibraryJava.MULTIANEWARRAY; - } - @Override public PcodeOp[] getPcode(Program program, InjectContext con) { AbstractConstantPoolInfoJava[] constantPool = getConstantPool(program); diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectPayloadJava.java b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectPayloadJava.java index 5072d501c6..6b380344a1 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectPayloadJava.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectPayloadJava.java @@ -17,29 +17,26 @@ package ghidra.app.util.pcodeInject; import java.io.IOException; -import ghidra.app.plugin.processors.sleigh.PcodeEmit; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.javaclass.format.ClassFileAnalysisState; import ghidra.javaclass.format.ClassFileJava; import ghidra.javaclass.format.constantpool.AbstractConstantPoolInfoJava; -import ghidra.program.model.lang.InjectContext; -import ghidra.program.model.lang.InjectPayload; +import ghidra.program.model.lang.InjectPayloadCallother; import ghidra.program.model.listing.Program; /** - * Subclasses of this class are used to generate pcode to inject for modeling - * java bytecode in pcode. + * Subclasses of this class are used to generate p-code to inject for modeling + * java bytecode in p-code. Each is attached to CALLOTHER p-code op. * */ -public abstract class InjectPayloadJava implements InjectPayload { +public abstract class InjectPayloadJava extends InjectPayloadCallother { protected SleighLanguage language; protected long uniqueBase; - private String sourceName; public InjectPayloadJava(String sourceName, SleighLanguage language, long uniqBase) { + super(sourceName); this.language = language; - this.sourceName = sourceName; this.uniqueBase = uniqBase; } @@ -54,39 +51,4 @@ public abstract class InjectPayloadJava implements InjectPayload { ClassFileJava classFile = analysisState.getClassFile(); return classFile.getConstantPool(); } - - @Override - public int getType() { - return InjectPayload.CALLOTHERFIXUP_TYPE; - } - - @Override - public String getSource() { - return sourceName; - } - - @Override - public int getParamShift() { - return 0; - } - - @Override - public void inject(InjectContext context, PcodeEmit emit) { - // Not used - } - - @Override - public boolean isFallThru() { - return true; - } - - @Override - public InjectParameter[] getInput() { - return null; - } - - @Override - public InjectParameter[] getOutput() { - return null; - } } diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectPayloadJavaParameters.java b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectPayloadJavaParameters.java index a34dfe95c6..4d48a5fa44 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectPayloadJavaParameters.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectPayloadJavaParameters.java @@ -31,9 +31,13 @@ import ghidra.program.model.listing.Program; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; import ghidra.util.Msg; +import ghidra.util.xml.SpecXmlUtils; +import ghidra.xml.*; public class InjectPayloadJavaParameters implements InjectPayload { + private String name; + private String sourceName; private InjectParameter[] noParams; private boolean analysisStateRecoverable; private AddressSpace constantSpace; @@ -46,7 +50,10 @@ public class InjectPayloadJavaParameters implements InjectPayload { private Varnode eight; private Varnode LVA; - public InjectPayloadJavaParameters(SleighLanguage language, long uniqBase) { + public InjectPayloadJavaParameters(String nm, String srcName, SleighLanguage language, + long uniqBase) { + name = nm; + sourceName = srcName; noParams = new InjectParameter[0]; analysisStateRecoverable = true; constantSpace = language.getAddressFactory().getConstantSpace(); @@ -70,7 +77,7 @@ public class InjectPayloadJavaParameters implements InjectPayload { @Override public String getName() { - return "javaparameters"; + return name; } @Override @@ -80,7 +87,7 @@ public class InjectPayloadJavaParameters implements InjectPayload { @Override public String getSource() { - return "javaparameters"; + return sourceName; } @Override @@ -98,6 +105,11 @@ public class InjectPayloadJavaParameters implements InjectPayload { return noParams; } + @Override + public boolean isErrorPlaceholder() { + return false; + } + @Override public void inject(InjectContext context, PcodeEmit emit) { //not used @@ -119,29 +131,30 @@ public class InjectPayloadJavaParameters implements InjectPayload { } ClassFileJava classFile = analysisState.getClassFile(); MethodInfoJava methodInfo = analysisState.getMethodInfo(con.baseAddr); - if (methodInfo == null){ + if (methodInfo == null) { return new PcodeOp[0]; } int descriptorIndex = methodInfo.getDescriptorIndex(); - ConstantPoolUtf8Info descriptorInfo = (ConstantPoolUtf8Info)(classFile.getConstantPool()[descriptorIndex]); + ConstantPoolUtf8Info descriptorInfo = + (ConstantPoolUtf8Info) (classFile.getConstantPool()[descriptorIndex]); String descriptor = descriptorInfo.getString(); List paramCategories = new ArrayList<>(); - if (!methodInfo.isStatic()){ + if (!methodInfo.isStatic()) { paramCategories.add(JavaComputationalCategory.CAT_1);//for the this pointer } paramCategories.addAll(DescriptorDecoder.getParameterCategories(descriptor)); int numOps = paramCategories.size(); - if (paramCategories.size() == 0){ + if (paramCategories.size() == 0) { //no this pointer, no parameters: nothing to do return new PcodeOp[0]; } - PcodeOp[] resOps = new PcodeOp[1 + 3*numOps]; + PcodeOp[] resOps = new PcodeOp[1 + 3 * numOps]; int seqNum = 0; //initialize LVA to contain 0 - PcodeOp copy = new PcodeOp(con.baseAddr,seqNum, PcodeOp.COPY); + PcodeOp copy = new PcodeOp(con.baseAddr, seqNum, PcodeOp.COPY); copy.setInput(zero, 0); copy.setOutput(LVA); resOps[seqNum++] = copy; @@ -149,8 +162,8 @@ public class InjectPayloadJavaParameters implements InjectPayload { Varnode tempLocation = null; Varnode increment = null; - for (JavaComputationalCategory cat : paramCategories){ - if (cat.equals(JavaComputationalCategory.CAT_1)){ + for (JavaComputationalCategory cat : paramCategories) { + if (cat.equals(JavaComputationalCategory.CAT_1)) { tempLocation = temp4; increment = four; } @@ -167,16 +180,16 @@ public class InjectPayloadJavaParameters implements InjectPayload { //copy temporary to LVA PcodeOp store = new PcodeOp(con.baseAddr, seqNum, PcodeOp.STORE); store.setInput(new Varnode(constantSpace.getAddress(lvaID), 4), 0); - store.setInput(LVA,1); + store.setInput(LVA, 1); store.setInput(tempLocation, 2); - resOps[seqNum++] = store; + resOps[seqNum++] = store; //increment LVA reg PcodeOp add = new PcodeOp(con.baseAddr, seqNum, PcodeOp.INT_ADD); add.setInput(LVA, 0); add.setInput(increment, 1); add.setOutput(LVA); - resOps[seqNum++] = add; - } + resOps[seqNum++] = add; + } return resOps; } @@ -184,4 +197,42 @@ public class InjectPayloadJavaParameters implements InjectPayload { public boolean isFallThru() { return true; } + + @Override + public boolean isIncidentalCopy() { + return false; + } + + @Override + public void saveXml(StringBuilder buffer) { + // Provide a minimal tag so decompiler can call-back + buffer.append("\n"); + } + + @Override + public void restoreXml(XmlPullParser parser, SleighLanguage language) throws XmlParseException { + XmlElement el = parser.start(); + String injectString = el.getAttribute("inject"); + if (injectString == null || !injectString.equals("uponentry")) { + throw new XmlParseException("Expecting inject=\"uponentry\" attribute"); + } + boolean isDynamic = SpecXmlUtils.decodeBoolean(el.getAttribute("dynamic")); + if (!isDynamic) { + throw new XmlParseException("Expecting dynamic attribute"); + } + parser.end(el); + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof InjectPayloadJavaParameters); // All instances are equal + } + + @Override + public int hashCode() { + return 123474217; // All instances are equal + } } diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectPutField.java b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectPutField.java index 7c44be1e7d..4189f268f9 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectPutField.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectPutField.java @@ -27,11 +27,6 @@ public class InjectPutField extends InjectPayloadJava { super(sourceName, language, uniqBase); } - @Override - public String getName() { - return PcodeInjectLibraryJava.PUTFIELD; - } - @Override public PcodeOp[] getPcode(Program program, InjectContext con) { AbstractConstantPoolInfoJava[] constantPool = getConstantPool(program); diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectPutStatic.java b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectPutStatic.java index 45616a4158..5e0c16d2d3 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectPutStatic.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/InjectPutStatic.java @@ -27,11 +27,6 @@ public class InjectPutStatic extends InjectPayloadJava { super(sourceName, language, uniqBase); } - @Override - public String getName() { - return PcodeInjectLibraryJava.PUTSTATIC; - } - @Override public PcodeOp[] getPcode(Program program, InjectContext con) { AbstractConstantPoolInfoJava[] constantPool = getConstantPool(program); diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/PcodeInjectLibraryJava.java b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/PcodeInjectLibraryJava.java index 7d50e96126..427201d8ce 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/PcodeInjectLibraryJava.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/app/util/pcodeInject/PcodeInjectLibraryJava.java @@ -116,56 +116,60 @@ public class PcodeInjectLibraryJava extends PcodeInjectLibrary { public static final int REFERENCE_SIZE = 4; private Map implementedOps; - private InjectPayloadJavaParameters paramPayload; public PcodeInjectLibraryJava(SleighLanguage l) { super(l); - long offset = l.getUniqueBase(); implementedOps = new HashMap<>(); - implementedOps.put(GETFIELD, new InjectGetField(SOURCENAME, l, offset)); - offset += 0x100; - implementedOps.put(GETSTATIC, new InjectGetStatic(SOURCENAME, l, offset)); - offset += 0x100; - implementedOps.put(INVOKE_DYNAMIC, new InjectInvokeDynamic(SOURCENAME, l, offset)); - offset += 0x100; - implementedOps.put(INVOKE_INTERFACE, new InjectInvokeInterface(SOURCENAME, l, offset)); - offset += 0x100; - implementedOps.put(INVOKE_SPECIAL, new InjectInvokeSpecial(SOURCENAME, l, offset)); - offset += 0x100; - implementedOps.put(INVOKE_STATIC, new InjectInvokeStatic(SOURCENAME, l, offset)); - offset += 0x100; - implementedOps.put(INVOKE_VIRTUAL, new InjectInvokeVirtual(SOURCENAME, l, offset)); - offset += 0x100; - InjectPayloadJava ldcInject = new InjectLdc(SOURCENAME, l, offset); - offset += 0x100; - implementedOps.put(LDC, ldcInject); - implementedOps.put(LDC2_W, ldcInject); - implementedOps.put(LDC_W, ldcInject); - implementedOps.put(MULTIANEWARRAY, new InjectMultiANewArray(SOURCENAME, l, offset)); - offset += 0x100; - implementedOps.put(PUTFIELD, new InjectPutField(SOURCENAME, l, offset)); - offset += 0x100; - implementedOps.put(PUTSTATIC, new InjectPutStatic(SOURCENAME, l, offset)); - offset += 0x100; + implementedOps.put(GETFIELD, new InjectGetField(SOURCENAME, l, uniqueBase)); + uniqueBase += 0x100; + implementedOps.put(GETSTATIC, new InjectGetStatic(SOURCENAME, l, uniqueBase)); + uniqueBase += 0x100; + implementedOps.put(INVOKE_DYNAMIC, new InjectInvokeDynamic(SOURCENAME, l, uniqueBase)); + uniqueBase += 0x100; + implementedOps.put(INVOKE_INTERFACE, new InjectInvokeInterface(SOURCENAME, l, uniqueBase)); + uniqueBase += 0x100; + implementedOps.put(INVOKE_SPECIAL, new InjectInvokeSpecial(SOURCENAME, l, uniqueBase)); + uniqueBase += 0x100; + implementedOps.put(INVOKE_STATIC, new InjectInvokeStatic(SOURCENAME, l, uniqueBase)); + uniqueBase += 0x100; + implementedOps.put(INVOKE_VIRTUAL, new InjectInvokeVirtual(SOURCENAME, l, uniqueBase)); + uniqueBase += 0x100; + implementedOps.put(LDC, new InjectLdc(SOURCENAME, l, uniqueBase)); + uniqueBase += 0x100; + implementedOps.put(LDC2_W, new InjectLdc(SOURCENAME, l, uniqueBase)); + uniqueBase += 0x100; + implementedOps.put(LDC_W, new InjectLdc(SOURCENAME, l, uniqueBase)); + uniqueBase += 0x100; + implementedOps.put(MULTIANEWARRAY, new InjectMultiANewArray(SOURCENAME, l, uniqueBase)); + uniqueBase += 0x100; + implementedOps.put(PUTFIELD, new InjectPutField(SOURCENAME, l, uniqueBase)); + uniqueBase += 0x100; + implementedOps.put(PUTSTATIC, new InjectPutStatic(SOURCENAME, l, uniqueBase)); + uniqueBase += 0x100; + } - paramPayload = new InjectPayloadJavaParameters(l, offset); + public PcodeInjectLibraryJava(PcodeInjectLibraryJava op2) { + super(op2); + implementedOps = op2.implementedOps; // Immutable } @Override - /** - * This method is called by DecompileCallback.getPcodeInject. - */ - public InjectPayload getPayload(int type, String name, Program program, String context) { - if (type == InjectPayload.CALLMECHANISM_TYPE) { - return paramPayload; - } + public PcodeInjectLibrary clone() { + return new PcodeInjectLibraryJava(this); + } - InjectPayloadJava payload = implementedOps.get(name); - if (payload == null) { - return super.getPayload(type, name, program, context); + @Override + public InjectPayload allocateInject(String sourceName, String name, int tp) { + if (tp == InjectPayload.CALLMECHANISM_TYPE) { + return new InjectPayloadJavaParameters(name, sourceName, language, tp); } - - return payload; + if (tp == InjectPayload.CALLOTHERFIXUP_TYPE) { + InjectPayloadJava payload = implementedOps.get(name); + if (payload != null) { + return payload; + } + } + return super.allocateInject(sourceName, name, tp); } @Override diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/javaclass/analyzers/JavaAnalyzer.java b/Ghidra/Processors/JVM/src/main/java/ghidra/javaclass/analyzers/JavaAnalyzer.java index 52e6a1c05e..c439d86f4e 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/javaclass/analyzers/JavaAnalyzer.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/javaclass/analyzers/JavaAnalyzer.java @@ -36,10 +36,10 @@ import ghidra.javaclass.flags.MethodsInfoAccessFlags; import ghidra.javaclass.format.*; import ghidra.javaclass.format.attributes.*; import ghidra.javaclass.format.constantpool.*; +import ghidra.program.database.ProgramCompilerSpec; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.data.*; -import ghidra.program.model.lang.BasicCompilerSpec; import ghidra.program.model.listing.*; import ghidra.program.model.listing.Function.FunctionUpdateType; import ghidra.program.model.mem.MemoryAccessException; @@ -138,7 +138,7 @@ public class JavaAnalyzer extends AbstractJavaAnalyzer implements AnalysisWorker disassembleMethods(program, classFile, monitor); processInstructions(program, constantPoolData, classFile, monitor); recordJavaVersionInfo(program, classFile); - BasicCompilerSpec.enableJavaLanguageDecompilation(program); + ProgramCompilerSpec.enableJavaLanguageDecompilation(program); return true; }