diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/GraphDisplayBrokerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/GraphDisplayBrokerPlugin.java index c6ed1c08c2..dd3d6e7a42 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/GraphDisplayBrokerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/GraphDisplayBrokerPlugin.java @@ -147,10 +147,10 @@ public class GraphDisplayBrokerPlugin extends Plugin } @Override - public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, - TaskMonitor monitor) throws GraphException { + public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, Map properties, + TaskMonitor monitor) throws GraphException { if (defaultGraphDisplayProvider != null) { - return defaultGraphDisplayProvider.getGraphDisplay(reuseGraph, monitor); + return defaultGraphDisplayProvider.getGraphDisplay(reuseGraph, properties, monitor); } return null; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraSourceBundle.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraSourceBundle.java index 241135958a..68f8c51ac7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraSourceBundle.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraSourceBundle.java @@ -47,6 +47,7 @@ import generic.io.NullPrintWriter; import generic.jar.ResourceFile; import ghidra.app.script.*; import ghidra.util.Msg; +import utilities.util.FileUtilities; /** * {@link GhidraSourceBundle} represents a Java source directory that is compiled on build to an OSGi bundle. @@ -173,20 +174,18 @@ public class GhidraSourceBundle extends GhidraBundle { * * @param sourceFile a source file from this bundle * @return the class name + * @throws ClassNotFoundException if {@code sourceFile} isn't contained in this bundle */ - public String classNameForScript(ResourceFile sourceFile) { - try { - String path = sourceFile.getCanonicalPath(); - - // get the relative path and chop ".java" from the end - path = path.substring(1 + getSourceDirectory().getCanonicalPath().length(), - path.length() - 5); - return path.replace(File.separatorChar, '.'); - } - catch (IOException e) { - Msg.error(this, "getting class name for script", e); - return null; + public String classNameForScript(ResourceFile sourceFile) throws ClassNotFoundException { + String relativePath = FileUtilities.relativizePath(getSourceDirectory(), sourceFile); + if (relativePath == null) { + throw new ClassNotFoundException( + String.format("Failed to find script file '%s' in source directory '%s'", + sourceFile, getSourceDirectory())); } + // chop ".java" from the end + relativePath = relativePath.substring(0, relativePath.length() - 5); + return relativePath.replace(File.separatorChar, '.'); } void clearBuildErrors(ResourceFile sourceFile) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java index e386c800d9..6099eceeef 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java @@ -32,6 +32,7 @@ import ghidra.app.util.headless.HeadlessAnalyzer; import ghidra.framework.Application; import ghidra.util.Msg; import ghidra.util.classfinder.ClassSearcher; +import utilities.util.FileUtilities; /** * A utility class for managing script directories and ScriptInfo objects. @@ -129,19 +130,13 @@ public class GhidraScriptUtil { * @return the source directory if found, or null if not */ public static ResourceFile findSourceDirectoryContaining(ResourceFile sourceFile) { - String sourcePath; - try { - sourcePath = sourceFile.getCanonicalPath(); - for (ResourceFile sourceDir : getScriptSourceDirectories()) { - if (sourcePath.startsWith(sourceDir.getCanonicalPath() + File.separatorChar)) { - return sourceDir; - } + for (ResourceFile sourceDir : getScriptSourceDirectories()) { + if (FileUtilities.relativizePath(sourceDir, sourceFile) != null) { + return sourceDir; } } - catch (IOException e) { - Msg.error(GhidraScriptUtil.class, - "Failed to find script in any script directory: " + sourceFile.toString(), e); - } + Msg.error(GhidraScriptUtil.class, + "Failed to find script in any script directory: " + sourceFile.toString()); return null; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/GraphDisplayBroker.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/GraphDisplayBroker.java index 47e41e8cad..a3156f3047 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/GraphDisplayBroker.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/GraphDisplayBroker.java @@ -15,7 +15,7 @@ */ package ghidra.app.services; -import java.util.List; +import java.util.*; import ghidra.app.plugin.core.graph.GraphDisplayBrokerListener; import ghidra.app.plugin.core.graph.GraphDisplayBrokerPlugin; @@ -60,7 +60,25 @@ public interface GraphDisplayBroker { * @return a {@link GraphDisplay} object to sends graphs to be displayed or exported. * @throws GraphException thrown if an error occurs trying to get a graph display */ - public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor) + public default GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor) + throws GraphException { + return getDefaultGraphDisplay(reuseGraph, Collections.emptyMap(), monitor); + } + + /** + * A convenience method for getting a {@link GraphDisplay} from the currently active provider + * + *

This method allows users to override default graph properties for the graph provider + * being created. See the graph provider implementation for a list of supported properties + * + * @param reuseGraph if true, the provider will attempt to re-use a current graph display + * @param properties a {@code Map} of property key/values that can be used to customize the display + * @param monitor the {@link TaskMonitor} that can be used to cancel the operation + * @return a {@link GraphDisplay} object to sends graphs to be displayed or exported. + * @throws GraphException thrown if an error occurs trying to get a graph display + */ + public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, Map properties, + TaskMonitor monitor) throws GraphException; /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/coff/archive/CoffArchiveConstants.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/coff/archive/CoffArchiveConstants.java index b60f817069..ce91eb0cc1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/coff/archive/CoffArchiveConstants.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/coff/archive/CoffArchiveConstants.java @@ -17,10 +17,7 @@ package ghidra.app.util.bin.format.coff.archive; public final class CoffArchiveConstants { - public final static String MAGIC = "!\n"; - public final static int MAGIC_LEN = MAGIC.length(); - public static final int MAGIC_LEN_CONST_EXPR = 8; + public static final String MAGIC = "!\n"; + public static final int MAGIC_LEN = MAGIC.length(); public static final byte[] MAGIC_BYTES = MAGIC.getBytes(); - - public final static String END_OF_HEADER_MAGIC = "'\n"; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/coff/archive/CoffArchiveHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/coff/archive/CoffArchiveHeader.java index 807a692bdf..1e7ccb327a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/coff/archive/CoffArchiveHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/coff/archive/CoffArchiveHeader.java @@ -47,8 +47,8 @@ public final class CoffArchiveHeader implements StructConverter { * @throws IOException */ public static boolean isMatch(ByteProvider provider) throws IOException { - return (provider.length() > 2) && CoffArchiveConstants.MAGIC.equals( - new String(provider.readBytes(0, CoffArchiveConstants.MAGIC_LEN))); + return (provider.length() > CoffArchiveConstants.MAGIC_LEN) && CoffArchiveConstants.MAGIC + .equals(new String(provider.readBytes(0, CoffArchiveConstants.MAGIC_LEN))); } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/ubi/FatHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/ubi/FatHeader.java index e2edf0bc87..14b4de0bb7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/ubi/FatHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/ubi/FatHeader.java @@ -23,8 +23,12 @@ import generic.continues.GenericFactory; import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.ByteProviderWrapper; import ghidra.app.util.bin.format.FactoryBundledWithBinaryReader; +import ghidra.app.util.bin.format.coff.CoffException; +import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader; +import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader; import ghidra.app.util.bin.format.macho.MachException; import ghidra.app.util.bin.format.macho.MachHeader; +import ghidra.util.task.TaskMonitor; /** * Represents a fat_header structure. @@ -71,9 +75,32 @@ public class FatHeader { } for (FatArch fatarch : architectures) { - ByteProviderWrapper wrapper = new ByteProviderWrapper(provider, fatarch.getOffset(), fatarch.getSize()); - MachHeader machHeader = MachHeader.createMachHeader(factory, wrapper); - machHeaders.add(machHeader); + ByteProviderWrapper wrapper = + new ByteProviderWrapper(provider, fatarch.getOffset(), fatarch.getSize()); + + // It could be a Mach-O or a COFF archive + CoffArchiveHeader caf = null; + try { + caf = CoffArchiveHeader.read(wrapper, TaskMonitor.DUMMY); + } + catch (CoffException e) { + throw new UbiException(e); + } + if (caf != null) { + for (CoffArchiveMemberHeader camh : caf.getArchiveMemberHeaders()) { + wrapper = new ByteProviderWrapper(provider, + fatarch.getOffset() + camh.getPayloadOffset(), camh.getSize()); + try { + machHeaders.add(MachHeader.createMachHeader(factory, wrapper)); + } + catch (MachException e) { + // Could be __.SYMDEF archive member instead of a Mach-O + } + } + } + else { + machHeaders.add(MachHeader.createMachHeader(factory, wrapper)); + } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java index e77ad1cf8d..8bd52e562d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java @@ -16,8 +16,7 @@ package ghidra.app.util.opinion; import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import ghidra.app.util.MemoryBlockUtils; import ghidra.app.util.bin.ByteProvider; @@ -215,26 +214,32 @@ public class MachoProgramBuilder { } // Create memory blocks for segments. - for (SegmentCommand segment : header.getAllSegments()) { + ListIterator it = header.getAllSegments().listIterator(); + while (it.hasNext()) { + int i = it.nextIndex(); + final SegmentCommand segment = it.next(); + if (monitor.isCancelled()) { break; } if (segment.getFileSize() > 0 && (allowZeroAddr || segment.getVMaddress() != 0)) { - if (createMemoryBlock(segment.getSegmentName(), - space.getAddress(segment.getVMaddress()), segment.getFileOffset(), - segment.getFileSize(), segment.getSegmentName(), source, segment.isRead(), - segment.isWrite(), segment.isExecute(), false) == null) { + String segmentName = segment.getSegmentName(); + if (segmentName.isBlank()) { + segmentName = "SEGMENT." + i; + } + if (createMemoryBlock(segmentName, space.getAddress(segment.getVMaddress()), + segment.getFileOffset(), segment.getFileSize(), segmentName, source, + segment.isRead(), segment.isWrite(), segment.isExecute(), false) == null) { log.appendMsg(String.format("Failed to create block: %s 0x%x 0x%x", segment.getSegmentName(), segment.getVMaddress(), segment.getVMsize())); } if (segment.getVMsize() > segment.getFileSize()) { // Pad the remaining address range with uninitialized data - if (createMemoryBlock(segment.getSegmentName(), + if (createMemoryBlock(segmentName, space.getAddress(segment.getVMaddress()).add(segment.getFileSize()), 0, - segment.getVMsize() - segment.getFileSize(), segment.getSegmentName(), - source, segment.isRead(), segment.isWrite(), segment.isExecute(), - true) == null) { + segment.getVMsize() - segment.getFileSize(), segmentName, source, + segment.isRead(), segment.isWrite(), segment.isExecute(), true) == null) { log.appendMsg(String.format("Failed to create block: %s 0x%x 0x%x", segment.getSegmentName(), segment.getVMaddress(), segment.getVMsize())); } @@ -1343,7 +1348,7 @@ public class MachoProgramBuilder { NList nList = machoHeader.getFirstLoadCommand(SymbolTableCommand.class).getSymbolAt( symbolIndex); Symbol symbol = SymbolUtilities.getLabelOrFunctionSymbol(program, nList.getString(), - err -> log.error("Macho", err)); + err -> log.appendMsg("Macho", err)); if (relocation.isPcRelocated()) { destinationAddress = symbol.getAddress().subtractWrap( diff --git a/Ghidra/Features/Decompiler/certification.manifest b/Ghidra/Features/Decompiler/certification.manifest index eaf5caa7ce..21965227d9 100644 --- a/Ghidra/Features/Decompiler/certification.manifest +++ b/Ghidra/Features/Decompiler/certification.manifest @@ -11,6 +11,25 @@ src/decompile/build.gradle||GHIDRA||||END| src/decompile/cpp/.gitignore||GHIDRA||||END| src/decompile/cpp/Doxyfile||GHIDRA|||Most of this file is autogenerated by doxygen which falls under the GPL - output from GPL products are NOT GPL! - mjbell4|END| src/decompile/cpp/Makefile||GHIDRA||||END| +src/decompile/datatests/deadvolatile.xml||GHIDRA||||END| +src/decompile/datatests/floatprint.xml||GHIDRA||||END| +src/decompile/datatests/forloop1.xml||GHIDRA||||END| +src/decompile/datatests/forloop_loaditer.xml||GHIDRA||||END| +src/decompile/datatests/forloop_thruspecial.xml||GHIDRA||||END| +src/decompile/datatests/forloop_varused.xml||GHIDRA||||END| +src/decompile/datatests/forloop_withskip.xml||GHIDRA||||END| +src/decompile/datatests/loopcomment.xml||GHIDRA||||END| +src/decompile/datatests/namespace.xml||GHIDRA||||END| +src/decompile/datatests/nestedoffset.xml||GHIDRA||||END| +src/decompile/datatests/noforloop_alias.xml||GHIDRA||||END| +src/decompile/datatests/noforloop_globcall.xml||GHIDRA||||END| +src/decompile/datatests/noforloop_iterused.xml||GHIDRA||||END| +src/decompile/datatests/offsetarray.xml||GHIDRA||||END| +src/decompile/datatests/promotecompare.xml||GHIDRA||||END| +src/decompile/datatests/readvolatile.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/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/GraphAST.java b/Ghidra/Features/Decompiler/ghidra_scripts/GraphAST.java index 116ef27dab..ed2f6f1df1 100644 --- a/Ghidra/Features/Decompiler/ghidra_scripts/GraphAST.java +++ b/Ghidra/Features/Decompiler/ghidra_scripts/GraphAST.java @@ -30,6 +30,7 @@ import ghidra.program.model.listing.Program; import ghidra.program.model.pcode.*; import ghidra.service.graph.*; import ghidra.util.Msg; +import java.util.*; public class GraphAST extends GhidraScript { protected static final String COLOR_ATTRIBUTE = "Color"; @@ -64,8 +65,14 @@ public class GraphAST extends GhidraScript { graph = new AttributedGraph(); buildGraph(); + Map properties = new HashMap<>(); + properties.put("selectedVertexColor", "0xFF1493"); + properties.put("selectedEdgeColor", "0xFF1493"); + properties.put("initialLayoutAlgorithm", "Hierarchical MinCross Coffman Graham"); + properties.put("displayVerticesAsIcons", "false"); + properties.put("vertexLabelPosition", "S"); GraphDisplay graphDisplay = - graphDisplayBroker.getDefaultGraphDisplay(false, monitor); + graphDisplayBroker.getDefaultGraphDisplay(false, properties, monitor); // graphDisplay.defineVertexAttribute(CODE_ATTRIBUTE); // // graphDisplay.defineVertexAttribute(SYMBOLS_ATTRIBUTE); // graphDisplay.defineEdgeAttribute(EDGE_TYPE_ATTRIBUTE); diff --git a/Ghidra/Features/Decompiler/src/decompile/.cproject b/Ghidra/Features/Decompiler/src/decompile/.cproject index 9604926cfd..51df7189ed 100644 --- a/Ghidra/Features/Decompiler/src/decompile/.cproject +++ b/Ghidra/Features/Decompiler/src/decompile/.cproject @@ -1,288 +1,146 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - make - - -j8 - - reallyclean - - true - - true - - true - - - - - - make - - -j8 - - sleigh_dbg - - true - - true - - true - - - - - - make - - -j8 - - ghidra_dbg - - true - - true - - true - - - - - - make - - -j8 - - decomp_dbg - - true - - true - - true - - - - - - make - - -j8 - - ghidra_opt - - true - - true - - true - - - - - - make - - -j8 - - decomp_opt - - true - - true - - true - - - - - - make - - -j8 - - sleigh_opt - - true - - true - - true - - - - - - make - - -j8 - - install_ghidradbg - - true - - true - - true - - - - - - make - - -j8 - - install_ghidraopt - - true - - true - - true - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + make + -j8 + reallyclean + true + true + true + + + make + -j8 + sleigh_dbg + true + true + true + + + make + -j8 + ghidra_dbg + true + true + true + + + make + -j8 + decomp_dbg + true + true + true + + + make + -j8 + ghidra_opt + true + true + true + + + make + -j8 + decomp_opt + true + true + true + + + make + -j8 + sleigh_opt + true + true + true + + + make + -j8 + install_ghidradbg + true + true + true + + + make + -j8 + install_ghidraopt + true + true + true + + + + + \ No newline at end of file diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/Doxyfile b/Ghidra/Features/Decompiler/src/decompile/cpp/Doxyfile index e8e78f0f4a..450c238560 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/Doxyfile +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/Doxyfile @@ -457,7 +457,7 @@ RECURSIVE = NO # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. -EXCLUDE = unify.hh unify.cc rulecompile.hh rulecompile.cc slgh_compile.hh slgh_compile.hh slghpattern.hh slghpattern.cc slghpatexpress.hh slghpatexpress.cc slghsymbol.hh slghsymbol.cc ifacedecomp.hh ifacedecomp.cc ifaceterm.hh ifaceterm.cc codedata.hh codedata.cc semantics.hh semantics.cc grammar.hh grammar.cc callgraph.hh callgraph.cc filemanage.hh filemanage.cc graph.hh graph.cc interface.hh interface.cc loadimage_bfd.hh loadimage_bfd.cc pcodecompile.cc pcodecompile.hh pcodeparse.hh pcodeparse.cc inject_sleigh.hh inject_sleigh.cc context.hh context.cc consolemain.cc sleighexample.cc xml.cc +EXCLUDE = unify.hh unify.cc rulecompile.hh rulecompile.cc slgh_compile.hh slgh_compile.cc slghparse.cc slghparse.hh slghscan.cc slghpattern.hh slghpattern.cc slghpatexpress.hh slghpatexpress.cc slghsymbol.hh slghsymbol.cc codedata.hh codedata.cc semantics.hh semantics.cc grammar.hh grammar.cc callgraph.hh callgraph.cc filemanage.hh filemanage.cc graph.hh graph.cc loadimage_bfd.hh loadimage_bfd.cc pcodecompile.cc pcodecompile.hh pcodeparse.hh pcodeparse.cc inject_sleigh.hh inject_sleigh.cc context.hh context.cc consolemain.cc sleighexample.cc xml.cc double.hh double.cc paramid.hh paramid.cc prefersplit.hh prefersplit.cc # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded @@ -870,18 +870,6 @@ GENERATE_XML = NO XML_OUTPUT = xml -# The XML_SCHEMA tag can be used to specify an XML schema, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_SCHEMA = - -# The XML_DTD tag can be used to specify an XML DTD, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_DTD = - # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/Makefile b/Ghidra/Features/Decompiler/src/decompile/cpp/Makefile index a1e69d6cda..f422f4b5b4 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/Makefile +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/Makefile @@ -56,6 +56,9 @@ LNK= # Source files ALL_SOURCE= $(wildcard *.cc) ALL_NAMES=$(subst .cc,,$(ALL_SOURCE)) +UNITTEST_SOURCE= $(wildcard ../unittests/*.cc) +UNITTEST_NAMES=$(subst .cc,,$(UNITTEST_SOURCE)) +UNITTEST_STRIP=$(subst ../unittests/,,$(UNITTEST_NAMES)) COREEXT_SOURCE= $(wildcard coreext_*.cc) COREEXT_NAMES=$(subst .cc,,$(COREEXT_SOURCE)) @@ -91,7 +94,7 @@ GHIDRA= ghidra_arch inject_ghidra ghidra_translate loadimage_ghidra \ # Additional files specific to the sleigh compiler SLACOMP=slgh_compile slghparse slghscan # Additional special files that should not be considered part of the library -SPECIAL=consolemain sleighexample test +SPECIAL=consolemain sleighexample test testfunction # Any additional modules for the command line decompiler EXTRA= $(filter-out $(CORE) $(DECCORE) $(SLEIGH) $(GHIDRA) $(SLACOMP) $(SPECIAL),$(ALL_NAMES)) @@ -114,8 +117,8 @@ COMMANDLINE_NAMES=$(CORE) $(DECCORE) $(EXTRA) $(SLEIGH) consolemain COMMANDLINE_DEBUG=-DCPUI_DEBUG -D__TERMINAL__ COMMANDLINE_OPT=-D__TERMINAL__ -TEST_NAMES=$(CORE) $(DECCORE) $(SLEIGH) test -TEST_DEBUG=-D__TERMINAL__ -g -O0 +TEST_NAMES=$(CORE) $(DECCORE) $(SLEIGH) $(EXTRA) testfunction test +TEST_DEBUG=-D__TERMINAL__ GHIDRA_NAMES=$(CORE) $(DECCORE) $(GHIDRA) GHIDRA_NAMES_DBG=$(GHIDRA_NAMES) callgraph ifacedecomp ifaceterm interface @@ -136,7 +139,7 @@ LIBDECOMP_NAMES=$(CORE) $(DECCORE) $(EXTRA) $(SLEIGH) # object file macros COMMANDLINE_DBG_OBJS=$(COMMANDLINE_NAMES:%=com_dbg/%.o) COMMANDLINE_OPT_OBJS=$(COMMANDLINE_NAMES:%=com_opt/%.o) -TEST_DEBUG_OBJS=$(TEST_NAMES:%=test_dbg/%.o) +TEST_DEBUG_OBJS=$(TEST_NAMES:%=test_dbg/%.o) $(UNITTEST_STRIP:%=test_dbg/%.o) GHIDRA_DBG_OBJS=$(GHIDRA_NAMES_DBG:%=ghi_dbg/%.o) GHIDRA_OPT_OBJS=$(GHIDRA_NAMES:%=ghi_opt/%.o) SLEIGH_DBG_OBJS=$(SLEIGH_NAMES:%=sla_dbg/%.o) @@ -214,7 +217,9 @@ com_dbg/%.o: %.cc com_opt/%.o: %.cc $(CXX) $(ARCH_TYPE) -c $(OPT_CXXFLAGS) $(ADDITIONAL_FLAGS) $(COMMANDLINE_OPT) $< -o $@ test_dbg/%.o: %.cc - $(CXX) $(ARCH_TYPE) -c $(OPT_CXXFLAGS) $(ADDITIONAL_FLAGS) $(TEST_DEBUG) $< -o $@ + $(CXX) $(ARCH_TYPE) -c $(DBG_CXXFLAGS) $(ADDITIONAL_FLAGS) $(TEST_DEBUG) $< -o $@ +test_dbg/%.o: ../unittests/%.cc + $(CXX) -I. $(ARCH_TYPE) -c $(DBG_CXXFLAGS) $(ADDITIONAL_FLAGS) $(TEST_DEBUG) $< -o $@ ghi_dbg/%.o: %.cc $(CXX) $(ARCH_TYPE) -c $(DBG_CXXFLAGS) $(ADDITIONAL_FLAGS) $(GHIDRA_DEBUG) $< -o $@ ghi_opt/%.o: %.cc @@ -248,7 +253,7 @@ decomp_opt: $(COMMANDLINE_OPT_OBJS) $(CXX) $(OPT_CXXFLAGS) $(ARCH_TYPE) -o decomp_opt $(COMMANDLINE_OPT_OBJS) $(BFDLIB) $(LNK) ghidra_test_dbg: $(TEST_DEBUG_OBJS) - $(CXX) $(OPT_CXXFLAGS) $(ARCH_TYPE) -o ghidra_test_dbg $(TEST_DEBUG_OBJS) $(BFDLIB) $(LNK) + $(CXX) $(DBG_CXXFLAGS) $(ARCH_TYPE) -o ghidra_test_dbg $(TEST_DEBUG_OBJS) $(BFDLIB) $(LNK) test: ghidra_test_dbg ./ghidra_test_dbg @@ -339,10 +344,10 @@ com_opt/depend: $(COMMANDLINE_NAMES:%=%.cc) sed 's,\(.*\)\.o[ :]*,com_opt/\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$ -test_dbg/depend: $(TEST_NAMES:%=%.cc) +test_dbg/depend: $(TEST_NAMES:%=%.cc) $(UNITTEST_NAMES:%=%.cc) mkdir -p test_dbg @set -e; rm -f $@; \ - $(CXX) -MM $(TEST_DEBUG) $^ > $@.$$$$; \ + $(CXX) -I. -MM $(TEST_DEBUG) $^ > $@.$$$$; \ sed 's,\(.*\)\.o[ :]*,test_dbg/\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$ diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc index 26a28a52fe..4a7c40afe6 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc @@ -64,6 +64,20 @@ ArchitectureCapability *ArchitectureCapability::findCapability(Document *doc) return (ArchitectureCapability *)0; } +/// Return the ArchitectureCapability object with the matching name +/// \param name is the name to match +/// \return the ArchitectureCapability or null if no match is found +ArchitectureCapability *ArchitectureCapability::getCapability(const string &name) + +{ + for(int4 i=0;igetName() == name) + return res; + } + return (ArchitectureCapability *)0; +} + /// Modify order that extensions are searched, to effect which gets a chance /// to run first. /// Right now all we need to do is make sure the raw architecture comes last diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh index 2e07c92034..53817b583e 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh @@ -104,6 +104,7 @@ public: static ArchitectureCapability *findCapability(const string &filename); ///< Find an extension to process a file static ArchitectureCapability *findCapability(Document *doc); ///< Find an extension to process an XML document + static ArchitectureCapability *getCapability(const string &name); ///< Get a capability by name static void sortCapabilities(void); ///< Sort extensions static uint4 getMajorVersion(void) { return majorversion; } ///< Get \e major decompiler version static uint4 getMinorVersion(void) { return minorversion; } ///< Get \e minor decompiler version diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/block.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/block.cc index 7cab29691e..86bbf61bc2 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/block.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/block.cc @@ -574,7 +574,10 @@ int4 FlowBlock::getOutIndex(const FlowBlock *bl) const void FlowBlock::printHeader(ostream &s) const { - s << dec << index << ' ' << getStart() << '-' << getStop(); + s << dec << index; + if (!getStart().isInvalid() && !getStop().isInvalid()) { + s << ' ' << getStart() << '-' << getStop(); + } } /// Recursively print out the hierarchical structure of \b this FlowBlock. diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/block.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/block.hh index a6a8125727..daf040a65e 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/block.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/block.hh @@ -597,8 +597,8 @@ class BlockWhileDo : public BlockGraph { bool testIterateForm(void) const; ///< Return \b false if the iterate statement is of an unacceptable form public: BlockWhileDo(void) { initializeOp = (PcodeOp *)0; iterateOp = (PcodeOp *)0; loopDef = (PcodeOp *)0; } ///< Constructor - PcodeOp *getInitializeOp(void) const { return initializeOp; } - PcodeOp *getIterateOp(void) const { return iterateOp; } + PcodeOp *getInitializeOp(void) const { return initializeOp; } ///< Get root of initialize statement or null + PcodeOp *getIterateOp(void) const { return iterateOp; } ///< Get root of iterate statement or null bool hasOverflowSyntax(void) const { return ((getFlags() & f_whiledo_overflow)!=0); } ///< Does \b this require overflow syntax void setOverflowSyntax(void) { setFlag(f_whiledo_overflow); } ///< Set that \b this requires overflow syntax virtual block_type getType(void) const { return t_whiledo; } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/comment.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/comment.hh index 0a79ad7ba9..73e70d6c8f 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/comment.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/comment.hh @@ -54,8 +54,8 @@ public: }; Comment(uint4 tp,const Address &fad,const Address &ad,int4 uq,const string &txt); ///< Constructor Comment(void) {} ///< Constructor for use with restoreXml - void setEmitted(bool val) const { emitted = val; } - bool isEmitted(void) const { return emitted; } + void setEmitted(bool val) const { emitted = val; } ///< Mark that \b this comment has been emitted + bool isEmitted(void) const { return emitted; } ///< Return \b true if \b this comment is already emitted uint4 getType(void) const { return type; } ///< Get the properties associated with the comment const Address &getFuncAddr(void) const { return funcaddr; } ///< Get the address of the function containing the comment const Address &getAddr(void) const { return addr; } ///< Get the address to which the instruction is attached diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc index 6fb1aa38ed..b9c43246fe 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc @@ -3477,9 +3477,10 @@ bool ActionDeadCode::isEventualConstant(Varnode *vn,int4 addCount,int4 loadCount /// \brief Check if there are any unconsumed LOADs that may be from volatile addresses. /// /// It may be too early to remove certain LOAD operations even though their result isn't -/// consumed because it be of a volatile address with side effects. If a LOAD meets this +/// consumed because it may be of a volatile address with side effects. If a LOAD meets this /// criteria, it is added to the worklist and \b true is returned. /// \param data is the function being analyzed +/// \param worklist is the container of consumed Varnodes to further process /// \return \b true if there was at least one LOAD added to the worklist bool ActionDeadCode::lastChanceLoad(Funcdata &data,vector &worklist) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/database.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/database.cc index e0068dceb2..1b4ec7b834 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/database.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/database.cc @@ -2817,7 +2817,7 @@ void Database::fillResolve(Scope *scope) /// Initialize a new symbol table, with no initial scopes or symbols. /// \param g is the Architecture that owns the symbol table -/// \param isByName is \b true if scope ids are calculated as a hash of the scope name. +/// \param idByName is \b true if scope ids are calculated as a hash of the scope name. Database::Database(Architecture *g,bool idByName) { diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.cc index f7c3e95c33..670d94c9f7 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.cc @@ -19,6 +19,7 @@ /// \param nm is the (base) name of the function /// \param scope is Symbol scope associated with the function /// \param addr is the entry address for the function +/// \param sym is the symbol representing the function /// \param sz is the number of bytes (of code) in the function body Funcdata::Funcdata(const string &nm,Scope *scope,const Address &addr,FunctionSymbol *sym,int4 sz) : baseaddr(addr), diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh index 32492629b8..dc5c877889 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh @@ -249,7 +249,6 @@ public: int4 numCalls(void) const { return qlst.size(); } ///< Get the number of calls made by \b this function FuncCallSpecs *getCallSpecs(int4 i) const { return qlst[i]; } ///< Get the i-th call specification FuncCallSpecs *getCallSpecs(const PcodeOp *op) const; ///< Get the call specification associated with a CALL op - void updateOpFromSpec(FuncCallSpecs *fc); int4 fillinExtrapop(void); ///< Recover and return the \e extrapop for this function // Varnode routines diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc index a42520cee8..c01052b5ad 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc @@ -291,7 +291,7 @@ void Funcdata::destroyVarnode(Varnode *vn) /// Check if the given storage range is a potential laned register. /// If so, record the storage with the matching laned register record. -/// \param s is the size of the storage range in bytes +/// \param size is the size of the storage range in bytes /// \param addr is the starting address of the storage range void Funcdata::checkForLanedRegister(int4 size,const Address &addr) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_arch.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_arch.cc index f0bd755d67..d34513ef04 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_arch.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_arch.cc @@ -690,6 +690,18 @@ void ArchitectureGhidra::getBytes(uint1 *buf,int4 size,const Address &inaddr) readResponseEnd(sin); } +/// \brief Get string data at a specific address +/// +/// The data is always returned as a sequence of bytes in UTF-8 format. The in-memory form of +/// the string may be different than UTF-8 but is always translated into UTF-8 by this method. +/// The caller can inform the in-memory format of the string by specifying a specific string +/// data-type. A maximum number of bytes to return is specified. If this is exceeded, a boolean +/// reference is set to \b true. +/// \param buffer will hold the string bytes in UTF-8 format +/// \param addr is program Address that holds the string data in memory +/// \param ct is string data-type expected +/// \param maxBytes is the maximum number of bytes to return +/// \param isTrunc is the boolean reference indicating whether the data is truncated void ArchitectureGhidra::getStringData(vector &buffer,const Address &addr,Datatype *ct,int4 maxBytes,bool &isTrunc) { diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_arch.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_arch.hh index 36d565cdea..f796c7ea76 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_arch.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_arch.hh @@ -127,7 +127,7 @@ public: bool getSendParamMeasures(void) const { return sendParamMeasures; } ///< Get the current setting for emitting parameter info - virtual void getStringData(vector &buffer,const Address &addr,Datatype *ct,int4 maxBytes,bool &isTrunc); + void getStringData(vector &buffer,const Address &addr,Datatype *ct,int4 maxBytes,bool &isTrunc); virtual void printMessage(const string &message) const; static void segvHandler(int4 sig); ///< Handler for a segment violation (SIGSEGV) signal diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.cc index 389553599e..883e07ba31 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.cc @@ -82,7 +82,6 @@ void IfaceDecompCapability::registerCommands(IfaceStatus *status) status->registerCom(new IfcPrintCTypes(),"print","C","types"); status->registerCom(new IfcPrintCXml(),"print","C","xml"); status->registerCom(new IfcPrintParamMeasures(),"print","parammeasures"); - status->registerCom(new IfcPrintParamMeasuresXml(),"print","parammeasures","xml"); status->registerCom(new IfcProduceC(),"produce","C"); status->registerCom(new IfcProducePrototypes(),"produce","prototypes"); status->registerCom(new IfcPrintRaw(),"print","raw"); @@ -97,7 +96,6 @@ void IfaceDecompCapability::registerCommands(IfaceStatus *status) status->registerCom(new IfcBreakaction(),"break","action"); status->registerCom(new IfcPrintSpaces(),"print","spaces"); status->registerCom(new IfcPrintHigh(),"print","high"); - status->registerCom(new IfcParamIDAnalysis(),"paramid","analysis"); status->registerCom(new IfcPrintTree(),"print","tree","varnode"); status->registerCom(new IfcPrintBlocktree(),"print","tree","block"); status->registerCom(new IfcPrintLocalrange(),"print","localrange"); @@ -148,6 +146,9 @@ void IfaceDecompCapability::registerCommands(IfaceStatus *status) #endif } +/// Runs over every function in the scope, or any sub-scope , calling +/// iterationCallback() +/// \param scope is the given scope void IfaceDecompCommand::iterateScopesRecursive(Scope *scope) { @@ -161,6 +162,8 @@ void IfaceDecompCommand::iterateScopesRecursive(Scope *scope) } } +/// Runs over every function in the scope calling iterationCallback(). +/// \param scope is the given scope void IfaceDecompCommand::iterateFunctionsAddrOrder(Scope *scope) { @@ -176,6 +179,8 @@ void IfaceDecompCommand::iterateFunctionsAddrOrder(Scope *scope) } } +/// Scopes are traversed depth-first, then within a scope, functions are +/// traversed in address order. void IfaceDecompCommand::iterateFunctionsAddrOrder(void) { @@ -184,6 +189,8 @@ void IfaceDecompCommand::iterateFunctionsAddrOrder(void) iterateScopesRecursive(dcp->conf->symboltab->getGlobalScope()); } +/// Traversal is based on the current CallGraph for the program. +/// Child functions are traversed before their parents. void IfaceDecompCommand::iterateFunctionsLeafOrder(void) { @@ -233,9 +240,13 @@ void IfaceDecompData::allocateCallGraph(void) cgraph = new CallGraph(conf); } +/// This is called if a command throws a low-level error. +/// It clears any analysis on the function, sets the current function +/// to null, and issues a warning. +/// \param s is the stream to write the warning to void IfaceDecompData::abortFunction(ostream &s) -{ // Clear references to current function +{ if (fd == (Funcdata *)0) return; s << "Unable to proceed with function: " << fd->getName() << endl; conf->clearAnalysis(fd); @@ -251,14 +262,30 @@ void IfaceDecompData::clearArchitecture(void) fd = (Funcdata *)0; } +/// \class IfcComment +/// \brief A comment within a command script: `% A comment in a script` +/// +/// This commands does nothing but attaches to comment tokens like: +/// - \# +/// - % +/// - // +/// +/// allowing comment lines in a script file void IfcComment::execute(istream &s) { //Do nothing } +/// \class IfcOption +/// \brief Adjust a decompiler option: `option [] [] []` +/// +/// Passes command-line parameters to an ArchOption object registered with +/// the current architecture's OptionDatabase. Options are looked up by name +/// and can be configure with up to 3 parameters. Options generally report success +/// or failure back to the console. void IfcOption::execute(istream &s) -{ // Adjust a generic option +{ string optname; string p1,p2,p3; @@ -293,6 +320,12 @@ void IfcOption::execute(istream &s) } } +/// \class IfcParseFile +/// \brief Parse a file with C declarations: `parse file ` +/// +/// The file must contain C syntax data-type and function declarations. +/// Data-types become part of the program, and function declarations, +/// if the symbol already exists, associate the prototype with the symbol. void IfcParseFile::execute(istream &s) { @@ -320,6 +353,16 @@ void IfcParseFile::execute(istream &s) fs.close(); } +/// \class IfcParseLine +/// \brief Parse a line of C syntax: `parse line ...` +/// +/// The line can contain a declaration either a data-type or a function prototype: +/// - `parse line typedef int4 *specialint;` +/// - `parse line struct mystruct { int4 a; int4 b; }` +/// - `parse line extern void myfunc(int4 a,int4 b);` +/// +/// Data-types go straight into the program. For a prototype, the function symbol +/// must already exist. void IfcParseLine::execute(istream &s) { @@ -339,6 +382,12 @@ void IfcParseLine::execute(istream &s) } } +/// \class IfcAdjustVma +/// \brief Change the base address of the load image: `adjust vma 0xabcd0123` +/// +/// The provided parameter is added to the current base address of the image. +/// This only affects the address of bytes in the image and so should be done +/// before functions and other symbols are layed down. void IfcAdjustVma::execute(istream &s) { @@ -358,32 +407,47 @@ void IfcAdjustVma::execute(istream &s) static void jump_callback(Funcdata &orig,Funcdata &fd); #endif -static void IfcFollowFlow(ostream &s,IfaceDecompData *dcp,const Address &offset,int4 size) +/// \brief Generate raw p-code for the current function +/// +/// Follow flow from the entry point of the function and generate the +/// raw p-code ops for all instructions, up to \e return instructions. +/// If a \e size in bytes is provided, it bounds the memory region where flow +/// can be followed. Otherwise, a zero \e size allows unbounded flow tracing. +/// \param s is a output stream for reporting function details or errors +/// \param size (if non-zero) is the maximum number of bytes to disassemble +void IfaceDecompData::followFlow(ostream &s,int4 size) { #ifdef OPACTION_DEBUG - if (dcp->jumptabledebug) - dcp->fd->enableJTCallback(jump_callback); + if (jumptabledebug) + fd->enableJTCallback(jump_callback); #endif try { if (size==0) { - Address baddr(dcp->fd->getAddress().getSpace(),0); - Address eaddr(dcp->fd->getAddress().getSpace(),dcp->fd->getAddress().getSpace()->getHighest()); - dcp->fd->followFlow(baddr,eaddr); + Address baddr(fd->getAddress().getSpace(),0); + Address eaddr(fd->getAddress().getSpace(),fd->getAddress().getSpace()->getHighest()); + fd->followFlow(baddr,eaddr); } else - dcp->fd->followFlow(offset,offset+size); - s << "Function " << dcp->fd->getName() << ": "; - dcp->fd->getAddress().printRaw(s); + fd->followFlow(fd->getAddress(),fd->getAddress()+size); + s << "Function " << fd->getName() << ": "; + fd->getAddress().printRaw(s); s << endl; } catch(RecovError &err) { - s << "Function " << dcp->fd->getName() << ": " << err.explain << endl; + s << "Function " << fd->getName() << ": " << err.explain << endl; } } +/// \class IfcFuncload +/// \brief Make a specific function current: `load function ` +/// +/// The name must be a fully qualified symbol with "::" separating namespaces. +/// If the symbol represents a function, that function becomes \e current for +/// the console. If there are bytes for the function, raw p-code and control-flow +/// are calculated. void IfcFuncload::execute(istream &s) -{ // Load a function into memory +{ string funcname; Address offset; @@ -401,12 +465,19 @@ void IfcFuncload::execute(istream &s) throw IfaceExecutionError("Unknown function name: "+funcname); if (!dcp->fd->hasNoCode()) - IfcFollowFlow(*status->optr,dcp,dcp->fd->getAddress(),0); + dcp->followFlow(*status->optr,0); } +/// \class IfcAddrrangeLoad +/// \brief Create a new function at an address: `load addr

[]` +/// +/// A new function is created at the provided address. If a name is provided, this +/// becomes the function symbol, otherwise a default name is generated. +/// The function becomes \e current for the interface, and if bytes are present, +/// raw p-code and control-flow are generated. void IfcAddrrangeLoad::execute(istream &s) -{ // Load address range as function +{ int4 size; string name; Address offset=parse_machaddr(s,size,*dcp->conf->types); // Read required address @@ -421,15 +492,22 @@ void IfcAddrrangeLoad::execute(istream &s) if (name.empty()) dcp->conf->nameFunction(offset,name); // Pick default name if necessary dcp->fd = dcp->conf->symboltab->getGlobalScope()->addFunction( offset,name)->getFunction(); - IfcFollowFlow(*status->optr,dcp,offset,size); + dcp->followFlow(*status->optr,size); } +/// \class IfcCleararch +/// \brief Clear the current architecture/program: `clear architecture` void IfcCleararch::execute(istream &s) { dcp->clearArchitecture(); } +/// \class IfcReadSymbols +/// \brief Read in symbols from the load image: `read symbols` +/// +/// If the load image format encodes symbol information. These are +/// read in and attached to the appropriate address. void IfcReadSymbols::execute(istream &s) { @@ -441,6 +519,16 @@ void IfcReadSymbols::execute(istream &s) dcp->conf->readLoaderSymbols("::"); } +/// \class IfcMapaddress +/// \brief Map a new symbol into the program: `map address
` +/// +/// Create a new variable in the current scope +/// \code +/// map address r0x1000 int4 globalvar +/// \endcode +/// The symbol specified in the type declaration can qualify the namespace using the "::" +/// specifier. If there is a current function, the variable is local to the function. +/// Otherwise the symbol is created relative to the global scope. void IfcMapaddress::execute(istream &s) { @@ -472,9 +560,16 @@ void IfcMapaddress::execute(istream &s) } +/// \class IfcMaphash +/// \brief Add a dynamic symbol to the current function: `map hash
` +/// +/// The command only creates local variables for the current function. +/// The name and data-type are taken from a C syntax type declaration. The symbol is +/// not associated with a particular storage address but with a specific Varnode in the data-flow, +/// specified by a code address and hash of the local data-flow structure. void IfcMaphash::execute(istream &s) -{ // Add a dynamic entry to the current function's symbol table given a known hash and pc address +{ if (dcp->fd == (Funcdata *)0) throw IfaceExecutionError("No function loaded"); Datatype *ct; @@ -491,9 +586,18 @@ void IfcMaphash::execute(istream &s) sym->getScope()->setAttribute(sym,Varnode::namelock|Varnode::typelock); } +/// \class IfcMapfunction +/// \brief Create a new function: `map function
[] [nocode]` +/// +/// Create a new function symbol at the provided address. +/// A symbol name can be provided, otherwise a default name is selected. +/// The new function becomes \e current for the console. +/// The provided address gives the entry point for the function. Unless the final keyword +/// "nocode" is provided, the underlying bytes in the load image are used for any +/// future disassembly or decompilation. void IfcMapfunction::execute(istream &s) -{ // Notate a function start with loading the instructions +{ string name; int4 size; if ((dcp->conf == (Architecture *)0)||(dcp->conf->loader == (LoadImage *)0)) @@ -514,9 +618,17 @@ void IfcMapfunction::execute(istream &s) dcp->fd->setNoCode(true); } +/// \class IfcMapexternalref +/// \brief Create an external ref symbol `map externalref
[]` +/// +/// Creates a symbol for a function pointer and associates a specific address as +/// a value for that symbol. The first address specified is the address of the symbol, +/// The second address is the address referred to by the pointer. Indirect calls +/// through the function pointer will be converted to direct calls to the referred address. +/// A symbol name can be provided, otherwise a default one is generated. void IfcMapexternalref::execute(istream &s) -{ // Link one address as reference to another address +{ int4 size1,size2; Address addr1 = parse_machaddr(s,size1,*dcp->conf->types); // Read externalref address Address addr2 = parse_machaddr(s,size2,*dcp->conf->types); // Read referred to address @@ -527,9 +639,15 @@ void IfcMapexternalref::execute(istream &s) dcp->conf->symboltab->getGlobalScope()->addExternalRef(addr1,addr2,name); } +/// \class IfcMaplabel +/// \brief Create a code label: `map label
` +/// +/// Label a specific code address. This creates a LabSymbol which is usually +/// an internal control-flow target. The symbol is local to the \e current function +/// if it exists, otherwise the symbol is added to the global scope. void IfcMaplabel::execute(istream &s) -{ // Put a code label at a given address +{ string name; s >> name; if (name.size()==0) @@ -547,9 +665,14 @@ void IfcMaplabel::execute(istream &s) scope->setAttribute(sym,Varnode::namelock|Varnode::typelock); } +/// \class IfcPrintdisasm +/// \brief Print disassembly of a memory range: `disassemble [ ]` +/// +/// If no addresses are provided, disassembly for the current function is displayed. +/// Otherwise disassembly is between the two provided addresses. void IfcPrintdisasm::execute(istream &s) -{ // Print disassembly of a function +{ Architecture *glb; Address addr; int4 size; @@ -580,9 +703,13 @@ void IfcPrintdisasm::execute(istream &s) } } +/// \class IfcDump +/// \brief Display bytes in the load image: `dump ` +/// +/// The command does a hex listing of the specific memory region. void IfcDump::execute(istream &s) -{ // Do hex listing of memory region +{ int4 size; uint1 *buffer; Address offset = parse_machaddr(s,size,*dcp->conf->types); @@ -592,9 +719,14 @@ void IfcDump::execute(istream &s) delete [] buffer; } +/// \class IfcDumpbinary +/// \brief Dump a memory to file: `binary ` +/// +/// Raw bytes from the specified memory region in the load image are written +/// to a file. void IfcDumpbinary::execute(istream &s) -{ // Write part of load image to file +{ int4 size; uint1 *buffer; Address offset = parse_machaddr(s,size,*dcp->conf->types); @@ -614,10 +746,16 @@ void IfcDumpbinary::execute(istream &s) delete [] buffer; os.close(); } - + +/// \class IfcDecompile +/// \brief Decompile the current function: `decompile` +/// +/// Decompilation is started for the current function. Any previous decompilation +/// analysis on the function is cleared first. The process respects +/// any active break points or traces, so decompilation may not complete. void IfcDecompile::execute(istream &s) -{ // Do decompilation of current function +{ int4 res; if (dcp->fd == (Funcdata *)0) @@ -647,10 +785,11 @@ void IfcDecompile::execute(istream &s) *status->optr << endl; } +/// \class IfcPrintCFlat +/// \brief Print current function without control-flow: `print C flat` void IfcPrintCFlat::execute(istream &s) -{ // Print current decompilation as C - // Do not print block structure +{ if (dcp->fd == (Funcdata *)0) throw IfaceExecutionError("No function selected"); @@ -660,9 +799,11 @@ void IfcPrintCFlat::execute(istream &s) dcp->conf->print->setFlat(false); } +/// \class IfcPrintCGlobals +/// \brief Print declarations for any known global variables: `print C globals` void IfcPrintCGlobals::execute(istream &s) -{ // Print all global variable declarations we know +{ if (dcp->conf == (Architecture *)0) throw IfaceExecutionError("No load image present"); @@ -670,9 +811,11 @@ void IfcPrintCGlobals::execute(istream &s) dcp->conf->print->docAllGlobals(); } +/// \class IfcPrintCTypes +/// \brief Print any known type definitions: `print C types` void IfcPrintCTypes::execute(istream &s) -{ // Print all type definitions +{ if (dcp->conf == (Architecture *)0) throw IfaceExecutionError("No load image present"); @@ -682,10 +825,11 @@ void IfcPrintCTypes::execute(istream &s) } } +/// \class IfcPrintCXml +/// \brief Print the current function with C syntax and XML markup:`print C xml` void IfcPrintCXml::execute(istream &s) -{ // Print current decompilation as C - // Do not print block structure +{ if (dcp->fd == (Funcdata *)0) throw IfaceExecutionError("No function selected"); @@ -695,9 +839,11 @@ void IfcPrintCXml::execute(istream &s) dcp->conf->print->setXML(false); } +/// \class IfcPrintCStruct +/// \brief Print the current function using C syntax:`print C` void IfcPrintCStruct::execute(istream &s) -{ // Print current decompilation as C +{ if (dcp->fd == (Funcdata *)0) throw IfaceExecutionError("No function selected"); @@ -705,6 +851,10 @@ void IfcPrintCStruct::execute(istream &s) dcp->conf->print->docFunction(dcp->fd); } +/// \class IfcPrintLanguage +/// \brief Print current output using a specific language: `print language ` +/// +/// The current function must already be decompiled. void IfcPrintLanguage::execute(istream &s) { @@ -725,15 +875,22 @@ void IfcPrintLanguage::execute(istream &s) dcp->conf->setPrintLanguage(curlangname); // Reset to original language } +/// \class IfcPrintRaw +/// \brief Print the raw p-code for the \e current function: `print raw` +/// +/// Each p-code op, in its present state, is printed to the console, labeled +/// with the address of its original instruction and any output and input varnodes. void IfcPrintRaw::execute(istream &s) -{ // Print current decompilation as cpui +{ if (dcp->fd == (Funcdata *)0) throw IfaceExecutionError("No function selected"); dcp->fd->printRaw(*status->fileoptr); } +/// \class IfcListaction +/// \brief List all current actions and rules for the decompiler: `list action` void IfcListaction::execute(istream &s) { @@ -742,6 +899,14 @@ void IfcListaction::execute(istream &s) dcp->conf->allacts.getCurrent()->print(*status->fileoptr,0,0); } +/// \class IfcListOverride +/// \brief Display any overrides for the current function: `list override` +/// +/// Overrides include: +/// - Forced gotos +/// - Dead code delays +/// - Indirect call overrides +/// - Indirect prototype overrides void IfcListOverride::execute(istream &s) { @@ -752,6 +917,12 @@ void IfcListOverride::execute(istream &s) dcp->fd->getOverride().printRaw(*status->optr,dcp->conf); } +/// \class IfcListprototypes +/// \brief List known prototype models: `list prototypes` +/// +/// All prototype models are listed with markup indicating the +/// \e default, the evaluation model for the active function, and +/// the evaluation model for called functions. void IfcListprototypes::execute(istream &s) { @@ -772,6 +943,12 @@ void IfcListprototypes::execute(istream &s) } } +/// \class IfcSetcontextrange +/// \brief Set a context variable: `set context [ ]` +/// +/// The named context variable is set to the provided value. +/// If a start and end address is provided, the context variable is set over this range, +/// otherwise the value is set as a default. void IfcSetcontextrange::execute(istream &s) { @@ -810,6 +987,12 @@ void IfcSetcontextrange::execute(istream &s) dcp->conf->context->setVariableRegion(name,addr1,addr2,value); } +/// \class IfcSettrackedrange +/// \brief Set the value of a register: `set track [ ]` +/// +/// The value for the register is picked up by the decompiler for functions in the tracked range. +/// The register is specified by name. A specific range can be provided, otherwise the value is +/// treated as a default. void IfcSettrackedrange::execute(istream &s) { @@ -853,9 +1036,17 @@ void IfcSettrackedrange::execute(istream &s) track.back().val = value; } +/// \class IfcBreakaction +/// \brief Set a breakpoint when a Rule or Action executes: `break action ` +/// +/// The break point can be on either an Action or Rule. The name can specify +/// partial path information to distinguish the Action/Rule. The breakpoint causes +/// the decompilation process to stop and return control to the console immediately +/// \e after the Action or Rule has executed, but only if there was an active transformation +/// to the function. void IfcBreakaction::execute(istream &s) -{ // Set an action breakpoint +{ bool res; string specify; @@ -872,9 +1063,16 @@ void IfcBreakaction::execute(istream &s) throw IfaceExecutionError("Bad action/rule specifier: "+specify); } +/// \class IfcBreakstart +/// \brief Set a break point at the start of an Action: `break start ` +/// +/// The break point can be on either an Action or a Rule. The name can specify +/// partial path information to distinguish the Action/Rule. The breakpoint causes +/// the decompilation process to stop and return control to the console just before +/// the Action/Rule would have executed. void IfcBreakstart::execute(istream &s) -{ // Set a start breakpoint +{ bool res; string specify; @@ -891,18 +1089,38 @@ void IfcBreakstart::execute(istream &s) throw IfaceExecutionError("Bad action/rule specifier: "+specify); } +/// \class IfcPrintTree +/// \brief Print all Varnodes in the \e current function: `print tree varnode` +/// +/// Information about every Varnode in the data-flow graph for the function is displayed. void IfcPrintTree::execute(istream &s) { + if (dcp->fd == (Funcdata *)0) + throw IfaceExecutionError("No function selected"); + dcp->fd->printVarnodeTree(*status->fileoptr); } +/// \class IfcPrintBlocktree +/// \brief Print a description of the \e current functions control-flow: `print tree block` +/// +/// The recovered control-flow structure is displayed as a hierarchical list of blocks, +/// showing the nesting and code ranges covered by the blocks. void IfcPrintBlocktree::execute(istream &s) { + if (dcp->fd == (Funcdata *)0) + throw IfaceExecutionError("No function selected"); + dcp->fd->printBlockTree(*status->fileoptr); } +/// \class IfcPrintSpaces +/// \brief Print all address spaces: `print spaces` +/// +/// Information about every address space in the architecture/program is written +/// to the console. void IfcPrintSpaces::execute(istream &s) { @@ -935,9 +1153,14 @@ void IfcPrintSpaces::execute(istream &s) } } +/// \class IfcPrintHigh +/// \brief Display all Varnodes in a HighVariable: `print high ` +/// +/// A HighVariable associated with the current function is specified by name. +/// Information about every Varnode merged into the variable is displayed. void IfcPrintHigh::execute(istream &s) -{ // List varnodes under one high +{ string varname; HighVariable *high; @@ -953,14 +1176,10 @@ void IfcPrintHigh::execute(istream &s) high->printInfo(*status->optr); } -void IfcParamIDAnalysis::execute(istream &s) -{ - ParamIDAnalysis pidanalysis( dcp->fd, false ); - pidanalysis.saveXml( *status->optr, true ); - *status->optr << "\n"; //tmp - //dcp->fd->saveXml( *status->optr, true ); //temporary until I write a new one with results. -} +/// \class IfcPrintParamMeasures +/// \brief Perform parameter-id analysis on the \e current function: `print parammeasures` void IfcPrintParamMeasures::execute(istream &s) + { if (dcp->fd == (Funcdata *)0) throw IfaceExecutionError("No function selected"); @@ -968,28 +1187,16 @@ void IfcPrintParamMeasures::execute(istream &s) ParamIDAnalysis pidanalysis( dcp->fd, false ); pidanalysis.savePretty( *status->fileoptr, true ); *status->fileoptr << "\n"; - -// dcp->conf->print->setOutputStream(status->fileoptr); -// dcp->conf->print->docFunction(dcp->fd); -} -void IfcPrintParamMeasuresXml::execute(istream &s) -{ - if (dcp->fd == (Funcdata *)0) - throw IfaceExecutionError("No function selected"); - - ParamIDAnalysis pidanalysis( dcp->fd, false ); - pidanalysis.saveXml( *status->fileoptr, true ); - *status->fileoptr << "\n"; - -// dcp->conf->print->setOutputStream(status->fileoptr); -// dcp->conf->print->setXML(true); -// dcp->conf->print->docFunction(dcp->fd); -// dcp->conf->print->setXML(false); } +/// \class IfcRename +/// \brief Rename a variable: `rename ` +/// +/// Change the name of a symbol. The provided name is searched for starting +/// in the scope of the current function. void IfcRename::execute(istream &s) -{ // Change the name of a symbol +{ string oldname,newname; s >> ws >> oldname >> ws >> newname >> ws; @@ -1018,9 +1225,14 @@ void IfcRename::execute(istream &s) sym->getScope()->setAttribute(sym,Varnode::namelock|Varnode::typelock); } +/// \class IfcRemove +/// \brief Remove a symbol by name: `remove ` +/// +/// The symbol is searched for starting in the current function's scope. +/// The resulting symbol is removed completely from the symbol table. void IfcRemove::execute(istream &s) -{ // Remove a symbol +{ string name; s >> ws >> name; @@ -1040,9 +1252,15 @@ void IfcRemove::execute(istream &s) symList[0]->getScope()->removeSymbol(symList[0]); } +/// \class IfcRetype +/// \brief Change the data-type of a symbol: `retype ` +/// +/// The symbol is searched for by name starting in the current function's scope. +/// If the type declaration includes a new name for the variable, the +/// variable is renamed as well. void IfcRetype::execute(istream &s) -{ // Change the type of a symbol +{ Datatype *ct; string name,newname; @@ -1074,27 +1292,66 @@ void IfcRetype::execute(istream &s) sym->getScope()->setAttribute(sym,Varnode::namelock); } } - -static Varnode *iface_read_varnode(IfaceDecompData *dcp,istream &s) -{ // Return varnode identified by input stream +/// The Varnode is selected from the \e current function. It is specified as a +/// storage location with info about its defining p-code in parantheses. +/// - `%EAX(r0x10000:0x65)` +/// - `%ECX(i)` +/// - `r0x10001000:4(:0x96)` +/// - `u0x00001100:1(:0x102)` +/// - `#0x1(0x10205:0x27)` +/// +/// The storage address space is given as the \e short-cut character followed by the +/// address offset. For register spaces, the name of the register can be given instead of the +/// offset. After the offset, a size can be specified with a ':' followed by the size in bytes. +/// If size is not provided and there is no register name, a default word size is assigned based +/// on the address space. +/// +/// The defining p-code op is specified either as: +/// - An address and sequence number: `%EAX(r0x10000:0x65)` +/// - Just a sequence number: `%EAX(:0x65)` or +/// - An "i" token for inputs: `%EAX(i)` +/// +/// For a constant Varnode, the storage offset is the actual value of the constant, and +/// the p-code address and sequence number must both be present and specify the p-code op +/// that \e reads the constant. +/// \param s is the given input stream +/// \return the Varnode object +Varnode *IfaceDecompData::readVarnode(istream &s) + +{ uintm uq; int4 defsize; Varnode *vn = (Varnode *)0; - if (dcp->fd == (Funcdata *)0) + if (fd == (Funcdata *)0) throw IfaceExecutionError("No function selected"); Address pc; - Address loc(parse_varnode(s,defsize,pc,uq,*dcp->conf->types)); - if (pc.isInvalid()&&(uq==~((uintm)0))) - vn = dcp->fd->findVarnodeInput(defsize,loc); + Address loc(parse_varnode(s,defsize,pc,uq,*conf->types)); + if (loc.getSpace()->getType() == IPTR_CONSTANT) { + if (pc.isInvalid() || (uq == ~((uintm)0))) + throw IfaceParseError("Missing p-code sequence number"); + SeqNum seq(pc,uq); + PcodeOp *op = fd->findOp(seq); + if (op != (PcodeOp *)0) { + for(int4 i=0;inumInput();++i) { + Varnode *tmpvn = op->getIn(i); + if (tmpvn->getAddr() == loc) { + vn = tmpvn; + break; + } + } + } + } + else if (pc.isInvalid()&&(uq==~((uintm)0))) + vn = fd->findVarnodeInput(defsize,loc); else if ((!pc.isInvalid())&&(uq!=~((uintm)0))) - vn = dcp->fd->findVarnodeWritten(defsize,loc,pc,uq); + vn = fd->findVarnodeWritten(defsize,loc,pc,uq); else { VarnodeLocSet::const_iterator iter,enditer; - iter = dcp->fd->beginLoc(defsize,loc); - enditer = dcp->fd->endLoc(defsize,loc); + iter = fd->beginLoc(defsize,loc); + enditer = fd->endLoc(defsize,loc); while(iter != enditer) { vn = *iter++; if (vn->isFree()) continue; @@ -1110,24 +1367,38 @@ static Varnode *iface_read_varnode(IfaceDecompData *dcp,istream &s) return vn; } +/// \class IfcPrintVarnode +/// \brief Print information about a Varnode: `print varnode ` +/// +/// Attributes of the indicated Varnode from the \e current function are printed +/// to the console. If the Varnode belongs to a HighVariable, information about +/// it and all its Varnodes are printed as well. void IfcPrintVarnode::execute(istream &s) -{ // Print information about a varnode +{ Varnode *vn; - vn = iface_read_varnode(dcp,s); + vn = dcp->readVarnode(s); if (vn->isAnnotation()||(!dcp->fd->isHighOn())) vn->printInfo(*status->optr); else vn->getHigh()->printInfo(*status->optr); } - + +/// \class IfcPrintCover +/// \brief Print cover info about a HighVariable: `print cover high ` +/// +/// A HighVariable is specified by its symbol name in the current function's scope. +/// Information about the code ranges where the HighVariable is in scope is printed. void IfcPrintCover::execute(istream &s) -{ // Print4 coverage information about a high +{ HighVariable *high; string name; + if (dcp->fd == (Funcdata *)0) + throw IfaceExecutionError("No function selected"); + s >> ws >> name; if (name.size()==0) throw IfaceParseError("Missing variable name"); @@ -1138,12 +1409,17 @@ void IfcPrintCover::execute(istream &s) high->printCover(*status->optr); } +/// \class IfcVarnodehighCover +/// \brief Print cover info about a HighVariable: `print cover varnodehigh ` +/// +/// The HighVariable is selected by specifying one of its Varnodes. +/// Information about the code ranges where the HighVariable is in scope is printed. void IfcVarnodehighCover::execute(istream &s) -{ // Print4 coverage information about a varnode's high +{ Varnode *vn; - vn = iface_read_varnode(dcp,s); + vn = dcp->readVarnode(s); if (vn == (Varnode *)0) throw IfaceParseError("Unknown varnode"); if (vn->getHigh() != (HighVariable *)0) @@ -1152,6 +1428,12 @@ void IfcVarnodehighCover::execute(istream &s) *status->optr << "Unmerged" << endl; } +/// \class IfcPrintExtrapop +/// \brief Print change to stack pointer for called function: `print extrapop []` +/// +/// For the selected function, the extra amount each called function changes the stack pointer +/// (over popping the return value) is printed to console. The function is selected by +/// name, or if no name is given, the \e current function is selected. void IfcPrintExtrapop::execute(istream &s) { @@ -1224,17 +1506,28 @@ void IfcPrintExtrapop::execute(istream &s) } } +/// \class IfcVarnodeCover +/// \brief Print cover information about a Varnode: `print cover varnode ` +/// +/// Information about code ranges where the single Varnode is in scope are printed. void IfcVarnodeCover::execute(istream &s) -{ // Print4 coverage information about a varnode +{ Varnode *vn; - vn = iface_read_varnode(dcp,s); + vn = dcp->readVarnode(s); if (vn == (Varnode *)0) throw IfaceParseError("Unknown varnode"); vn->printCover(*status->optr); } +/// \class IfcNameVarnode +/// \brief Attach a named symbol to a specific Varnode: `name varnode ` +/// +/// A new local symbol is created for the \e current function, and +/// is attached to the specified Varnode. The \e current function must be decompiled +/// again to see the effects. The new symbol is \e name-locked with the specified +/// name, but the data-type of the symbol is allowed to float. void IfcNameVarnode::execute(istream &s) { @@ -1266,9 +1559,17 @@ void IfcNameVarnode::execute(istream &s) *status->fileoptr << " to scope " << scope->getFullName() << endl; } +/// \class IfcTypeVarnode +/// \brief Attach a typed symbol to a specific Varnode: `type varnode ` +/// +/// A new local symbol is created for the \e current function, and +/// is attached to the specified Varnode. The \e current function must be decompiled +/// again to see the effects. The new symbol is \e type-locked with the data-type specified +/// in the type declaration. If a name is specified in the declaration, the symbol +/// is \e name-locked as well. void IfcTypeVarnode::execute(istream &s) -{ // Set the type of a varnode +{ int4 size; uintm uq; Datatype *ct; @@ -1296,38 +1597,18 @@ void IfcTypeVarnode::execute(istream &s) *status->fileoptr << " to scope " << scope->getFullName() << endl; } -static Varnode *find_varnode_via_op(istream &s,Funcdata *fd,const TypeFactory &typegrp) - -{ - uintm uq; - Address pc(parse_op(s,uq,typegrp)); - PcodeOp *op = fd->findOp(SeqNum(pc,uq)); - if (op == (PcodeOp *)0) - throw IfaceExecutionError("Unable to find indicated op"); - - int4 slot; - slot = -2; - s >> dec >> slot; - if (slot == -2) - throw IfaceParseError("Missing op slot number"); - Varnode *vn; - if (slot == -1) - vn = op->getOut(); - else { - if (slot >= op->numInput()) - throw IfaceExecutionError("op slot number is out of range"); - vn = op->getIn(slot); - } - return vn; -} - +/// \class IfcForceHex +/// \brief Mark a constant to be printed in hex format: `force hex ` +/// +/// A selected constant Varnode in the \e current function is marked so +/// that it will be printed in hexadecimal format in decompiler output. void IfcForceHex::execute(istream &s) { if (dcp->fd == (Funcdata *)0) throw IfaceExecutionError("No function selected"); - Varnode *vn = find_varnode_via_op(s,dcp->fd,*dcp->conf->types); + Varnode *vn = dcp->readVarnode(s); if (!vn->isConstant()) throw IfaceExecutionError("Can only force hex on a constant"); type_metatype mt = vn->getType()->getMetatype(); @@ -1342,13 +1623,18 @@ void IfcForceHex::execute(istream &s) *status->optr << "Successfully forced hex display" << endl; } +/// \class IfcForceDec +/// \brief Mark a constant to be printed in decimal format: `force dec ` +/// +/// A selected constant Varnode in the \e current function is marked so +/// that it will be printed in decimal format in decompiler output. void IfcForceDec::execute(istream &s) { if (dcp->fd == (Funcdata *)0) throw IfaceExecutionError("No function selected"); - Varnode *vn = find_varnode_via_op(s,dcp->fd,*dcp->conf->types); + Varnode *vn = dcp->readVarnode(s); if (!vn->isConstant()) throw IfaceExecutionError("Can only force hex on a constant"); type_metatype mt = vn->getType()->getMetatype(); @@ -1363,11 +1649,21 @@ void IfcForceDec::execute(istream &s) *status->optr << "Successfully forced dec display" << endl; } +/// \class IfcForcegoto +/// \brief Force a branch to be an unstructured \b goto: `force goto ` +/// +/// Create an override that forces the decompiler to treat the specified branch +/// as unstructured. The branch will be modeled as a \b goto statement. +/// The branch is specified by first providing the address of the branching instruction, +/// then the destination address. void IfcForcegoto::execute(istream &s) { int4 discard; + if (dcp->fd == (Funcdata *)0) + throw IfaceExecutionError("No function selected"); + s >> ws; Address target(parse_machaddr(s,discard,*dcp->conf->types)); s >> ws; @@ -1375,6 +1671,13 @@ void IfcForcegoto::execute(istream &s) dcp->fd->getOverride().insertForceGoto(target,dest); } +/// \class IfcProtooverride +/// \brief Override the prototype of a called function: `override prototype
` +/// +/// Force a specified prototype declaration on a called function when decompiling +/// the current function. The current function must be decompiled again to see the effect. +/// The called function is indicated by the address of its calling instruction. +/// The prototype only affects decompilation for the \e current function. void IfcProtooverride::execute(istream &s) { @@ -1403,6 +1706,20 @@ void IfcProtooverride::execute(istream &s) dcp->fd->clear(); // Clear any analysis (this leaves overrides intact) } +/// \class IfcJumpOverride +/// \brief Provide an overriding jump-table for an indirect branch: `override jumptable ...` +/// +/// The command expects the address of an indirect branch in the \e current function, +/// followed by the keyword \b table then a list of possible target addresses of the branch. +/// \code +/// override jumptable r0x1000 table r0x1020 r0x1030 r0x1043 ... +/// \endcode +/// The command can optionally take the keyword \b startval followed by an +/// integer indicating the value taken by the \e normalized switch variable that +/// produces the first address in the table. +/// \code +/// override jumptable startval 10 table r0x1020 r0x1030 ... +/// \endcode void IfcJumpOverride::execute(istream &s) { @@ -1444,6 +1761,15 @@ void IfcJumpOverride::execute(istream &s) *status->optr << "Successfully installed jumptable override" << endl; } +/// \class IfcFlowOverride +/// \brief Create a control-flow override: `override flow
branch|call|callreturn|return` +/// +/// Change the nature of the control-flow at the specified address, as indicated by the +/// final token on the command-line: +/// - branch - Change the CALL or RETURN to a BRANCH +/// - call - Change a BRANCH or RETURN to a CALL +/// - callreturn - Change a BRANCH or RETURN to a CALL followed by a RETURN +/// - return - Change a CALLIND or BRANCHIND to a RETURN void IfcFlowOverride::execute(istream &s) { @@ -1467,6 +1793,13 @@ void IfcFlowOverride::execute(istream &s) *status->optr << "Successfully added override" << endl; } +/// \class IfcDeadcodedelay +/// \brief Change when dead code elimination starts: `deadcode delay ` +/// +/// An address space is selected by name, along with a pass number. +/// Dead code elimination for Varnodes in that address space is changed to start +/// during that pass. If there is a \e current function, the delay is altered only for +/// that function, otherwise the delay is set globally for all functions. void IfcDeadcodedelay::execute(istream &s) { @@ -1493,6 +1826,11 @@ void IfcDeadcodedelay::execute(istream &s) } } +/// \class IfcGlobalAdd +/// \brief Add a memory range as discoverable global variables: `global add ` +/// +/// The decompiler will treat Varnodes stored in the new memory range as persistent +/// global variables. void IfcGlobalAdd::execute(istream &s) { @@ -1508,6 +1846,11 @@ void IfcGlobalAdd::execute(istream &s) dcp->conf->symboltab->addRange(scope,addr.getSpace(),first,last); } +/// \class IfcGlobalRemove +/// \brief Remove a memory range from discoverable global variables: `global remove ` +/// +/// The will no longer treat Varnodes stored in the memory range as persistent global +/// variables. The will be treated as local or temporary storage. void IfcGlobalRemove::execute(istream &s) { @@ -1523,6 +1866,11 @@ void IfcGlobalRemove::execute(istream &s) dcp->conf->symboltab->removeRange(scope,addr.getSpace(),first,last); } +/// \class IfcGlobalify +/// \brief Treat all normal memory as discoverable global variables: `global spaces` +/// +/// This has the drastic effect that the decompiler will treat all registers and stack +/// locations as global variables. void IfcGlobalify::execute(istream &s) { @@ -1532,6 +1880,10 @@ void IfcGlobalify::execute(istream &s) *status->optr << "Successfully made all registers/memory locations global" << endl; } +/// \class IfcGlobalRegisters +/// \brief Name global registers: `global registers` +/// +/// Name any global symbol stored in a register with the name of the register. void IfcGlobalRegisters::execute(istream &s) { @@ -1567,7 +1919,11 @@ void IfcGlobalRegisters::execute(istream &s) *status->optr << "Successfully made a global symbol for " << count << " registers" << endl; } -static bool nontrivial_use(Varnode *vn) +/// The use is non-trivial if it can be traced to any p-code operation except +/// a COPY, CAST, INDIRECT, or MULTIEQUAL. +/// \param vn is the given Varnode +/// \return \b true if there is a non-trivial use +bool IfcPrintInputs::nonTrivialUse(Varnode *vn) { vector vnlist; @@ -1601,9 +1957,14 @@ static bool nontrivial_use(Varnode *vn) return res; } -static int4 check_restore(Varnode *vn) +/// Look for any value flowing into the Varnode coming from anything +/// other than an input Varnode with the same storage. The value can flow through +/// a COPY, CAST, INDIRECT, or MULTIEQUAL +/// \param vn is the given Varnode +/// \return 0 if Varnode is restored, 1 otherwise +int4 IfcPrintInputs::checkRestore(Varnode *vn) -{ // Return 0 if vn is written to from an input at the same location +{ vector vnlist; int4 res = 0; vnlist.push_back(vn); @@ -1658,7 +2019,11 @@ static int4 check_restore(Varnode *vn) return res; } -static bool find_restore(Varnode *vn,Funcdata *fd) +/// For the given storage location, check that it is \e restored +/// from its original input value. +/// \param vn is the given storage location +/// \param fd is the function being analyzed +bool IfcPrintInputs::findRestore(Varnode *vn,Funcdata *fd) { VarnodeLocSet::const_iterator iter,enditer; @@ -1673,14 +2038,18 @@ static bool find_restore(Varnode *vn,Funcdata *fd) if (!vn->isWritten()) continue; PcodeOp *op = vn->getDef(); if (op->code() == CPUI_INDIRECT) continue; // Not a global return address force - int4 res = check_restore(vn); + int4 res = checkRestore(vn); if (res != 0) return false; count += 1; } return (count>0); } -static void print_function_inputs(Funcdata *fd,ostream &s) +/// For each input Varnode, print information about the Varnode, +/// any explicit symbol it represents, and info about how the value is used. +/// \param fd is the function +/// \param s is the output stream to write to +void IfcPrintInputs::print(Funcdata *fd,ostream &s) { VarnodeDefSet::const_iterator iter,enditer; @@ -1697,8 +2066,8 @@ static void print_function_inputs(Funcdata *fd,ostream &s) if (sym != (Symbol *)0) s << " " << sym->getName(); } - bool findres = find_restore(vn,fd); - bool nontriv = nontrivial_use(vn); + bool findres = findRestore(vn,fd); + bool nontriv = nonTrivialUse(vn); if (findres && !nontriv) s << " restored"; else if (nontriv) @@ -1707,15 +2076,21 @@ static void print_function_inputs(Funcdata *fd,ostream &s) } } +/// \class IfcPrintInputs +/// \brief Print info about the current function's input Varnodes: `print inputs` void IfcPrintInputs::execute(istream &s) { if (dcp->fd == (Funcdata *)0) throw IfaceExecutionError("No function selected"); - print_function_inputs(dcp->fd,*status->fileoptr); + print(dcp->fd,*status->fileoptr); } +/// \class IfcPrintInputsAll +/// \brief Print info about input Varnodes for all functions: `print inputs all` +/// +/// Each function is decompiled, and info about its input Varnodes are printed. void IfcPrintInputsAll::execute(istream &s) { @@ -1736,7 +2111,7 @@ void IfcPrintInputsAll::iterationCallback(Funcdata *fd) dcp->conf->clearAnalysis(fd); // Clear any old analysis dcp->conf->allacts.getCurrent()->reset(*fd); dcp->conf->allacts.getCurrent()->perform( *fd ); - print_function_inputs(fd,*status->fileoptr); + IfcPrintInputs::print(fd,*status->fileoptr); } catch(LowlevelError &err) { *status->optr << "Skipping " << fd->getName() << ": " << err.explain << endl; @@ -1744,6 +2119,11 @@ void IfcPrintInputsAll::iterationCallback(Funcdata *fd) dcp->conf->clearAnalysis(fd); } +/// \class IfcLockPrototype +/// \brief Lock in the \e current function's prototype: `prototype lock` +/// +/// Lock in the existing formal parameter names and data-types for any future +/// decompilation. Both input parameters and the return value are locked. void IfcLockPrototype::execute(istream &s) { @@ -1754,6 +2134,11 @@ void IfcLockPrototype::execute(istream &s) dcp->fd->getFuncProto().setOutputLock(true); } +/// \class IfcUnlockPrototype +/// \brief Unlock the \e current function's prototype: `prototype unlock` +/// +/// Unlock all input parameters and the return value, so future decompilation +/// is not constrained with their data-type or name. void IfcUnlockPrototype::execute(istream &s) { @@ -1764,6 +2149,11 @@ void IfcUnlockPrototype::execute(istream &s) dcp->fd->getFuncProto().setOutputLock(false); } +/// \class IfcPrintLocalrange +/// \brief Print range of locals on the stack: `print localrange` +/// +/// Print the memory range(s) on the stack is or could be used for +/// void IfcPrintLocalrange::execute(istream &s) { @@ -1773,6 +2163,11 @@ void IfcPrintLocalrange::execute(istream &s) dcp->fd->printLocalRange( *status->optr ); } +/// \class IfcPrintMap +/// \brief Print info about a scope/namespace: `print map ` +/// +/// Prints information about the discoverable memory ranges for the scope, +/// and prints a description of every symbol in the scope. void IfcPrintMap::execute(istream &s) { @@ -1797,10 +2192,15 @@ void IfcPrintMap::execute(istream &s) scope->printBounds(*status->fileoptr); scope->printEntries(*status->fileoptr); } - + +/// \class IfcProduceC +/// \brief Write decompilation for all functions to a file: `produce C ` +/// +/// Iterate over all functions in the program. For each function, decompilation is +/// performed and output is appended to the file. void IfcProduceC::execute(istream &s) -{ // Produce C output of every known function +{ string name; s >> ws >> name; @@ -1846,9 +2246,13 @@ void IfcProduceC::iterationCallback(Funcdata *fd) dcp->conf->clearAnalysis(fd); } +/// \class IfcProducePrototypes +/// \brief Determine the prototype model for all functions: `produce prototypes` +/// +/// Functions are walked in leaf order. void IfcProducePrototypes::execute(istream &s) -{ // Walk callgraph in leaf-first order, calculate prototype +{ if (dcp->conf == (Architecture *)0) throw IfaceExecutionError("No load image"); if (dcp->cgraph == (CallGraph *)0) @@ -1905,9 +2309,13 @@ void IfcProducePrototypes::iterationCallback(Funcdata *fd) dcp->conf->clearAnalysis(fd); } +/// \class IfcContinue +/// \brief Continue decompilation after a break point: `continue` +/// +/// This command assumes decompilation has been started and has hit a break point. void IfcContinue::execute(istream &s) -{ // Continue decompilation after breakpoint +{ int4 res; if (dcp->conf == (Architecture *)0) @@ -1934,11 +2342,19 @@ void IfcContinue::execute(istream &s) *status->optr << endl; } +/// \class IfcGraphDataflow +/// \brief Write a graph representation of data-flow to a file: `graph dataflow ` +/// +/// The data-flow graph for the \e current function, in its current state of transform, +/// is written to the indicated file. void IfcGraphDataflow::execute(istream &s) -{ // Dump data-flow graph +{ string filename; + if (dcp->fd == (Funcdata *)0) + throw IfaceExecutionError("No function selected"); + s >> filename; if (filename.size()==0) throw IfaceParseError("Missing output file"); @@ -1952,11 +2368,19 @@ void IfcGraphDataflow::execute(istream &s) thefile.close(); } +/// \class IfcGraphControlflow +/// \brief Write a graph representation of control-flow to a file: `graph controlflow ` +/// +/// The control-flow graph for the \e current function, in its current state of transform, +/// is written to the indicated file. void IfcGraphControlflow::execute(istream &s) -{ // Dump control-flow graph +{ string filename; + if (dcp->fd == (Funcdata *)0) + throw IfaceExecutionError("No function selected"); + s >> filename; if (filename.size()==0) throw IfaceParseError("Missing output file"); @@ -1970,11 +2394,19 @@ void IfcGraphControlflow::execute(istream &s) thefile.close(); } +/// \class IfcGraphDom +/// \brief Write the forward dominance graph to a file: `graph dom ` +/// +/// The dominance tree, associated with the control-flow graph of the \e current function +/// in its current state of transform, is written to the indicated file. void IfcGraphDom::execute(istream &s) -{ // Dump forward dominator graph +{ string filename; + if (dcp->fd == (Funcdata *)0) + throw IfaceExecutionError("No function selected"); + s >> filename; if (filename.size()==0) throw IfaceParseError("Missing output file"); @@ -1988,6 +2420,13 @@ void IfcGraphDom::execute(istream &s) thefile.close(); } +/// \class IfcCommentInstr +/// \brief Attach a comment to an address: `comment
comment text...` +/// +/// Add a comment to the database, suitable for integration into decompiler output +/// for the \e current function. The command-line takes the address of the +/// machine instruction which the comment will be attached to and is followed by +/// the text of the comment. void IfcCommentInstr::execute(istream &s) { // Comment on a particular address within current function @@ -2012,9 +2451,12 @@ void IfcCommentInstr::execute(istream &s) dcp->fd->getAddress(),addr,comment); } -static void duplicate_hash(Funcdata *fd,ostream &s) +/// For each duplicate discovered, a message is written to the provided stream. +/// \param fd is the given function to search +/// \param s is the stream to write messages to +void IfcDuplicateHash::check(Funcdata *fd,ostream &s) -{ // Make sure no two varnodes of -fd- have the same hash +{ DynamicHash dhash; VarnodeLocSet::const_iterator iter,enditer; @@ -2070,9 +2512,14 @@ static void duplicate_hash(Funcdata *fd,ostream &s) } } +/// \class IfcDuplicateHash +/// \brief Check for duplicate hashes in functions: `duplicate hash` +/// +/// All functions in the architecture/program are decompiled, and for each +/// a check is made for Varnode pairs with identical hash values. void IfcDuplicateHash::execute(istream &s) -{ // Make sure no two varnodes in the +{ iterateFunctionsAddrOrder(); } @@ -2098,7 +2545,7 @@ void IfcDuplicateHash::iterationCallback(Funcdata *fd) duration = ((float)(end_time-start_time))/CLOCKS_PER_SEC; duration *= 1000.0; *status->optr << " time=" << fixed << setprecision(0) << duration << " ms" << endl; - duplicate_hash(fd,*status->optr); + check(fd,*status->optr); } catch(LowlevelError &err) { *status->optr << "Skipping " << fd->getName() << ": " << err.explain << endl; @@ -2106,10 +2553,15 @@ void IfcDuplicateHash::iterationCallback(Funcdata *fd) dcp->conf->clearAnalysis(fd); } - +/// \class IfcCallGraphBuild +/// \brief Build the call-graph for the architecture/program: `callgraph build` +/// +/// Build, or rebuild, the call-graph with nodes for all existing functions. +/// Functions are to decompiled to recover destinations of indirect calls. +/// Going forward, the graph is held in memory and is accessible by other commands. void IfcCallGraphBuild::execute(istream &s) -{ // Build call graph from existing function starts +{ dcp->allocateCallGraph(); dcp->cgraph->buildAllNodes(); // Build a node in the graph for existing symbols @@ -2130,7 +2582,7 @@ void IfcCallGraphBuild::iterationCallback(Funcdata *fd) } if (quick) { dcp->fd = fd; - IfcFollowFlow(*status->optr,dcp,fd->getAddress(),0); + dcp->followFlow(*status->optr,0); } else { try { @@ -2154,9 +2606,15 @@ void IfcCallGraphBuild::iterationCallback(Funcdata *fd) dcp->conf->clearAnalysis(fd); } +/// \class IfcCallGraphBuildQuick +/// \brief Build the call-graph using quick analysis: `callgraph build quick` +/// +/// Build the call-graph for the architecture/program. For each function, disassembly +/// is performed to discover call edges, rather then full decompilation. Some forms +/// of direct call may not be discovered. void IfcCallGraphBuildQuick::execute(istream &s) -{ // Build call graph from existing function starts, do only disassembly +{ dcp->allocateCallGraph(); dcp->cgraph->buildAllNodes(); // Build a node in the graph for existing symbols quick = true; @@ -2164,6 +2622,11 @@ void IfcCallGraphBuildQuick::execute(istream &s) *status->optr << "Successfully built callgraph" << endl; } +/// \class IfcCallGraphDump +/// \brief Write the current call-graph to a file: `callgraph dump ` +/// +/// The existing call-graph object is written to the provided file as an +/// XML document. void IfcCallGraphDump::execute(istream &s) { @@ -2185,6 +2648,12 @@ void IfcCallGraphDump::execute(istream &s) *status->optr << "Successfully saved callgraph to " << name << endl; } +/// \class IfcCallGraphLoad +/// \brief Load the call-graph from a file: `callgraph load ` +/// +/// A call-graph is loaded from the provided XML document. Nodes in the +/// call-graph are linked to existing functions by symbol name. This command +/// reports call-graph nodes that could not be linked. void IfcCallGraphLoad::execute(istream &s) { @@ -2227,9 +2696,14 @@ void IfcCallGraphLoad::execute(istream &s) *status->optr << "Successfully associated functions with callgraph nodes" << endl; } +/// \class IfcCallGraphList +/// \brief List all functions in \e leaf order: `callgraph list` +/// +/// The existing call-graph is walked, displaying function names to the console. +/// Child functions are displayed before their parents. void IfcCallGraphList::execute(istream &s) -{ // List all functions in leaf-first order +{ if (dcp->cgraph == (CallGraph *)0) throw IfaceExecutionError("Callgraph not generated"); @@ -2242,8 +2716,19 @@ void IfcCallGraphList::iterationCallback(Funcdata *fd) *status->optr << fd->getName() << endl; } -static void readPcodeSnippet(istream &s,string &name,string &outname,vector &inname,string &pcodestring) - +/// \brief Scan a single-line p-code snippet declaration from the given stream +/// +/// A declarator is scanned first, providing a name to associate with the snippet, as well +/// as potential names of the formal \e output Varnode and \e input Varnodes. +/// The body of the snippet is then surrounded by '{' and '}' The snippet name, +/// input/output names, and the body are passed back to the caller. +/// \param s is the given stream to scan +/// \param name passes back the name of the snippet +/// \param outname passes back the formal output parameter name, or is empty +/// \param inname passes back an array of the formal input parameter names +/// \param pcodestring passes back the snippet body +void IfcCallFixup::readPcodeSnippet(istream &s,string &name,string &outname,vector &inname, + string &pcodestring) { char bracket; s >> outname; @@ -2266,6 +2751,16 @@ static void readPcodeSnippet(istream &s,string &name,string &outname,vectorprintTemplate(*status->optr); } +/// \class IfcCallOtherFixup +/// \brief Add a new callother fix-up to the program: `fixup callother ...` +/// +/// The new fix-up is suitable for replacing specific user-defined (CALLOTHER) +/// p-code operations. The declarator provides the name of the fix-up and can also +/// provide formal input and output parameters. +/// \code +/// fixup callother outvar myfixup2(invar1,invar2) { outvar = invar1 + invar2; } +/// \endcode void IfcCallOtherFixup::execute(istream &s) { string useropname,outname,pcodestring; vector inname; - readPcodeSnippet(s,useropname,outname,inname,pcodestring); + IfcCallFixup::readPcodeSnippet(s,useropname,outname,inname,pcodestring); dcp->conf->userops.manualCallOtherFixup(useropname,outname,inname,pcodestring,dcp->conf); *status->optr << "Successfully registered callotherfixup" << endl; } +/// \class IfcVolatile +/// \brief Mark a memory range as volatile: `volatile ` +/// +/// The memory range provided on the command-line is marked as \e volatile, warning +/// the decompiler analysis that values in the range my change unexpectedly. void IfcVolatile::execute(istream &s) -{ // Mark a range as volatile +{ int4 size = 0; if (dcp->conf == (Architecture *)0) throw IfaceExecutionError("No load image present"); @@ -2312,6 +2821,12 @@ void IfcVolatile::execute(istream &s) *status->optr << "Successfully marked range as volatile" << endl; } +/// \class IfcReadonly +/// \brief Mark a memory range as read-only: `readonly ` +/// +/// The memory range provided on the command-line is marked as \e read-only, which +/// allows the decompiler to propagate values pulled from the LoadImage for the range +/// as constants. void IfcReadonly::execute(istream &s) { @@ -2328,9 +2843,17 @@ void IfcReadonly::execute(istream &s) *status->optr << "Successfully marked range as readonly" << endl; } +/// \class IfcPreferSplit +/// \brief Mark a storage location to be split: `prefersplit ` +/// +/// The storage location is marked for splitting in any future decompilation. +/// During decompilation, any Varnode matching the storage location on the command-line +/// will be generally split into two pieces, where the final command-line parameter +/// indicates the number of bytes in the first piece. A Varnode is split only if operations +/// involving it can also be split. See PreferSplitManager. void IfcPreferSplit::execute(istream &s) -{ // Mark a particular storage location as something we would prefer to split +{ int4 size = 0; if (dcp->conf == (Architecture *)0) throw IfaceExecutionError("No load image present"); @@ -2356,9 +2879,14 @@ void IfcPreferSplit::execute(istream &s) *status->optr << "Successfully added split record" << endl; } +/// \class IfcStructureBlocks +/// \brief Structure an external control-flow graph: `structure blocks ` +/// +/// The control-flow graph is read in from XML file, structuring is performed, and the +/// result is written out to a separate XML file. void IfcStructureBlocks::execute(istream &s) -{ // Read in a control description file, structure the result and write it out +{ if (dcp->conf == (Architecture *)0) throw IfaceExecutionError("No load image present"); @@ -2459,6 +2987,12 @@ void IfcExperimentalRules::execute(istream &s) } #endif +/// \class IfcPrintActionstats +/// \brief Print transform statistics for the decompiler engine: `print actionstats` +/// +/// Counts for each Action and Rule are displayed; showing the number of attempts, +/// both successful and not, that were made to apply each one. Counts can accumulate +/// over multiple decompilations. void IfcPrintActionstats::execute(istream &s) { @@ -2470,6 +3004,10 @@ void IfcPrintActionstats::execute(istream &s) dcp->conf->allacts.getCurrent()->printStatistics(*status->fileoptr); } +/// \class IfcResetActionstats +/// \brief Reset transform statistics for the decompiler engine: `reset actionstats` +/// +/// Counts for each Action and Rule are reset to zero. void IfcResetActionstats::execute(istream &s) { @@ -2481,6 +3019,11 @@ void IfcResetActionstats::execute(istream &s) dcp->conf->allacts.getCurrent()->resetStats(); } +/// \class IfcCountPcode +/// \brief Count p-code in the \e current function: `count pcode` +/// +/// The count is based on the number of existing p-code operations in +/// the current function, which may vary depending on the state of it transformation. void IfcCountPcode::execute(istream &s) { @@ -2501,6 +3044,15 @@ void IfcCountPcode::execute(istream &s) *status->optr << "Count - pcode = " << dec << count << endl; } +/// \class IfcAnalyzeRange +/// \brief Run value-set analysis on the \e current function: `analyze range full|partial ` +/// +/// The analysis targets a single varnode as specified on the command-line and is based on +/// the existing data-flow graph for the current function. +/// The possible values that can reach the varnode at its point of definition, and +/// at any point it is involved in a LOAD or STORE, are displayed. +/// The keywords \b full and \b partial choose whether the value-set analysis uses +/// full or partial widening. void IfcAnalyzeRange::execute(istream &s) { @@ -2519,7 +3071,7 @@ void IfcAnalyzeRange::execute(istream &s) } else throw IfaceParseError("Must specify \"full\" or \"partial\" widening"); - Varnode *vn = iface_read_varnode(dcp,s); + Varnode *vn = dcp->readVarnode(s); vector sinks; vector reads; sinks.push_back(vn); @@ -2713,11 +3265,16 @@ void IfcBreakjump::execute(istream &s) #endif +/// Execute one command and handle any exceptions. +/// Error messages are printed to the console. For low-level errors, +/// the current function is reset to null +/// \param status is the console interface +/// \param dcp is the shared program data void execute(IfaceStatus *status,IfaceDecompData *dcp) -{ // Execute and catch exceptions +{ try { - status->runCommand(); // Try to run one commandline + status->runCommand(); // Try to run one command-line return; } catch(IfaceParseError &err) { @@ -2746,7 +3303,13 @@ void execute(IfaceStatus *status,IfaceDecompData *dcp) status->evaluateError(); } -void mainloop(IfaceStatus *status) { +/// Execution loops until either the \e done field in the console is set +/// or if all streams have ended. This handles popping script states pushed +/// on by the IfcSource command. +/// \param status is the console interface +void mainloop(IfaceStatus *status) + +{ IfaceDecompData *dcp = (IfaceDecompData *)status->getData("decompile"); for(;;) { while(!status->isStreamFinished()) { @@ -2760,6 +3323,11 @@ void mainloop(IfaceStatus *status) { } } +/// \class IfcSource +/// \brief Execute a command script : `source ` +/// +/// A file is opened as a new streaming source of command-lines. +/// The stream is pushed onto the stack for the console. void IfcSource::execute(istream &s) { diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.hh index 6f73281c43..16f9f89838 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.hh @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -// Interface to the decompilation routines +/// \file ifacedecomp.hh +/// \brief Console interface commands for the decompiler engine #ifndef __IFACE_DECOMP__ #define __IFACE_DECOMP__ @@ -27,20 +28,22 @@ #include "rulecompile.hh" #endif +/// \brief Interface capability point for all decompiler commands class IfaceDecompCapability : public IfaceCapability { - static IfaceDecompCapability ifaceDecompCapability; // Singleton instance - IfaceDecompCapability(void); // Singleton - IfaceDecompCapability(const IfaceDecompCapability &op2); // Not implemented - IfaceDecompCapability &operator=(const IfaceDecompCapability &op2); // Not implemented + static IfaceDecompCapability ifaceDecompCapability; ///< Singleton instance + IfaceDecompCapability(void); ///< Singleton constructor + IfaceDecompCapability(const IfaceDecompCapability &op2); ///< Not implemented + IfaceDecompCapability &operator=(const IfaceDecompCapability &op2); ///< Not implemented public: virtual void registerCommands(IfaceStatus *status); }; +/// \brief Common data shared by decompiler commands class IfaceDecompData : public IfaceData { public: - Funcdata *fd; // Current function data - Architecture *conf; - CallGraph *cgraph; + Funcdata *fd; ///< Current function active in the console + Architecture *conf; ///< Current architecture/program active in the console + CallGraph *cgraph; ///< Call-graph information for the program map prototypePieces; void storePrototypePieces( Funcdata *fd_in, PrototypePieces pp_in ) { prototypePieces.insert(pair(fd_in,pp_in)); } @@ -52,18 +55,24 @@ public: #ifdef OPACTION_DEBUG bool jumptabledebug; #endif - IfaceDecompData(void); + IfaceDecompData(void); ///< Constructor virtual ~IfaceDecompData(void); - void allocateCallGraph(void); - void abortFunction(ostream &s); - void clearArchitecture(void); + void allocateCallGraph(void); ///< Allocate the call-graph object + void abortFunction(ostream &s); ///< Clear references to current function + void clearArchitecture(void); ///< Free all resources for the current architecture/program + void followFlow(ostream &s,int4 size); + Varnode *readVarnode(istream &s); ///< Read a varnode from the given stream }; +/// \brief Disassembly emitter that prints to a console stream +/// +/// An instruction is printed to a stream simply, as an address +/// followed by the mnemonic and then column aligned operands. class IfaceAssemblyEmit : public AssemblyEmit { - int4 mnemonicpad; // How much to pad the mnemonic - ostream *s; + int4 mnemonicpad; ///< How much to pad the mnemonic + ostream *s; ///< The current stream to write to public: - IfaceAssemblyEmit(ostream *val,int4 mp) { s = val; mnemonicpad=mp; } + IfaceAssemblyEmit(ostream *val,int4 mp) { s = val; mnemonicpad=mp; } ///< Constructor virtual void dump(const Address &addr,const string &mnem,const string &body) { addr.printRaw(*s); *s << ": " << mnem; @@ -72,22 +81,31 @@ public: } }; -extern void execute(IfaceStatus *status,IfaceDecompData *dcp); -extern void mainloop(IfaceStatus *status); +extern void execute(IfaceStatus *status,IfaceDecompData *dcp); ///< Execute one command for the console +extern void mainloop(IfaceStatus *status); ///< Execute commands as they become available +/// \brief Root class for all decompiler specific commands +/// +/// Commands share the data object IfaceDecompData and are capable of +/// iterating over all functions in the program/architecture. class IfaceDecompCommand : public IfaceCommand { protected: - IfaceStatus *status; - IfaceDecompData *dcp; - void iterateScopesRecursive(Scope *scope); - void iterateFunctionsAddrOrder(Scope *scope); + IfaceStatus *status; ///< The console owning \b this command + IfaceDecompData *dcp; ///< Data common to decompiler commands + void iterateScopesRecursive(Scope *scope); ///< Iterate recursively over all functions in given scope + void iterateFunctionsAddrOrder(Scope *scope); ///< Iterate over all functions in a given scope public: virtual void setData(IfaceStatus *root,IfaceData *data) { status = root; dcp = (IfaceDecompData *)data; } virtual string getModule(void) const { return "decompile"; } virtual IfaceData *createData(void) { return new IfaceDecompData(); } + + /// \brief Perform the per-function aspect of \b this command. + /// + /// \param fd is the particular function to operate on virtual void iterationCallback(Funcdata *fd) {} - void iterateFunctionsAddrOrder(void); - void iterateFunctionsLeafOrder(void); + + void iterateFunctionsAddrOrder(void); ///< Iterate command over all functions in all scopes + void iterateFunctionsLeafOrder(void); ///< Iterate command over all functions in a call-graph traversal }; class IfcSource : public IfaceDecompCommand { @@ -257,11 +275,6 @@ public: virtual void execute(istream &s); }; -class IfcBreakjump : public IfaceDecompCommand { -public: - virtual void execute(istream &s); -}; - class IfcPrintTree : public IfaceDecompCommand { public: virtual void execute(istream &s); @@ -282,18 +295,10 @@ public: virtual void execute(istream &s); }; -class IfcParamIDAnalysis : public IfaceDecompCommand { -public: - virtual void execute(istream &s); -}; class IfcPrintParamMeasures : public IfaceDecompCommand { public: virtual void execute(istream &s); }; -class IfcPrintParamMeasuresXml : public IfaceDecompCommand { -public: - virtual void execute(istream &s); -}; class IfcRename : public IfaceDecompCommand { public: @@ -403,6 +408,10 @@ public: class IfcPrintInputs : public IfaceDecompCommand { public: virtual void execute(istream &s); + static bool nonTrivialUse(Varnode *vn); ///< Check for non-trivial use of given Varnode + static int4 checkRestore(Varnode *vn); ///< Check if a Varnode is \e restored to its original input value + static bool findRestore(Varnode *vn,Funcdata *fd); ///< Check if storage is \e restored + static void print(Funcdata *fd,ostream &s); ///< Print information about function inputs }; class IfcPrintInputsAll : public IfaceDecompCommand { @@ -465,6 +474,8 @@ class IfcDuplicateHash : public IfaceDecompCommand { public: virtual void execute(istream &s); virtual void iterationCallback(Funcdata *fd); + static void check(Funcdata *fd,ostream &s); ///< Check for duplicate hashes in given function + }; class IfcCallGraphDump : public IfaceDecompCommand { @@ -474,7 +485,7 @@ public: class IfcCallGraphBuild : public IfaceDecompCommand { protected: - bool quick; + bool quick; ///< Set to \b true if a quick analysis is desired public: virtual void execute(istream &s); virtual void iterationCallback(Funcdata *fd); @@ -490,8 +501,6 @@ public: }; class IfcCallGraphList : public IfaceDecompCommand { -protected: - bool quick; public: virtual void execute(istream &s); virtual void iterationCallback(Funcdata *fd); @@ -505,6 +514,8 @@ public: class IfcCallFixup : public IfaceDecompCommand { public: virtual void execute(istream &s); + static void readPcodeSnippet(istream &s,string &name,string &outname,vector &inname, + string &pcodestring); }; class IfcCallOtherFixup : public IfaceDecompCommand { @@ -557,12 +568,12 @@ class IfcParseRule : public IfaceDecompCommand { public: virtual void execute(istream &s); }; -#endif class IfcExperimentalRules : public IfaceDecompCommand { public: virtual void execute(istream &s); }; +#endif #ifdef OPACTION_DEBUG class IfcDebugAction : public IfaceDecompCommand { @@ -600,6 +611,11 @@ public: virtual void execute(istream &s); }; +class IfcBreakjump : public IfaceDecompCommand { +public: + virtual void execute(istream &s); +}; + #endif #endif diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ifaceterm.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ifaceterm.cc index a7a105c024..41e8ab718d 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ifaceterm.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ifaceterm.cc @@ -16,8 +16,9 @@ #include "ifaceterm.hh" IfaceTerm::IfaceTerm(const string &prmpt,istream &is,ostream &os) - : IfaceStatus(prmpt,is,os) + : IfaceStatus(prmpt,os) { + sptr = &is; #ifdef __TERMINAL__ struct termios ittypass; @@ -57,9 +58,16 @@ IfaceTerm::~IfaceTerm(void) #endif } +/// Respond to a TAB key press and try to 'complete' any existing tokens. +/// The method is handed the current state of the command-line in a string, and +/// it updates the command-line in place. +/// +/// \param line is current command-line and will hold the final completion +/// \param cursor is the current position of the cursor +/// \return the (possibly new) position of the cursor, after completion int4 IfaceTerm::doCompletion(string &line,int4 cursor) -{ // Try to complete the current command +{ vector fullcommand; istringstream s(line); string tok; @@ -229,3 +237,28 @@ void IfaceTerm::readLine(string &line) } while(val != '\n'); } +void IfaceTerm::pushScript(const string &filename,const string &newprompt) + +{ + ifstream *s = new ifstream(filename.c_str()); + if (!*s) + throw IfaceParseError("Unable to open script file"); + inputstack.push_back(sptr); + sptr = s; + IfaceStatus::pushScript(filename,newprompt); +} + +void IfaceTerm::popScript(void) + +{ + delete sptr; + sptr = inputstack.back(); + inputstack.pop_back(); +} + +bool IfaceTerm::isStreamFinished(void) const + +{ + if (done||inerror) return true; + return sptr->eof(); +} diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ifaceterm.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/ifaceterm.hh index 46b3dc6c15..3c1eb0092d 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ifaceterm.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ifaceterm.hh @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -// Add some terminal capabilities to the command-line interface +/// \file ifaceterm.hh +/// \brief Add some terminal capabilities to the command-line interface (IfaceStatus) + #include "interface.hh" #ifdef __TERMINAL__ @@ -23,15 +25,26 @@ extern "C" { } #endif +/// \brief Implement the command-line interface on top of a specific input stream +/// +/// An initial input stream is provided as the base stream to parse for commands. +/// Additional input streams can be stacked by invoking scripts. +/// If the stream supports it, the stream parser recognizes special command-line editing +/// and completion keys. class IfaceTerm : public IfaceStatus { #ifdef __TERMINAL__ - bool is_terminal; // True if the input stream is a terminal - int4 ifd; // Underlying file descriptor - struct termios itty; // Original terminal settings + bool is_terminal; ///< True if the input stream is a terminal + int4 ifd; ///< Underlying file descriptor + struct termios itty; ///< Original terminal settings #endif - int4 doCompletion(string &line,int4 cursor); + istream *sptr; ///< The base input stream for the interface + vector inputstack; ///< Stack of nested input streams + int4 doCompletion(string &line,int4 cursor); ///< 'Complete' the current command line virtual void readLine(string &line); public: - IfaceTerm(const string &prmpt,istream &is,ostream &os); + IfaceTerm(const string &prmpt,istream &is,ostream &os); ///< Constructor virtual ~IfaceTerm(void); + virtual void pushScript(const string &filename,const string &newprompt); + virtual void popScript(void); + virtual bool isStreamFinished(void) const; }; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/interface.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/interface.cc index b90f391f6a..9c5674b8f0 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/interface.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/interface.cc @@ -29,9 +29,12 @@ void IfaceCapability::initialize(void) thelist.push_back(this); } +/// Allow each capability to register its own commands +/// +/// \param status is the command line interface to register commands with void IfaceCapability::registerAllCommands(IfaceStatus *status) -{ // Allow each capability to register its own commands +{ for(uint4 i=0;iregisterCommands(status); } @@ -114,10 +117,12 @@ bool RemoteSocket::isSocketOpen(void) #endif -IfaceStatus::IfaceStatus(const string &prmpt,istream &is,ostream &os,int4 mxhist) +/// \param prmpt is the base command line prompt +/// \param os is the base stream to write output to +/// \param mxhist is the maximum number of lines to store in history +IfaceStatus::IfaceStatus(const string &prmpt,ostream &os,int4 mxhist) { - sptr = &is; optr = &os; fileoptr = optr; // Bulk out, defaults to command line output sorted = false; @@ -129,28 +134,30 @@ IfaceStatus::IfaceStatus(const string &prmpt,istream &is,ostream &os,int4 mxhist curhistory = 0; } +/// \brief Provide a new script file to execute, with an associated command prompt +/// +/// The script provides a subsidiary input stream to the current stream. +/// Once commands from the script are complete, processing will resume on this stream. +/// \param filename is the name of the file containing the script +/// \param newprompt is the command line prompt void IfaceStatus::pushScript(const string &filename,const string &newprompt) -{ // Push new input stream on stack (with new prompt) - ifstream *s = new ifstream(filename.c_str()); - if (!*s) - throw IfaceParseError("Unable to open script file"); - inputstack.push_back(sptr); +{ promptstack.push_back(prompt); uint4 flags = 0; if (errorisdone) flags |= 1; flagstack.push_back(flags); - sptr = s; prompt = newprompt; } +/// \brief Return to processing the parent stream +/// +/// The current input stream, as established by a script, is popped from the stack, +/// along with its command prompt, and processing continues with the previous stream. void IfaceStatus::popScript(void) -{ // Pop the current input stream (and current prompt) - delete sptr; - sptr = inputstack.back(); - inputstack.pop_back(); +{ prompt = promptstack.back(); promptstack.pop_back(); uint4 flags = flagstack.back(); @@ -162,15 +169,17 @@ void IfaceStatus::popScript(void) void IfaceStatus::reset(void) { - while(!inputstack.empty()) + while(!promptstack.empty()) popScript(); errorisdone = false; done = false; } +/// The line is saved in a circular history buffer +/// \param line is the command line to save void IfaceStatus::saveHistory(const string &line) -{ // Save line in circular history buffer +{ if (history.size() < maxhistory) history.push_back(line); else @@ -180,6 +189,10 @@ void IfaceStatus::saveHistory(const string &line) curhistory = 0; } +/// A command line is selected by specifying how many steps in time +/// to go back through the list of successful command lines. +/// \param line will hold the selected command line from history +/// \param i is the number of steps back to go void IfaceStatus::getHistory(string &line,int4 i) const { @@ -191,9 +204,10 @@ void IfaceStatus::getHistory(string &line,int4 i) const line = history[i]; } +// The last command has failed, decide if we are completely abandoning this stream void IfaceStatus::evaluateError(void) -{ // The last command has failed, decide if we are completely abandoning this stream +{ if (errorisdone) { *optr << "Aborting process" << endl; inerror = true; @@ -208,6 +222,7 @@ void IfaceStatus::evaluateError(void) inerror = false; } +/// Concatenate a list of tokens into a single string, separated by a space character void IfaceStatus::wordsToString(string &res,const vector &list) { @@ -228,7 +243,7 @@ IfaceStatus::~IfaceStatus(void) ((ofstream *)fileoptr)->close(); delete fileoptr; } - while(!inputstack.empty()) + while(!promptstack.empty()) popScript(); for(int4 i=0;iaddWord(nm1); if (nm2 != (const char *)0) fptr->addWord(nm2); @@ -270,15 +296,24 @@ void IfaceStatus::registerCom(IfaceCommand *fptr,const char *nm1, fptr->setData(this,data); // Inform command of its data } +/// Commands (IfaceCommand) are associated with a particular module that has +/// a formal name and a data object associated with it. This method +/// retrieves the module specific data object by name. +/// \param nm is the name of the module +/// \return the IfaceData object or null IfaceData *IfaceStatus::getData(const string &nm) const -{ // Get data corresponding to the named module +{ map::const_iterator iter = datamap.find(nm); if (iter == datamap.end()) return (IfaceData *)0; return (*iter).second; } +/// A single command line is read (via readLine) and executed. +/// If the command is successfully executed, the command line is +/// committed to history and \b true is returned. +/// \return \b true if a command successfully executes bool IfaceStatus::runCommand(void) { @@ -318,9 +353,16 @@ bool IfaceStatus::runCommand(void) return true; // Indicate a command was executed } -void IfaceStatus::restrict(vector::const_iterator &first, - vector::const_iterator &last, - vector &input) +/// \brief Restrict range of possible commands given a list of command line tokens +/// +/// Given a set of tokens partially describing a command, provide the most narrow +/// range of IfaceCommand objects that could be referred to. +/// \param first will hold an iterator to the first command in the range +/// \param last will hold an iterator (one after) the last command in the range +/// \param input is the list of command tokens to match on +void IfaceStatus::restrictCom(vector::const_iterator &first, + vector::const_iterator &last, + vector &input) { vector::const_iterator newfirst,newlast; @@ -356,16 +398,22 @@ static bool maxmatch(string &res,const string &op1,const string &op2) return true; } +/// \brief Expand tokens from the given input stream to a full command +/// +/// A range of possible commands is returned. Processing of the stream +/// stops as soon as at least one complete command is recognized. +/// Tokens partially matching a command are expanded to the full command +/// and passed back. +/// \param expand will hold the list of expanded tokens +/// \param s is the input stream tokens are read from +/// \param first will hold the beginning of the matching range of commands +/// \param last will hold the end of the matching range of commands +/// \return the number of matching commands int4 IfaceStatus::expandCom(vector &expand,istream &s, vector::const_iterator &first, vector::const_iterator &last) -{ // Expand tokens on stream to full command - // Return range of possible commands - // If command is complete with extra arguments - // return (dont process) remaining args - // Return number of matching commands - +{ int4 pos; // Which word are we currently expanding string tok; bool res; @@ -395,7 +443,7 @@ int4 IfaceStatus::expandCom(vector &expand,istream &s, } s >> tok; // Get next token expand.push_back(tok); - restrict(first,last,expand); + restrictCom(first,last,expand); if (first == last) // If subrange is empty, return 0 return 0; res = maxmatch(tok, (*first)->getCommandWord(pos), (*(last-1))->getCommandWord(pos)); @@ -412,9 +460,13 @@ void IfaceCommand::addWords(const vector &wordlist) com.push_back( *iter ); } +/// The commands are ordered lexicographically and alphabetically by +/// the comparing tokens in their respective command line strings +/// \param op2 is the other command to compare with \b this +/// \return -1, 0, 1 if \b this is earlier, equal to, or after to the other command int4 IfaceCommand::compare(const IfaceCommand &op2) const -{ // Sort command based on names +{ int4 res; vector::const_iterator iter1,iter2; @@ -433,12 +485,15 @@ int4 IfaceCommand::compare(const IfaceCommand &op2) const return 0; // Never reaches here } +/// \param res is overwritten with the full command line string void IfaceCommand::commandString(string &res) const { IfaceStatus::wordsToString(res,com); } +/// \class IfcQuit +/// \brief Quit command to terminate processing from the given interface void IfcQuit::execute(istream &s) { // Generic quit call back @@ -448,6 +503,8 @@ void IfcQuit::execute(istream &s) status->done = true; // Set flag to drop out of mainloop } +/// \class IfcHistory +/// \brief History command to list the most recent successful commands void IfcHistory::execute(istream &s) { // List most recent command lines @@ -471,6 +528,8 @@ void IfcHistory::execute(istream &s) } } +/// \class IfcOpenfile +/// \brief Open file command to redirect bulk output to a specific file stream void IfcOpenfile::execute(istream &s) { @@ -491,6 +550,8 @@ void IfcOpenfile::execute(istream &s) } } +/// \class IfcOpenfileAppend +/// \brief Open file command directing bulk output to be appended to a specific file void IfcOpenfileAppend::execute(istream &s) { @@ -511,6 +572,10 @@ void IfcOpenfileAppend::execute(istream &s) } } +/// \class IfcClosefile +/// \brief Close command, closing the current bulk output file. +/// +/// Subsequent bulk output is redirected to the basic interface output stream void IfcClosefile::execute(istream &s) { @@ -521,6 +586,8 @@ void IfcClosefile::execute(istream &s) status->fileoptr = status->optr; } +/// \class IfcEcho +/// \brief Echo command to echo the current command line to the bulk output stream void IfcEcho::execute(istream &s) { // Echo command line to fileoptr diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/interface.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/interface.hh index 9edf4dc6fb..7635a824b3 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/interface.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/interface.hh @@ -4,32 +4,17 @@ * 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. */ -// Very generic command line executor class: IfaceStatus -// A new class instance derived from IfaceCommand is attached to a command line via registerCom -// i.e. -// IfaceStatus stat(cin,cout); -// stat.registerCom(new IfcQuit(),"quit"); -// stat.registerCom(new IfcOpenfileAppend(),"openfile","append"); -// stat.mainloop(); - -// Command line processing is started with mainloop, which prints a -// prompt set with setprompt, allows bash style command line editing, including -// command completion and history, and executes the corresponding IfaceCommand.execute callback. -// Command words only have to match enough to disambiguate it from other commands. - -// Custom history size can be passed in constructor to IfaceStatus. -// Applications should inherit from base class IfaceStatus in order -// to get custom data into IfaceCommand callbacks and to redefine -// the virtual function execute for custom error handling. +/// \file interface.hh +/// \brief Classes and utilities for a \e generic command-line interface #ifndef __INTERFACE__ #define __INTERFACE__ @@ -70,43 +55,87 @@ public: #endif +/// \brief An exception specific to the command line interface struct IfaceError { - string explain; // Explanatory string - IfaceError(const string &s) { explain = s; } + string explain; ///< Explanatory string + IfaceError(const string &s) { explain = s; } ///< Constructor }; +/// \brief An exception describing a parsing error in a command line +/// +/// Thrown when attempting to parse a command line. Options are missing or are in +/// the wrong form etc. struct IfaceParseError : public IfaceError { - IfaceParseError(const string &s) : IfaceError(s) {} + IfaceParseError(const string &s) : IfaceError(s) {} ///< Constructor }; +/// \brief An exception throw during the execution of a command +/// +/// Processing of a specific command has started but has reached an error state struct IfaceExecutionError : public IfaceError { - IfaceExecutionError(const string &s) : IfaceError(s) {} + IfaceExecutionError(const string &s) : IfaceError(s) {} ///< Constructor }; class IfaceStatus; // Forward declaration -class IfaceData { // Data specialized for a particular command +/// \brief Data specialized for a particular command module +/// +/// IfaceCommands can have specialized data that is shared with other commands in +/// the same module. This is the root object for all such data. +class IfaceData { public: - virtual ~IfaceData(void) {} + virtual ~IfaceData(void) {} ///< Destructor }; +/// \brief A command that can be executed from the command line +/// +/// The command has data associated with it (via setData()) and is executed +/// via the execute() method. The command can get additional parameters from +/// the command line by reading the input stream passed to it. +/// The command is associated with a specific sequence of words (tokens) +/// that should appear at the start of the command line. class IfaceCommand { - vector com; // The command + vector com; ///< The token sequence associated with the command public: - virtual ~IfaceCommand(void) {} + virtual ~IfaceCommand(void) {} ///< Destructor + + /// \brief Associate a specific data object with this command. + /// + /// \param root is the interface object this command is registered with + /// \param data is the data object the command should use virtual void setData(IfaceStatus *root,IfaceData *data)=0; + + /// Execute this command. Additional state can be read from the given command line stream. + /// Otherwise, the command gets its data from its registered IfaceData object + /// \param s is the input stream from the command line virtual void execute(istream &s)=0; + + /// \brief Get the formal module name to which this command belongs + /// + /// Commands in the same module share data through their registered IfaceData object + /// \return the formal module name virtual string getModule(void) const=0; + + /// \brief Create a specialized data object for \b this command (and its module) + /// + /// This method is only called once per module + /// \return the newly created data object for the module virtual IfaceData *createData(void)=0; + + /// \brief Add a token to the command line string associated with this command + /// + /// \param temp is the new token to add void addWord(const string &temp) { com.push_back(temp); } - void removeWord(void) { com.pop_back(); } - const string &getCommandWord(int4 i) const { return com[i]; } - void addWords(const vector &wordlist); - int4 numWords(void) const { return com.size(); } - void commandString(string &res) const; - int4 compare(const IfaceCommand &op2) const; + + void removeWord(void) { com.pop_back(); } ///< Remove the last token from the associated command line string + const string &getCommandWord(int4 i) const { return com[i]; } ///< Get the i-th command token + void addWords(const vector &wordlist); ///< Add words to the associated command line string + int4 numWords(void) const { return com.size(); } ///< Return the number of tokens in the command line string + void commandString(string &res) const; ///< Get the complete command line string + int4 compare(const IfaceCommand &op2) const; ///< Order two commands by their command line strings }; +/// \brief A dummy command used during parsing class IfaceCommandDummy : public IfaceCommand { public: virtual void setData(IfaceStatus *root,IfaceData *data) {} @@ -115,74 +144,113 @@ public: virtual IfaceData *createData(void) { return (IfaceData *)0; } }; +/// \brief Compare to commands as pointers +/// +/// \param a is a pointer to the first command +/// \param b is a pointer to the second command +/// \return \b true if the first pointer is ordered before the second inline bool compare_ifacecommand(const IfaceCommand *a,const IfaceCommand *b) { return (0>a->compare(*b)); } +/// \brief Groups of console commands that are \e discovered by the loader +/// +/// Any IfaceCommand that is registered with a grouping derived from this class +/// is automatically made available to any IfaceStatus object just by calling +/// the static registerAllCommands() class IfaceCapability : public CapabilityPoint { - static vector thelist; + static vector thelist; ///< The global list of discovered command groupings protected: - string name; // Identifying name for the capability + string name; ///< Identifying name for the capability public: - const string &getName(void) const { return name; } + const string &getName(void) const { return name; } ///< Get the name of the capability virtual void initialize(void); - virtual void registerCommands(IfaceStatus *status)=0; + virtual void registerCommands(IfaceStatus *status)=0; ///< Register commands for \b this grouping - static void registerAllCommands(IfaceStatus *status); + static void registerAllCommands(IfaceStatus *status); ///< Register all discovered commands with the interface }; +/// \brief A generic console mode interface and command executor +/// +/// Input is provided one command line at a time by providing calling readLine(). +/// Output goes to a provided ostream, \e optr. Output to a separate bulk stream +/// can be enabled by setting \e fileoptr. +/// +/// A derived IfaceCommand is attached to a command string via registerCom() +/// i.e. +/// stat.registerCom(new IfcQuit(),"quit"); +/// stat.registerCom(new IfcOpenfileAppend(),"openfile","append"); +/// stat.mainloop(); + +/// Command line processing is started with mainloop(), which prints a command prompt, +/// allows command line editing, including command completion and history, and executes +/// the corresponding IfaceComman::execute() callback. +/// Command words only have to match enough to disambiguate it from other commands. + +/// A Custom history size and command prompt can be passed to the constructor. +/// Applications should inherit from base class IfaceStatus in order to +/// - Override the readLine() method +/// - Override pushScript() and popScript() to allow command scripts +/// - Get custom data into IfaceCommand callbacks class IfaceStatus { - vector inputstack; - vector promptstack; - vector flagstack; - string prompt; - int4 maxhistory; - int4 curhistory; // most recent history - vector history; - bool sorted; // Are commands sorted - bool inerror; // -true- if last command did not succeed - bool errorisdone; // -true- if any error terminates the process - void restrict(vector::const_iterator &first,vector::const_iterator &last,vector &input); - virtual void readLine(string &line) { getline(*sptr,line,'\n'); } - void saveHistory(const string &line); + vector promptstack; ///< Stack of command prompts corresponding to script nesting level + vector flagstack; ///< Stack of flag state corresponding to script nesting level + string prompt; ///< The current command prompt + int4 maxhistory; ///< Maximum number of command lines to store in history + int4 curhistory; ///< Most recent history + vector history; ///< History of commands executed through this interface + bool sorted; ///< Set to \b true if commands are sorted + bool errorisdone; ///< Set to \b true if any error terminates the process + void restrictCom(vector::const_iterator &first, + vector::const_iterator &last,vector &input); + + /// \brief Read the next command line + /// + /// \param line is filled in with the next command to execute + virtual void readLine(string &line)=0; + void saveHistory(const string &line); ///< Store the given command line into \e history protected: - istream *sptr; // Where to get input - vector comlist; // List of commands - map datamap; // Data associated with particular modules + bool inerror; ///< Set to \b true if last command did not succeed + vector comlist; ///< List of registered commands + map datamap; ///< Data associated with particular modules int4 expandCom(vector &expand,istream &s, vector::const_iterator &first, vector::const_iterator &last); public: - bool done; - ostream *optr; // Where to put command line output - ostream *fileoptr; // Where to put bulk output + bool done; ///< Set to \b true (by a command) to indicate processing is finished + ostream *optr; ///< Where to put command line output + ostream *fileoptr; ///< Where to put bulk output - IfaceStatus(const string &prmpt,istream &is,ostream &os,int4 mxhist=10); - virtual ~IfaceStatus(void); - void setErrorIsDone(bool val) { errorisdone = val; } - void pushScript(const string &filename,const string &newprompt); - void popScript(void); - void reset(void); - int4 getNumInputStreamSize(void) const { return inputstack.size(); } - void writePrompt(void) { *optr << prompt; } + IfaceStatus(const string &prmpt,ostream &os,int4 mxhist=10); ///< Constructor + virtual ~IfaceStatus(void); ///< Destructor + void setErrorIsDone(bool val) { errorisdone = val; } ///< Set if processing should terminate on an error + virtual void pushScript(const string &filename,const string &newprompt); + virtual void popScript(void); + void reset(void); ///< Pop any existing script streams and return to processing from the base stream + int4 getNumInputStreamSize(void) const { return promptstack.size(); } ///< Get depth of script nesting + void writePrompt(void) { *optr << prompt; } ///< Write the current command prompt to the current output stream void registerCom(IfaceCommand *fptr, const char *nm1, const char *nm2 = (const char *)0, const char *nm3 = (const char *)0, const char *nm4 = (const char *)0, const char *nm5 = (const char *)0); - IfaceData *getData(const string &nm) const; - bool runCommand(void); - void getHistory(string &line,int4 i) const; - int4 getHistorySize(void) const { return history.size(); } - bool isStreamFinished(void) const { if (done||inerror) return true; return sptr->eof(); } - bool isInError(void) const { return inerror; } - void evaluateError(void); - static void wordsToString(string &res,const vector &list); + IfaceData *getData(const string &nm) const; ///< Get data associated with a IfaceCommand module + bool runCommand(void); ///< Run the next command + void getHistory(string &line,int4 i) const; ///< Get the i-th command line from history + int4 getHistorySize(void) const { return history.size(); } ///< Get the number of command lines in history + virtual bool isStreamFinished(void) const=0; ///< Return \b true if the current stream is finished + bool isInError(void) const { return inerror; } ///< Return \b true if the last command failed + void evaluateError(void); ///< Adjust which stream to process based on last error + static void wordsToString(string &res,const vector &list); ///< Concatenate tokens }; +/// \brief A root class for a basic set of commands +/// +/// Commands derived from this class are in the "base" module. +/// They are useful as part of any interface class IfaceBaseCommand : public IfaceCommand { protected: - IfaceStatus *status; + IfaceStatus *status; ///< The interface owning this command instance public: virtual void setData(IfaceStatus *root,IfaceData *data) { status = root; } virtual string getModule(void) const { return "base"; } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.hh index fdced4e67a..2b29a6c855 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.hh @@ -500,7 +500,7 @@ class JumpTable { /// \brief An address table index and its corresponding out-edge struct IndexPair { int4 blockPosition; ///< Out-edge index for the basic-block - int4 addressIndex; /// Index of address targetting the basic-block + int4 addressIndex; ///< Index of address targeting the basic-block IndexPair(int4 pos,int4 index) { blockPosition = pos; addressIndex = index; } ///< Constructor bool operator<(const IndexPair &op2) const; ///< Compare by position then by index static bool compareByPosition(const IndexPair &op1,const IndexPair &op2); ///< Compare just by position diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/options.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/options.cc index 2ec50db12b..12dd1874e8 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/options.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/options.cc @@ -838,6 +838,10 @@ string OptionAliasBlock::apply(Architecture *glb,const string &p1,const string & return "Alias block level set to " + p1; } +/// \class OptionMaxInstruction +/// \brief Maximum number of instructions that can be processed in a single function +/// +/// The first parameter is an integer specifying the maximum. string OptionMaxInstruction::apply(Architecture *glb,const string &p1,const string &p2,const string &p3) const { diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/override.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/override.cc index 48adb18329..a998ba2817 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/override.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/override.cc @@ -249,7 +249,7 @@ void Override::printRaw(ostream &s,Architecture *glb) const s << "dead code delay on " << spc->getName() << " set to " << dec << deadcodedelay[i] << endl; } - for(iter=forcegoto.begin();iter!=forcegoto.end();++iter) + for(iter=indirectover.begin();iter!=indirectover.end();++iter) s << "override indirect at " << (*iter).first << " to call directly to " << (*iter).second << endl; map::const_iterator fiter; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/prettyprint.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/prettyprint.cc index 009d5bbb64..4b687b53f1 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/prettyprint.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/prettyprint.cc @@ -595,11 +595,21 @@ void EmitPrettyPrint::overflow(void) else break; } + int4 newspaceremain; if (!indentstack.empty()) - spaceremain = indentstack.back(); + newspaceremain = indentstack.back(); else - spaceremain = maxlinesize; + newspaceremain = maxlinesize; + if (newspaceremain == spaceremain) + return; // Line breaking doesn't give us any additional space + if (commentmode && (newspaceremain == spaceremain + commentfill.size())) + return; // Line breaking doesn't give us any additional space + spaceremain = newspaceremain; lowlevel->tagLine(maxlinesize-spaceremain); + if (commentmode &&(commentfill.size() != 0)) { + lowlevel->print(commentfill.c_str(),EmitXml::comment_color); + spaceremain -= commentfill.size(); + } } /// Content and markup is sent to the low-level emitter if appropriate. The diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/prettyprint.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/prettyprint.hh index 5de1739874..053ec38d0f 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/prettyprint.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/prettyprint.hh @@ -727,7 +727,7 @@ class EmitPrettyPrint : public EmitXml { void print(const TokenSplit &tok); ///< Output the given token to the low-level emitter void advanceleft(void); ///< Emit tokens that have been fully committed void scan(void); ///< Process a new token - void resetDefaultsPrettyPrint(void) { setMaxLineSize(100); } + void resetDefaultsPrettyPrint(void) { setMaxLineSize(100); } ///< Reset the defaults public: EmitPrettyPrint(void); ///< Construct with an initial maximum line size virtual ~EmitPrettyPrint(void); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc index 8fed0baa45..79da4cfa31 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/printc.cc @@ -199,9 +199,9 @@ void PrintC::pushSymbolScope(const Symbol *symbol) } } -/// Emit the elements of the given function's namespace path that distinguish it within +/// Emit the elements of the given symbol's namespace path that distinguish it within /// the current scope. -/// \param fd is the given function +/// \param symbol is the given Symbol void PrintC::emitSymbolScope(const Symbol *symbol) { diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc index a097b4a446..a1c01498ed 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc @@ -9085,6 +9085,11 @@ int4 RulePiecePathology::applyOp(PcodeOp *op,Funcdata &data) return tracePathologyForward(op, data); } +/// \class RuleXorSwap +/// \brief Simplify limited chains of XOR operations +/// +/// `V = (a ^ b) ^ a => V = b` +/// `V = a ^ (b ^ a) => V = b` void RuleXorSwap::getOpList(vector &oplist) const { diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/sleigh.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/sleigh.cc index 0c2d470e06..bf65a9d5f0 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/sleigh.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/sleigh.cc @@ -32,10 +32,13 @@ PcodeCacher::~PcodeCacher(void) delete [] poolstart; } +/// Expand the VarnodeData pool so that \e size more elements fit, and return +/// a pointer to first available element. +/// \param size is the number of elements to expand the pool by +/// \return the first available VarnodeData VarnodeData *PcodeCacher::expandPool(uint4 size) -{ // Expand the pool so that -size- more elements fit - // Return pointer to first available element +{ uint4 curmax = endpool - poolstart; uint4 cursize = curpool - poolstart; if (cursize + size <= curmax) @@ -75,18 +78,26 @@ VarnodeData *PcodeCacher::expandPool(uint4 size) return newpool + cursize; } +/// Store off a reference to the Varnode and the absolute index of the next +/// instruction. The Varnode must be an operand of the current instruction. +/// \param ptr is the Varnode reference void PcodeCacher::addLabelRef(VarnodeData *ptr) -{ // Store off a reference to a label and the next instruction - // address +{ label_refs.emplace_back(); label_refs.back().dataptr = ptr; label_refs.back().calling_index = issued.size(); } +/// The label has an id that is referred to by Varnodes holding +/// intra-instruction branch targets, prior to converting +/// them to a \e relative \e branch offset. The label is associated with +/// the absolute index of the next PcodeData object to be issued, +/// facilitating this conversion. +/// \param id is the given id of the label void PcodeCacher::addLabel(uint4 id) -{ // Attach a label to the address of the next instruction +{ while(labels.size() <= id) labels.push_back(0xbadbeef); labels[ id ] = issued.size(); @@ -101,11 +112,12 @@ void PcodeCacher::clear(void) labels.clear(); } +/// Assuming all the PcodeData has been generated for an +/// instruction, go resolve any relative offsets and back +/// patch their value(s) into the PcodeData void PcodeCacher::resolveRelatives(void) -{ // Assuming all the PcodeData has been generated for an - // instruction, go resolve any relative offsets and back - // patch their value(s) into the PcodeData +{ list::const_iterator iter; for(iter=label_refs.begin();iter!=label_refs.end();++iter) { VarnodeData *ptr = (*iter).dataptr; @@ -119,18 +131,25 @@ void PcodeCacher::resolveRelatives(void) } } +/// Each p-code operation is presented to the emitter via its dump() method. +/// \param addr is the Address associated with the p-code operation +/// \param emt is the emitter void PcodeCacher::emit(const Address &addr,PcodeEmit *emt) const -{ // Emit any cached pcode +{ vector::const_iterator iter; for(iter=issued.begin();iter!=issued.end();++iter) emt->dump(addr,(*iter).opc,(*iter).outvar,(*iter).invar,(*iter).isize); } +/// \brief Generate a concrete VarnodeData object from the given template (VarnodeTpl) +/// +/// \param vntpl is the template to reference +/// \param vn is the object to fill in with concrete values void SleighBuilder::generateLocation(const VarnodeTpl *vntpl,VarnodeData &vn) -{ // Generate a concrete varnode -vn- from the template -vntpl- +{ vn.space = vntpl->getSpace().fixSpace(*walker); vn.size = vntpl->getSize().fix(*walker); if (vn.space == const_space) @@ -143,9 +162,18 @@ void SleighBuilder::generateLocation(const VarnodeTpl *vntpl,VarnodeData &vn) vn.offset = vn.space->wrapOffset(vntpl->getOffset().fix(*walker)); } +/// \brief Generate a pointer VarnodeData from a dynamic template (VarnodeTpl) +/// +/// The symbol represents a value referenced through a dynamic pointer. +/// This method generates the varnode representing the pointer itself and also +/// returns the address space in anticipation of generating the LOAD or STORE +/// that actually manipulates the value. +/// \param vntpl is the dynamic template to reference +/// \param vn is the object to fill with concrete values +/// \return the address space being pointed to AddrSpace *SleighBuilder::generatePointer(const VarnodeTpl *vntpl,VarnodeData &vn) -{ // Generate the pointer varnode -vn- from a dynamic template -vntpl- +{ const FixedHandle &hand(walker->getFixedHandle(vntpl->getOffset().getHandleIndex())); vn.space = hand.offset_space; vn.size = hand.offset_size; @@ -218,9 +246,17 @@ void SleighBuilder::dump(OpTpl *op) } } +/// \brief Build a named p-code section of a constructor that contains only implied BUILD directives +/// +/// If a named section of a constructor is empty, we still need to walk +/// through any subtables that might contain p-code in their named sections. +/// This method treats each subtable operand as an implied \e build directive, +/// in the otherwise empty section. +/// \param ct is the matching currently Constructor being built +/// \param secnum is the particular \e named section number to build void SleighBuilder::buildEmpty(Constructor *ct,int4 secnum) -{ // Build a named p-code section of a constructor that contains only implied BUILD directives +{ int4 numops = ct->getNumOperands(); for(int4 i=0;igetIn(0)->getOffset().getReal(); // Recover operand index from build statement // Check if operand is a subtable SubtableSymbol *sym = (SubtableSymbol *)walker->getConstructor()->getOperand(index)->getDefiningSymbol(); @@ -283,8 +331,9 @@ void SleighBuilder::appendBuild(OpTpl *bld,int4 secnum) void SleighBuilder::delaySlot(OpTpl *op) -{ // Append pcode for an entire instruction (delay slot) - // in the middle of the current instruction +{ + // Append pcode for an entire instruction (delay slot) + // in the middle of the current instruction ParserWalker *tmp = walker; uintb olduniqueoffset = uniqueoffset; @@ -319,7 +368,8 @@ void SleighBuilder::setLabel(OpTpl *op) void SleighBuilder::appendCrossBuild(OpTpl *bld,int4 secnum) -{ // Weave in the p-code section from an instruction at another address +{ + // Weave in the p-code section from an instruction at another address // bld-param(0) contains the address of the instruction // bld-param(1) contains the section number if (secnum>=0) @@ -352,6 +402,8 @@ void SleighBuilder::appendCrossBuild(OpTpl *bld,int4 secnum) uniqueoffset = olduniqueoffset; } +/// \param min is the minimum number of allocations before a reuse is expected +/// \param hashsize is the number of elements in the hash-table void DisassemblyCache::initialize(int4 min,int4 hashsize) { @@ -382,6 +434,10 @@ void DisassemblyCache::free(void) delete [] hashtable; } +/// \param ccache is the ContextCache front-end shared across all the parser contexts +/// \param cspace is the constant address space used for minting constant Varnodes +/// \param cachesize is the number of distinct ParserContext objects in this cache +/// \param windowsize is the size of the ParserContext hash-table DisassemblyCache::DisassemblyCache(ContextCache *ccache,AddrSpace *cspace,int4 cachesize,int4 windowsize) { @@ -390,13 +446,17 @@ DisassemblyCache::DisassemblyCache(ContextCache *ccache,AddrSpace *cspace,int4 c initialize(cachesize,windowsize); // Set default settings for the cache } +/// Return a (possibly cached) ParserContext that is associated with \e addr +/// If n different calls to this interface are made with n different Addresses, if +/// - n <= minimumreuse AND +/// - all the addresses are within the windowsize (=mask+1) +/// +/// then the cacher guarantees that you get all different ParserContext objects +/// \param addr is the Address to disassemble at +/// \return the ParserContext associated with the address ParserContext *DisassemblyCache::getParserContext(const Address &addr) -{ // Return a (possibly cached) ParserContext that is associated with -addr- - // If n different calls to this interface are made with n different Addresses, if - // n <= minimumreuse AND - // all the addresses are within the windowsize (=mask+1) - // then the cacher guarantees that you get all different ParserContext objects +{ int4 hashindex = ((int4) addr.getOffset()) & mask; ParserContext *res = hashtable[ hashindex ]; if (res->getAddr() == addr) @@ -411,6 +471,8 @@ ParserContext *DisassemblyCache::getParserContext(const Address &addr) return res; } +/// \param ld is the LoadImage to draw program bytes from +/// \param c_db is the context database Sleigh::Sleigh(LoadImage *ld,ContextDatabase *c_db) : SleighBase() @@ -435,10 +497,13 @@ Sleigh::~Sleigh(void) clearForDelete(); } +/// Completely clear everything except the base and reconstruct +/// with a new LoadImage and ContextDatabase +/// \param ld is the new LoadImage +/// \param c_db is the new ContextDatabase void Sleigh::reset(LoadImage *ld,ContextDatabase *c_db) -{ // Completely clear everything except the base and reconstruct - // with a new loader and context +{ clearForDelete(); pcode_cache.clear(); loader = ld; @@ -447,6 +512,8 @@ void Sleigh::reset(LoadImage *ld,ContextDatabase *c_db) discache = (DisassemblyCache *)0; } +/// The .sla file from the document store is loaded and cache objects are prepared +/// \param store is the document store containing the main \ tag. void Sleigh::initialize(DocumentStorage &store) { @@ -467,10 +534,18 @@ void Sleigh::initialize(DocumentStorage &store) discache = new DisassemblyCache(cache,getConstantSpace(),parser_cachesize,parser_windowsize); } +/// \brief Obtain a parse tree for the instruction at the given address +/// +/// The tree may be cached from a previous access. If the address +/// has not been parsed, disassembly is performed, and a new parse tree +/// is prepared. Depending on the desired \e state, the parse tree +/// can be prepared either for disassembly or for p-code generation. +/// \param addr is the given address of the instruction +/// \param state is the desired parse state. +/// \return the parse tree object (ParseContext) ParserContext *Sleigh::obtainContext(const Address &addr,int4 state) const -{ // Obtain a ParserContext for the instruction at the given -addr-. This may be cached. - // Make sure parsing has proceeded to at least the given -state. +{ ParserContext *pos = discache->getParserContext(addr); int4 curstate = pos->getParserState(); if (curstate >= state) @@ -485,10 +560,11 @@ ParserContext *Sleigh::obtainContext(const Address &addr,int4 state) const return pos; } +/// Resolve \e all the constructors involved in the instruction at the indicated address +/// \param pos is the parse object that will hold the resulting tree void Sleigh::resolve(ParserContext &pos) const -{ // Resolve ALL the constructors involved in the - // instruction at this address +{ loader->loadFill(pos.getBuffer(),16,pos.getAddr()); ParserWalkerChange walker(&pos); pos.deallocateState(walker); // Clear the previous resolve and initialize the walker @@ -538,9 +614,12 @@ void Sleigh::resolve(ParserContext &pos) const pos.setParserState(ParserContext::disassembly); } +/// Resolve handle templates for the given parse tree, assuming Constructors +/// are already resolved. +/// \param pos is the given parse tree void Sleigh::resolveHandles(ParserContext &pos) const -{ // Resolve handles (assuming Constructors already resolved) +{ TripleSymbol *triple; Constructor *ct; int4 oper,numoper; @@ -671,7 +750,7 @@ int4 Sleigh::oneInstruction(PcodeEmit &emit,const Address &baseaddr) const void Sleigh::registerContext(const string &name,int4 sbit,int4 ebit) -{ // Inform translator of existence of context variable +{ context_db->registerVariable(name,sbit,ebit); } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/sleigh.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/sleigh.hh index 6519a09900..1a758a4881 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/sleigh.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/sleigh.hh @@ -13,6 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/// \file sleigh.hh +/// \brief Classes and utilities for the main SLEIGH engine + #ifndef __SLEIGH__ #define __SLEIGH__ @@ -20,29 +23,52 @@ class LoadImage; +/// \brief Class for describing a relative p-code branch destination +/// +/// An intra-instruction p-code branch takes a \e relative operand. +/// The actual value produced during p-code generation is calculated at +/// the last second using \b this. It stores the index of the BRANCH +/// instruction and a reference to its destination operand. This initially +/// holds a reference to a destination \e label symbol, but is later updated +/// with the final relative value. struct RelativeRecord { - VarnodeData *dataptr; // Record containing relative offset - uintb calling_index; // Index of instruction containing relative offset + VarnodeData *dataptr; ///< Varnode indicating relative offset + uintb calling_index; ///< Index of instruction containing relative offset }; -struct PcodeData { // Data for building one pcode instruction - OpCode opc; - VarnodeData *outvar; // Points to outvar is there is an output - VarnodeData *invar; // Inputs - int4 isize; // Number of inputs +/// \brief Data for building one p-code instruction +/// +/// Raw data used by the emitter to produce a single PcodeOp +struct PcodeData { + OpCode opc; ///< The op code + VarnodeData *outvar; ///< Output Varnode data (or null) + VarnodeData *invar; ///< Array of input Varnode data + int4 isize; ///< Number of input Varnodes }; -class PcodeCacher { // Cached chunk of pcode, prior to emitting - VarnodeData *poolstart; - VarnodeData *curpool; - VarnodeData *endpool; - vector issued; - list label_refs; // References to labels - vector labels; // Locations of labels - VarnodeData *expandPool(uint4 size); +/// \brief Class for caching a chunk of p-code, prior to emitting +/// +/// The engine accumulates PcodeData and VarnodeData objects for +/// a single instruction. Once the full instruction is constructed, +/// the objects are passed to the emitter (PcodeEmit) via the emit() method. +/// The class acts as a pool of memory for PcodeData and VarnodeData objects +/// that can be reused repeatedly to emit multiple instructions. +class PcodeCacher { + VarnodeData *poolstart; ///< Start of the pool of VarnodeData objects + VarnodeData *curpool; ///< First unused VarnodeData + VarnodeData *endpool; ///< End of the pool of VarnodeData objects + vector issued; ///< P-code ops issued for the current instruction + list label_refs; ///< References to labels + vector labels; ///< Locations of labels + VarnodeData *expandPool(uint4 size); ///< Expand the memory pool public: - PcodeCacher(void); - ~PcodeCacher(void); + PcodeCacher(void); ///< Constructor + ~PcodeCacher(void); ///< Destructor + + /// \brief Allocate data objects for a new set of Varnodes + /// + /// \param size is the number of objects to allocate + /// \return a pointer to the array of available VarnodeData objects VarnodeData *allocateVarnodes(uint4 size) { VarnodeData *newptr = curpool + size; if (newptr <= endpool) { @@ -52,6 +78,10 @@ public: } return expandPool(size); } + + /// \brief Allocate a data object for a new p-code operation + /// + /// \return the new PcodeData object PcodeData *allocateInstruction(void) { issued.emplace_back(); PcodeData *res = &issued.back(); @@ -59,41 +89,54 @@ public: res->invar = (VarnodeData *)0; return res; } - void addLabelRef(VarnodeData *ptr); - void addLabel(uint4 id); - void clear(void); - void resolveRelatives(void); - void emit(const Address &addr,PcodeEmit *emt) const; + void addLabelRef(VarnodeData *ptr); ///< Denote a Varnode holding a \e relative \e branch offset + void addLabel(uint4 id); ///< Attach a label to the \e next p-code instruction + void clear(void); ///< Reset the cache so that all objects are unallocated + void resolveRelatives(void); ///< Rewrite branch target Varnodes as \e relative offsets + void emit(const Address &addr,PcodeEmit *emt) const; ///< Pass the cached p-code data to the emitter }; +/// \brief A container for disassembly context used by the SLEIGH engine +/// +/// This acts as a factor for the ParserContext objects which are used to disassemble +/// a single instruction. These all share a ContextCache which is a front end for +/// accessing the ContextDatabase and resolving context variables from the SLEIGH spec. +/// ParserContext objects are stored in a hash-table keyed by the address of the instruction. class DisassemblyCache { - ContextCache *contextcache; - AddrSpace *constspace; - int4 minimumreuse; // Can call getParserContext this many times, before a ParserContext is reused - uint4 mask; // Size of the hashtable in form 2^n-1 - ParserContext **list; // (circular) array of currently cached ParserContext objects - int4 nextfree; // Current end/beginning of circular list - ParserContext **hashtable; // Hashtable for looking up ParserContext via Address - void initialize(int4 min,int4 hashsize); - void free(void); + ContextCache *contextcache; ///< Cached values from the ContextDatabase + AddrSpace *constspace; ///< The constant address space + int4 minimumreuse; ///< Can call getParserContext this many times, before a ParserContext is reused + uint4 mask; ///< Size of the hashtable in form 2^n-1 + ParserContext **list; ///< (circular) array of currently cached ParserContext objects + int4 nextfree; ///< Current end/beginning of circular list + ParserContext **hashtable; ///< Hashtable for looking up ParserContext via Address + void initialize(int4 min,int4 hashsize); ///< Initialize the hash-table of ParserContexts + void free(void); ///< Free the hash-table of ParserContexts public: - DisassemblyCache(ContextCache *ccache,AddrSpace *cspace,int4 cachesize,int4 windowsize); - ~DisassemblyCache(void) { free(); } - ParserContext *getParserContext(const Address &addr); + DisassemblyCache(ContextCache *ccache,AddrSpace *cspace,int4 cachesize,int4 windowsize); ///< Constructor + ~DisassemblyCache(void) { free(); } ///< Destructor + ParserContext *getParserContext(const Address &addr); ///< Get the parser for a particular Address }; +/// \brief Build p-code from a pre-parsed instruction +/// +/// Through the build() method, \b this walks the parse tree and prepares data +/// for final emission as p-code. (The final emitting is done separately through the +/// PcodeCacher.emit() method). Generally, only p-code for one instruction is prepared. +/// But, through the \b delay-slot mechanism, build() may recursively visit +/// additional instructions. class SleighBuilder : public PcodeBuilder { virtual void dump( OpTpl *op ); - AddrSpace *const_space; - AddrSpace *uniq_space; - uintb uniquemask; - uintb uniqueoffset; - DisassemblyCache *discache; - PcodeCacher *cache; + AddrSpace *const_space; ///< The constant address space + AddrSpace *uniq_space; ///< The unique address space + uintb uniquemask; ///< Mask of address bits to use to uniquify temporary registers + uintb uniqueoffset; ///< Uniquifier bits for \b this instruction + DisassemblyCache *discache; ///< Cache of disassembled instructions + PcodeCacher *cache; ///< Cache accumulating p-code data for the instruction void buildEmpty(Constructor *ct,int4 secnum); void generateLocation(const VarnodeTpl *vntpl,VarnodeData &vn); AddrSpace *generatePointer(const VarnodeTpl *vntpl,VarnodeData &vn); - void setUniqueOffset(const Address &addr); + void setUniqueOffset(const Address &addr); ///< Set uniquifying bits for the current instruction public: SleighBuilder(ParserWalker *w,DisassemblyCache *dcache,PcodeCacher *pc,AddrSpace *cspc,AddrSpace *uspc,uint4 umask); virtual void appendBuild(OpTpl *bld,int4 secnum); @@ -102,21 +145,31 @@ public: virtual void appendCrossBuild(OpTpl *bld,int4 secnum); }; +/// \brief A full SLEIGH engine +/// +/// Its provided with a LoadImage of the bytes to be disassembled and +/// a ContextDatabase. +/// +/// Assembly is produced via the printAssembly() method, provided with an +/// AssemblyEmit object and an Address. +/// +/// P-code is produced via the oneInstruction() method, provided with a PcodeEmit +/// object and an Address. class Sleigh : public SleighBase { - LoadImage *loader; - ContextDatabase *context_db; - ContextCache *cache; - mutable DisassemblyCache *discache; - mutable PcodeCacher pcode_cache; - void clearForDelete(void); + LoadImage *loader; ///< The mapped bytes in the program + ContextDatabase *context_db; ///< Database of context values steering disassembly + ContextCache *cache; ///< Cache of recently used context values + mutable DisassemblyCache *discache; ///< Cache of recently parsed instructions + mutable PcodeCacher pcode_cache; ///< Cache of p-code data just prior to emitting + void clearForDelete(void); ///< Delete the context and disassembly caches protected: ParserContext *obtainContext(const Address &addr,int4 state) const; - void resolve(ParserContext &pos) const; - void resolveHandles(ParserContext &pos) const; + void resolve(ParserContext &pos) const; ///< Generate a parse tree suitable for disassembly + void resolveHandles(ParserContext &pos) const; ///< Prepare the parse tree for p-code generation public: - Sleigh(LoadImage *ld,ContextDatabase *c_db); - virtual ~Sleigh(void); - void reset(LoadImage *ld,ContextDatabase *c_db); + Sleigh(LoadImage *ld,ContextDatabase *c_db); ///< Constructor + virtual ~Sleigh(void); ///< Destructor + void reset(LoadImage *ld,ContextDatabase *c_db); ///< Reset the engine for a new program virtual void initialize(DocumentStorage &store); virtual void registerContext(const string &name,int4 sbit,int4 ebit); virtual void setContextDefault(const string &nm,uintm val); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/string_ghidra.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/string_ghidra.hh index 870d5cad0d..884e7e6719 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/string_ghidra.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/string_ghidra.hh @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/// \file ghidra_string.hh +/// \file string_ghidra.hh /// \brief Implementation of the StringManager through the ghidra client #ifndef __STRING_GHIDRA__ diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/stringmanage.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/stringmanage.hh index b8db95d2bd..64f091d234 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/stringmanage.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/stringmanage.hh @@ -30,10 +30,11 @@ class Architecture; /// Stores the decoded string until its needed for presentation. class StringManager { protected: + /// \brief String data (a sequence of bytes) stored by StringManager class StringData { public: - bool isTruncated; // \b true if the the string is truncated - vector byteData; // UTF8 encoded string data + bool isTruncated; ///< \b true if the the string is truncated + vector byteData; ///< UTF8 encoded string data }; map stringMap; ///< Map from address to string data int4 maximumChars; ///< Maximum characters in a string before truncating diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh index c205f711b0..561a2245eb 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh @@ -162,6 +162,12 @@ public: bool doTrace(void); ///< Trace logical value as far as possible }; +/// \brief Class for splitting data-flow on \e laned registers +/// +/// From a root Varnode and a description of its \e lanes, trace data-flow as far as +/// possible through the function, propagating each lane, using the doTrace() method. Then +/// using the apply() method, data-flow can be split, making each lane in every traced +/// register into an explicit Varnode class LaneDivide : public TransformManager { /// \brief Description of a large Varnode that needs to be traced (in the worklist) class WorkNode { diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/test.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/test.cc index ab99382a3c..aad6686bc6 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/test.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/test.cc @@ -13,470 +13,81 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/// \file test.cc -/// \brief Unit tests for Ghidra C++ components. - -#include "float.hh" -#include "opbehavior.hh" #include "test.hh" +#include "testfunction.hh" -#include -#include -#include +vector UnitTest::tests; -#include -#include +/// Run all the tests unless a non-empty set of names is passed in. +/// In which case, only the named tests in the set are run. +/// \param testNames is the set of names +void UnitTest::run(set &testNames) -// utility functions -float floatFromRawBits(uintb e) { - float f; - memcpy(&f, &e, 4); - return f; -} +{ + int total = 0; + int passed = 0; -uintb floatToRawBits(float f) { - uintb result = 0; - memcpy(&result, &f, 4); - return result; -} - -double doubleFromRawBits(uintb e) { - double f; - memcpy(&f, &e, 8); - return f; -} - -uintb doubleToRawBits(double f) { - uintb result = 0; - memcpy(&result, &f, 8); - return result; -} - -// macros to preserve call site -#define ASSERT_FLOAT_ENCODING(f) \ - do { \ - FloatFormat format(4); \ - \ - uintb true_encoding = floatToRawBits(f); \ - uintb encoding = format.getEncoding(f); \ - \ - ASSERT_EQUALS(true_encoding, encoding); \ - } while (0); - -#define ASSERT_DOUBLE_ENCODING(f) \ - do { \ - FloatFormat format(8); \ - \ - uintb true_encoding = doubleToRawBits(f); \ - uintb encoding = format.getEncoding(f); \ - \ - ASSERT_EQUALS(true_encoding, encoding); \ - } while (0); - -//// FloatFormat tests - -static std::vector float_test_values{ - -0.0f, - +0.0f, - -1.0f, - +1.0f, - - -1.234f, - +1.234f, - - -std::numeric_limits::denorm_min(), - std::numeric_limits::denorm_min(), - - std::numeric_limits::min() - std::numeric_limits::denorm_min(), - std::numeric_limits::min(), - std::numeric_limits::min() + std::numeric_limits::denorm_min(), - - -std::numeric_limits::min() + std::numeric_limits::denorm_min(), - -std::numeric_limits::min(), - -std::numeric_limits::min() - std::numeric_limits::denorm_min(), - - std::numeric_limits::max(), - - std::numeric_limits::quiet_NaN(), - - -std::numeric_limits::infinity(), - std::numeric_limits::infinity() -}; - -static std::vector int_test_values = { - 0, -1, 1, 1234, -1234, std::numeric_limits::min(), std::numeric_limits::max() -}; - -TEST(float_encoding_normal) { - ASSERT_FLOAT_ENCODING(1.234); - ASSERT_FLOAT_ENCODING(-1.234); -} - -TEST(double_encoding_normal) { - ASSERT_DOUBLE_ENCODING(1.234); - ASSERT_DOUBLE_ENCODING(-1.234); -} - -TEST(float_encoding_nan) { - ASSERT_FLOAT_ENCODING(std::numeric_limits::quiet_NaN()); - ASSERT_FLOAT_ENCODING(-std::numeric_limits::quiet_NaN()); -} - -TEST(double_encoding_nan) { - ASSERT_DOUBLE_ENCODING(std::numeric_limits::quiet_NaN()); - ASSERT_DOUBLE_ENCODING(-std::numeric_limits::quiet_NaN()); -} - -TEST(float_encoding_subnormal) { - ASSERT_FLOAT_ENCODING(std::numeric_limits::denorm_min()); - ASSERT_FLOAT_ENCODING(-std::numeric_limits::denorm_min()); -} - -TEST(double_encoding_subnormal) { - ASSERT_DOUBLE_ENCODING(std::numeric_limits::denorm_min()); - ASSERT_DOUBLE_ENCODING(-std::numeric_limits::denorm_min()); -} - -TEST(float_encoding_min_normal) { - ASSERT_FLOAT_ENCODING(std::numeric_limits::min()); - ASSERT_FLOAT_ENCODING(-std::numeric_limits::min()); -} - -TEST(double_encoding_min_normal) { - ASSERT_DOUBLE_ENCODING(std::numeric_limits::min()); - ASSERT_DOUBLE_ENCODING(-std::numeric_limits::min()); -} - -TEST(float_encoding_infinity) { - ASSERT_FLOAT_ENCODING(std::numeric_limits::infinity()); - ASSERT_FLOAT_ENCODING(-std::numeric_limits::infinity()); -} - -TEST(double_encoding_infinity) { - ASSERT_DOUBLE_ENCODING(std::numeric_limits::infinity()); - ASSERT_DOUBLE_ENCODING(-std::numeric_limits::infinity()); -} - -TEST(float_midpoint_rounding) { - FloatFormat ff(4); - // IEEE754 recommends "round to nearest even" for binary formats, like single and double - // precision floating point. It rounds to the nearest integer (significand) when unambiguous, - // and to the nearest even on the midpoint. - - // There are 52 bits of significand in a double and 23 in a float. - // Below we construct a sequence of double precision values to demonstrate each case - // in rounding, - - // d0 - zeros in low 29 bits, round down - // d1 - on the rounding midpoint with integer even integer part, round down - // d2 - just above the midpoint, round up - double d0 = doubleFromRawBits(0x4010000000000000L); - double d1 = doubleFromRawBits(0x4010000010000000L); - double d2 = doubleFromRawBits(0x4010000010000001L); - - // d3 - zeros in low 29 bits, round down - // d4 - on the rounding midpoint with integer part odd, round up - // d5 - just above the midpoint, round up - double d3 = doubleFromRawBits(0x4010000020000000L); - double d4 = doubleFromRawBits(0x4010000030000000L); - double d5 = doubleFromRawBits(0x4010000030000001L); - - float f0 = (float)d0; - float f1 = (float)d1; - float f2 = (float)d2; - float f3 = (float)d3; - float f4 = (float)d4; - float f5 = (float)d5; - - uintb e0 = ff.getEncoding(d0); - uintb e1 = ff.getEncoding(d1); - uintb e2 = ff.getEncoding(d2); - uintb e3 = ff.getEncoding(d3); - uintb e4 = ff.getEncoding(d4); - uintb e5 = ff.getEncoding(d5); - - ASSERT_EQUALS(floatToRawBits(f0), e0); - ASSERT_EQUALS(floatToRawBits(f1), e1); - ASSERT_EQUALS(floatToRawBits(f2), e2); - ASSERT_EQUALS(floatToRawBits(f3), e3); - ASSERT_EQUALS(floatToRawBits(f4), e4); - ASSERT_EQUALS(floatToRawBits(f5), e5); - - ASSERT_EQUALS(e0, e1); - ASSERT_NOT_EQUALS(e1, e2); - - ASSERT_NOT_EQUALS(e3, e4); - ASSERT_EQUALS(e4, e5); -} - -// op tests - -// generated - -TEST(float_opNan) { - FloatFormat format(4); - - for(float f:float_test_values) { - uintb true_result = isnan(f); - uintb encoding = format.getEncoding(f); - uintb result = format.opNan(encoding); - - ASSERT_EQUALS(true_result, result); + for(auto &t : UnitTest::tests) { + if (testNames.size() > 0 && testNames.find(t->name) == testNames.end()) { + continue; } -} - - -TEST(float_opNeg) { - FloatFormat format(4); - - for(float f:float_test_values) { - uintb true_result = floatToRawBits(-f); - uintb encoding = format.getEncoding(f); - uintb result = format.opNeg(encoding); - - ASSERT_EQUALS(true_result, result); + std::cerr << "testing : " << t->name << " ..." << std::endl; + ++total; + try { + t->func(); + ++passed; + std::cerr << " passed." << std::endl; + } catch(...) { } + } + std::cerr << "==============================" << std::endl; + std::cerr << passed << "/" << total << " tests passed." << std::endl; } +int main(int argc, char **argv) { + bool runUnitTests = true; + bool runDataTests = true; -TEST(float_opAbs) { - FloatFormat format(4); - - for(float f:float_test_values) { - uintb true_result = floatToRawBits(abs(f)); - uintb encoding = format.getEncoding(f); - uintb result = format.opAbs(encoding); - - ASSERT_EQUALS(true_result, result); + argc -= 1; + argv += 1; + set unitTestNames; + set dataTestNames; + string dirname("../datatests"); + if (argc > 0) { + string command(argv[0]); + if (command == "-path") { + dirname = argv[1]; + runDataTests = true; + argv += 2; + argc -= 2; } -} - - -TEST(float_opSqrt) { - FloatFormat format(4); - - for(float f:float_test_values) { - uintb true_result = floatToRawBits(sqrtf(f)); - uintb encoding = format.getEncoding(f); - uintb result = format.opSqrt(encoding); - - ASSERT_EQUALS(true_result, result); + } + if (argc > 0) { + string command(argv[0]); + if (command == "unittests") { + runUnitTests = true; + runDataTests = false; // Run only unit tests + unitTestNames.insert(argv + 1,argv + argc); } -} - - -TEST(float_opCeil) { - FloatFormat format(4); - - for(float f:float_test_values) { - uintb true_result = floatToRawBits(ceilf(f)); - uintb encoding = format.getEncoding(f); - uintb result = format.opCeil(encoding); - - ASSERT_EQUALS(true_result, result); + else if (command == "datatests") { + runUnitTests = false; // Run only data-tests + runDataTests = true; + dataTestNames.insert(argv + 1,argv + argc); } -} - - -TEST(float_opFloor) { - FloatFormat format(4); - - for(float f:float_test_values) { - uintb true_result = floatToRawBits(floorf(f)); - uintb encoding = format.getEncoding(f); - uintb result = format.opFloor(encoding); - - ASSERT_EQUALS(true_result, result); + else { + cout << "USAGE: ghidra_test [-path ] [[unittests|datatests] [testname1 testname2 ...]]" << endl; } + } + if (runUnitTests) + UnitTest::run(unitTestNames); + if (runDataTests) { + cout << endl << endl; + const char *sleighhomepath = getenv("SLEIGHHOME"); + if (sleighhomepath != (const char *)0) + cout << "Using SLEIGHHOME=" << sleighhomepath << endl; + else + cout << "No SLEIGHHOME environment variable" << endl; + startDecompilerLibrary(sleighhomepath); + FunctionTestCollection::runTestCollections(dirname,dataTestNames); + } } - - -TEST(float_opRound) { - FloatFormat format(4); - - for(float f:float_test_values) { - uintb true_result = floatToRawBits(roundf(f)); - uintb encoding = format.getEncoding(f); - uintb result = format.opRound(encoding); - - ASSERT_EQUALS(true_result, result); - } -} - - -TEST(float_opInt2Float_size4) { - FloatFormat format(4); - - for(int i:int_test_values) { - uintb true_result = floatToRawBits((float)i); - uintb result = format.opInt2Float(i, 4); - - ASSERT_EQUALS(true_result, result); - } -} -// TODO other sized ints - -TEST(float_to_double_opFloat2Float) { - FloatFormat format(4); - FloatFormat format8(8); - - for(float f:float_test_values) { - uintb true_result = doubleToRawBits((double)f); - uintb encoding = format.getEncoding(f); - uintb result = format.opFloat2Float(encoding, format8); - - ASSERT_EQUALS(true_result, result); - } -} - -// TODO float2float going the other direction, double_to_float_opFloat2Float - - -TEST(float_opTrunc_to_int) { - FloatFormat format(4); - FloatFormat format8(8); - - for(float f:float_test_values) { - // avoid undefined behavior - if((int64_t)f > std::numeric_limits::max() || (int64_t)f < std::numeric_limits::min()) - continue; - uintb true_result = ((uintb)(int32_t)f) & 0xffffffff; - uintb encoding = format.getEncoding(f); - uintb result = format.opTrunc(encoding, 4); - - ASSERT_EQUALS(true_result, result); - } -} - -// TODO trunc to other sizes - - - -TEST(float_opEqual) { - FloatFormat format(4); - - for(float f1:float_test_values) { - uintb encoding1 = format.getEncoding(f1); - for(float f2:float_test_values) { - uintb true_result = (f1==f2); - uintb encoding2 = format.getEncoding(f2); - uintb result = format.opEqual(encoding1, encoding2); - - ASSERT_EQUALS(true_result, result); - } - } -} - - -TEST(float_opNotEqual) { - FloatFormat format(4); - - for(float f1:float_test_values) { - uintb encoding1 = format.getEncoding(f1); - for(float f2:float_test_values) { - uintb true_result = (f1!=f2); - uintb encoding2 = format.getEncoding(f2); - uintb result = format.opNotEqual(encoding1, encoding2); - - ASSERT_EQUALS(true_result, result); - } - } -} - - -TEST(float_opLess) { - FloatFormat format(4); - - for(float f1:float_test_values) { - uintb encoding1 = format.getEncoding(f1); - for(float f2:float_test_values) { - uintb true_result = (f1 -#include -#include -#include #include #include +#include +#include -namespace { - struct Test; - typedef void (*testfunc_t)(); +typedef void (*testfunc_t)(); ///< A unit-test function - std::vector tests; +/// \brief Simple unit test class +/// +/// The macro TEST instantiates this object with a name and function pointer. +/// The static run() method calls all the function pointers of all instantiated +/// objects. +struct UnitTest { + static std::vector tests; ///< The collection of test objects + std::string name; ///< Name of the test + testfunc_t func; ///< Call-back function executing the test - struct Test { - std::string name; - testfunc_t func; + /// \brief Constructor + /// + /// \param name is the identifier for the test + /// \param func is a call-back function that executes the test + UnitTest(const std::string &name,testfunc_t func) : + name(name), func(func) + { + tests.push_back(this); + } - Test(const std::string &name, testfunc_t func) : name(name), func(func) { - tests.push_back(this); - } - }; -} // namespace + static void run(std::set &testNames); ///< Run all the instantiated tests +}; + +/// \brief Main unit test macro #define TEST(testname) \ void testname(); \ - Test testname##_obj{ #testname, testname }; \ + UnitTest testname##_obj{ #testname, testname }; \ void testname() +/// \brief Assert that a boolean is \b true for a unit test #define ASSERT(test) \ if (!(test)) { \ std::cerr << " failed at " << __FILE__ << ":" << __LINE__ << " asserting \"" << #test << "\"." << std::endl; \ throw 0; \ } +/// \brief Assert that two values are equal for a unit test #define ASSERT_EQUALS(a, b) \ if ((a) != (b)) { \ std::stringstream ssa, ssb; \ @@ -71,6 +82,7 @@ namespace { throw 0; \ } +/// \brief Assert that two values are not equal for a unit test #define ASSERT_NOT_EQUALS(a, b) \ if ((a) == (b)) { \ std::stringstream ssa, ssb; \ @@ -80,26 +92,3 @@ namespace { << " != " << ssb.str() << "\"." << std::endl; \ throw 0; \ } - -int main(int argc, char **argv) { - int total = 0; - int passed = 0; - - std::set testnames(argv + 1, argv + argc); - - for (auto &t : tests) { - if(testnames.size()>0 && testnames.find(t->name)==testnames.end()) { - continue; - } - std::cerr << "testing : " << t->name << " ..." << std::endl; - ++total; - try { - t->func(); - ++passed; - std::cerr << " passed." << std::endl; - } catch (...) { - } - } - std::cerr << "==============================" << std::endl; - std::cerr << passed << "/" << total << " tests passed." << std::endl; -} diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.cc new file mode 100644 index 0000000000..a18e21ea91 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.cc @@ -0,0 +1,345 @@ +/* ### + * 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. + */ +#include "testfunction.hh" +#include "filemanage.hh" + +void FunctionTestProperty::startTest(void) const + +{ + count = 0; +} + +void FunctionTestProperty::processLine(const string &line) const + +{ + if (regex_search(line,pattern)) + count += 1; +} + +bool FunctionTestProperty::endTest(void) const + +{ + return (count >= minimumMatch && count <= maximumMatch); +} + +void FunctionTestProperty::restoreXml(const Element *el) + +{ + name = el->getAttributeValue("name"); + istringstream s1(el->getAttributeValue("min")); + s1 >> minimumMatch; + istringstream s2(el->getAttributeValue("max")); + s2 >> maximumMatch; + pattern = regex(el->getContent()); +} + +void ConsoleCommands::readLine(string &line) + +{ + if (pos >= commands.size()) { + line.clear(); + return; + } + line = commands[pos]; + pos += 1; +} + +ConsoleCommands::ConsoleCommands(void) : + IfaceStatus("> ", cout) +{ + pos = 0; + IfaceCapability::registerAllCommands(this); +} + +void ConsoleCommands::reset(void) + +{ + commands.clear(); + pos = 0; + inerror = false; + done = false; +} + +/// \param el is the root \ +read_volatile.*0x1000 + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/floatprint.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/floatprint.xml new file mode 100644 index 0000000000..85f266bf50 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/floatprint.xml @@ -0,0 +1,70 @@ + + + + + 554889e5f30f100539 +030000f30f1105550b2000f30f10052d +030000f30f1105490b2000f30f100521 +030000f30f11053d0b2000f30f100515 +030000f30f1105310b2000f30f100509 +030000660fefc9f30f5ec1f30f11051d +0b2000660fefc0660fefc9f30f5ec1f3 +0f11050d0b2000f30f1005e1020000f3 +0f1105010b2000f20f1005d9020000f2 +0f1105f90a2000f20f1005d1020000f2 +0f1105f10a2000f20f1005c9020000f2 +0f1105e90a2000f20f1005c1020000f2 +0f1105e10a2000f20f1005b902000066 +0fefc9f20f5ec1f20f1105d10a200066 +0fefc0660fefc9f20f5ec1f20f1105c5 +0a2000f20f100595020000f20f1105bd +0a2000905dc3 + + + abaaaa3e +000000406f1283babd3786350000803f +24d4523600000000555555555555e53f +0000000000000040fca9f1d24d6250bf +bbbdd7d9df7cdb3d000000000000f03f +7e7480d3845aca3e + + + + +floatv1 = 0.3333333; +floatv2 = 2.0; +floatv3 = -0.001; +floatv4 = 1e-06; +floatv5 = INFINITY; +floatv6 = -NAN; +floatv7 = 3.141592e-06; +double1 = 0.6666666666666666; +double2 = 2.0; +double3 = -0.001; +double4 = 1e-10; +double5 = INFINITY; +double6 = -NAN; +double7 = 3.141592653589793e-06; + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/forloop1.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/forloop1.xml new file mode 100644 index 0000000000..50d158608e --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/forloop1.xml @@ -0,0 +1,22 @@ + + + + + 554889e5534883ec18 +897decbb00000000eb0dbf20084000e8 +fcfeffff83c3013b5dec7cee904883c4 +185b5dc3 + + + + +for.*iVar1 = 0; iVar1 < max; iVar1 = iVar1 \+ 1match> + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/forloop_loaditer.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/forloop_loaditer.xml new file mode 100644 index 0000000000..0cfa489efe --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/forloop_loaditer.xml @@ -0,0 +1,31 @@ + + + + + 554889e548 +83ec2048897de8c745fc00000000eb10 +488b45e8488b4018488945e88345fc01 +48837de80075e98b45fc89c6bf440840 +00b800000000e805feffff90c9c3 + + + 436f756e74203d2025640a00 + + + + + +for \(loopvar = ptr; loopvar != \(int4 \*\*\)0x0; loopvar = \(int4 \*\*\)loopvar\[3\]\) +count = count \+ 1; + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/forloop_thruspecial.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/forloop_thruspecial.xml new file mode 100644 index 0000000000..1cd8ae0dfc --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/forloop_thruspecial.xml @@ -0,0 +1,30 @@ + + + + + 554889e553 +4883ec18897dec488975e0bb03000000 +eb1c83c301488b45e0c7000a00000048 +8345e004bf38084000e842feffff3b5d +ec7cdf904883c4185b5dc3 + + + 4d616b6520612063616c6c00 + + + + + +for.*iVar1 = 3; iVar1 < max; iVar1 = iVar1 \+ 1 +\*piStack.* = 10; +puts.*Make a call.*; + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/forloop_varused.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/forloop_varused.xml new file mode 100644 index 0000000000..fc44b562a0 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/forloop_varused.xml @@ -0,0 +1,31 @@ + + + + + 554889e5534883ec18897dec +bb00000000eb2e89d883e00385c07511 +bf26084000b800000000e8d1feffffeb +1189debf2e084000b800000000e8befe +ffff83c3013b5dec7ccd904883c4185b +5dc3 + + + 30206d6f6420340076616c203d2025640a00 + + + + + +for.*uVar1 = 0;.*uVar1 < max; uVar1 = uVar1 \+ 1 +if \(\(uVar1 & 3\) == 0\) +val = %d.*uVar1 + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/forloop_withskip.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/forloop_withskip.xml new file mode 100644 index 0000000000..124245c3ab --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/forloop_withskip.xml @@ -0,0 +1,24 @@ + + + + + 554889e553897df4bb00000000eb +0c837df40a7e0383c30183c3018b057d +0a200039c37cea905b5dc3 + + + + +for.*iVar1 = 0; iVar1 < globvar; iVar1 = iVar1 \+ 1 +if \(10 < val\) +iVar1 = iVar1 \+ 1; + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/loopcomment.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/loopcomment.xml new file mode 100644 index 0000000000..7f246f291a --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/loopcomment.xml @@ -0,0 +1,47 @@ + + + + + 554889e54883ec20897dec89 +75e88955e4894de08b45ec83f8097f1c +837de8647e168b45e43de8030000750c +488d3d0d020000e864feffffc745fc00 +000000eb28488d3d04020000e84ffeff +ff488d45ec4889c7e88dffffff488d45 +e04889c7e881ffffff8345fc018b45e0 +3dc70000007f378b45ec83f8137f2f81 +7dfcc70000007ebdeb24488d3dc80100 +00e80afeffff488d45ec4889c7e848ff +ffff488d45e04889c7e83cffffff8b45 +ec83f8630f9ec28b45e083f80a0f9fc0 +21d084c075c48b45ec83c00a8945ec48 +8d3d8e010000e8c5fdffff8b45ec89c6 +488d3d85010000b800000000e8bffdff +ff488d45ec4889c7e8edfeffff488d45 +e44889c7e8e1feffff8b45ec3dcf0700 +007f088b45e483f81d7eab8b45ec89c6 +488d3d49010000b800000000e87ffdff +ff90c9c3 + + + + +^ */\* ifcomment \*/ *$ +^ */\* forcomment \*/ *$ +^ */\* whilecomment \*/ *$ +^ */\* dowhilecomment \*/ *$ +.*/\* + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/namespace.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/namespace.xml new file mode 100644 index 0000000000..55da795dc6 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/namespace.xml @@ -0,0 +1,29 @@ + + + + + 554889e5897dfc8b45 +fc83c0018905e60a20008b55fc89d0c1 +e00201d001c08905d80a20008b15d60a +20008b45fc01d05dc3 + + + + +a::b::assign_vals\(int4 spam\) +^ ::spam = spam \+ 1; +^ a::spam = spam \* 10; +return spam \+ c::spam; + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/nestedoffset.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/nestedoffset.xml new file mode 100644 index 0000000000..06b6bb7251 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/nestedoffset.xml @@ -0,0 +1,25 @@ + + + + + 554889e5488d441602 +488d04878b005dc3 + + + + +return ptr->array\[b \+ a\]; +field +\* 4 + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/noforloop_alias.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/noforloop_alias.xml new file mode 100644 index 0000000000..043fe7cc39 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/noforloop_alias.xml @@ -0,0 +1,35 @@ + + + + +554889e54883ec20897decc745f00100 +0000c745f400000000c745f802000000 +c745fc03000000eb298b45f483c00189 +45f4488d45f04889c7e8adffffff8b45 +f489c6bf5d084000b800000000e85efd +ffff8b45f43945ec7fcf90c9c3 + + + 56616c203d2025640a00 + + + + + + +while \(i\[1\] < max\) +i\[1\] = i\[1\] \+ 1; +i\[[0-3]\] = [0-3]; +might_change\(i\); + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/noforloop_globcall.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/noforloop_globcall.xml new file mode 100644 index 0000000000..6ea11f09d4 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/noforloop_globcall.xml @@ -0,0 +1,27 @@ + + + + + 554889 +e54883ec0848897df8c7052d09200000 +000000eb1b8b052509200083c0018905 +1c092000488b45f84889c7e85bffffff +8b050a09200083f8097eda90c9c3 + + + + +while \(globvar < 10\) +globvar = 0; +globvar = globvar \+ 1; + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/noforloop_iterused.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/noforloop_iterused.xml new file mode 100644 index 0000000000..a1ebaf5acf --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/noforloop_iterused.xml @@ -0,0 +1,32 @@ + + + + + 5548 +89e5534883ec18897decbb0a000000eb +1d89debf50084000b800000000e8defd +ffff83c3016bc3648905c20920003b5d +ec7cde904883c4185b5dc3 + + + 4265666f7265203d2025640a00 + + + + + +while \(.*uVar1 < max\) +uVar1 = 10; +uVar1 = uVar1 \+ 1; +globvar = uVar1 \* 100; + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/offsetarray.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/offsetarray.xml new file mode 100644 index 0000000000..43c2597957 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/offsetarray.xml @@ -0,0 +1,27 @@ + + + + +534889fb4881ec900000004889e7e8ed +0f00008b049c4881c4900000005bc3 + + + + + +return mStack.*array\[param_1 \+ -2\] +firstfield + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/promotecompare.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/promotecompare.xml new file mode 100644 index 0000000000..b92369455f --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/promotecompare.xml @@ -0,0 +1,20 @@ + + + +5589e58b55080fb60284c0750eeb17 + + +0fb6420184c0740e83c20183e8303c09 +76ee5d31c0c35db801000000c3 + + + + +9.*uint1..cVar.*0x30 +uint1 + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/readvolatile.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/readvolatile.xml new file mode 100644 index 0000000000..9187ec7e6f --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/readvolatile.xml @@ -0,0 +1,24 @@ + + + + + 1028001e4200114000141140 +000a114000141140000a1140000a4e73 + + + + +read_volatile.*NVRAM.*30 +write_volatile.*NVRAM + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/threedim.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/threedim.xml new file mode 100644 index 0000000000..c020d93855 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/threedim.xml @@ -0,0 +1,32 @@ + + + + +554889e5897dec8975e8c745fc140000 +00eb758b05b73f2c008b55ec4863ca48 +988b55fc4863d24869f0d00700004889 +d048c1e0024801d04801c04801f04801 +c88b0485601060008945f88b057f3f2c +008b55e84863ca48988b55fc4863d248 +69f0d00700004889d048c1e0024801d0 +4801c04801f0488d14088b45f8890495 +601060008345fc01837dfc1d7e858b45 +f85dc3 + + + + +iStack16 = myarray\[globindex\]\[iStack12\]\[valin\]; +myarray\[globindex\]\[iStack12\]\[valout\] = iStack16; + \* + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/twodim.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/twodim.xml new file mode 100644 index 0000000000..6ef4a9a218 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/twodim.xml @@ -0,0 +1,29 @@ + + + + + 554889e5897dec89 +75e88b05240b20008b55ec4863ca4863 +d04889d048c1e0024801d04801c88b04 +85601060008945fc8b05fe0a20008b55 +fc8d4a0a8b55e84863f24863d04889d0 +48c1e0024801d04801f0890c85601060 +008b45fc5dc3 + + + + +iVar. = myarray\[globindex\]\[valin\]; +myarray\[globindex\]\[valout\] = iVar. \+ 10; + \* + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/wayoffarray.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/wayoffarray.xml new file mode 100644 index 0000000000..c7c28d298b --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/wayoffarray.xml @@ -0,0 +1,27 @@ + + + + +534889fb4881ec900000004889e7e8ed +0f00008b449cf84881c4900000005bc3 + + + + + +return mStack.*array\[param_1 \+ -4\] +firstfield + diff --git a/Ghidra/Features/Decompiler/src/decompile/unittests/testfloatemu.cc b/Ghidra/Features/Decompiler/src/decompile/unittests/testfloatemu.cc new file mode 100644 index 0000000000..ab99382a3c --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/unittests/testfloatemu.cc @@ -0,0 +1,482 @@ +/* ### + * 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. + */ +/// \file test.cc +/// \brief Unit tests for Ghidra C++ components. + +#include "float.hh" +#include "opbehavior.hh" +#include "test.hh" + +#include +#include +#include + +#include +#include + +// utility functions +float floatFromRawBits(uintb e) { + float f; + memcpy(&f, &e, 4); + return f; +} + +uintb floatToRawBits(float f) { + uintb result = 0; + memcpy(&result, &f, 4); + return result; +} + +double doubleFromRawBits(uintb e) { + double f; + memcpy(&f, &e, 8); + return f; +} + +uintb doubleToRawBits(double f) { + uintb result = 0; + memcpy(&result, &f, 8); + return result; +} + +// macros to preserve call site +#define ASSERT_FLOAT_ENCODING(f) \ + do { \ + FloatFormat format(4); \ + \ + uintb true_encoding = floatToRawBits(f); \ + uintb encoding = format.getEncoding(f); \ + \ + ASSERT_EQUALS(true_encoding, encoding); \ + } while (0); + +#define ASSERT_DOUBLE_ENCODING(f) \ + do { \ + FloatFormat format(8); \ + \ + uintb true_encoding = doubleToRawBits(f); \ + uintb encoding = format.getEncoding(f); \ + \ + ASSERT_EQUALS(true_encoding, encoding); \ + } while (0); + +//// FloatFormat tests + +static std::vector float_test_values{ + -0.0f, + +0.0f, + -1.0f, + +1.0f, + + -1.234f, + +1.234f, + + -std::numeric_limits::denorm_min(), + std::numeric_limits::denorm_min(), + + std::numeric_limits::min() - std::numeric_limits::denorm_min(), + std::numeric_limits::min(), + std::numeric_limits::min() + std::numeric_limits::denorm_min(), + + -std::numeric_limits::min() + std::numeric_limits::denorm_min(), + -std::numeric_limits::min(), + -std::numeric_limits::min() - std::numeric_limits::denorm_min(), + + std::numeric_limits::max(), + + std::numeric_limits::quiet_NaN(), + + -std::numeric_limits::infinity(), + std::numeric_limits::infinity() +}; + +static std::vector int_test_values = { + 0, -1, 1, 1234, -1234, std::numeric_limits::min(), std::numeric_limits::max() +}; + +TEST(float_encoding_normal) { + ASSERT_FLOAT_ENCODING(1.234); + ASSERT_FLOAT_ENCODING(-1.234); +} + +TEST(double_encoding_normal) { + ASSERT_DOUBLE_ENCODING(1.234); + ASSERT_DOUBLE_ENCODING(-1.234); +} + +TEST(float_encoding_nan) { + ASSERT_FLOAT_ENCODING(std::numeric_limits::quiet_NaN()); + ASSERT_FLOAT_ENCODING(-std::numeric_limits::quiet_NaN()); +} + +TEST(double_encoding_nan) { + ASSERT_DOUBLE_ENCODING(std::numeric_limits::quiet_NaN()); + ASSERT_DOUBLE_ENCODING(-std::numeric_limits::quiet_NaN()); +} + +TEST(float_encoding_subnormal) { + ASSERT_FLOAT_ENCODING(std::numeric_limits::denorm_min()); + ASSERT_FLOAT_ENCODING(-std::numeric_limits::denorm_min()); +} + +TEST(double_encoding_subnormal) { + ASSERT_DOUBLE_ENCODING(std::numeric_limits::denorm_min()); + ASSERT_DOUBLE_ENCODING(-std::numeric_limits::denorm_min()); +} + +TEST(float_encoding_min_normal) { + ASSERT_FLOAT_ENCODING(std::numeric_limits::min()); + ASSERT_FLOAT_ENCODING(-std::numeric_limits::min()); +} + +TEST(double_encoding_min_normal) { + ASSERT_DOUBLE_ENCODING(std::numeric_limits::min()); + ASSERT_DOUBLE_ENCODING(-std::numeric_limits::min()); +} + +TEST(float_encoding_infinity) { + ASSERT_FLOAT_ENCODING(std::numeric_limits::infinity()); + ASSERT_FLOAT_ENCODING(-std::numeric_limits::infinity()); +} + +TEST(double_encoding_infinity) { + ASSERT_DOUBLE_ENCODING(std::numeric_limits::infinity()); + ASSERT_DOUBLE_ENCODING(-std::numeric_limits::infinity()); +} + +TEST(float_midpoint_rounding) { + FloatFormat ff(4); + // IEEE754 recommends "round to nearest even" for binary formats, like single and double + // precision floating point. It rounds to the nearest integer (significand) when unambiguous, + // and to the nearest even on the midpoint. + + // There are 52 bits of significand in a double and 23 in a float. + // Below we construct a sequence of double precision values to demonstrate each case + // in rounding, + + // d0 - zeros in low 29 bits, round down + // d1 - on the rounding midpoint with integer even integer part, round down + // d2 - just above the midpoint, round up + double d0 = doubleFromRawBits(0x4010000000000000L); + double d1 = doubleFromRawBits(0x4010000010000000L); + double d2 = doubleFromRawBits(0x4010000010000001L); + + // d3 - zeros in low 29 bits, round down + // d4 - on the rounding midpoint with integer part odd, round up + // d5 - just above the midpoint, round up + double d3 = doubleFromRawBits(0x4010000020000000L); + double d4 = doubleFromRawBits(0x4010000030000000L); + double d5 = doubleFromRawBits(0x4010000030000001L); + + float f0 = (float)d0; + float f1 = (float)d1; + float f2 = (float)d2; + float f3 = (float)d3; + float f4 = (float)d4; + float f5 = (float)d5; + + uintb e0 = ff.getEncoding(d0); + uintb e1 = ff.getEncoding(d1); + uintb e2 = ff.getEncoding(d2); + uintb e3 = ff.getEncoding(d3); + uintb e4 = ff.getEncoding(d4); + uintb e5 = ff.getEncoding(d5); + + ASSERT_EQUALS(floatToRawBits(f0), e0); + ASSERT_EQUALS(floatToRawBits(f1), e1); + ASSERT_EQUALS(floatToRawBits(f2), e2); + ASSERT_EQUALS(floatToRawBits(f3), e3); + ASSERT_EQUALS(floatToRawBits(f4), e4); + ASSERT_EQUALS(floatToRawBits(f5), e5); + + ASSERT_EQUALS(e0, e1); + ASSERT_NOT_EQUALS(e1, e2); + + ASSERT_NOT_EQUALS(e3, e4); + ASSERT_EQUALS(e4, e5); +} + +// op tests + +// generated + +TEST(float_opNan) { + FloatFormat format(4); + + for(float f:float_test_values) { + uintb true_result = isnan(f); + uintb encoding = format.getEncoding(f); + uintb result = format.opNan(encoding); + + ASSERT_EQUALS(true_result, result); + } +} + + +TEST(float_opNeg) { + FloatFormat format(4); + + for(float f:float_test_values) { + uintb true_result = floatToRawBits(-f); + uintb encoding = format.getEncoding(f); + uintb result = format.opNeg(encoding); + + ASSERT_EQUALS(true_result, result); + } +} + + +TEST(float_opAbs) { + FloatFormat format(4); + + for(float f:float_test_values) { + uintb true_result = floatToRawBits(abs(f)); + uintb encoding = format.getEncoding(f); + uintb result = format.opAbs(encoding); + + ASSERT_EQUALS(true_result, result); + } +} + + +TEST(float_opSqrt) { + FloatFormat format(4); + + for(float f:float_test_values) { + uintb true_result = floatToRawBits(sqrtf(f)); + uintb encoding = format.getEncoding(f); + uintb result = format.opSqrt(encoding); + + ASSERT_EQUALS(true_result, result); + } +} + + +TEST(float_opCeil) { + FloatFormat format(4); + + for(float f:float_test_values) { + uintb true_result = floatToRawBits(ceilf(f)); + uintb encoding = format.getEncoding(f); + uintb result = format.opCeil(encoding); + + ASSERT_EQUALS(true_result, result); + } +} + + +TEST(float_opFloor) { + FloatFormat format(4); + + for(float f:float_test_values) { + uintb true_result = floatToRawBits(floorf(f)); + uintb encoding = format.getEncoding(f); + uintb result = format.opFloor(encoding); + + ASSERT_EQUALS(true_result, result); + } +} + + +TEST(float_opRound) { + FloatFormat format(4); + + for(float f:float_test_values) { + uintb true_result = floatToRawBits(roundf(f)); + uintb encoding = format.getEncoding(f); + uintb result = format.opRound(encoding); + + ASSERT_EQUALS(true_result, result); + } +} + + +TEST(float_opInt2Float_size4) { + FloatFormat format(4); + + for(int i:int_test_values) { + uintb true_result = floatToRawBits((float)i); + uintb result = format.opInt2Float(i, 4); + + ASSERT_EQUALS(true_result, result); + } +} +// TODO other sized ints + +TEST(float_to_double_opFloat2Float) { + FloatFormat format(4); + FloatFormat format8(8); + + for(float f:float_test_values) { + uintb true_result = doubleToRawBits((double)f); + uintb encoding = format.getEncoding(f); + uintb result = format.opFloat2Float(encoding, format8); + + ASSERT_EQUALS(true_result, result); + } +} + +// TODO float2float going the other direction, double_to_float_opFloat2Float + + +TEST(float_opTrunc_to_int) { + FloatFormat format(4); + FloatFormat format8(8); + + for(float f:float_test_values) { + // avoid undefined behavior + if((int64_t)f > std::numeric_limits::max() || (int64_t)f < std::numeric_limits::min()) + continue; + uintb true_result = ((uintb)(int32_t)f) & 0xffffffff; + uintb encoding = format.getEncoding(f); + uintb result = format.opTrunc(encoding, 4); + + ASSERT_EQUALS(true_result, result); + } +} + +// TODO trunc to other sizes + + + +TEST(float_opEqual) { + FloatFormat format(4); + + for(float f1:float_test_values) { + uintb encoding1 = format.getEncoding(f1); + for(float f2:float_test_values) { + uintb true_result = (f1==f2); + uintb encoding2 = format.getEncoding(f2); + uintb result = format.opEqual(encoding1, encoding2); + + ASSERT_EQUALS(true_result, result); + } + } +} + + +TEST(float_opNotEqual) { + FloatFormat format(4); + + for(float f1:float_test_values) { + uintb encoding1 = format.getEncoding(f1); + for(float f2:float_test_values) { + uintb true_result = (f1!=f2); + uintb encoding2 = format.getEncoding(f2); + uintb result = format.opNotEqual(encoding1, encoding2); + + ASSERT_EQUALS(true_result, result); + } + } +} + + +TEST(float_opLess) { + FloatFormat format(4); + + for(float f1:float_test_values) { + uintb encoding1 = format.getEncoding(f1); + for(float f2:float_test_values) { + uintb true_result = (f1 properties = new HashMap<>(); + properties.put("selectedVertexColor", "0xFF1493"); + properties.put("selectedEdgeColor", "0xFF1493"); + properties.put("initialLayoutAlgorithm", "Hierarchical MinCross Coffman Graham"); + GraphDisplay display = graphService.getDefaultGraphDisplay(!newGraph, properties, monitor); ASTGraphDisplayListener displayListener = new ASTGraphDisplayListener(tool, display, hfunction, graphType); display.setGraphDisplayListener(displayListener); @@ -116,7 +122,7 @@ public class ASTGraphTask extends Task { display.defineVertexAttribute(CODE_ATTRIBUTE); display.defineVertexAttribute(SYMBOLS_ATTRIBUTE); - display.setVertexLabel(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true, + display.setVertexLabelAttribute(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true, graphType == GraphType.CONTROL_FLOW_GRAPH ? (codeLimitPerBlock + 1) : 1); String description = diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/coff/CoffArchiveFileSystemFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/coff/CoffArchiveFileSystemFactory.java index 14b1584788..d8a725f18d 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/coff/CoffArchiveFileSystemFactory.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/coff/CoffArchiveFileSystemFactory.java @@ -30,7 +30,7 @@ import utilities.util.ArrayUtilities; public class CoffArchiveFileSystemFactory implements GFileSystemFactoryFull, GFileSystemProbeBytesOnly { - public static final int PROBE_BYTES_REQUIRED = CoffArchiveConstants.MAGIC_LEN_CONST_EXPR; + public static final int PROBE_BYTES_REQUIRED = CoffArchiveConstants.MAGIC_LEN; @Override public CoffArchiveFileSystem create(FSRL containerFSRL, FSRLRoot targetFSRL, diff --git a/Ghidra/Features/GraphServices/Module.manifest b/Ghidra/Features/GraphServices/Module.manifest index 6cdf923c2e..361687fc49 100644 --- a/Ghidra/Features/GraphServices/Module.manifest +++ b/Ghidra/Features/GraphServices/Module.manifest @@ -1,7 +1,9 @@ EXCLUDE FROM GHIDRA JAR: true -MODULE FILE LICENSE: lib/jungrapht-visualization-1.0.jar BSD -MODULE FILE LICENSE: lib/jungrapht-layout-1.0.jar BSD +MODULE FILE LICENSE: lib/jungrapht-visualization-1.1.jar BSD +MODULE FILE LICENSE: lib/jungrapht-layout-1.1.jar BSD +MODULE FILE LICENSE: lib/jgrapht-core-1.5.0.jar LGPL 2.1 +MODULE FILE LICENSE: lib/jgrapht-io-1.5.0.jar LGPL 2.1 MODULE FILE LICENSE: lib/jheaps-0.13.jar Apache License 2.0 MODULE FILE LICENSE: lib/log4j-slf4j-impl-2.12.1.jar Apache License 2.0 MODULE FILE LICENSE: lib/slf4j-api-1.7.25.jar MIT diff --git a/Ghidra/Features/GraphServices/build.gradle b/Ghidra/Features/GraphServices/build.gradle index c41037b56d..9d3c632bf9 100644 --- a/Ghidra/Features/GraphServices/build.gradle +++ b/Ghidra/Features/GraphServices/build.gradle @@ -11,14 +11,23 @@ eclipse.project.name = 'Features Graph Services' dependencies { compile project(":Base") - compile "com.github.tomnelson:jungrapht-visualization:1.0" - compile "com.github.tomnelson:jungrapht-layout:1.0" - + // jungrapht - exclude slf4j which produces a conflict with other uses with Ghidra + compile ("com.github.tomnelson:jungrapht-visualization:1.1") { exclude group: "org.slf4j", module: "slf4j-api" } + compile ("com.github.tomnelson:jungrapht-layout:1.1") { exclude group: "org.slf4j", module: "slf4j-api" } + + compile "org.jgrapht:jgrapht-core:1.5.0" + + // not using jgrapht-io code that depends on antlr, so exclude antlr + compile ("org.jgrapht:jgrapht-io:1.5.0") { exclude group: "org.antlr", module: "antlr4-runtime" } + runtime "org.slf4j:slf4j-api:1.7.25" + // use this if you want no slf4j log messages runtime "org.slf4j:slf4j-nop:1.7.25" + // use this if you want slf4j log messages sent to log4j -// runtime "org.apache.logging.log4j:log4j-slf4j-impl:2.12.1" + // runtime "org.apache.logging.log4j:log4j-slf4j-impl:2.12.1" + runtime "org.jheaps:jheaps:0.13" helpPath project(path: ":Base", configuration: 'helpPath') diff --git a/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/GraphDisplay.htm b/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/GraphDisplay.htm index 3ebb957878..ae6bf805d0 100644 --- a/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/GraphDisplay.htm +++ b/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/GraphDisplay.htm @@ -213,7 +213,11 @@
  • Expand Selected Vertices - Any group vertices are reverted back to the vertices that it contains.
  • - +
  • + Grow Selection To Entire Component - Adds to the selection all vertices that are reachable from the + currently selected vertices.
  • + +

    Vertex Popup Actions

    diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplay.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplay.java index b01fe8e654..85b415c096 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplay.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplay.java @@ -19,7 +19,7 @@ import java.util.*; import org.jgrapht.Graph; -import docking.action.DockingAction; +import docking.action.DockingActionIf; import docking.widgets.EventTrigger; import ghidra.app.services.GraphDisplayBroker; import ghidra.framework.plugintool.PluginTool; @@ -59,8 +59,6 @@ class ExportAttributedGraphDisplay implements GraphDisplay { listener.dispose(); } - - /** * set the {@link AttributedGraph} for visualization * @param attributedGraph the {@link AttributedGraph} to visualize @@ -90,7 +88,8 @@ class ExportAttributedGraphDisplay implements GraphDisplay { } @Override - public void setVertexLabel(String attributeName, int alignment, int size, boolean monospace, + public void setVertexLabelAttribute(String attributeName, int alignment, int size, + boolean monospace, int maxLines) { // no effect } @@ -122,7 +121,7 @@ class ExportAttributedGraphDisplay implements GraphDisplay { } @Override - public void addAction(DockingAction action) { + public void addAction(DockingActionIf action) { // do nothing, actions are not supported by this display } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java index 22800bded6..41a21ce1e2 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java @@ -19,10 +19,12 @@ import static org.jungrapht.visualization.MultiLayerTransformer.Layer.*; import static org.jungrapht.visualization.renderers.BiModalRenderer.*; import java.awt.*; +import java.awt.Dimension; import java.awt.event.*; import java.awt.geom.Point2D; import java.util.*; import java.util.List; +import java.util.function.Function; import java.util.function.Predicate; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -43,8 +45,11 @@ import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm; import org.jungrapht.visualization.layout.algorithms.util.InitialDimensionFunction; import org.jungrapht.visualization.layout.model.LayoutModel; import org.jungrapht.visualization.layout.model.Point; +import org.jungrapht.visualization.layout.model.Rectangle; import org.jungrapht.visualization.renderers.*; import org.jungrapht.visualization.renderers.Renderer; +import org.jungrapht.visualization.renderers.Renderer.VertexLabel; +import org.jungrapht.visualization.renderers.Renderer.VertexLabel.Position; import org.jungrapht.visualization.selection.MutableSelectedState; import org.jungrapht.visualization.selection.VertexEndpointsSelectedEdgeSelectedState; import org.jungrapht.visualization.transform.*; @@ -53,7 +58,8 @@ import org.jungrapht.visualization.transform.shape.MagnifyShapeTransformer; import org.jungrapht.visualization.util.RectangleUtils; import docking.ActionContext; -import docking.action.DockingAction; +import docking.DockingActionProxy; +import docking.action.DockingActionIf; import docking.action.ToggleDockingAction; import docking.action.builder.*; import docking.menu.ActionState; @@ -64,8 +70,7 @@ import ghidra.framework.plugintool.PluginTool; import ghidra.graph.AttributeFilters; import ghidra.graph.job.GraphJobRunner; import ghidra.graph.viewer.popup.*; -import ghidra.graph.visualization.mouse.JgtPluggableGraphMouse; -import ghidra.graph.visualization.mouse.JgtUtils; +import ghidra.graph.visualization.mouse.*; import ghidra.service.graph.*; import ghidra.util.*; import ghidra.util.exception.CancelledException; @@ -75,18 +80,46 @@ import resources.Icons; /** * Delegates to a {@link VisualizationViewer} to draw a graph visualization + * + *

    This graph uses the following properties: + *

      + *
    • selectedVertexColor - hex color using '0x' or '#', with 6 digits + *
    • + *
    • selectedEdgeColor - hex color using '0x' or '#', with 6 digits + *
    • + *
    • displayVerticesAsIcons - if true, shapes will be used to draw vertices based upon + * {@link GhidraIconCache}; false, then vertex shapes will be created from + * {@link ProgramGraphFunctions#getVertexShape(Attributed)} + *
    • + *
    • vertexLabelPosition - see {@link Position} + *
    • + *
    • initialLayoutAlgorithm - the name of the layout algorithm to be used for the initial + * graph layout + *
    • + *
    + * */ public class DefaultGraphDisplay implements GraphDisplay { private static final String ACTION_OWNER = "GraphServices"; - private static final String FAVORED_EDGE = "Fall-Through"; + /* + A handful of properties that can be set via the constructor + */ + private static final String SELECTED_VERTEX_COLOR = "selectedVertexColor"; + private static final String SELECTED_EDGE_COLOR = "selectedEdgeColor"; + private static final String INITIAL_LAYOUT_ALGORITHM = "initialLayoutAlgorithm"; + private static final String DISPLAY_VERTICES_AS_ICONS = "displayVerticesAsIcons"; + private static final String VERTEX_LABEL_POSITION = "vertexLabelPosition"; + private static final int MAX_NODES = Integer.getInteger("maxNodes", 10000); private static final Dimension PREFERRED_VIEW_SIZE = new Dimension(1000, 1000); private static final Dimension PREFERRED_LAYOUT_SIZE = new Dimension(3000, 3000); private Logger log = Logger.getLogger(DefaultGraphDisplay.class.getName()); + private Map displayProperties = new HashMap<>(); + private Set addedActions = new LinkedHashSet<>(); private GraphDisplayListener listener = new DummyGraphDisplayListener(); private String title; @@ -154,7 +187,7 @@ public class DefaultGraphDisplay implements GraphDisplay { /** * Handles all mouse interaction */ - private JgtPluggableGraphMouse graphMouse; + private JgtGraphMouse graphMouse; private ToggleDockingAction hideSelectedAction; private ToggleDockingAction hideUnselectedAction; @@ -167,12 +200,15 @@ public class DefaultGraphDisplay implements GraphDisplay { /** * Create the initial display, the graph-less visualization viewer, and its controls * @param displayProvider provides a {@link PluginTool} for Docking features + * @param displayProperties graph properties that will override the default graph properties * @param id the unique display id */ - DefaultGraphDisplay(DefaultGraphDisplayProvider displayProvider, int id) { + DefaultGraphDisplay(DefaultGraphDisplayProvider displayProvider, + Map displayProperties, int id) { this.graphDisplayProvider = displayProvider; this.displayId = id; this.pluginTool = graphDisplayProvider.getPluginTool(); + this.displayProperties = displayProperties; this.viewer = createViewer(); buildHighlighers(); @@ -206,6 +242,16 @@ public class DefaultGraphDisplay implements GraphDisplay { connectSelectionStateListeners(); } + private Color getSelectedVertexColor() { + String property = displayProperties.getOrDefault(SELECTED_VERTEX_COLOR, "0xFF0000"); + return Colors.getHexColor(property); + } + + private Color getSelectedEdgeColor() { + String property = displayProperties.getOrDefault(SELECTED_EDGE_COLOR, "0xFF0000"); + return Colors.getHexColor(property); + } + JComponent getComponent() { JComponent component = viewer.getComponent(); component.setFocusable(true); @@ -231,10 +277,11 @@ public class DefaultGraphDisplay implements GraphDisplay { .getTransformer(VIEW); MagnifyShapeTransformer shapeTransformer = MagnifyShapeTransformer.builder(lens) - // this lens' delegate is the viewer's VIEW layer + // this lens' delegate is the viewer's VIEW layer, abandoned above .delegate(transformer) .build(); - LensGraphMouse lensGraphMouse = new DefaultLensGraphMouse<>(magnificationPlugin); + LensGraphMouse lensGraphMouse = + DefaultLensGraphMouse.builder().magnificationPlugin(magnificationPlugin).build(); return MagnifyImageLensSupport.builder(viewer) .lensTransformer(shapeTransformer) .lensGraphMouse(lensGraphMouse) @@ -249,7 +296,7 @@ public class DefaultGraphDisplay implements GraphDisplay { MultiSelectedVertexPaintable multiSelectedVertexPaintable = MultiSelectedVertexPaintable.builder(viewer) .selectionStrokeMin(4.f) - .selectionPaint(Color.red) + .selectionPaint(getSelectedVertexColor()) .useBounds(false) .build(); @@ -257,7 +304,7 @@ public class DefaultGraphDisplay implements GraphDisplay { SingleSelectedVertexPaintable singleSelectedVertexPaintable = SingleSelectedVertexPaintable.builder(viewer) .selectionStrokeMin(4.f) - .selectionPaint(Color.red) + .selectionPaint(getSelectedVertexColor()) .selectedVertexFunction(vs -> this.focusedVertex) .build(); @@ -375,14 +422,20 @@ public class DefaultGraphDisplay implements GraphDisplay { .popupMenuPath("Go To Edge Source") .popupMenuGroup("Go To") .withContext(EdgeGraphActionContext.class) - .onAction(c -> setFocusedVertex(graph.getEdgeSource(c.getClickedEdge()))) + .onAction(c -> { + selectEdge(c.getClickedEdge()); + setFocusedVertex(graph.getEdgeSource(c.getClickedEdge())); + }) .buildAndInstallLocal(componentProvider); new ActionBuilder("Edge Target", ACTION_OWNER) .popupMenuPath("Go To Edge Target") .popupMenuGroup("Go To") .withContext(EdgeGraphActionContext.class) - .onAction(c -> setFocusedVertex(graph.getEdgeTarget(c.getClickedEdge()))) + .onAction(c -> { + selectEdge(c.getClickedEdge()); + setFocusedVertex(graph.getEdgeTarget(c.getClickedEdge())); + }) .buildAndInstallLocal(componentProvider); hideSelectedAction = new ToggleActionBuilder("Hide Selected", ACTION_OWNER) @@ -426,6 +479,18 @@ public class DefaultGraphDisplay implements GraphDisplay { .onAction(c -> growSelection(getSourceVerticesFromSelected())) .buildAndInstallLocal(componentProvider); + new ActionBuilder("Grow Selection To Entire Component", ACTION_OWNER) + .popupMenuPath("Grow Selection To Entire Component") + .popupMenuGroup("z", "4") + .description( + "Extends the current selection by including the target/source vertices " + + "of all edges whose source/target is selected") + .keyBinding("ctrl C") + .enabledWhen(c -> !isAllSelected(getSourceVerticesFromSelected()) && + !isAllSelected(getTargetVerticesFromSelected())) + .onAction(c -> growSelection(getAllComponentVerticesFromSelected())) + .buildAndInstallLocal(componentProvider); + new ActionBuilder("Clear Selection", ACTION_OWNER) .popupMenuPath("Clear Selection") .popupMenuGroup("z", "5") @@ -438,7 +503,7 @@ public class DefaultGraphDisplay implements GraphDisplay { .popupMenuPath("Display Selected as New Graph") .popupMenuGroup("zz", "5") .description("Creates a subgraph from the selected nodes") - .enabledWhen(c -> !viewer.getSelectedVertexState().getSelected().isEmpty()) + .enabledWhen(c -> !viewer.getSelectedVertices().isEmpty()) .onAction(c -> createAndDisplaySubGraph()) .buildAndInstallLocal(componentProvider); @@ -505,23 +570,24 @@ public class DefaultGraphDisplay implements GraphDisplay { } private boolean hasSelection() { - return !(viewer.getSelectedVertexState().getSelected().isEmpty() && - viewer.getSelectedEdgeState().getSelected().isEmpty()); + return !(viewer.getSelectedVertices().isEmpty() && + viewer.getSelectedEdges().isEmpty()); } private boolean isSelected(AttributedVertex v) { - return viewer.getSelectedVertexState().isSelected(v); + return viewer.getSelectedVertices().contains(v); } private boolean isSelected(AttributedEdge e) { - return viewer.getSelectedEdgeState().isSelected(e); + return viewer.getSelectedEdges().contains(e); } private void createAndDisplaySubGraph() { GraphDisplay display = graphDisplayProvider.getGraphDisplay(false, TaskMonitor.DUMMY); try { - display.setGraph(createSubGraph(), "SubGraph", false, TaskMonitor.DUMMY); + display.setGraph(createSubGraph(), title + " - Sub-graph", false, TaskMonitor.DUMMY); display.setGraphDisplayListener(listener.cloneWith(display)); + copyActionsToNewGraph((DefaultGraphDisplay) display); } catch (CancelledException e) { // using Dummy, so can't happen @@ -529,16 +595,16 @@ public class DefaultGraphDisplay implements GraphDisplay { } private AttributedGraph createSubGraph() { - Set selected = viewer.getSelectedVertexState().getSelected(); + Set selected = viewer.getSelectedVertices(); Graph subGraph = new AsSubgraph<>(graph, selected); AttributedGraph newGraph = new AttributedGraph(); subGraph.vertexSet().forEach(newGraph::addVertex); - subGraph.edgeSet().forEach(e -> { + for (AttributedEdge e : subGraph.edgeSet()) { AttributedVertex source = subGraph.getEdgeSource(e); AttributedVertex target = subGraph.getEdgeTarget(e); newGraph.addEdge(source, target, e); - }); + } return newGraph; } @@ -547,42 +613,86 @@ public class DefaultGraphDisplay implements GraphDisplay { } private boolean isAllSelected(Set vertices) { - return viewer.getSelectedVertexState().getSelected().containsAll(vertices); - } - - private Set getTargetVerticesFromSelected() { - Set targets = new HashSet<>(); - Set selectedVertices = getSelectedVertices(); - selectedVertices.forEach(v -> { - Set edges = graph.outgoingEdgesOf(v); - edges.forEach(e -> targets.add(graph.getEdgeTarget(e))); - }); - return targets; + return viewer.getSelectedVertices().containsAll(vertices); } private Set getSourceVerticesFromSelected() { Set sources = new HashSet<>(); Set selectedVertices = getSelectedVertices(); - selectedVertices.forEach(v -> { + for (AttributedVertex v : selectedVertices) { Set edges = graph.incomingEdgesOf(v); edges.forEach(e -> sources.add(graph.getEdgeSource(e))); - }); + } return sources; } + private Set getUnselectedSourceVerticesFromSelected() { + MutableSelectedState selectedVertexState = + viewer.getSelectedVertexState(); + return getSourceVerticesFromSelected().stream() + .filter(v -> !selectedVertexState.isSelected(v)) + .collect(Collectors.toSet()); + } + + private Set getTargetVerticesFromSelected() { + Set targets = new HashSet<>(); + Set selectedVertices = getSelectedVertices(); + for (AttributedVertex v : selectedVertices) { + Set edges = graph.outgoingEdgesOf(v); + edges.forEach(e -> targets.add(graph.getEdgeTarget(e))); + } + return targets; + } + + private Set getUnselectedTargetVerticesFromSelected() { + MutableSelectedState selectedVertexState = + viewer.getSelectedVertexState(); + return getTargetVerticesFromSelected().stream() + .filter(v -> !selectedVertexState.isSelected(v)) + .collect(Collectors.toSet()); + } + + private Set getAllDownstreamVerticesFromSelected() { + Set downstream = new HashSet<>(); + Set targets = getUnselectedTargetVerticesFromSelected(); + while (!targets.isEmpty()) { + downstream.addAll(targets); + growSelection(targets); + targets = getUnselectedTargetVerticesFromSelected(); + } + return downstream; + } + + private Set getAllUpstreamVerticesFromSelected() { + Set upstream = new HashSet<>(); + Set sources = getUnselectedSourceVerticesFromSelected(); + while (!sources.isEmpty()) { + growSelection(sources); + upstream.addAll(sources); + sources = getUnselectedSourceVerticesFromSelected(); + } + return upstream; + } + + public Set getAllComponentVerticesFromSelected() { + Set componentVertices = getAllDownstreamVerticesFromSelected(); + componentVertices.addAll(getAllUpstreamVerticesFromSelected()); + return componentVertices; + } + private void invertSelection() { switchableSelectionListener.setEnabled(false); try { MutableSelectedState selectedVertexState = viewer.getSelectedVertexState(); - graph.vertexSet().forEach(v -> { + for (AttributedVertex v : graph.vertexSet()) { if (selectedVertexState.isSelected(v)) { selectedVertexState.deselect(v); } else { selectedVertexState.select(v); } - }); + } Set selected = selectedVertexState.getSelected(); notifySelectionChanged(selected); } @@ -659,18 +769,31 @@ public class DefaultGraphDisplay implements GraphDisplay { SatelliteVisualizationViewer.builder(parentViewer) .viewSize(satelliteSize) .build(); - satellite.setGraphMouse(new DefaultSatelliteGraphMouse()); + + // + // JUNGRAPHT CHANGE 3 + // + satellite.setGraphMouse(new JgtSatelliteGraphMouse()); + satellite.getRenderContext().setEdgeDrawPaintFunction(Colors::getColor); satellite.getRenderContext() .setEdgeStrokeFunction(ProgramGraphFunctions::getEdgeStroke); - satellite.getRenderContext().setVertexFillPaintFunction(Colors::getColor); + satellite.getRenderContext() + .setEdgeDrawPaintFunction(viewer.getRenderContext().getEdgeDrawPaintFunction()); + satellite.getRenderContext() + .setVertexFillPaintFunction(viewer.getRenderContext().getVertexFillPaintFunction()); + satellite.getRenderContext() + .setVertexDrawPaintFunction(viewer.getRenderContext().getVertexDrawPaintFunction()); + satellite.scaleToLayout(); satellite.getRenderContext().setVertexLabelFunction(n -> null); // always get the current predicate from the main view and test with it, satellite.getRenderContext() - .setVertexIncludePredicate(v -> viewer.getRenderContext().getVertexIncludePredicate().test(v)); + .setVertexIncludePredicate( + v -> viewer.getRenderContext().getVertexIncludePredicate().test(v)); satellite.getRenderContext() - .setEdgeIncludePredicate(e -> viewer.getRenderContext().getEdgeIncludePredicate().test(e)); + .setEdgeIncludePredicate( + e -> viewer.getRenderContext().getEdgeIncludePredicate().test(e)); satellite.getComponent().setBorder(BorderFactory.createEtchedBorder()); parentViewer.getComponent().addComponentListener(new ComponentAdapter() { @Override @@ -825,21 +948,28 @@ public class DefaultGraphDisplay implements GraphDisplay { focusedVertex = null; graph = attributedGraph; - layoutTransitionManager.setEdgeComparator(new EdgeComparator(graph, "EdgeType", - DefaultGraphDisplay.FAVORED_EDGE)); - configureViewerPreferredSize(); Swing.runNow(() -> { // set the graph but defer the layout algorithm setting viewer.getVisualizationModel().setGraph(graph, false); configureFilters(); + setInitialLayoutAlgorithm(); + }); + componentProvider.setVisible(true); + } + + private void setInitialLayoutAlgorithm() { + if (displayProperties.containsKey(INITIAL_LAYOUT_ALGORITHM)) { + String layoutAlgorithmName = displayProperties.get(INITIAL_LAYOUT_ALGORITHM); + layoutTransitionManager.setLayout(layoutAlgorithmName); + } + else { LayoutAlgorithm initialLayoutAlgorithm = layoutTransitionManager.getInitialLayoutAlgorithm(); initialLayoutAlgorithm.setAfter(() -> centerAndScale()); viewer.getVisualizationModel().setLayoutAlgorithm(initialLayoutAlgorithm); - }); - componentProvider.setVisible(true); + } } /** @@ -928,14 +1058,12 @@ public class DefaultGraphDisplay implements GraphDisplay { log.fine("defineEdgeAttribute " + attributeName + " is not implemented"); } - /* - * @see ghidra.program.model.graph.GraphDisplay#setVertexLabel(java.lang.String, int, int, boolean, int) - */ @Override - public void setVertexLabel(String attributeName, int alignment, int size, boolean monospace, + public void setVertexLabelAttribute(String attributeName, int alignment, int size, + boolean monospace, int maxLines) { log.fine("setVertexLabel " + attributeName); - // this would have to set the label function, the label font function + this.iconCache.setPreferredVertexLabelAttribute(attributeName); } /** @@ -1071,11 +1199,11 @@ public class DefaultGraphDisplay implements GraphDisplay { } /** - * create and return a {@link VisualizationViewer} to display graphs + * Create and return a {@link VisualizationViewer} to display graphs * @return the new VisualizationViewer */ - public VisualizationViewer createViewer() { - final VisualizationViewer vv = + protected VisualizationViewer createViewer() { + VisualizationViewer vv = VisualizationViewer. builder() .multiSelectionStrategySupplier( () -> freeFormSelection ? MultiSelectionStrategy.arbitrary() @@ -1119,21 +1247,7 @@ public class DefaultGraphDisplay implements GraphDisplay { this.iconCache = new GhidraIconCache(); RenderContext renderContext = vv.getRenderContext(); - // set up the shape and color functions - IconShapeFunction nodeImageShapeFunction = - new IconShapeFunction<>(new EllipseShapeFunction<>()); - - vv.getRenderContext().setVertexIconFunction(iconCache::get); - - // cause the vertices to be drawn with custom icons/shapes - nodeImageShapeFunction.setIconFunction(iconCache::get); - renderContext.setVertexShapeFunction(nodeImageShapeFunction); - renderContext.setVertexIconFunction(iconCache::get); - - vv.setInitialDimensionFunction(InitialDimensionFunction - .builder( - nodeImageShapeFunction.andThen(s -> RectangleUtils.convert(s.getBounds2D()))) - .build()); + setVertexPreferences(vv); // the selectedEdgeState will be controlled by the vertices that are selected. // if both endpoints of an edge are selected, select that edge. @@ -1143,18 +1257,17 @@ public class DefaultGraphDisplay implements GraphDisplay { // selected edges will be drawn with a wider stroke renderContext.setEdgeStrokeFunction( - e -> renderContext.getSelectedEdgeState().isSelected(e) ? new BasicStroke(20.f) + e -> isSelected(e) ? new BasicStroke(20.f) : ProgramGraphFunctions.getEdgeStroke(e)); + // selected edges will be drawn in red (instead of default) + Color selectedEdgeColor = getSelectedEdgeColor(); renderContext.setEdgeDrawPaintFunction( - e -> renderContext.getSelectedEdgeState().isSelected(e) ? Color.red - : Colors.getColor(e)); + e -> isSelected(e) ? selectedEdgeColor : Colors.getColor(e)); renderContext.setArrowDrawPaintFunction( - e -> renderContext.getSelectedEdgeState().isSelected(e) ? Color.red - : Colors.getColor(e)); + e -> isSelected(e) ? selectedEdgeColor : Colors.getColor(e)); renderContext.setArrowFillPaintFunction( - e -> renderContext.getSelectedEdgeState().isSelected(e) ? Color.red - : Colors.getColor(e)); + e -> isSelected(e) ? selectedEdgeColor : Colors.getColor(e)); // assign the shapes to the modal renderer ModalRenderer modalRenderer = vv.getRenderer(); @@ -1162,12 +1275,16 @@ public class DefaultGraphDisplay implements GraphDisplay { Renderer.Vertex vertexRenderer = modalRenderer.getVertexRenderer(LIGHTWEIGHT); + // cause the lightweight (optimized) renderer to use the vertex shapes instead // of using default shapes. + if (vertexRenderer instanceof LightweightVertexRenderer) { + Function vertexShapeFunction = + renderContext.getVertexShapeFunction(); LightweightVertexRenderer lightweightVertexRenderer = (LightweightVertexRenderer) vertexRenderer; - lightweightVertexRenderer.setVertexShapeFunction(ProgramGraphFunctions::getVertexShape); + lightweightVertexRenderer.setVertexShapeFunction(vertexShapeFunction); } renderContext.setVertexLabelRenderer(new JLabelVertexLabelRenderer(Color.black)); @@ -1184,58 +1301,79 @@ public class DefaultGraphDisplay implements GraphDisplay { vv.getComponent().removeMouseListener(mouseListener); } - graphMouse = new JgtPluggableGraphMouse(this); + graphMouse = new JgtGraphMouse(this); vv.setGraphMouse(graphMouse); return vv; } - /** - * Item listener for selection changes in the graph with the additional - * capability of being able to disable the listener without removing it. - */ - class SwitchableSelectionItemListener implements ItemListener { - boolean enabled = true; + private void setVertexPreferences(VisualizationViewer vv) { + RenderContext renderContext = vv.getRenderContext(); + String useIcons = + displayProperties.getOrDefault(DISPLAY_VERTICES_AS_ICONS, Boolean.TRUE.toString()); + Function toRectangle = s -> RectangleUtils.convert(s.getBounds2D()); + if (Boolean.parseBoolean(useIcons)) { + // set up the shape and color functions + IconShapeFunction nodeShaper = + new IconShapeFunction<>(new EllipseShapeFunction<>()); - @Override - public void itemStateChanged(ItemEvent e) { - if (enabled) { - Swing.runLater(() -> run(e)); - } + nodeShaper.setIconFunction(iconCache::get); + renderContext.setVertexShapeFunction(nodeShaper); + renderContext.setVertexIconFunction(iconCache::get); + + vv.setInitialDimensionFunction(InitialDimensionFunction + .builder(nodeShaper.andThen(toRectangle)) + .build()); } - - private void run(ItemEvent e) { - // there was a change in the set of selected vertices. - // if the focused vertex is null, set it from one of the selected - // vertices - if (e.getStateChange() == ItemEvent.SELECTED) { - Set selectedVertices = getSelectedVertices(); - notifySelectionChanged(new HashSet(selectedVertices)); - - if (selectedVertices.size() == 1) { - // if only one vertex was selected, make it the focused vertex - setFocusedVertex(selectedVertices.stream().findFirst().get()); - } - else if (DefaultGraphDisplay.this.focusedVertex == null) { - // if there is currently no focused Vertex, attempt to get - // one from the selectedVertices - setFocusedVertex(selectedVertices.stream().findFirst().orElse(null)); - } - } - else if (e.getStateChange() == ItemEvent.DESELECTED) { - Set selectedVertices = getSelectedVertices(); - notifySelectionChanged(selectedVertices); - } - viewer.repaint(); - } - - void setEnabled(boolean enabled) { - this.enabled = enabled; + else { + vv.getRenderContext().setVertexShapeFunction(ProgramGraphFunctions::getVertexShape); + vv.setInitialDimensionFunction(InitialDimensionFunction + .builder(renderContext.getVertexShapeFunction() + .andThen(toRectangle)) + .build()); + vv.getRenderContext().setVertexLabelFunction(Object::toString); + vv.getRenderContext() + .setVertexLabelPosition( + VertexLabel.Position.valueOf( + displayProperties.getOrDefault(VERTEX_LABEL_POSITION, "AUTO"))); } } + private void copyActionsToNewGraph(DefaultGraphDisplay display) { + + for (DockingActionIf action : addedActions) { + if (display.containsAction(action)) { + // ignore actions added by the graph itself and any actions that the end user may + // accidentally add more than once + continue; + } + + display.addAction(new DockingActionProxy(action)); + } + + } + + private boolean containsAction(DockingActionIf action) { + + String name = action.getFullName(); // name and owner + for (DockingActionIf existingAction : addedActions) { + if (name.equals(existingAction.getFullName())) { + return true; + } + } + return false; + } + @Override - public void addAction(DockingAction action) { + public void addAction(DockingActionIf action) { + + if (containsAction(action)) { + Msg.warn(this, "Action with same name and owner already exixts in graph: " + + action.getFullName()); + return; + } + + addedActions.add(action); Swing.runLater(() -> componentProvider.addLocalAction(action)); } @@ -1246,7 +1384,7 @@ public class DefaultGraphDisplay implements GraphDisplay { @Override public Set getSelectedVertices() { - return viewer.getSelectedVertexState().getSelected(); + return viewer.getSelectedVertices(); } public ActionContext getActionContext(MouseEvent e) { @@ -1314,12 +1452,17 @@ public class DefaultGraphDisplay implements GraphDisplay { Swing.runLater(() -> { // remove all actions componentProvider.removeAllLocalActions(); + addedActions.clear(); // put the standard graph actions back createToolbarActions(); createPopupActions(); }); } +//================================================================================================== +// Inner Classes +//================================================================================================== + // class passed to the PopupRegulator to help construct info popups for the graph private class GraphDisplayPopupSource implements PopupSource { @@ -1418,4 +1561,49 @@ public class DefaultGraphDisplay implements GraphDisplay { // this graph display does not have a notion of emphasizing } } + + /** + * Item listener for selection changes in the graph with the additional + * capability of being able to disable the listener without removing it. + */ + private class SwitchableSelectionItemListener implements ItemListener { + boolean enabled = true; + + @Override + public void itemStateChanged(ItemEvent e) { + if (enabled) { + Swing.runLater(() -> run(e)); + } + } + + private void run(ItemEvent e) { + // there was a change in the set of selected vertices. + // if the focused vertex is null, set it from one of the selected + // vertices + if (e.getStateChange() == ItemEvent.SELECTED) { + Set selectedVertices = getSelectedVertices(); + notifySelectionChanged(new HashSet(selectedVertices)); + + if (selectedVertices.size() == 1) { + // if only one vertex was selected, make it the focused vertex + setFocusedVertex(selectedVertices.stream().findFirst().get()); + } + else if (DefaultGraphDisplay.this.focusedVertex == null) { + // if there is currently no focused Vertex, attempt to get + // one from the selectedVertices + setFocusedVertex(selectedVertices.stream().findFirst().orElse(null)); + } + } + else if (e.getStateChange() == ItemEvent.DESELECTED) { + Set selectedVertices = getSelectedVertices(); + notifySelectionChanged(selectedVertices); + } + viewer.repaint(); + } + + void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayComponentProvider.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayComponentProvider.java index 7658bb42db..7531194e05 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayComponentProvider.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayComponentProvider.java @@ -57,10 +57,11 @@ public class DefaultGraphDisplayComponentProvider extends ComponentProviderAdapt public void closeComponent() { if (display != null) { super.closeComponent(); - // to prevent looping, null out display before callings its close method. + // to prevent looping, null out display before calling its close method. GraphDisplay closingDisplay = display; display = null; closingDisplay.close(); + removeAllLocalActions(); } } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayProvider.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayProvider.java index 8d53b68725..e109ba3b99 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayProvider.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayProvider.java @@ -15,7 +15,9 @@ */ package ghidra.graph.visualization; +import java.util.Collections; import java.util.HashSet; +import java.util.Map; import java.util.Set; import ghidra.framework.options.Options; @@ -52,7 +54,12 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider { } @Override - public GraphDisplay getGraphDisplay(boolean reuseGraph, + public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) { + return getGraphDisplay(reuseGraph, Collections.emptyMap(), monitor); + } + + @Override + public GraphDisplay getGraphDisplay(boolean reuseGraph, Map properties, TaskMonitor monitor) { if (reuseGraph && !displays.isEmpty()) { @@ -62,7 +69,7 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider { } DefaultGraphDisplay display = - Swing.runNow(() -> new DefaultGraphDisplay(this, displayCounter++)); + Swing.runNow(() -> new DefaultGraphDisplay(this, properties, displayCounter++)); displays.add(display); return display; } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/EdgeComparator.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/EdgeComparator.java index 8c193902eb..607177defb 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/EdgeComparator.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/EdgeComparator.java @@ -15,33 +15,44 @@ */ package ghidra.graph.visualization; -import java.util.*; -import java.util.stream.Collectors; - import ghidra.service.graph.AttributedEdge; -import ghidra.service.graph.AttributedGraph; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * {@code Comparator} to order {@code AttributedEdge}s based on their position in a + * supplied {@code List}. + * + */ public class EdgeComparator implements Comparator { - private final Set prioritized; - public EdgeComparator(AttributedGraph graph, String attributeName, String value) { - prioritized = graph.edgeSet() - .stream() - .filter(e -> Objects.equals(e.getAttribute(attributeName), value)) - .collect(Collectors.toSet()); + /** + * {@code Map} of EdgeType attribute value to integer priority + */ + private Map edgePriorityMap = new HashMap(); + + /** + * Create an instance and place the list values into the {@code edgePriorityMap} + * with a one-up counter expressing their relative priority + * @param edgePriorityList + */ + public EdgeComparator(List edgePriorityList) { + edgePriorityList.forEach(s -> edgePriorityMap.put(s, edgePriorityList.indexOf(s))); } + /** + * {@inheritdoc} + * Compares the {@code AttributedEdge}s using their priority in the supplied {@code edgePriorityMap} + */ @Override public int compare(AttributedEdge edgeOne, AttributedEdge edgeTwo) { - boolean edgeOnePriority = prioritized.contains(edgeOne); - boolean edgeTwoPriority = prioritized.contains(edgeTwo); - if (edgeOnePriority && !edgeTwoPriority) { - return -1; - } - else if (!edgeOnePriority && edgeTwoPriority) { - return 1; - } - return 0; + return priority(edgeOne).compareTo(priority(edgeTwo)); } + private Integer priority(AttributedEdge e) { + return edgePriorityMap.getOrDefault(e.getAttribute("EdgeType"), 0); + } } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraGraphCollapser.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraGraphCollapser.java index 4b63a3c0be..b5b7e0549d 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraGraphCollapser.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraGraphCollapser.java @@ -19,7 +19,7 @@ import java.util.*; import org.jungrapht.visualization.VisualizationServer; import org.jungrapht.visualization.selection.MutableSelectedState; -import org.jungrapht.visualization.subLayout.VisualGraphCollapser; +import org.jungrapht.visualization.sublayout.VisualGraphCollapser; import ghidra.service.graph.AttributedEdge; import ghidra.service.graph.AttributedVertex; @@ -31,26 +31,14 @@ import ghidra.service.graph.AttributedVertex; public class GhidraGraphCollapser extends VisualGraphCollapser { public GhidraGraphCollapser(VisualizationServer vv) { - super(vv, null); - } - - @Override - public AttributedVertex collapse(Collection selected) { - // Unusual Code Alert! - We are forced to set the vertex supplier here - // instead of in the constructor because we need the set of vertices that are - // going to be grouped at the GroupVertex construction time because it will create - // a final id that is based on its contained vertices. A better solution would - // be for the super class to take in a vertex factory that can take in the selected - // nodes as function parameter when creating the containing GroupVertex. - super.setVertexSupplier(() -> GroupVertex.groupVertices(selected)); - return super.collapse(selected); + super(vv); } /** * Ungroups any GroupVertices that are selected */ public void ungroupSelectedVertices() { - expand(vv.getSelectedVertexState().getSelected()); + expand(vv.getSelectedVertices()); } /** @@ -63,7 +51,7 @@ public class GhidraGraphCollapser extends VisualGraphCollapser selectedEState = vv.getSelectedEdgeState(); Collection selected = selectedVState.getSelected(); if (selected.size() > 1) { - AttributedVertex groupVertex = collapse(selected); + AttributedVertex groupVertex = collapse(selected, s -> GroupVertex.groupVertices(selected)); selectedVState.clear(); selectedEState.clear(); selectedVState.select(groupVertex); diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraIconCache.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraIconCache.java index 64f2c19d18..419b71b3e6 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraIconCache.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraIconCache.java @@ -42,6 +42,7 @@ public class GhidraIconCache { private final Map map = new ConcurrentHashMap<>(); private final IconShape.Function iconShapeFunction = new IconShape.Function(); + private String preferredVeretxLabelAttribute = null; Icon get(AttributedVertex vertex) { @@ -62,7 +63,8 @@ public class GhidraIconCache { } private Icon createIcon(AttributedVertex vertex) { - rendererLabel.setText(ProgramGraphFunctions.getLabel(vertex)); + rendererLabel + .setText(ProgramGraphFunctions.getLabel(vertex, preferredVeretxLabelAttribute)); rendererLabel.setFont(new Font(DEFAULT_FONT_NAME, Font.BOLD, DEFAULT_FONT_SIZE)); rendererLabel.setForeground(Color.black); rendererLabel.setBackground(Color.white); @@ -100,17 +102,21 @@ public class GhidraIconCache { // triangles have a non-zero +/- yoffset instead of centering the label case TRIANGLE: // scale the vertex shape - scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() * LABEL_TO_ICON_PROPORTION; - scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * LABEL_TO_ICON_PROPORTION; + scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() * + LABEL_TO_ICON_PROPORTION; + scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * + LABEL_TO_ICON_PROPORTION; vertexShape = AffineTransform.getScaleInstance(scalex, scaley) - .createTransformedShape(vertexShape); + .createTransformedShape(vertexShape); offset = -(int) ((vertexShape.getBounds().getHeight() - labelSize.getHeight()) / 2); break; case INVERTED_TRIANGLE: - scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() * LABEL_TO_ICON_PROPORTION; - scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * LABEL_TO_ICON_PROPORTION; + scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() * + LABEL_TO_ICON_PROPORTION; + scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * + LABEL_TO_ICON_PROPORTION; vertexShape = AffineTransform.getScaleInstance(scalex, scaley) - .createTransformedShape(vertexShape); + .createTransformedShape(vertexShape); offset = (int) ((vertexShape.getBounds().getHeight() - labelSize.getHeight()) / 2); break; @@ -119,17 +125,17 @@ public class GhidraIconCache { scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth(); scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight(); vertexShape = AffineTransform.getScaleInstance(scalex, scaley) - .createTransformedShape(vertexShape); + .createTransformedShape(vertexShape); break; - // diamonds and ellipses reduce the label size to fit + // diamonds and ellipses reduce the label size to fit case DIAMOND: default: // ELLIPSE scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() * 1.1; scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * 1.1; vertexShape = AffineTransform.getScaleInstance(scalex, scaley) - .createTransformedShape(vertexShape); + .createTransformedShape(vertexShape); break; } Rectangle vertexBounds = vertexShape.getBounds(); @@ -163,13 +169,13 @@ public class GhidraIconCache { label.paint(graphics); // draw the shape again, but lighter (on top of the label) offsetTransform = - AffineTransform.getTranslateInstance(strokeThickness + vertexBounds.width / 2.0, - strokeThickness + vertexBounds.height / 2.0); + AffineTransform.getTranslateInstance(strokeThickness + vertexBounds.width / 2.0, + strokeThickness + vertexBounds.height / 2.0); offsetTransform.preConcatenate(graphicsTransform); graphics.setTransform(offsetTransform); Paint paint = Colors.getColor(vertex); if (paint instanceof Color) { - Color color = (Color)paint; + Color color = (Color) paint; Color transparent = new Color(color.getRed(), color.getGreen(), color.getBlue(), 50); graphics.setPaint(transparent); graphics.setStroke(new BasicStroke(strokeThickness)); @@ -193,4 +199,12 @@ public class GhidraIconCache { public void evict(AttributedVertex vertex) { map.remove(vertex); } + + /** + * Sets the vertex label to the value of the passed attribute name + * @param attributeName the attribute key for the vertex label value to be displayed + */ + public void setPreferredVertexLabelAttribute(String attributeName) { + this.preferredVeretxLabelAttribute = attributeName; + } } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutFunction.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutFunction.java index a37f94c048..c248961f26 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutFunction.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutFunction.java @@ -98,8 +98,8 @@ class LayoutFunction . builder() .verticalVertexSpacing(300); case TREE: - return TreeLayoutAlgorithm - .builder(); + return EdgeAwareTreeLayoutAlgorithm + .edgeAwareBuilder(); case TIDIER_TREE: default: return TidierTreeLayoutAlgorithm diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutTransitionManager.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutTransitionManager.java index ccaf19f064..c500ba8f51 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutTransitionManager.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutTransitionManager.java @@ -18,6 +18,7 @@ package ghidra.graph.visualization; import static ghidra.graph.visualization.LayoutFunction.*; import java.util.Comparator; +import java.util.List; import java.util.function.Function; import java.util.function.Predicate; @@ -48,10 +49,23 @@ class LayoutTransitionManager { */ Predicate rootPredicate; + public static final List EDGE_PRIORITY_LIST = + List.of( + "Fall-Through", + "Conditional-Return", + "Unconditional-Jump", + "Conditional-Jump", + "Unconditional-Call", + "Conditional-Call", + "Terminator", + "Computed", + "Indirection", + "Entry"); /** * a {@link Comparator} to sort edges during layout graph traversal + * The default uses the {@code EDGE_PRIORTITY_LIST } */ - Comparator edgeComparator = (e1, e2) -> 0; + Comparator edgeComparator = new EdgeComparator(EDGE_PRIORITY_LIST); /** * a {@link Function} to provide {@link Rectangle} (and thus bounds} for vertices @@ -106,8 +120,6 @@ class LayoutTransitionManager { } if (layoutAlgorithm instanceof TreeLayout) { ((TreeLayout) layoutAlgorithm).setRootPredicate(rootPredicate); - layoutAlgorithm.setAfter(new PostProcessRunnable<>( - visualizationServer.getVisualizationModel().getLayoutModel())); } // remove any previously added layout paintables removePaintable(radialLayoutRings); @@ -130,8 +142,7 @@ class LayoutTransitionManager { ((EdgeSorting) layoutAlgorithm).setEdgeComparator(edgeComparator); } LayoutAlgorithmTransition.apply(visualizationServer, - layoutAlgorithm, - new PostProcessRunnable<>(visualizationServer.getVisualizationModel().getLayoutModel())); + layoutAlgorithm); } private void removePaintable(VisualizationServer.Paintable paintable) { @@ -150,9 +161,6 @@ class LayoutTransitionManager { .setRootPredicate(rootPredicate); ((TreeLayout) initialLayoutAlgorithm) .setVertexBoundsFunction(vertexBoundsFunction); - initialLayoutAlgorithm.setAfter(new PostProcessRunnable<>( - visualizationServer.getVisualizationModel().getLayoutModel())); - } if (initialLayoutAlgorithm instanceof EdgeSorting) { ((EdgeSorting) initialLayoutAlgorithm) diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/PostProcessRunnable.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/PostProcessRunnable.java deleted file mode 100644 index da03727fc4..0000000000 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/PostProcessRunnable.java +++ /dev/null @@ -1,80 +0,0 @@ -/* ### - * 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.graph.visualization; - -import java.util.*; - -import org.jgrapht.Graph; -import org.jungrapht.visualization.layout.model.LayoutModel; - -/** - * to post-process tree layouts to move vertices that overlap a vertical edge that - * is not incident on the vertex. - * This can be removed after jungrapht-layout-1.1 - * @param vertex type - * @param edge type - */ -public class PostProcessRunnable implements Runnable { - - LayoutModel layoutModel; - - public PostProcessRunnable(LayoutModel layoutModel) { - this.layoutModel = layoutModel; - } - - @Override - public void run() { - moveVerticesThatOverlapVerticalEdges(layoutModel); - } - - protected int moveVerticesThatOverlapVerticalEdges(LayoutModel layoutModel) { - int offset = 100; - int moved = 0; - Graph graph = layoutModel.getGraph(); - Map> verticalEdgeMap = new LinkedHashMap<>(); - graph.edgeSet() - .stream() - .filter(e -> layoutModel.apply(graph.getEdgeSource(e)).x == layoutModel - .apply(graph.getEdgeTarget(e)).x) - .forEach(e -> verticalEdgeMap - .computeIfAbsent(layoutModel.apply(graph.getEdgeSource(e)).x, - k -> new HashSet<>()) - .add(e)); - - for (V v : graph.vertexSet()) { - double x = layoutModel.apply(v).x; - for (E edge : verticalEdgeMap.getOrDefault(x, Collections.emptySet())) { - V source = graph.getEdgeSource(edge); - V target = graph.getEdgeTarget(edge); - if (!v.equals(source) && !v.equals(target)) { - double lowy = layoutModel.apply(source).y; - double hiy = layoutModel.apply(target).y; - if (lowy > hiy) { - double temp = lowy; - lowy = hiy; - hiy = temp; - } - double vy = layoutModel.apply(v).y; - if (lowy <= vy && vy <= hiy) { - layoutModel.set(v, layoutModel.apply(v).add(offset, 0)); - moved++; - } - } - } - } - return moved; - } -} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/ProgramGraphFunctions.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/ProgramGraphFunctions.java index 650780d2d7..95834b2269 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/ProgramGraphFunctions.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/ProgramGraphFunctions.java @@ -15,7 +15,7 @@ */ package ghidra.graph.visualization; -import static org.jungrapht.visualization.VisualizationServer.*; +import static org.jungrapht.visualization.layout.util.PropertyLoader.*; import java.awt.*; import java.util.Map; @@ -125,13 +125,14 @@ abstract class ProgramGraphFunctions { /** * gets a display label from an {@link Attributed} object (vertex) * @param attributed the attributed object to get a label for + * @param preferredLabelAttribute the attribute to use for the label, if available * @return the label for the given {@link Attributed} */ - public static String getLabel(Attributed attributed) { + public static String getLabel(Attributed attributed, String preferredLabelAttribute) { Map map = attributed.getAttributeMap(); String name = StringEscapeUtils.escapeHtml4(map.get("Name")); - if (map.containsKey("Code")) { - name = StringEscapeUtils.escapeHtml4(map.get("Code")); + if (map.containsKey(preferredLabelAttribute)) { + name = StringEscapeUtils.escapeHtml4(map.get(preferredLabelAttribute)); } return "" + String.join("

    ", Splitter.on('\n').split(name)); } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/AbstractJgtGraphMousePlugin.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/AbstractJgtGraphMousePlugin.java index d6bc5ea70d..ae2fe8a73b 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/AbstractJgtGraphMousePlugin.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/AbstractJgtGraphMousePlugin.java @@ -45,14 +45,6 @@ public abstract class AbstractJgtGraphMousePlugin protected V selectedVertex; protected E selectedEdge; - public AbstractJgtGraphMousePlugin() { - this(InputEvent.BUTTON1_DOWN_MASK); - } - - public AbstractJgtGraphMousePlugin(int selectionModifiers) { - super(selectionModifiers); - } - public VisualizationViewer getViewer(MouseEvent e) { VisualizationViewer viewer = getGraphViewer(e); return viewer; diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtCursorRestoringPlugin.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtCursorRestoringPlugin.java index cb576b621f..6e75a9dc42 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtCursorRestoringPlugin.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtCursorRestoringPlugin.java @@ -33,10 +33,6 @@ import org.jungrapht.visualization.control.AbstractGraphMousePlugin; public class JgtCursorRestoringPlugin extends AbstractGraphMousePlugin implements MouseMotionListener { - public JgtCursorRestoringPlugin() { - super(0); - } - @Override public void mouseDragged(MouseEvent e) { // don't care diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtEdgeNavigationPlugin.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtEdgeNavigationPlugin.java index f1514ce714..03c0942f43 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtEdgeNavigationPlugin.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtEdgeNavigationPlugin.java @@ -34,10 +34,19 @@ import ghidra.graph.visualization.CenterAnimationJob; */ public class JgtEdgeNavigationPlugin extends AbstractJgtGraphMousePlugin { - public JgtEdgeNavigationPlugin() { + protected int getSingleSelectionMask; + + public JgtEdgeNavigationPlugin(int singleSelectionMask) { + this.singleSelectionMask = singleSelectionMask; this.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); } + protected int singleSelectionMask; + + public boolean checkModifiers(MouseEvent e) { + return e.getModifiersEx() == singleSelectionMask; + } + @Override public void mousePressed(MouseEvent e) { if (!checkModifiers(e)) { diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtPluggableGraphMouse.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtGraphMouse.java similarity index 63% rename from Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtPluggableGraphMouse.java rename to Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtGraphMouse.java index be575731c8..b198d513b1 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtPluggableGraphMouse.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtGraphMouse.java @@ -19,7 +19,6 @@ import java.awt.event.InputEvent; import org.jungrapht.visualization.control.*; -import docking.DockingUtils; import ghidra.graph.visualization.DefaultGraphDisplay; import ghidra.service.graph.AttributedEdge; import ghidra.service.graph.AttributedVertex; @@ -27,14 +26,14 @@ import ghidra.service.graph.AttributedVertex; /** * Pluggable graph mouse for jungrapht */ -public class JgtPluggableGraphMouse extends DefaultGraphMouse { +public class JgtGraphMouse extends DefaultGraphMouse { private DefaultGraphDisplay graphDisplay; // TODO we should not need the graph display for any mouse plugins, but the API is net yet // robust enough to communicate fully without it - public JgtPluggableGraphMouse(DefaultGraphDisplay graphDisplay) { - super(DefaultGraphMouse. builder()); + public JgtGraphMouse(DefaultGraphDisplay graphDisplay) { + super(DefaultGraphMouse.builder()); this.graphDisplay = graphDisplay; } @@ -47,24 +46,29 @@ public class JgtPluggableGraphMouse extends DefaultGraphMouse()); + add(new JgtEdgeNavigationPlugin<>(InputEvent.BUTTON1_DOWN_MASK)); - add(new JgtVertexFocusingPlugin(graphDisplay)); + add(new JgtVertexFocusingPlugin<>(InputEvent.BUTTON1_DOWN_MASK, graphDisplay)); - // scaling - add(new ScalingGraphMousePlugin(new CrossoverScalingControl(), 0, in, out)); + // + // JUNGRAPHT CHANGE 1,2 + // + // Note: this code can go away when we can turn off the picking square + add(new JgtSelectingGraphMousePlugin()); + // add(new SelectingGraphMousePlugin<>()); + + add(new RegionSelectingGraphMousePlugin<>()); // the grab/pan feature - add(new JgtTranslatingPlugin()); + add(new TranslatingGraphMousePlugin(InputEvent.BUTTON1_DOWN_MASK)); - add(new SelectingGraphMousePlugin( - InputEvent.BUTTON1_DOWN_MASK, - 0, - DockingUtils.CONTROL_KEY_MODIFIER_MASK)); + // scaling + add(new ScalingGraphMousePlugin()); // cursor cleanup - add(new JgtCursorRestoringPlugin()); + add(new JgtCursorRestoringPlugin<>()); setPluginsLoaded(); } + } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSatelliteGraphMouse.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSatelliteGraphMouse.java new file mode 100644 index 0000000000..da8f9dab09 --- /dev/null +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSatelliteGraphMouse.java @@ -0,0 +1,60 @@ +/* ### + * 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.graph.visualization.mouse; + +import org.jungrapht.visualization.control.*; + +import ghidra.service.graph.AttributedEdge; +import ghidra.service.graph.AttributedVertex; + +// Note: if the jungrapht API changes to fix '// JUNGRAPHT CHANGE 1' and '2', then this class +// can be removed +public class JgtSatelliteGraphMouse + extends DefaultSatelliteGraphMouse { + + @Override + public void loadPlugins() { + scalingPlugin = + new SatelliteScalingGraphMousePlugin( + new CrossoverScalingControl(), + scalingMask, + xAxisScalingMask, + yAxisScalingMask, + in, + out); + + // + // JUNGRAPHT CHANGE 3 + // + SelectingGraphMousePlugin mySelectingPlugin = + new JgtSelectingGraphMousePlugin(singleSelectionMask, addSingleSelectionMask); + mySelectingPlugin.setLocked(true); + selectingPlugin = mySelectingPlugin; + + regionSelectingPlugin = + RegionSelectingGraphMousePlugin.builder() + .regionSelectionMask(regionSelectionMask) + .addRegionSelectionMask(addRegionSelectionMask) + .regionSelectionCompleteMask(regionSelectionCompleteMask) + .addRegionSelectionCompleteMask(addRegionSelectionCompleteMask) + .build(); + translatingPlugin = new SatelliteTranslatingGraphMousePlugin(translatingMask); + add(selectingPlugin); + add(regionSelectingPlugin); + add(translatingPlugin); + add(scalingPlugin); + } +} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSelectingGraphMousePlugin.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSelectingGraphMousePlugin.java new file mode 100644 index 0000000000..e2f15d4917 --- /dev/null +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSelectingGraphMousePlugin.java @@ -0,0 +1,111 @@ +/* ### + * 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.graph.visualization.mouse; + +import java.awt.Graphics; +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; + +import org.jungrapht.visualization.VisualizationServer; +import org.jungrapht.visualization.VisualizationServer.Paintable; +import org.jungrapht.visualization.control.GraphElementAccessor; +import org.jungrapht.visualization.control.SelectingGraphMousePlugin; +import org.jungrapht.visualization.layout.model.LayoutModel; +import org.jungrapht.visualization.selection.MutableSelectedState; +import org.jungrapht.visualization.selection.ShapePickSupport; + +import ghidra.service.graph.AttributedEdge; +import ghidra.service.graph.AttributedVertex; + +public class JgtSelectingGraphMousePlugin + extends SelectingGraphMousePlugin { + + private Paintable dummyPickFootprintPaintable = new Paintable() { + @Override + public void paint(Graphics g) { + // stub + } + + @Override + public boolean useTransform() { + return false; + } + }; + + JgtSelectingGraphMousePlugin() { + super(); + + // + // JUNGRAPHT CHANGE 1 + // turn off painting of the picking square + this.pickFootprintPaintable = dummyPickFootprintPaintable; + } + + public JgtSelectingGraphMousePlugin(int singleSelectionMask, int addSingleSelectionMask) { + super(singleSelectionMask, addSingleSelectionMask); + + // + // JUNGRAPHT CHANGE 1 + // turn off painting of the picking square + this.pickFootprintPaintable = dummyPickFootprintPaintable; + } + + // JUNGRAPHT CHANGE 2 + @Override + protected boolean singleVertexSelection( + MouseEvent e, Point2D layoutPoint, boolean addToSelection) { + VisualizationServer vv = + (VisualizationServer) e.getSource(); + GraphElementAccessor pickSupport = + vv.getPickSupport(); + MutableSelectedState selectedVertexState = + vv.getSelectedVertexState(); + LayoutModel layoutModel = vv.getVisualizationModel().getLayoutModel(); + if (pickSupport instanceof ShapePickSupport) { + ShapePickSupport shapePickSupport = + (ShapePickSupport) pickSupport; + vertex = shapePickSupport.getVertex(layoutModel, footprintRectangle); + } + else { + vertex = pickSupport.getVertex(layoutModel, layoutPoint.getX(), layoutPoint.getY()); + } + + if (vertex != null) { + if (!selectedVertexState.isSelected(vertex)) { + if (!addToSelection) { + selectedVertexState.clear(); + } + selectedVertexState.select(vertex); + deselectedVertex = null; + } + else { + // If this vertex is still around in mouseReleased, it will be deselected + // If this vertex was pressed again in order to drag it, it will be set + // to null in the mouseDragged method + + // + // JUNGRAPHT CHANGE 2 HERE + // + if (addToSelection) { + deselectedVertex = vertex; + } + } + e.consume(); + return true; + } + return false; + } +} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtTranslatingPlugin.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtTranslatingPlugin.java index cf2ff54aff..56e3e99ac8 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtTranslatingPlugin.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtTranslatingPlugin.java @@ -40,16 +40,21 @@ public class JgtTranslatingPlugin private boolean panning; private boolean isHandlingEvent; + private int translatingMask; public JgtTranslatingPlugin() { this(InputEvent.BUTTON1_DOWN_MASK); } public JgtTranslatingPlugin(int modifiers) { - super(modifiers); + this.translatingMask = modifiers; this.cursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR); } + public boolean checkModifiers(MouseEvent e) { + return e.getModifiersEx() == translatingMask; + } + @Override public void mousePressed(MouseEvent e) { boolean accepted = checkModifiers(e) && isInDraggingArea(e); diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtUtils.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtUtils.java index 33a15ac02a..519dc1763a 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtUtils.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtUtils.java @@ -15,8 +15,6 @@ */ package ghidra.graph.visualization.mouse; -import static org.jungrapht.visualization.VisualizationServer.*; - import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; @@ -27,6 +25,8 @@ import org.jungrapht.visualization.control.TransformSupport; import org.jungrapht.visualization.layout.model.LayoutModel; import org.jungrapht.visualization.selection.ShapePickSupport; +import static org.jungrapht.visualization.layout.util.PropertyLoader.PREFIX; + /** * Keeper of shared logic for jungrapht handling */ diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtVertexFocusingPlugin.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtVertexFocusingPlugin.java index cfac3f259c..7f16d08ed0 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtVertexFocusingPlugin.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtVertexFocusingPlugin.java @@ -29,11 +29,18 @@ import ghidra.service.graph.AttributedVertex; public class JgtVertexFocusingPlugin extends AbstractJgtGraphMousePlugin { private DefaultGraphDisplay graphDisplay; + protected int singleSelectionMask; - public JgtVertexFocusingPlugin(DefaultGraphDisplay graphDisplay) { + public JgtVertexFocusingPlugin(int singleSelectionMask, DefaultGraphDisplay graphDisplay) { + this.singleSelectionMask = singleSelectionMask; this.graphDisplay = graphDisplay; } + @Override + public boolean checkModifiers(MouseEvent e) { + return e.getModifiersEx() == singleSelectionMask; + } + @Override public void mousePressed(MouseEvent e) { if (!checkModifiers(e)) { diff --git a/Ghidra/Features/GraphServices/src/main/resources/jungrapht.properties b/Ghidra/Features/GraphServices/src/main/resources/jungrapht.properties index d05dea0cc3..707a0815ae 100644 --- a/Ghidra/Features/GraphServices/src/main/resources/jungrapht.properties +++ b/Ghidra/Features/GraphServices/src/main/resources/jungrapht.properties @@ -42,13 +42,10 @@ jungrapht.mincross.verticalOffset=5 # how many times to run the full all-level cross count jungrapht.mincross.maxLevelCross=10 -pass + # how many times to iterate over the layers while swapping node positions (mincross) jungrapht.mincrossTransposeLimit=5 -# not using spatial data structures for edges -jungrapht.edgeSpatialSupport=NONE - # over 200 and the eiglsperger algorithm will run instead of the sugiyama jungrapht.mincross.eiglspergerThreshold=200 @@ -63,5 +60,21 @@ jungrapht.initialDimensionVertexDensity=0.3f jungrapht.minScale=0.001 jungrapht.maxScale=4.0 -# not using spatial data structures for vertices at this time. May remove after jungrapht 1.1 -jungrapht.vertexSpatialSupport=NONE +# spatial data structures for vertices +jungrapht.vertexSpatialSupport=RTREE +# spatial data structures for edges +jungrapht.edgeSpatialSupport=RTREE + +#mouse modifiers +# the mask for single vertex/edge selection +jungrapht.singleSelectionMask=MB1 +# the mask to augment the selection with a single vertex/edge +jungrapht.addSingleSelectionMask=MB1_MENU +# the mask to select vertices within a region +jungrapht.regionSelectionMask=MB1_MENU +# the mask to augment the selection with vertices in a region +jungrapht.addRegionSelectionMask=MB1_SHIFT_MENU +# the mask to indicate that the selection region is complete/closed and selection may commence +jungrapht.regionSelectionCompleteMask=MENU +# the mask to indicate that the selection region for augmentation is complete/closed and selection may commence +jungrapht.addRegionSelectionCompleteMask=SHIFT_MENU diff --git a/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/cmd/data/rtti/RttiUtil.java b/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/cmd/data/rtti/RttiUtil.java index 838e871ea9..4fd197d895 100644 --- a/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/cmd/data/rtti/RttiUtil.java +++ b/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/cmd/data/rtti/RttiUtil.java @@ -128,6 +128,7 @@ public class RttiUtil { Memory memory = program.getMemory(); MemoryBlock textBlock = memory.getBlock(".text"); + MemoryBlock nepBlock = memory.getBlock(".nep"); AddressSetView initializedAddresses = memory.getLoadedAndInitializedAddressSet(); PseudoDisassembler pseudoDisassembler = new PseudoDisassembler(program); @@ -148,9 +149,16 @@ public class RttiUtil { if (!initializedAddresses.contains(referencedAddress)) { break; // Not pointing to initialized memory. } - if ((textBlock != null) ? !textBlock.equals(memory.getBlock(referencedAddress)) - : false) { - break; // Not pointing to text section. + + // check in .text and .nep if either exists + if ( textBlock != null || nepBlock != null) { + MemoryBlock refedBlock = memory.getBlock(referencedAddress); + boolean inTextBlock = ((textBlock != null) && textBlock.equals(refedBlock)); + boolean inNepBlock = ((nepBlock != null) && nepBlock.equals(refedBlock)); + // if not in either labeled .text/.nep block, then bad vftable pointer + if (!(inTextBlock || inNepBlock)) { + break; // Not pointing to good section. + } } // any references after the first one ends the table @@ -158,7 +166,7 @@ public class RttiUtil { break; } - if (!pseudoDisassembler.isValidSubroutine(referencedAddress, true)) { + if (!pseudoDisassembler.isValidSubroutine(referencedAddress, true, false)) { break; // Not pointing to possible function. } diff --git a/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/cmd/data/rtti/VfTableModel.java b/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/cmd/data/rtti/VfTableModel.java index 636608aa02..62b0accf6e 100644 --- a/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/cmd/data/rtti/VfTableModel.java +++ b/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/cmd/data/rtti/VfTableModel.java @@ -37,13 +37,14 @@ import ghidra.program.model.listing.Program; public class VfTableModel extends AbstractCreateDataTypeModel { public static final String DATA_TYPE_NAME = "vftable"; + private static final int NO_LAST_COUNT = -1; private DataType dataType; private Rtti4Model rtti4Model; private Program lastProgram; private DataType lastDataType; - private int lastElementCount = -1; + private int lastElementCount = NO_LAST_COUNT; private int elementCount = 0; /** @@ -126,7 +127,7 @@ public class VfTableModel extends AbstractCreateDataTypeModel { */ private DataType getDataType(Program program) { - if (program != lastProgram) { + if (program != lastProgram || lastElementCount == NO_LAST_COUNT) { setIsDataTypeAlreadyBasedOnCount(true); lastProgram = program; diff --git a/Ghidra/Features/ProgramGraph/src/main/help/help/topics/ProgramGraphPlugin/ProgramGraph.htm b/Ghidra/Features/ProgramGraph/src/main/help/help/topics/ProgramGraphPlugin/ProgramGraph.htm index a67e138644..1a71d1c47e 100644 --- a/Ghidra/Features/ProgramGraph/src/main/help/help/topics/ProgramGraphPlugin/ProgramGraph.htm +++ b/Ghidra/Features/ProgramGraph/src/main/help/help/topics/ProgramGraphPlugin/ProgramGraph.htm @@ -362,9 +362,9 @@

    -

    Rename Vertex

    +

    Rename Symbol

    -

    Allows the user to rename the symbol represented by the given vertex. +

    Allows the user to rename the symbol in the program represented by the given vertex.

    diff --git a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockGraphTask.java b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockGraphTask.java index 7fda8e3bd5..c524e880c2 100644 --- a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockGraphTask.java +++ b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockGraphTask.java @@ -18,8 +18,10 @@ package ghidra.graph.program; import java.awt.Color; import java.util.*; +import docking.action.builder.ActionBuilder; import docking.widgets.EventTrigger; import ghidra.app.plugin.core.colorizer.ColorizingService; +import ghidra.app.util.AddEditDialog; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.*; import ghidra.program.model.block.*; @@ -28,8 +30,7 @@ import ghidra.program.model.symbol.*; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.service.graph.*; -import ghidra.util.HTMLUtilities; -import ghidra.util.Msg; +import ghidra.util.*; import ghidra.util.exception.CancelledException; import ghidra.util.exception.GraphException; import ghidra.util.task.Task; @@ -145,12 +146,13 @@ public class BlockGraphTask extends Task { GraphDisplay display = graphProvider.getGraphDisplay(reuseGraph, monitor); BlockModelGraphDisplayListener listener = new BlockModelGraphDisplayListener(tool, blockModel, display); + addActions(display, v -> listener.getAddress(v)); display.setGraphDisplayListener(listener); if (showCode) { display.defineVertexAttribute(CODE_ATTRIBUTE); display.defineVertexAttribute(SYMBOLS_ATTRIBUTE); - display.setVertexLabel(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true, + display.setVertexLabelAttribute(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true, codeLimitPerBlock + 1); } display.setGraph(graph, graphTitle, appendGraph, monitor); @@ -175,6 +177,37 @@ public class BlockGraphTask extends Task { } } + private void addActions(GraphDisplay display, + java.util.function.Function addressFunction) { + + display.addAction(new ActionBuilder("Rename Symbol", "Block Graph") + .popupMenuPath("Rename Symbol") + .withContext(VertexGraphActionContext.class) + .helpLocation(new HelpLocation("ProgramGraphPlugin", "Rename_Symbol")) + // only enable action when vertex corresponds to an address + .enabledWhen(c -> addressFunction.apply(c.getClickedVertex()) != null) + .onAction(c -> updateVertexName(addressFunction, c)) + .build()); + } + + private void updateVertexName( + java.util.function.Function addressFunction, + VertexGraphActionContext context) { + + AttributedVertex vertex = context.getClickedVertex(); + Address address = addressFunction.apply(vertex); + Symbol symbol = program.getSymbolTable().getPrimarySymbol(address); + + if (symbol == null) { + AddEditDialog dialog = new AddEditDialog("Create Label", tool); + dialog.addLabel(address, program, context.getComponentProvider()); + } + else { + AddEditDialog dialog = new AddEditDialog("Edit Label", tool); + dialog.editLabel(symbol, program, context.getComponentProvider()); + } + } + /** * Set the maximum number of code lines which will be used per block when * showCode is enabled. @@ -318,10 +351,8 @@ public class BlockGraphTask extends Task { CodeBlockReference cbRef = refIter.next(); CodeBlock db = cbRef.getDestinationBlock(); - - // must be a reference to a data block if (db == null) { - continue; + continue; // must be a reference to a data block } // don't include destination if it does not overlap selection @@ -336,7 +367,6 @@ public class BlockGraphTask extends Task { } // put the edge in the graph - String edgeAddr = cbRef.getReferent().toString(); AttributedEdge newEdge = graph.addEdge(fromVertex, toVertex); // set it's attributes (really its name) diff --git a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockModelGraphDisplayListener.java b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockModelGraphDisplayListener.java index 7a03962fca..2acd7567ca 100644 --- a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockModelGraphDisplayListener.java +++ b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockModelGraphDisplayListener.java @@ -17,16 +17,13 @@ package ghidra.graph.program; import java.util.*; -import docking.action.builder.ActionBuilder; import ghidra.app.plugin.core.graph.AddressBasedGraphDisplayListener; -import ghidra.app.util.AddEditDialog; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.*; import ghidra.program.model.block.*; import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.SymbolTable; import ghidra.service.graph.*; -import ghidra.util.HelpLocation; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -41,18 +38,11 @@ public class BlockModelGraphDisplayListener extends AddressBasedGraphDisplayList GraphDisplay display) { super(tool, blockModel.getProgram(), display); this.blockModel = blockModel; - addActions(display); } - private void addActions(GraphDisplay display) { - display.addAction(new ActionBuilder("Rename Vertex", "Block Graph") - .popupMenuPath("Rename Vertex") - .withContext(VertexGraphActionContext.class) - .helpLocation(new HelpLocation("ProgramGraphPlugin", "Rename Vertex")) - // only enable action when vertex corresponds to an address - .enabledWhen(c -> getAddress(c.getClickedVertex().getId()) != null) - .onAction(this::updateVertexName) - .build()); + @Override + public Address getAddress(AttributedVertex vertex) { + return super.getAddress(vertex); } @Override @@ -79,25 +69,7 @@ public class BlockModelGraphDisplayListener extends AddressBasedGraphDisplayList // Identify all blocks which have an entry point within the selection address set Set vertices = new HashSet<>(); try { - SymbolTable symTable = program.getSymbolTable(); - CodeBlockIterator cbIter = - blockModel.getCodeBlocksContaining(addrSet, TaskMonitor.DUMMY); - while (cbIter.hasNext()) { - CodeBlock block = cbIter.next(); - String addrString; - Address addr = block.getFirstStartAddress(); - if (addr.isExternalAddress()) { - Symbol s = symTable.getPrimarySymbol(addr); - addrString = s.getName(true); - } - else { - addrString = addr.toString(); - } - AttributedVertex vertex = graphDisplay.getGraph().getVertex(addrString); - if (vertex != null) { - vertices.add(vertex); - } - } + addVerticesForAddresses(addrSet, vertices); } catch (CancelledException e) { // Will not happen with dummyMonitor @@ -107,37 +79,39 @@ public class BlockModelGraphDisplayListener extends AddressBasedGraphDisplayList return vertices; } + private void addVerticesForAddresses(AddressSetView addrSet, Set vertices) + throws CancelledException { + + SymbolTable symTable = program.getSymbolTable(); + CodeBlockIterator it = + blockModel.getCodeBlocksContaining(addrSet, TaskMonitor.DUMMY); + while (it.hasNext()) { + CodeBlock block = it.next(); + String addrString; + Address addr = block.getFirstStartAddress(); + if (addr.isExternalAddress()) { + Symbol s = symTable.getPrimarySymbol(addr); + addrString = s.getName(true); + } + else { + addrString = addr.toString(); + } + AttributedVertex vertex = graphDisplay.getGraph().getVertex(addrString); + if (vertex != null) { + vertices.add(vertex); + } + } + } + @Override protected AddressSet getAddresses(Set vertices) { - AddressSet addrSet = new AddressSet(); + AddressSet addrSet = new AddressSet(); try { // for each address string, translate it into a block // and add it to the address set. for (AttributedVertex vertex : vertices) { - Address blockAddr = getAddress(vertex); - if (!isValidAddress(blockAddr)) { - continue; - } - CodeBlock blocks[] = null; - if (blockModel != null) { - CodeBlock block = blockModel.getCodeBlockAt(blockAddr, TaskMonitor.DUMMY); - if (block != null) { - blocks = new CodeBlock[1]; - blocks[0] = block; - } - else { - blocks = blockModel.getCodeBlocksContaining(blockAddr, TaskMonitor.DUMMY); - } - } - if (blocks != null && blocks.length > 0) { - for (CodeBlock block : blocks) { - addrSet.add(block); - } - } - else { - addrSet.addRange(blockAddr, blockAddr); - } + addBlockAddresses(addrSet, vertex); } } catch (CancelledException e) { @@ -147,6 +121,35 @@ public class BlockModelGraphDisplayListener extends AddressBasedGraphDisplayList return addrSet; } + private void addBlockAddresses(AddressSet addrSet, AttributedVertex vertex) + throws CancelledException { + + Address blockAddr = getAddress(vertex); + if (!isValidAddress(blockAddr)) { + return; + } + + CodeBlock blocks[] = null; + if (blockModel != null) { + CodeBlock block = blockModel.getCodeBlockAt(blockAddr, TaskMonitor.DUMMY); + if (block != null) { + blocks = new CodeBlock[1]; + blocks[0] = block; + } + else { + blocks = blockModel.getCodeBlocksContaining(blockAddr, TaskMonitor.DUMMY); + } + } + if (blocks != null && blocks.length > 0) { + for (CodeBlock block : blocks) { + addrSet.add(block); + } + } + else { + addrSet.addRange(blockAddr, blockAddr); + } + } + protected boolean isValidAddress(Address addr) { if (addr == null || program == null) { return false; @@ -154,21 +157,6 @@ public class BlockModelGraphDisplayListener extends AddressBasedGraphDisplayList return program.getMemory().contains(addr) || addr.isExternalAddress(); } - private void updateVertexName(VertexGraphActionContext context) { - AttributedVertex vertex = context.getClickedVertex(); - Address address = getAddress(vertex); - Symbol symbol = program.getSymbolTable().getPrimarySymbol(address); - - if (symbol == null) { - AddEditDialog dialog = new AddEditDialog("Create Label", tool); - dialog.addLabel(address, program, context.getComponentProvider()); - } - else { - AddEditDialog dialog = new AddEditDialog("Edit Label", tool); - dialog.editLabel(symbol, program, context.getComponentProvider()); - } - } - @Override public GraphDisplayListener cloneWith(GraphDisplay newGraphDisplay) { return new BlockModelGraphDisplayListener(tool, blockModel, newGraphDisplay); diff --git a/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/TestGraphDisplay.java b/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/TestGraphDisplay.java index a538b39ee2..61c2d279c0 100644 --- a/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/TestGraphDisplay.java +++ b/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/TestGraphDisplay.java @@ -18,7 +18,7 @@ package ghidra.graph.program; import java.util.HashSet; import java.util.Set; -import docking.action.DockingAction; +import docking.action.DockingActionIf; import docking.widgets.EventTrigger; import ghidra.service.graph.*; import ghidra.util.exception.CancelledException; @@ -74,7 +74,8 @@ public class TestGraphDisplay implements GraphDisplay { } @Override - public void setVertexLabel(String attributeName, int alignment, int size, boolean monospace, + public void setVertexLabelAttribute(String attributeName, int alignment, int size, + boolean monospace, int maxLines) { // nothing } @@ -116,8 +117,7 @@ public class TestGraphDisplay implements GraphDisplay { } @Override - public void addAction(DockingAction action) { + public void addAction(DockingActionIf action) { // do nothing, actions are not supported by this display } - } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/WrappingVerticalLayoutTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/WrappingVerticalLayoutTextField.java index 9123414861..2244301df5 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/WrappingVerticalLayoutTextField.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/WrappingVerticalLayoutTextField.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. @@ -18,35 +17,46 @@ package docking.widgets.fieldpanel.field; import docking.widgets.fieldpanel.support.*; - public class WrappingVerticalLayoutTextField extends VerticalLayoutTextField { - - /** - * This constructor will create a text field from an single AttributedString. The string will - * be word wrapped. - * - * @param textElement the AttributedString to display - * @param startX the x position to draw the string - * @param width the max width allocated to this field - * @param maxLines the max number of lines to display - * @param hlFactory the highlight factory - */ - public WrappingVerticalLayoutTextField(FieldElement textElement, - int startX, - int width, - int maxLines, - HighlightFactory hlFactory) { - - super(FieldUtils.wrap(textElement, width), startX, width, maxLines, hlFactory, ""); - } - /** + /** + * This constructor will create a text field from an single AttributedString. The string will + * be word wrapped. + * + * @param textElement the AttributedString to display + * @param startX the x position to draw the string + * @param width the max width allocated to this field + * @param maxLines the max number of lines to display + * @param hlFactory the highlight factory + */ + public WrappingVerticalLayoutTextField(FieldElement textElement, int startX, int width, + int maxLines, HighlightFactory hlFactory) { + + super(FieldUtils.wrap(textElement, width), startX, width, maxLines, hlFactory, ""); + } + + /** + * Create a text field from a single FieldElement. The text is wrapped, either an words or simply + * @param textElement is the element to display + * @param startX is the position to draw the string + * @param width is the max width allocated to this field + * @param maxLines is the max number of lines to display + * @param hlFactory is the highlight factory + * @param breakOnWhiteSpace is true if wrapping should break on word boundaries + */ + public WrappingVerticalLayoutTextField(FieldElement textElement, int startX, int width, + int maxLines, HighlightFactory hlFactory, boolean breakOnWhiteSpace) { + super(FieldUtils.wrap(textElement, width, breakOnWhiteSpace), startX, width, maxLines, + hlFactory, ""); + } + + /** * Finds the corresponding row, column for string index, and offset * @param index index into the string array * @param offset offset into the indexed string. */ - @Override - public RowColLocation dataToScreenLocation(int index, int offset) { - return textOffsetToScreenLocation(offset); - } + @Override + public RowColLocation dataToScreenLocation(int index, int offset) { + return textOffsetToScreenLocation(offset); + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldUtils.java index f93835d8fc..5e0ddbab88 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldUtils.java @@ -31,7 +31,7 @@ public class FieldUtils { } public static List wrap(List fieldElements, int width) { - List wrappedElements = new ArrayList(); + List wrappedElements = new ArrayList<>(); for (FieldElement fieldElement : fieldElements) { wrappedElements.addAll(wordWrapList(fieldElement, width)); } @@ -52,7 +52,7 @@ public class FieldUtils { return new FieldElement[] { originalFieldElement }; } - List lines = new ArrayList(); + List lines = new ArrayList<>(); int wordWrapPos = findWordWrapPosition(originalFieldElement, width); while (wordWrapPos > 0) { lines.add(originalFieldElement.substring(0, wordWrapPos)); @@ -66,6 +66,42 @@ public class FieldUtils { return lines.toArray(new FieldElement[lines.size()]); } + /** + * Splits the given FieldElement into sub-elements by wrapping the element in some fashion. + * If breakOnWhiteSpace is indicated, wrapping will break lines on a white space character + * if possible, otherwise wrapping occurs on the last possible character. + * @param fieldElement is the element to wrap + * @param width is the maximum width to allow before wrapping + * @param breakOnWhiteSpace determines whether line breaks should happen at white space chars + * @return the wrapped elements + */ + public static FieldElement[] wrap(FieldElement fieldElement, int width, + boolean breakOnWhiteSpace) { + if (breakOnWhiteSpace) { + return wrap(fieldElement, width); + } + FieldElement originalFieldElement = fieldElement.replaceAll(WHITE_SPACE, ' '); + if (originalFieldElement.getStringWidth() <= width) { + return new FieldElement[] { originalFieldElement }; + } + + List lines = new ArrayList<>(); + int wordWrapPos = originalFieldElement.getMaxCharactersForWidth(width); + if (wordWrapPos == originalFieldElement.length()) { + wordWrapPos = 0; + } + while (wordWrapPos > 0) { + lines.add(originalFieldElement.substring(0, wordWrapPos)); + originalFieldElement = originalFieldElement.substring(wordWrapPos); + wordWrapPos = originalFieldElement.getMaxCharactersForWidth(width); + if (wordWrapPos == originalFieldElement.length()) { + wordWrapPos = 0; + } + } + lines.add(originalFieldElement); + return lines.toArray(new FieldElement[lines.size()]); + } + /** * Splits the given FieldElement into sub-elements by wrapping the element on whitespace. * @@ -74,7 +110,7 @@ public class FieldUtils { * @return The wrapped elements */ public static List wordWrapList(FieldElement fieldElement, int width) { - List lines = new ArrayList(); + List lines = new ArrayList<>(); FieldElement originalFieldElement = fieldElement.replaceAll(WHITE_SPACE, ' '); if (originalFieldElement.getStringWidth() <= width) { diff --git a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentQ.java b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentQ.java index 3fcff98639..f52b772549 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentQ.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentQ.java @@ -114,8 +114,8 @@ import ghidra.util.task.TaskMonitor; * } * }; * - * QItemListener itemListener = new QItemListener() { - * public void itemProcessed(QResult result) { + * {@literal QItemListener itemListener = new QItemListener()} { + * {@literal public void itemProcessed(QResult result)} { * RESULT result = result.getResult(); * // work on my result... * } diff --git a/Ghidra/Framework/Generic/src/test/java/utilities/util/FileUtilitiesTest.java b/Ghidra/Framework/Generic/src/test/java/utilities/util/FileUtilitiesTest.java index 1b428d42c3..142c6b4ca6 100644 --- a/Ghidra/Framework/Generic/src/test/java/utilities/util/FileUtilitiesTest.java +++ b/Ghidra/Framework/Generic/src/test/java/utilities/util/FileUtilitiesTest.java @@ -140,6 +140,38 @@ public class FileUtilitiesTest { assertNull(relative); } + @Test + public void testRelativizePath_ResourceFiles() { + ResourceFile f1 = new ResourceFile(new File("/a/b")); + ResourceFile f2 = new ResourceFile(new File("/a/b/c")); + String relative = FileUtilities.relativizePath(f1, f2); + assertEquals("c", relative); + } + + @Test + public void testRelativizePath_ResourceFiles2() { + ResourceFile f1 = new ResourceFile(new File("/a/b")); + ResourceFile f2 = new ResourceFile(new File("/a/b/c/d")); + String relative = FileUtilities.relativizePath(f1, f2); + assertEquals("c/d", relative); + } + + @Test + public void testRelativizePath_ResourceFiles_NotRelated() { + ResourceFile f1 = new ResourceFile(new File("/a/b")); + ResourceFile f2 = new ResourceFile(new File("/c/d")); + String relative = FileUtilities.relativizePath(f1, f2); + assertNull(relative); + } + + @Test + public void testRelativizePath_ResourceFiles_Same() { + ResourceFile f1 = new ResourceFile(new File("/a/b")); + ResourceFile f2 = new ResourceFile(new File("/a/b")); + String relative = FileUtilities.relativizePath(f1, f2); + assertNull(relative); + } + private ResourceFile createNestedTempFile(String path) throws Exception { String tmpdir = AbstractGenericTest.getTestDirectoryPath(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/floatformat/BigFloat.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/floatformat/BigFloat.java index f80938011f..78074b1d63 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/floatformat/BigFloat.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/floatformat/BigFloat.java @@ -15,7 +15,8 @@ */ package ghidra.pcode.floatformat; -import java.math.*; +import java.math.BigDecimal; +import java.math.BigInteger; /** * An IEEE 754 floating point class. @@ -94,12 +95,15 @@ public strictfp class BigFloat implements Comparable { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } BigFloat other = (BigFloat) obj; if (expbits != other.expbits) { return false; @@ -225,11 +229,12 @@ public strictfp class BigFloat implements Comparable { * This function is used internally to round after a computation. * *

    Assume that the true value is - * sign * (unscaled + eps) * 2 ^ (scale-fracbits) + *

       sign * (unscaled + eps) * 2 ^ (scale-fracbits)
     	 * and
    -	 *    unscaled.bitLength() > fracbits+1
    -	 *     (or the value is subnormal with at least 1 bit of extra precision)
    -	 *  
    +	 *   unscaled.bitLength() > fracbits+1 
    +	 *
    +	 * (or the value is subnormal with at least 1 bit of extra precision)
    +	 * 
    * @param eps < 1 */ protected void internalRound(boolean eps) { @@ -320,8 +325,9 @@ public strictfp class BigFloat implements Comparable { case SIGNALING_NAN: return "sNaN"; case INFINITE: - if (sign < 0) + if (sign < 0) { return "-inf"; + } return "+inf"; case FINITE: String s = (sign < 0) ? "-" : ""; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/floatformat/FloatFormat.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/floatformat/FloatFormat.java index e0368f7074..f0d2b98687 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/floatformat/FloatFormat.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/floatformat/FloatFormat.java @@ -220,7 +220,9 @@ public strictfp class FloatFormat { // set sign bit and return the result if size <= 8 private long setSign(long x, boolean sign) { if (!sign) + { return x; // Assume bit is already zero + } long mask = 1; mask <<= signbit_pos; x |= mask; // Stick in the bit @@ -299,7 +301,7 @@ public strictfp class FloatFormat { /** * Decode {@code encoding} to a BigFloat using this format. * - * NB: this method should not be used if {@link #size}>8 + * NB: this method should not be used if {@link #size}>8 * * @param encoding the encoding * @return the decoded value as a BigFloat @@ -535,7 +537,7 @@ public strictfp class FloatFormat { /** * Convert an encoded value to a binary floating point representation. * - * NB: this method should not be used if {@link #size}>8 + * NB: this method should not be used if {@link #size}>8 * * @param encoding the encoding of a floating point value in this format * @return a binary string representation of the encoded floating point {@code encoding} @@ -548,8 +550,9 @@ public strictfp class FloatFormat { switch (kind) { case INFINITE: - if (sgn) + if (sgn) { return "-inf"; + } return "+inf"; case QUIET_NAN: return "qNaN"; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressSetView.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressSetView.java index c832eaed3d..81aec8a393 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressSetView.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressSetView.java @@ -242,7 +242,7 @@ public interface AddressSetView extends Iterable { /** * Trim address set removing all addresses less-than-or-equal to specified - * address based upon {@link Address#compareTo(Address)} behavior. + * address based upon {@link Address} comparison. * The address set may contain address ranges from multiple * address spaces. * @param set address set to be trimmed @@ -269,7 +269,7 @@ public interface AddressSetView extends Iterable { /** * Trim address set removing all addresses greater-than-or-equal to specified - * address based upon {@link Address#compareTo(Address)} behavior. + * address based upon {@link Address} comparison. * The address set may contain address ranges from multiple * address spaces. * @param set address set to be trimmed diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java index 5374264cc7..696671614e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java @@ -17,7 +17,7 @@ package ghidra.service.graph; import java.util.Set; -import docking.action.DockingAction; +import docking.action.DockingActionIf; import docking.widgets.EventTrigger; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -43,7 +43,7 @@ public interface GraphDisplay { public void setGraphDisplayListener(GraphDisplayListener listener); /** - * Tells the graph display window to focus the vertex with the given id. + * Tells the graph display window to focus the vertex with the given id * * @param vertex the vertex to focus * @param eventTrigger Provides a hint to the GraphDisplay as to why we are updating the @@ -63,7 +63,8 @@ public interface GraphDisplay { /** * Returns the currently focused vertex or null if no vertex is focused - * @return the currently focused vertex or null if no vertex is focused. + * + * @return the currently focused vertex or null if no vertex is focused */ public AttributedVertex getFocusedVertex(); @@ -82,6 +83,7 @@ public interface GraphDisplay { /** * Returns a set of vertex ids for all the currently selected vertices + * * @return a set of vertex ids for all the currently selected vertices */ public Set getSelectedVertices(); @@ -94,7 +96,7 @@ public interface GraphDisplay { /** * Defines a vertex attribute type for this graph window * - * @param name the name of the attribute which may be attached to vertices. + * @param name the name of the attribute which may be attached to vertices */ public void defineVertexAttribute(String name); @@ -106,22 +108,25 @@ public interface GraphDisplay { public void defineEdgeAttribute(String name); /** - * Sets the name of the attribute which should be used as the primary vertex label in the display. - * @param attributeName the name of the attribute to use as the display label for vertices. + * Sets the name of the attribute which should be used as the primary vertex label + * + * @param attributeName the name of the attribute to use as the display label for vertices * @param alignment (ALIGN_LEFT, ALIGN_RIGHT, or ALIGN_CENTER) * @param size the font size to use for the display label * @param monospace true if the font should be monospaced * @param maxLines the maximum number lines to display in the vertex labels */ - public void setVertexLabel(String attributeName, int alignment, int size, boolean monospace, + public void setVertexLabelAttribute(String attributeName, int alignment, int size, + boolean monospace, int maxLines); /** * Sets the graph to be displayed or consumed by this graph display + * * @param graph the graph to display or consume * @param title a title for the graph * @param monitor a {@link TaskMonitor} which can be used to cancel the graphing operation - * @param append if true, append the new graph to any existing graph. + * @param append if true, append the new graph to any existing graph * @throws CancelledException thrown if the graphing operation was cancelled */ public void setGraph(AttributedGraph graph, String title, boolean append, @@ -135,6 +140,7 @@ public interface GraphDisplay { /** * Updates a vertex to a new name + * * @param vertex the vertex to rename * @param newName the new name for the vertex */ @@ -142,6 +148,7 @@ public interface GraphDisplay { /** * Returns the title of the current graph + * * @return the title of the current graph */ public String getGraphTitle(); @@ -149,8 +156,8 @@ public interface GraphDisplay { /** * Adds the action to the graph display. Not all GraphDisplays support adding custom * actions, so this may have no effect. - * @param action the action to add. + * + * @param action the action to add */ - public void addAction(DockingAction action); - + public void addAction(DockingActionIf action); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayProvider.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayProvider.java index fedf585ab5..80b36c556e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayProvider.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayProvider.java @@ -22,6 +22,9 @@ import ghidra.util.classfinder.ExtensionPoint; import ghidra.util.exception.GraphException; import ghidra.util.task.TaskMonitor; +import java.util.Collections; +import java.util.Map; + /** * Basic interface for objects that can display or otherwise consume a generic graph */ @@ -41,8 +44,21 @@ public interface GraphDisplayProvider extends ExtensionPoint { * @return A GraphDisplay that can be used to display (or otherwise consume - e.g. export) the graph * @throws GraphException thrown if there is a problem creating a GraphDisplay */ - public GraphDisplay getGraphDisplay(boolean reuseGraph, - TaskMonitor monitor) throws GraphException; + public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) throws GraphException; + + /** + * Returns a GraphDisplay that can be used to "display" a graph + * + * @param reuseGraph if true, this provider will attempt to re-use an existing GraphDisplay + * @param properties a {@code Map} of property key/values that can be used to customize the display + * @param monitor the {@link TaskMonitor} that can be used to monitor and cancel the operation + * @return A GraphDisplay that can be used to display (or otherwise consume - e.g. export) the graph + * @throws GraphException thrown if there is a problem creating a GraphDisplay + */ + default GraphDisplay getGraphDisplay(boolean reuseGraph, Map properties, + TaskMonitor monitor) throws GraphException { + return getGraphDisplay(reuseGraph, monitor); + } /** * Provides an opportunity for this provider to register and read tool options diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/VertexGraphActionContext.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/VertexGraphActionContext.java index f155ca3023..192aa9175c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/VertexGraphActionContext.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/VertexGraphActionContext.java @@ -15,6 +15,7 @@ */ package ghidra.service.graph; +import java.util.Objects; import java.util.Set; import docking.ComponentProvider; @@ -31,7 +32,7 @@ public class VertexGraphActionContext extends GraphActionContext { AttributedVertex locatedVertex, AttributedVertex clickedVertex) { super(componentProvider, graph, selectedVertices, locatedVertex); - this.clickedVertex = clickedVertex; + this.clickedVertex = Objects.requireNonNull(clickedVertex); } /** diff --git a/Ghidra/Framework/Utility/src/main/java/utilities/util/FileUtilities.java b/Ghidra/Framework/Utility/src/main/java/utilities/util/FileUtilities.java index 02ad63ab1a..2d3ada122d 100644 --- a/Ghidra/Framework/Utility/src/main/java/utilities/util/FileUtilities.java +++ b/Ghidra/Framework/Utility/src/main/java/utilities/util/FileUtilities.java @@ -902,6 +902,32 @@ public final class FileUtilities { return childPath; } + /** + * Return the relative path string of one resource file in another. If + * no path can be constructed or the files are the same, then null is returned. + * + * Note: unlike {@link #relativizePath(File, File)}, this function does not resolve symbolic links. + * + *

    For example, given, in this order, two files with these paths + * /a/b and /a/b/c, this method will return 'c'. + * + * @param f1 the parent resource file + * @param f2 the child resource file + * @return the relative path of {@code f2} in {@code f1} + */ + public static String relativizePath(ResourceFile f1, ResourceFile f2) { + StringBuilder sb = new StringBuilder(f2.getName()); + f2 = f2.getParentFile(); + while (f2 != null) { + if (f1.equals(f2)) { + return sb.toString(); + } + sb.insert(0, f2.getName() + File.separator); + f2 = f2.getParentFile(); + } + return null; + } + public static boolean exists(URI uri) { String scheme = uri.getScheme(); diff --git a/Ghidra/Processors/AARCH64/data/languages/AARCH64base.sinc b/Ghidra/Processors/AARCH64/data/languages/AARCH64base.sinc index 91efb79ed0..ca764510d1 100644 --- a/Ghidra/Processors/AARCH64/data/languages/AARCH64base.sinc +++ b/Ghidra/Processors/AARCH64/data/languages/AARCH64base.sinc @@ -1331,6 +1331,17 @@ is sf=1 & op=1 & s=1 & b_2428=0x1a & b_2123=2 & Rm_GPR64 & CondOp & b_1111=0 & o affectflags(); } +# C6.2.50 CFINV page C6-860 line 48145 MATCH xd500401f/mask=xfffff0ff +# C6.2.194 MSR (immediate) page C6-1126 line 62879 MATCH xd500401f/mask=xfff8f01f +# CONSTRUCT xd500401f/mask=xfffff0ff MATCHED 2 DOCUMENTED OPCODES +# xd500401f/mask=xfffff0ff NOT MATCHED BY ANY CONSTRUCTOR + +:cfinv +is b_1231=0b11010101000000000100 & b_0811 & b_0007=0b00011111 +{ + CY = !CY; +} + # C6.2.52 CINC page C6-862 line 48243 MATCH x1a800400/mask=x7fe00c00 # C6.2.70 CSET page C6-892 line 49783 MATCH x1a9f07e0/mask=x7fff0fe0 # C6.2.72 CSINC page C6-896 line 49956 MATCH x1a800400/mask=x7fe00c00 diff --git a/Ghidra/Processors/AARCH64/data/languages/AARCH64neon.sinc b/Ghidra/Processors/AARCH64/data/languages/AARCH64neon.sinc index a6c78a047b..c77d861f12 100644 --- a/Ghidra/Processors/AARCH64/data/languages/AARCH64neon.sinc +++ b/Ghidra/Processors/AARCH64/data/languages/AARCH64neon.sinc @@ -466,9 +466,9 @@ is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=1 & b_2121=1 & Rm_VPR128.4S is b_3031=1 & u=0 & b_2428=0x1e & b_23=1 & b_1722=0x38 & b_1216=0x1b & b_1011=2 & Rn_VPR128.2D & Rd_FPR64 & Zd { # sipd infix Rd_FPR64 = +(Rn_VPR128.2D) on pairs lane size (8 to 8) - tmp1 = Rn_VPR128.2D[0,64]; - tmp2 = Rn_VPR128.2D[64,64]; - Rd_FPR64[0,64] = tmp1 + tmp2; + local tmp1 = Rn_VPR128.2D[0,64]; + local tmp2 = Rn_VPR128.2D[64,64]; + Rd_FPR64 = tmp1 + tmp2; zext_zd(Zd); # zero upper 24 bytes of Zd } @@ -483,8 +483,8 @@ is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=0 & b_2121=1 & Rm_VPR128.16 { TMPQ1 = 0; # sipd infix TMPQ1 = +(Rn_VPR128.16B,Rm_VPR128.16B) on pairs lane size (1 to 1) - tmp2 = Rn_VPR128.16B[0,8]; - tmp3 = Rn_VPR128.16B[8,8]; + local tmp2 = Rn_VPR128.16B[0,8]; + local tmp3 = Rn_VPR128.16B[8,8]; TMPQ1[0,8] = tmp2 + tmp3; tmp2 = Rn_VPR128.16B[16,8]; tmp3 = Rn_VPR128.16B[24,8]; @@ -546,8 +546,8 @@ is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=3 & b_2121=1 & Rm_VPR128.2D { TMPQ1 = 0; # sipd infix TMPQ1 = +(Rn_VPR128.2D,Rm_VPR128.2D) on pairs lane size (8 to 8) - tmp2 = Rn_VPR128.2D[0,64]; - tmp3 = Rn_VPR128.2D[64,64]; + local tmp2 = Rn_VPR128.2D[0,64]; + local tmp3 = Rn_VPR128.2D[64,64]; TMPQ1[0,64] = tmp2 + tmp3; tmp2 = Rm_VPR128.2D[0,64]; tmp3 = Rm_VPR128.2D[64,64]; @@ -567,8 +567,8 @@ is b_3131=0 & q=0 & u=0 & b_2428=0xe & advSIMD3.size=2 & b_2121=1 & Rm_VPR64.2S { TMPD1 = 0; # sipd infix TMPD1 = +(Rn_VPR64.2S,Rm_VPR64.2S) on pairs lane size (4 to 4) - tmp2 = Rn_VPR64.2S[0,32]; - tmp3 = Rn_VPR64.2S[32,32]; + local tmp2 = Rn_VPR64.2S[0,32]; + local tmp3 = Rn_VPR64.2S[32,32]; TMPD1[0,32] = tmp2 + tmp3; tmp2 = Rm_VPR64.2S[0,32]; tmp3 = Rm_VPR64.2S[32,32]; @@ -588,8 +588,8 @@ is b_3131=0 & q=0 & u=0 & b_2428=0xe & advSIMD3.size=1 & b_2121=1 & Rm_VPR64.4H { TMPD1 = 0; # sipd infix TMPD1 = +(Rn_VPR64.4H,Rm_VPR64.4H) on pairs lane size (2 to 2) - tmp2 = Rn_VPR64.4H[0,16]; - tmp3 = Rn_VPR64.4H[16,16]; + local tmp2 = Rn_VPR64.4H[0,16]; + local tmp3 = Rn_VPR64.4H[16,16]; TMPD1[0,16] = tmp2 + tmp3; tmp2 = Rn_VPR64.4H[32,16]; tmp3 = Rn_VPR64.4H[48,16]; @@ -615,8 +615,8 @@ is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=2 & b_2121=1 & Rm_VPR128.4S { TMPQ1 = 0; # sipd infix TMPQ1 = +(Rn_VPR128.4S,Rm_VPR128.4S) on pairs lane size (4 to 4) - tmp2 = Rn_VPR128.4S[0,32]; - tmp3 = Rn_VPR128.4S[32,32]; + local tmp2 = Rn_VPR128.4S[0,32]; + local tmp3 = Rn_VPR128.4S[32,32]; TMPQ1[0,32] = tmp2 + tmp3; tmp2 = Rn_VPR128.4S[64,32]; tmp3 = Rn_VPR128.4S[96,32]; @@ -642,8 +642,8 @@ is b_3131=0 & q=0 & u=0 & b_2428=0xe & advSIMD3.size=0 & b_2121=1 & Rm_VPR64.8B { TMPD1 = 0; # sipd infix TMPD1 = +(Rn_VPR64.8B,Rm_VPR64.8B) on pairs lane size (1 to 1) - tmp2 = Rn_VPR64.8B[0,8]; - tmp3 = Rn_VPR64.8B[8,8]; + local tmp2 = Rn_VPR64.8B[0,8]; + local tmp3 = Rn_VPR64.8B[8,8]; TMPD1[0,8] = tmp2 + tmp3; tmp2 = Rn_VPR64.8B[16,8]; tmp3 = Rn_VPR64.8B[24,8]; @@ -681,8 +681,8 @@ is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=1 & b_2121=1 & Rm_VPR128.8H { TMPQ1 = 0; # sipd infix TMPQ1 = +(Rn_VPR128.8H,Rm_VPR128.8H) on pairs lane size (2 to 2) - tmp2 = Rn_VPR128.8H[0,16]; - tmp3 = Rn_VPR128.8H[16,16]; + local tmp2 = Rn_VPR128.8H[0,16]; + local tmp3 = Rn_VPR128.8H[16,16]; TMPQ1[0,16] = tmp2 + tmp3; tmp2 = Rn_VPR128.8H[32,16]; tmp3 = Rn_VPR128.8H[48,16]; @@ -3376,9 +3376,9 @@ is m=0 & b_3030=0 & s=0 & b_2428=0x1e & ftype=0 & b_2121=1 & Rm_FPR32 & b_1215=0 is b_3031=1 & u=1 & b_2428=0x1e & b_23=0 & b_1722=0x38 & b_1216=0xd & b_1011=2 & Rn_VPR128.2D & Rd_FPR64 & Zd { # sipd infix Rd_FPR64 = f+(Rn_VPR128.2D) on pairs lane size (8 to 8) - tmp1 = Rn_VPR128.2D[0,64]; - tmp2 = Rn_VPR128.2D[64,64]; - Rd_FPR64[0,64] = tmp1 f+ tmp2; + local tmp1 = Rn_VPR128.2D[0,64]; + local tmp2 = Rn_VPR128.2D[64,64]; + Rd_FPR64 = tmp1 f+ tmp2; zext_zd(Zd); # zero upper 24 bytes of Zd } @@ -3392,9 +3392,9 @@ is b_3031=1 & u=1 & b_2428=0x1e & b_23=0 & b_1722=0x38 & b_1216=0xd & b_1011=2 & is b_3031=1 & u=1 & b_2428=0x1e & b_23=0 & b_1722=0x18 & b_1216=0xd & b_1011=2 & Rn_VPR64.2S & Rd_FPR32 & Zd { # sipd infix Rd_FPR32 = f+(Rn_VPR64.2S) on pairs lane size (4 to 4) - tmp1 = Rn_VPR64.2S[0,32]; - tmp2 = Rn_VPR64.2S[32,32]; - Rd_FPR32[0,32] = tmp1 f+ tmp2; + local tmp1 = Rn_VPR64.2S[0,32]; + local tmp2 = Rn_VPR64.2S[32,32]; + Rd_FPR32 = tmp1 f+ tmp2; zext_zs(Zd); # zero upper 28 bytes of Zd } @@ -3409,9 +3409,9 @@ is b_3031=1 & u=1 & b_2428=0x1e & b_23=0 & b_1722=0x18 & b_1216=0xd & b_1011=2 & is b_1031=0b0101111000110000110110 & Rd_FPR16 & vRn_VPR128 & Rn_FPR32 & Zd { # sipd infix Rd_FPR16 = f+(Rn_FPR32) on pairs lane size (2 to 2) - tmp1 = Rn_FPR32[0,16]; - tmp2 = Rn_FPR32[16,16]; - Rd_FPR16[0,16] = tmp1 f+ tmp2; + local tmp1 = Rn_FPR32[0,16]; + local tmp2 = Rn_FPR32[16,16]; + Rd_FPR16 = tmp1 f+ tmp2; zext_zh(Zd); # zero upper 30 bytes of Zd } @@ -3426,8 +3426,8 @@ is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=1 & b_21=1 & Rm_VPR128.2D & { TMPQ1 = 0; # sipd infix TMPQ1 = f+(Rn_VPR128.2D,Rm_VPR128.2D) on pairs lane size (8 to 8) - tmp2 = Rn_VPR128.2D[0,64]; - tmp3 = Rn_VPR128.2D[64,64]; + local tmp2 = Rn_VPR128.2D[0,64]; + local tmp3 = Rn_VPR128.2D[64,64]; TMPQ1[0,64] = tmp2 f+ tmp3; tmp2 = Rm_VPR128.2D[0,64]; tmp3 = Rm_VPR128.2D[64,64]; @@ -3447,8 +3447,8 @@ is b_3131=0 & q=0 & u=1 & b_2428=0xe & advSIMD3.size=0 & b_21=1 & Rm_VPR64.2S & { TMPD1 = 0; # sipd infix TMPD1 = f+(Rn_VPR64.2S,Rm_VPR64.2S) on pairs lane size (4 to 4) - tmp2 = Rn_VPR64.2S[0,32]; - tmp3 = Rn_VPR64.2S[32,32]; + local tmp2 = Rn_VPR64.2S[0,32]; + local tmp3 = Rn_VPR64.2S[32,32]; TMPD1[0,32] = tmp2 f+ tmp3; tmp2 = Rm_VPR64.2S[0,32]; tmp3 = Rm_VPR64.2S[32,32]; @@ -3468,8 +3468,8 @@ is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=0 & b_21=1 & Rm_VPR128.4S & { TMPQ1 = 0; # sipd infix TMPQ1 = f+(Rn_VPR128.4S,Rm_VPR128.4S) on pairs lane size (4 to 4) - tmp2 = Rn_VPR128.4S[0,32]; - tmp3 = Rn_VPR128.4S[32,32]; + local tmp2 = Rn_VPR128.4S[0,32]; + local tmp3 = Rn_VPR128.4S[32,32]; TMPQ1[0,32] = tmp2 f+ tmp3; tmp2 = Rn_VPR128.4S[64,32]; tmp3 = Rn_VPR128.4S[96,32]; @@ -3496,8 +3496,8 @@ is b_31=0 & b_30=0 & b_2129=0b101110010 & b_1015=0b000101 & Rd_VPR64.4H & Rn_VPR { TMPD1 = 0; # sipd infix TMPD1 = f+(Rm_VPR64.4H,Rn_VPR64.4H) on pairs lane size (2 to 2) - tmp2 = Rm_VPR64.4H[0,16]; - tmp3 = Rm_VPR64.4H[16,16]; + local tmp2 = Rm_VPR64.4H[0,16]; + local tmp3 = Rm_VPR64.4H[16,16]; TMPD1[0,16] = tmp2 f+ tmp3; tmp2 = Rm_VPR64.4H[32,16]; tmp3 = Rm_VPR64.4H[48,16]; @@ -3524,8 +3524,8 @@ is b_31=0 & b_30=1 & b_2129=0b101110010 & b_1015=0b000101 & Rd_VPR128.8H & Rn_VP { TMPQ1 = 0; # sipd infix TMPQ1 = f+(Rn_VPR128.8H,Rm_VPR128.8H) on pairs lane size (2 to 2) - tmp2 = Rn_VPR128.8H[0,16]; - tmp3 = Rn_VPR128.8H[16,16]; + local tmp2 = Rn_VPR128.8H[0,16]; + local tmp3 = Rn_VPR128.8H[16,16]; TMPQ1[0,16] = tmp2 f+ tmp3; tmp2 = Rn_VPR128.8H[32,16]; tmp3 = Rn_VPR128.8H[48,16]; @@ -13271,10 +13271,10 @@ is b_31=0 & b_30=0 & b_2429=0b001110 & b_2223=0b00 & b_1021=0b100000011010 & Rd_ { TMPD1 = 0; # sipd infix TMPD1 = +(Rn_VPR64.8B) on pairs lane size (1 to 2) - tmp2 = Rn_VPR64.8B[0,8]; - tmp4 = sext(tmp2); - tmp3 = Rn_VPR64.8B[8,8]; - tmp5 = sext(tmp3); + local tmp2 = Rn_VPR64.8B[0,8]; + local tmp4 = sext(tmp2); + local tmp3 = Rn_VPR64.8B[8,8]; + local tmp5 = sext(tmp3); TMPD1[0,16] = tmp4 + tmp5; tmp2 = Rn_VPR64.8B[16,8]; tmp4 = sext(tmp2); @@ -13311,10 +13311,10 @@ is b_31=0 & b_30=1 & b_2429=0b001110 & b_2223=0b00 & b_1021=0b100000011010 & Rd_ { TMPQ1 = 0; # sipd infix TMPQ1 = +(Rn_VPR128.16B) on pairs lane size (1 to 2) - tmp2 = Rn_VPR128.16B[0,8]; - tmp4 = sext(tmp2); - tmp3 = Rn_VPR128.16B[8,8]; - tmp5 = sext(tmp3); + local tmp2 = Rn_VPR128.16B[0,8]; + local tmp4 = sext(tmp2); + local tmp3 = Rn_VPR128.16B[8,8]; + local tmp5 = sext(tmp3); TMPQ1[0,16] = tmp4 + tmp5; tmp2 = Rn_VPR128.16B[16,8]; tmp4 = sext(tmp2); @@ -13375,10 +13375,10 @@ is b_31=0 & b_30=0 & b_2429=0b001110 & b_2223=0b01 & b_1021=0b100000011010 & Rd_ { TMPD1 = 0; # sipd infix TMPD1 = +(Rn_VPR64.4H) on pairs lane size (2 to 4) - tmp2 = Rn_VPR64.4H[0,16]; - tmp4 = sext(tmp2); - tmp3 = Rn_VPR64.4H[16,16]; - tmp5 = sext(tmp3); + local tmp2 = Rn_VPR64.4H[0,16]; + local tmp4 = sext(tmp2); + local tmp3 = Rn_VPR64.4H[16,16]; + local tmp5 = sext(tmp3); TMPD1[0,32] = tmp4 + tmp5; tmp2 = Rn_VPR64.4H[32,16]; tmp4 = sext(tmp2); @@ -13403,10 +13403,10 @@ is b_31=0 & b_30=1 & b_2429=0b001110 & b_2223=0b01 & b_1021=0b100000011010 & Rd_ { TMPQ1 = 0; # sipd infix TMPQ1 = +(Rn_VPR128.8H) on pairs lane size (2 to 4) - tmp2 = Rn_VPR128.8H[0,16]; - tmp4 = sext(tmp2); - tmp3 = Rn_VPR128.8H[16,16]; - tmp5 = sext(tmp3); + local tmp2 = Rn_VPR128.8H[0,16]; + local tmp4 = sext(tmp2); + local tmp3 = Rn_VPR128.8H[16,16]; + local tmp5 = sext(tmp3); TMPQ1[0,32] = tmp4 + tmp5; tmp2 = Rn_VPR128.8H[32,16]; tmp4 = sext(tmp2); @@ -13441,14 +13441,15 @@ is b_31=0 & b_30=1 & b_2429=0b001110 & b_2223=0b01 & b_1021=0b100000011010 & Rd_ :sadalp Rd_VPR64.1D, Rn_VPR64.2S is b_31=0 & b_30=0 & b_2429=0b001110 & b_2223=0b10 & b_1021=0b100000011010 & Rd_VPR64.1D & Rn_VPR64.2S & Zd { + TMPD1 = 0; # sipd infix TMPD1 = +(Rn_VPR64.2S) on pairs lane size (4 to 8) - tmp2 = Rn_VPR64.2S[0,32]; - tmp4 = sext(tmp2); - tmp3 = Rn_VPR64.2S[32,32]; - tmp5 = sext(tmp3); - tmpd1 = tmp4 + tmp5; + local tmp2 = Rn_VPR64.2S[0,32]; + local tmp4 = sext(tmp2); + local tmp3 = Rn_VPR64.2S[32,32]; + local tmp5 = sext(tmp3); + TMPD1 = tmp4 + tmp5; # simd infix Rd_VPR64.1D = Rd_VPR64.1D + TMPD1 on lane size 8 - Rd_VPR64.1D[0,64] = Rd_VPR64.1D[0,64] + tmpd1; + Rd_VPR64.1D = Rd_VPR64.1D + TMPD1; zext_zd(Zd); # zero upper 24 bytes of Zd } @@ -13464,10 +13465,10 @@ is b_31=0 & b_30=1 & b_2429=0b001110 & b_2223=0b10 & b_1021=0b100000011010 & Rd_ { TMPQ1 = 0; # sipd infix TMPQ1 = +(Rn_VPR128.4S) on pairs lane size (4 to 8) - tmp2 = Rn_VPR128.4S[0,32]; - tmp4 = sext(tmp2); - tmp3 = Rn_VPR128.4S[32,32]; - tmp5 = sext(tmp3); + local tmp2 = Rn_VPR128.4S[0,32]; + local tmp4 = sext(tmp2); + local tmp3 = Rn_VPR128.4S[32,32]; + local tmp5 = sext(tmp3); TMPQ1[0,64] = tmp4 + tmp5; tmp2 = Rn_VPR128.4S[64,32]; tmp4 = sext(tmp2); @@ -13671,11 +13672,11 @@ is b_3131=0 & q=0 & u=0 & b_2428=0xe & advSIMD3.size=2 & b_1721=0x10 & b_1216=2 { TMPD1 = Rn_VPR64.2S; # sipd infix Rd_VPR64.1D = +(TMPD1) on pairs lane size (4 to 8) - tmp2 = TMPD1[0,32]; - tmp4 = sext(tmp2); - tmp3 = TMPD1[32,32]; - tmp5 = sext(tmp3); - Rd_VPR64.1D[0,64] = tmp4 + tmp5; + local tmp2 = TMPD1[0,32]; + local tmp4 = sext(tmp2); + local tmp3 = TMPD1[32,32]; + local tmp5 = sext(tmp3); + Rd_VPR64.1D = tmp4 + tmp5; zext_zd(Zd); # zero upper 24 bytes of Zd } @@ -13690,10 +13691,10 @@ is b_3131=0 & q=0 & u=0 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x10 & b_1216=2 { TMPD1 = Rn_VPR64.4H; # sipd infix Rd_VPR64.2S = +(TMPD1) on pairs lane size (2 to 4) - tmp2 = TMPD1[0,16]; - tmp4 = sext(tmp2); - tmp3 = TMPD1[16,16]; - tmp5 = sext(tmp3); + local tmp2 = TMPD1[0,16]; + local tmp4 = sext(tmp2); + local tmp3 = TMPD1[16,16]; + local tmp5 = sext(tmp3); Rd_VPR64.2S[0,32] = tmp4 + tmp5; tmp2 = TMPD1[32,16]; tmp4 = sext(tmp2); @@ -13714,10 +13715,10 @@ is b_3131=0 & q=0 & u=0 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x10 & b_1216=2 { TMPD1 = Rn_VPR64.8B; # sipd infix Rd_VPR64.4H = +(TMPD1) on pairs lane size (1 to 2) - tmp2 = TMPD1[0,8]; - tmp4 = sext(tmp2); - tmp3 = TMPD1[8,8]; - tmp5 = sext(tmp3); + local tmp2 = TMPD1[0,8]; + local tmp4 = sext(tmp2); + local tmp3 = TMPD1[8,8]; + local tmp5 = sext(tmp3); Rd_VPR64.4H[0,16] = tmp4 + tmp5; tmp2 = TMPD1[16,8]; tmp4 = sext(tmp2); @@ -13748,10 +13749,10 @@ is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=2 & b_1721=0x10 & b_1216=2 { TMPQ1 = Rn_VPR128.4S; # sipd infix Rd_VPR128.2D = +(TMPQ1) on pairs lane size (4 to 8) - tmp2 = TMPQ1[0,32]; - tmp4 = sext(tmp2); - tmp3 = TMPQ1[32,32]; - tmp5 = sext(tmp3); + local tmp2 = TMPQ1[0,32]; + local tmp4 = sext(tmp2); + local tmp3 = TMPQ1[32,32]; + local tmp5 = sext(tmp3); Rd_VPR128.2D[0,64] = tmp4 + tmp5; tmp2 = TMPQ1[64,32]; tmp4 = sext(tmp2); @@ -13772,10 +13773,10 @@ is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x10 & b_1216=2 { TMPQ1 = Rn_VPR128.8H; # sipd infix Rd_VPR128.4S = +(TMPQ1) on pairs lane size (2 to 4) - tmp2 = TMPQ1[0,16]; - tmp4 = sext(tmp2); - tmp3 = TMPQ1[16,16]; - tmp5 = sext(tmp3); + local tmp2 = TMPQ1[0,16]; + local tmp4 = sext(tmp2); + local tmp3 = TMPQ1[16,16]; + local tmp5 = sext(tmp3); Rd_VPR128.4S[0,32] = tmp4 + tmp5; tmp2 = TMPQ1[32,16]; tmp4 = sext(tmp2); @@ -13806,10 +13807,10 @@ is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x10 & b_1216=2 { TMPQ1 = Rn_VPR128.16B; # sipd infix Rd_VPR128.8H = +(TMPQ1) on pairs lane size (1 to 2) - tmp2 = TMPQ1[0,8]; - tmp4 = sext(tmp2); - tmp3 = TMPQ1[8,8]; - tmp5 = sext(tmp3); + local tmp2 = TMPQ1[0,8]; + local tmp4 = sext(tmp2); + local tmp3 = TMPQ1[8,8]; + local tmp5 = sext(tmp3); Rd_VPR128.8H[0,16] = tmp4 + tmp5; tmp2 = TMPQ1[16,8]; tmp4 = sext(tmp2); @@ -23639,10 +23640,10 @@ is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x10 & b_1216=0x { TMPQ1 = 0; # sipd infix TMPQ1 = +(Rn_VPR128.16B) on pairs lane size (1 to 2) - tmp2 = Rn_VPR128.16B[0,8]; - tmp4 = zext(tmp2); - tmp3 = Rn_VPR128.16B[8,8]; - tmp5 = zext(tmp3); + local tmp2 = Rn_VPR128.16B[0,8]; + local tmp4 = zext(tmp2); + local tmp3 = Rn_VPR128.16B[8,8]; + local tmp5 = zext(tmp3); TMPQ1[0,16] = tmp4 + tmp5; tmp2 = Rn_VPR128.16B[16,8]; tmp4 = zext(tmp2); @@ -23700,14 +23701,15 @@ is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x10 & b_1216=0x :uadalp Rd_VPR64.1D, Rn_VPR64.2S is b_3131=0 & q=0 & u=1 & b_2428=0xe & advSIMD3.size=2 & b_1721=0x10 & b_1216=0x6 & b_1011=2 & Rn_VPR64.2S & Rd_VPR64.1D & Zd { + TMPD1 = 0; # sipd infix TMPD1 = +(Rn_VPR64.2S) on pairs lane size (4 to 8) - tmp2 = Rn_VPR64.2S[0,32]; - tmp4 = zext(tmp2); - tmp3 = Rn_VPR64.2S[32,32]; - tmp5 = zext(tmp3); - tmpd1 = tmp4 + tmp5; + local tmp2 = Rn_VPR64.2S[0,32]; + local tmp4 = zext(tmp2); + local tmp3 = Rn_VPR64.2S[32,32]; + local tmp5 = zext(tmp3); + TMPD1 = tmp4 + tmp5; # simd infix Rd_VPR64.1D = Rd_VPR64.1D + TMPD1 on lane size 8 - Rd_VPR64.1D[0,64] = Rd_VPR64.1D[0,64] + tmpd1; + Rd_VPR64.1D = Rd_VPR64.1D + TMPD1; zext_zd(Zd); # zero upper 24 bytes of Zd } @@ -23722,10 +23724,10 @@ is b_3131=0 & q=0 & u=1 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x10 & b_1216=0x { TMPD1 = 0; # sipd infix TMPD1 = +(Rn_VPR64.4H) on pairs lane size (2 to 4) - tmp2 = Rn_VPR64.4H[0,16]; - tmp4 = zext(tmp2); - tmp3 = Rn_VPR64.4H[16,16]; - tmp5 = zext(tmp3); + local tmp2 = Rn_VPR64.4H[0,16]; + local tmp4 = zext(tmp2); + local tmp3 = Rn_VPR64.4H[16,16]; + local tmp5 = zext(tmp3); TMPD1[0,32] = tmp4 + tmp5; tmp2 = Rn_VPR64.4H[32,16]; tmp4 = zext(tmp2); @@ -23749,10 +23751,10 @@ is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=2 & b_1721=0x10 & b_1216=0x { TMPQ1 = 0; # sipd infix TMPQ1 = +(Rn_VPR128.4S) on pairs lane size (4 to 8) - tmp2 = Rn_VPR128.4S[0,32]; - tmp4 = zext(tmp2); - tmp3 = Rn_VPR128.4S[32,32]; - tmp5 = zext(tmp3); + local tmp2 = Rn_VPR128.4S[0,32]; + local tmp4 = zext(tmp2); + local tmp3 = Rn_VPR128.4S[32,32]; + local tmp5 = zext(tmp3); TMPQ1[0,64] = tmp4 + tmp5; tmp2 = Rn_VPR128.4S[64,32]; tmp4 = zext(tmp2); @@ -23776,10 +23778,10 @@ is b_3131=0 & q=0 & u=1 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x10 & b_1216=0x { TMPD1 = 0; # sipd infix TMPD1 = +(Rn_VPR64.8B) on pairs lane size (1 to 2) - tmp2 = Rn_VPR64.8B[0,8]; - tmp4 = zext(tmp2); - tmp3 = Rn_VPR64.8B[8,8]; - tmp5 = zext(tmp3); + local tmp2 = Rn_VPR64.8B[0,8]; + local tmp4 = zext(tmp2); + local tmp3 = Rn_VPR64.8B[8,8]; + local tmp5 = zext(tmp3); TMPD1[0,16] = tmp4 + tmp5; tmp2 = Rn_VPR64.8B[16,8]; tmp4 = zext(tmp2); @@ -23815,10 +23817,10 @@ is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x10 & b_1216=0x { TMPQ1 = 0; # sipd infix TMPQ1 = +(Rn_VPR128.8H) on pairs lane size (2 to 4) - tmp2 = Rn_VPR128.8H[0,16]; - tmp4 = zext(tmp2); - tmp3 = Rn_VPR128.8H[16,16]; - tmp5 = zext(tmp3); + local tmp2 = Rn_VPR128.8H[0,16]; + local tmp4 = zext(tmp2); + local tmp3 = Rn_VPR128.8H[16,16]; + local tmp5 = zext(tmp3); TMPQ1[0,32] = tmp4 + tmp5; tmp2 = Rn_VPR128.8H[32,16]; tmp4 = zext(tmp2); @@ -24035,10 +24037,10 @@ is b_31=0 & b_30=0 & b_2429=0b101110 & b_2223=0b00 & b_1021=0b100000001010 & Rd_ { TMPD1 = Rn_VPR64.8B; # sipd infix Rd_VPR64.4H = +(TMPD1) on pairs lane size (1 to 2) - tmp2 = TMPD1[0,8]; - tmp4 = zext(tmp2); - tmp3 = TMPD1[8,8]; - tmp5 = zext(tmp3); + local tmp2 = TMPD1[0,8]; + local tmp4 = zext(tmp2); + local tmp3 = TMPD1[8,8]; + local tmp5 = zext(tmp3); Rd_VPR64.4H[0,16] = tmp4 + tmp5; tmp2 = TMPD1[16,8]; tmp4 = zext(tmp2); @@ -24070,10 +24072,10 @@ is b_31=0 & b_30=1 & b_2429=0b101110 & b_2223=0b00 & b_1021=0b100000001010 & Rd_ { TMPQ1 = Rn_VPR128.16B; # sipd infix Rd_VPR128.8H = +(TMPQ1) on pairs lane size (1 to 2) - tmp2 = TMPQ1[0,8]; - tmp4 = zext(tmp2); - tmp3 = TMPQ1[8,8]; - tmp5 = zext(tmp3); + local tmp2 = TMPQ1[0,8]; + local tmp4 = zext(tmp2); + local tmp3 = TMPQ1[8,8]; + local tmp5 = zext(tmp3); Rd_VPR128.8H[0,16] = tmp4 + tmp5; tmp2 = TMPQ1[16,8]; tmp4 = zext(tmp2); @@ -24125,10 +24127,10 @@ is b_31=0 & b_30=0 & b_2429=0b101110 & b_2223=0b01 & b_1021=0b100000001010 & Rd_ { TMPD1 = Rn_VPR64.4H; # sipd infix Rd_VPR64.2S = +(TMPD1) on pairs lane size (2 to 4) - tmp2 = TMPD1[0,16]; - tmp4 = zext(tmp2); - tmp3 = TMPD1[16,16]; - tmp5 = zext(tmp3); + local tmp2 = TMPD1[0,16]; + local tmp4 = zext(tmp2); + local tmp3 = TMPD1[16,16]; + local tmp5 = zext(tmp3); Rd_VPR64.2S[0,32] = tmp4 + tmp5; tmp2 = TMPD1[32,16]; tmp4 = zext(tmp2); @@ -24150,10 +24152,10 @@ is b_31=0 & b_30=1 & b_2429=0b101110 & b_2223=0b01 & b_1021=0b100000001010 & Rd_ { TMPQ1 = Rn_VPR128.8H; # sipd infix Rd_VPR128.4S = +(TMPQ1) on pairs lane size (2 to 4) - tmp2 = TMPQ1[0,16]; - tmp4 = zext(tmp2); - tmp3 = TMPQ1[16,16]; - tmp5 = zext(tmp3); + local tmp2 = TMPQ1[0,16]; + local tmp4 = zext(tmp2); + local tmp3 = TMPQ1[16,16]; + local tmp5 = zext(tmp3); Rd_VPR128.4S[0,32] = tmp4 + tmp5; tmp2 = TMPQ1[32,16]; tmp4 = zext(tmp2); @@ -24185,11 +24187,11 @@ is b_31=0 & b_30=0 & b_2429=0b101110 & b_2223=0b10 & b_1021=0b100000001010 & Rd_ { TMPD1 = Rn_VPR64.2S; # sipd infix Rd_VPR64.1D = +(TMPD1) on pairs lane size (4 to 8) - tmp2 = TMPD1[0,32]; - tmp4 = zext(tmp2); - tmp3 = TMPD1[32,32]; - tmp5 = zext(tmp3); - Rd_VPR64.1D[0,64] = tmp4 + tmp5; + local tmp2 = TMPD1[0,32]; + local tmp4 = zext(tmp2); + local tmp3 = TMPD1[32,32]; + local tmp5 = zext(tmp3); + Rd_VPR64.1D = tmp4 + tmp5; zext_zd(Zd); # zero upper 24 bytes of Zd } @@ -24205,10 +24207,10 @@ is b_31=0 & b_30=1 & b_2429=0b101110 & b_2223=0b10 & b_1021=0b100000001010 & Rd_ { TMPQ1 = Rn_VPR128.4S; # sipd infix Rd_VPR128.2D = +(TMPQ1) on pairs lane size (4 to 8) - tmp2 = TMPQ1[0,32]; - tmp4 = zext(tmp2); - tmp3 = TMPQ1[32,32]; - tmp5 = zext(tmp3); + local tmp2 = TMPQ1[0,32]; + local tmp4 = zext(tmp2); + local tmp3 = TMPQ1[32,32]; + local tmp5 = zext(tmp3); Rd_VPR128.2D[0,64] = tmp4 + tmp5; tmp2 = TMPQ1[64,32]; tmp4 = zext(tmp2); diff --git a/Ghidra/Processors/PowerPC/src/main/java/ghidra/app/util/bin/format/elf/relocation/PowerPC64_ElfRelocationHandler.java b/Ghidra/Processors/PowerPC/src/main/java/ghidra/app/util/bin/format/elf/relocation/PowerPC64_ElfRelocationHandler.java index 40fbfd26b3..c6021c645b 100644 --- a/Ghidra/Processors/PowerPC/src/main/java/ghidra/app/util/bin/format/elf/relocation/PowerPC64_ElfRelocationHandler.java +++ b/Ghidra/Processors/PowerPC/src/main/java/ghidra/app/util/bin/format/elf/relocation/PowerPC64_ElfRelocationHandler.java @@ -103,6 +103,10 @@ public class PowerPC64_ElfRelocationHandler extends ElfRelocationHandler { } switch (type) { + case PowerPC64_ElfRelocationConstants.R_PPC64_COPY: + markAsWarning(program, relocationAddress, "R_PPC64_COPY", sym.getNameAsString(), + symbolIndex, "Runtime copy not supported", elfRelocationContext.getLog()); + break; case PowerPC64_ElfRelocationConstants.R_PPC64_ADDR32: newValue = (int) (symbolValue + addend); memory.setInt(relocationAddress, newValue); diff --git a/Ghidra/Processors/PowerPC/src/main/java/ghidra/app/util/bin/format/elf/relocation/PowerPC_ElfRelocationHandler.java b/Ghidra/Processors/PowerPC/src/main/java/ghidra/app/util/bin/format/elf/relocation/PowerPC_ElfRelocationHandler.java index 10184d3cc1..248f830c45 100644 --- a/Ghidra/Processors/PowerPC/src/main/java/ghidra/app/util/bin/format/elf/relocation/PowerPC_ElfRelocationHandler.java +++ b/Ghidra/Processors/PowerPC/src/main/java/ghidra/app/util/bin/format/elf/relocation/PowerPC_ElfRelocationHandler.java @@ -82,6 +82,10 @@ public class PowerPC_ElfRelocationHandler extends ElfRelocationHandler { int newValue = 0; switch (type) { + case PowerPC_ElfRelocationConstants.R_PPC_COPY: + markAsWarning(program, relocationAddress, "R_PPC_COPY", sym.getNameAsString(), + symbolIndex, "Runtime copy not supported", elfRelocationContext.getLog()); + break; case PowerPC_ElfRelocationConstants.R_PPC_ADDR32: case PowerPC_ElfRelocationConstants.R_PPC_UADDR32: case PowerPC_ElfRelocationConstants.R_PPC_GLOB_DAT: diff --git a/Ghidra/Processors/x86/src/main/java/ghidra/app/util/bin/format/elf/relocation/X86_64_ElfRelocationHandler.java b/Ghidra/Processors/x86/src/main/java/ghidra/app/util/bin/format/elf/relocation/X86_64_ElfRelocationHandler.java index 213a957b31..c19386d51b 100644 --- a/Ghidra/Processors/x86/src/main/java/ghidra/app/util/bin/format/elf/relocation/X86_64_ElfRelocationHandler.java +++ b/Ghidra/Processors/x86/src/main/java/ghidra/app/util/bin/format/elf/relocation/X86_64_ElfRelocationHandler.java @@ -207,11 +207,8 @@ public class X86_64_ElfRelocationHandler extends ElfRelocationHandler { memory.setLong(relocationAddress, value); break; case X86_64_ElfRelocationConstants.R_X86_64_IRELATIVE: - // NOTE: We don't support this since the code actually uses a function to - // compute the relocation value (i.e., indirect) - appliedSymbol = false; - markAsError(program, relocationAddress, "R_X86_64_IRELATIVE", symbolName, - "indirect computed relocation not supported", elfRelocationContext.getLog()); + value = addend + elfRelocationContext.getImageBaseWordAdjustmentOffset(); + memory.setLong(relocationAddress, value); break; // case ElfRelocationConstants.R_X86_64_TLSGD: diff --git a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/GraphActionTest.java b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/GraphActionTest.java index b1ffaa1874..ff1d180e52 100644 --- a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/GraphActionTest.java +++ b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/GraphActionTest.java @@ -299,7 +299,7 @@ public class GraphActionTest extends AbstractGhidraHeadedIntegrationTest { } - @Test + // @Test TODO GP-658 public void testSelectNodeThatIsDoubleGrouped() { select(a, b, c); collapse(); @@ -332,7 +332,7 @@ public class GraphActionTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(vertex instanceof GroupVertex); } - @Test + // @Test TODO GP-658 public void testFocusNodeThatIsDoubleGrouped() { select(a, b, c); collapse(); @@ -362,7 +362,7 @@ public class GraphActionTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(graphSpy.isFocused(a)); } - @Test + // @Test TODO GP-658 public void testListenerNotificatinWhenDoubleGroupedNodeFocused() { select(a, b, c); collapse(); @@ -392,7 +392,7 @@ public class GraphActionTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(graphSpy.isSelected(a, b, c)); } - @Test + // @Test TODO GP-658 public void testSelectNotificatinWhenDoubleGroupedNodeFocused() { select(a, b, c); collapse(); diff --git a/gradle/root/distribution.gradle b/gradle/root/distribution.gradle index dc6790e059..cd32b10fcf 100644 --- a/gradle/root/distribution.gradle +++ b/gradle/root/distribution.gradle @@ -125,6 +125,8 @@ task createJavadocs(type: Javadoc, description: 'Generate javadocs for all proje // generate documentation using html5 options.addBooleanOption("html5", true) + options.addBooleanOption('Xdoclint:none', true) + // Some internal packages are not public and need to be exported. options.addMultilineStringsOption("-add-exports").setValue(["java.desktop/sun.awt.image=ALL-UNNAMED", "java.desktop/sun.awt=ALL-UNNAMED", @@ -132,7 +134,8 @@ task createJavadocs(type: Javadoc, description: 'Generate javadocs for all proje "java.base/sun.security.provider=ALL-UNNAMED", "java.base/sun.security.util=ALL-UNNAMED"]) } - + + /********************************************************************************* * JSONDOCS - RAW * @@ -179,6 +182,7 @@ task createJsondocs(type: Javadoc, description: 'Generate JSON docs for all proj // Newer versions of gradle set this to true by default. // The JsonDoclet doesn't have the -notimestamp option so ensure it isn't set. options.setNoTimestamp(false) + // Some internal packages are not public and need to be exported. options.addMultilineStringsOption("-add-exports").setValue(["java.desktop/sun.awt.image=ALL-UNNAMED", diff --git a/gradle/root/eclipse.gradle b/gradle/root/eclipse.gradle index 8e811e173b..4da0583e38 100644 --- a/gradle/root/eclipse.gradle +++ b/gradle/root/eclipse.gradle @@ -155,3 +155,6 @@ eclipse { } } } + + // the GhidraLauncher depends on this file to build the classpath in dev mode + tasks.eclipse.dependsOn { generateLibraryDependencyMapping }