Merge remote-tracking branch 'origin/master' into debugger

This commit is contained in:
Dan 2021-02-08 15:52:21 -05:00
commit f5ec74f2c3
124 changed files with 4397 additions and 1873 deletions

View file

@ -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<String, String> properties,
TaskMonitor monitor) throws GraphException {
if (defaultGraphDisplayProvider != null) {
return defaultGraphDisplayProvider.getGraphDisplay(reuseGraph, monitor);
return defaultGraphDisplayProvider.getGraphDisplay(reuseGraph, properties, monitor);
}
return null;
}

View file

@ -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) {

View file

@ -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;
}

View file

@ -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
*
* <p>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<String, String> properties,
TaskMonitor monitor)
throws GraphException;
/**

View file

@ -17,10 +17,7 @@ package ghidra.app.util.bin.format.coff.archive;
public final class CoffArchiveConstants {
public final static String MAGIC = "!<arch>\n";
public final static int MAGIC_LEN = MAGIC.length();
public static final int MAGIC_LEN_CONST_EXPR = 8;
public static final String MAGIC = "!<arch>\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";
}

View file

@ -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)));
}
/**

View file

@ -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));
}
}
}

View file

@ -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<SegmentCommand> 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(

View file

@ -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|

View file

@ -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<String, String> 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);

View file

@ -1,288 +1,146 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
<storageModule moduleId="org.eclipse.cdt.core.settings">
<cconfiguration id="cdt.managedbuild.toolchain.gnu.base.1693333286">
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.base.1693333286" moduleId="org.eclipse.cdt.core.settings" name="Default">
<externalSettings/>
<extensions>
<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
</extensions>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<configuration artifactName="${ProjName}" buildProperties="" description="" id="cdt.managedbuild.toolchain.gnu.base.1693333286" name="Default" parent="org.eclipse.cdt.build.core.emptycfg">
<folderInfo id="cdt.managedbuild.toolchain.gnu.base.1693333286.895166479" name="/" resourcePath="">
<toolChain id="cdt.managedbuild.toolchain.gnu.base.1966692717" name="Linux GCC" superClass="cdt.managedbuild.toolchain.gnu.base">
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.target.gnu.platform.base.2081261468" name="Debug Platform" osList="linux,hpux,aix,qnx" superClass="cdt.managedbuild.target.gnu.platform.base"/>
<builder buildPath="${workspace_loc:/_Decompiler}/cpp" id="cdt.managedbuild.target.gnu.builder.base.2048731974" incrementalBuildTarget="ghidra_opt" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" parallelBuildOn="true" parallelizationNumber="optimal" superClass="cdt.managedbuild.target.gnu.builder.base"/>
<tool id="cdt.managedbuild.tool.gnu.archiver.base.1325849601" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/>
<tool id="cdt.managedbuild.tool.gnu.cpp.compiler.base.233123430" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.base">
<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.1087598226" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
</tool>
<tool id="cdt.managedbuild.tool.gnu.c.compiler.base.1211851151" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.base">
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.12359898" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
</tool>
<tool id="cdt.managedbuild.tool.gnu.c.linker.base.1810518227" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.base"/>
<tool id="cdt.managedbuild.tool.gnu.cpp.linker.base.1320265924" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.base">
<inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.361769658" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
</inputType>
</tool>
<tool id="cdt.managedbuild.tool.gnu.assembler.base.74027553" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.base">
<inputType id="cdt.managedbuild.tool.gnu.assembler.input.1774124767" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
</tool>
</toolChain>
</folderInfo>
<sourceEntries>
<entry excluding="extcpp" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="extcpp"/>
</sourceEntries>
</configuration>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
</cconfiguration>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<project id="_Decompiler.null.1084391757" name="_Decompiler"/>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
<storageModule moduleId="refreshScope" versionNumber="2">
<configuration configurationName="Default">
<resource resourceType="PROJECT" workspacePath="/_Decompiler"/>
</configuration>
</storageModule>
<storageModule moduleId="scannerConfiguration">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1693333286;cdt.managedbuild.toolchain.gnu.base.1693333286.895166479;cdt.managedbuild.tool.gnu.c.compiler.base.1211851151;cdt.managedbuild.tool.gnu.c.compiler.input.12359898">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
</scannerConfigBuildInfo>
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1693333286;cdt.managedbuild.toolchain.gnu.base.1693333286.895166479;cdt.managedbuild.tool.gnu.cpp.compiler.base.233123430;cdt.managedbuild.tool.gnu.cpp.compiler.input.1087598226">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
</scannerConfigBuildInfo>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets">
<buildTargets>
<target name="reallyclean" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>reallyclean</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="sleigh_dbg" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>sleigh_dbg</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="ghidra_dbg" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>ghidra_dbg</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="decomp_dbg" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>decomp_dbg</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="ghidra_opt" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>ghidra_opt</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="decomp_opt" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>decomp_opt</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="sleigh_opt" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>sleigh_opt</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="install_ghidradbg" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>install_ghidradbg</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="install_ghidraopt" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>install_ghidraopt</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
</buildTargets>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.internal.ui.text.commentOwnerProjectMappings"/>
</cproject>
<storageModule moduleId="org.eclipse.cdt.core.settings">
<cconfiguration id="cdt.managedbuild.toolchain.gnu.base.1693333286">
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.base.1693333286" moduleId="org.eclipse.cdt.core.settings" name="Default">
<externalSettings/>
<extensions>
<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
</extensions>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<configuration artifactName="${ProjName}" buildProperties="" description="" id="cdt.managedbuild.toolchain.gnu.base.1693333286" name="Default" parent="org.eclipse.cdt.build.core.emptycfg">
<folderInfo id="cdt.managedbuild.toolchain.gnu.base.1693333286.895166479" name="/" resourcePath="">
<toolChain id="cdt.managedbuild.toolchain.gnu.base.1966692717" name="Linux GCC" superClass="cdt.managedbuild.toolchain.gnu.base">
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.target.gnu.platform.base.2081261468" name="Debug Platform" osList="linux,hpux,aix,qnx" superClass="cdt.managedbuild.target.gnu.platform.base"/>
<builder buildPath="${workspace_loc:/_Decompiler}/cpp" id="cdt.managedbuild.target.gnu.builder.base.2048731974" incrementalBuildTarget="ghidra_opt" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" parallelBuildOn="true" parallelizationNumber="optimal" superClass="cdt.managedbuild.target.gnu.builder.base"/>
<tool id="cdt.managedbuild.tool.gnu.archiver.base.1325849601" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/>
<tool id="cdt.managedbuild.tool.gnu.cpp.compiler.base.233123430" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.base">
<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.1087598226" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
</tool>
<tool id="cdt.managedbuild.tool.gnu.c.compiler.base.1211851151" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.base">
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.12359898" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
</tool>
<tool id="cdt.managedbuild.tool.gnu.c.linker.base.1810518227" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.base"/>
<tool id="cdt.managedbuild.tool.gnu.cpp.linker.base.1320265924" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.base">
<inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.361769658" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
</inputType>
</tool>
<tool id="cdt.managedbuild.tool.gnu.assembler.base.74027553" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.base">
<inputType id="cdt.managedbuild.tool.gnu.assembler.input.1774124767" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
</tool>
</toolChain>
</folderInfo>
<sourceEntries>
<entry excluding="unittests|extcpp" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="extcpp"/>
<entry flags="VALUE_WORKSPACE_PATH" kind="sourcePath" name="unittests"/>
</sourceEntries>
</configuration>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
</cconfiguration>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<project id="_Decompiler.null.1084391757" name="_Decompiler"/>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
<storageModule moduleId="refreshScope" versionNumber="2">
<configuration configurationName="Default">
<resource resourceType="PROJECT" workspacePath="/_Decompiler"/>
</configuration>
</storageModule>
<storageModule moduleId="scannerConfiguration">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1693333286;cdt.managedbuild.toolchain.gnu.base.1693333286.895166479;cdt.managedbuild.tool.gnu.c.compiler.base.1211851151;cdt.managedbuild.tool.gnu.c.compiler.input.12359898">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
</scannerConfigBuildInfo>
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1693333286;cdt.managedbuild.toolchain.gnu.base.1693333286.895166479;cdt.managedbuild.tool.gnu.cpp.compiler.base.233123430;cdt.managedbuild.tool.gnu.cpp.compiler.input.1087598226">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
</scannerConfigBuildInfo>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets">
<buildTargets>
<target name="reallyclean" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>reallyclean</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="sleigh_dbg" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>sleigh_dbg</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="ghidra_dbg" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>ghidra_dbg</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="decomp_dbg" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>decomp_dbg</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="ghidra_opt" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>ghidra_opt</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="decomp_opt" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>decomp_opt</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="sleigh_opt" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>sleigh_opt</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="install_ghidradbg" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>install_ghidradbg</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="install_ghidraopt" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>install_ghidraopt</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
</buildTargets>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.internal.ui.text.commentOwnerProjectMappings"/>
</cproject>

View file

@ -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

View file

@ -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 $@.$$$$

View file

@ -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;i<thelist.size();++i) {
ArchitectureCapability *res = thelist[i];
if (res->getName() == 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

View file

@ -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

View file

@ -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.

View file

@ -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; }

View file

@ -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

View file

@ -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<Varnode *> &worklist)

View file

@ -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)
{

View file

@ -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),

View file

@ -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

View file

@ -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)

View file

@ -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<uint1> &buffer,const Address &addr,Datatype *ct,int4 maxBytes,bool &isTrunc)
{

View file

@ -127,7 +127,7 @@ public:
bool getSendParamMeasures(void) const { return sendParamMeasures; } ///< Get the current setting for emitting parameter info
virtual void getStringData(vector<uint1> &buffer,const Address &addr,Datatype *ct,int4 maxBytes,bool &isTrunc);
void getStringData(vector<uint1> &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

File diff suppressed because it is too large Load diff

View file

@ -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<Funcdata*,PrototypePieces> prototypePieces;
void storePrototypePieces( Funcdata *fd_in, PrototypePieces pp_in ) { prototypePieces.insert(pair<Funcdata*,PrototypePieces>(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<string> &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

View file

@ -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<string> 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();
}

View file

@ -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<istream *> 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;
};

View file

@ -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;i<thelist.size();++i)
thelist[i]->registerCommands(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<string> &list)
{
@ -228,7 +243,7 @@ IfaceStatus::~IfaceStatus(void)
((ofstream *)fileoptr)->close();
delete fileoptr;
}
while(!inputstack.empty())
while(!promptstack.empty())
popScript();
for(int4 i=0;i<comlist.size();++i)
delete comlist[i];
@ -238,13 +253,24 @@ IfaceStatus::~IfaceStatus(void)
delete (*iter).second;
}
/// \brief Register a command with this interface
///
/// A command object is associated with one or more tokens on the command line.
/// A string containing up to 5 tokens can be associated with the command.
///
/// \param fptr is the IfaceCommand object
/// \param nm1 is the first token representing the command
/// \param nm2 is the second token (or null)
/// \param nm3 is the third token (or null)
/// \param nm4 is the fourth token (or null)
/// \param nm5 is the fifth token (or null)
void IfaceStatus::registerCom(IfaceCommand *fptr,const char *nm1,
const char *nm2,
const char *nm3,
const char *nm4,
const char *nm5)
{ // Register an interface command
{
fptr->addWord(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<string,IfaceData *>::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<IfaceCommand *>::const_iterator &first,
vector<IfaceCommand *>::const_iterator &last,
vector<string> &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<IfaceCommand *>::const_iterator &first,
vector<IfaceCommand *>::const_iterator &last,
vector<string> &input)
{
vector<IfaceCommand *>::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<string> &expand,istream &s,
vector<IfaceCommand *>::const_iterator &first,
vector<IfaceCommand *>::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<string> &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<string> &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<string>::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

View file

@ -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<string> com; // The command
vector<string> 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<string> &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<string> &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<IfaceCapability *> thelist;
static vector<IfaceCapability *> 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<istream *> inputstack;
vector<string> promptstack;
vector<uint4> flagstack;
string prompt;
int4 maxhistory;
int4 curhistory; // most recent history
vector<string> 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<IfaceCommand *>::const_iterator &first,vector<IfaceCommand *>::const_iterator &last,vector<string> &input);
virtual void readLine(string &line) { getline(*sptr,line,'\n'); }
void saveHistory(const string &line);
vector<string> promptstack; ///< Stack of command prompts corresponding to script nesting level
vector<uint4> 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<string> 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<IfaceCommand *>::const_iterator &first,
vector<IfaceCommand *>::const_iterator &last,vector<string> &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<IfaceCommand *> comlist; // List of commands
map<string,IfaceData *> datamap; // Data associated with particular modules
bool inerror; ///< Set to \b true if last command did not succeed
vector<IfaceCommand *> comlist; ///< List of registered commands
map<string,IfaceData *> datamap; ///< Data associated with particular modules
int4 expandCom(vector<string> &expand,istream &s,
vector<IfaceCommand *>::const_iterator &first,
vector<IfaceCommand *>::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<string> &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<string> &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"; }

View file

@ -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

View file

@ -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
{

View file

@ -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<Address,FuncProto *>::const_iterator fiter;

View file

@ -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

View file

@ -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);

View file

@ -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)
{

View file

@ -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<uint4> &oplist) const
{

View file

@ -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<RelativeRecord>::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<PcodeData>::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;i<numops;++i) {
@ -238,12 +274,23 @@ void SleighBuilder::buildEmpty(Constructor *ct,int4 secnum)
}
}
/// Bits used to make temporary registers unique across multiple instructions
/// are generated based on the given address.
/// \param addr is the given Address
void SleighBuilder::setUniqueOffset(const Address &addr)
{
uniqueoffset = (addr.getOffset() & uniquemask)<<4;
}
/// \brief Constructor
///
/// \param w is the parsed instruction
/// \param dcache is a cache of nearby instruction parses
/// \param pc will hold the PcodeData and VarnodeData objects produced by \b this builder
/// \param cspc is the constant address space
/// \param uspc is the unique address space
/// \param umask is the mask to use to find unique bits within an Address
SleighBuilder::SleighBuilder(ParserWalker *w,DisassemblyCache *dcache,PcodeCacher *pc,AddrSpace *cspc,
AddrSpace *uspc,uint4 umask)
: PcodeBuilder(0)
@ -259,7 +306,8 @@ SleighBuilder::SleighBuilder(ParserWalker *w,DisassemblyCache *dcache,PcodeCache
void SleighBuilder::appendBuild(OpTpl *bld,int4 secnum)
{ // Append pcode for a particular build statement
{
// Append p-code for a particular build statement
int4 index = bld->getIn(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 \<sleigh> 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);
}

View file

@ -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<PcodeData> issued;
list<RelativeRecord> label_refs; // References to labels
vector<uintb> 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<PcodeData> issued; ///< P-code ops issued for the current instruction
list<RelativeRecord> label_refs; ///< References to labels
vector<uintb> 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);

View file

@ -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__

View file

@ -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<uint1> byteData; // UTF8 encoded string data
bool isTruncated; ///< \b true if the the string is truncated
vector<uint1> byteData; ///< UTF8 encoded string data
};
map<Address,StringData> stringMap; ///< Map from address to string data
int4 maximumChars; ///< Maximum characters in a string before truncating

View file

@ -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 {

View file

@ -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 <cmath>
#include <cstdint>
#include <cstring>
vector<UnitTest *> UnitTest::tests;
#include <limits>
#include <vector>
/// 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<string> &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> float_test_values{
-0.0f,
+0.0f,
-1.0f,
+1.0f,
-1.234f,
+1.234f,
-std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::min() - std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::min(),
std::numeric_limits<float>::min() + std::numeric_limits<float>::denorm_min(),
-std::numeric_limits<float>::min() + std::numeric_limits<float>::denorm_min(),
-std::numeric_limits<float>::min(),
-std::numeric_limits<float>::min() - std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::quiet_NaN(),
-std::numeric_limits<float>::infinity(),
std::numeric_limits<float>::infinity()
};
static std::vector<int> int_test_values = {
0, -1, 1, 1234, -1234, std::numeric_limits<int>::min(), std::numeric_limits<int>::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<float>::quiet_NaN());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::quiet_NaN());
}
TEST(double_encoding_nan) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::quiet_NaN());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::quiet_NaN());
}
TEST(float_encoding_subnormal) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::denorm_min());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::denorm_min());
}
TEST(double_encoding_subnormal) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::denorm_min());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::denorm_min());
}
TEST(float_encoding_min_normal) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::min());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::min());
}
TEST(double_encoding_min_normal) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::min());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::min());
}
TEST(float_encoding_infinity) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::infinity());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::infinity());
}
TEST(double_encoding_infinity) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::infinity());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::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<string> unitTestNames;
set<string> 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 <datatestdir>] [[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<int>::max() || (int64_t)f < std::numeric_limits<int>::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<f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opLess(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opLessEqual) {
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.opLessEqual(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opAdd) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1+f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opAdd(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opDiv) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1/f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opDiv(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opMult) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1*f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opMult(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opSub) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1-f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opSub(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
// end generated

View file

@ -19,48 +19,59 @@
/// Include this file and any additional headers. Use TEST(testname) as
/// prototype in test function definitions. E.g.
/// test.cc:
/// #include "float.hh"
/// #include "test.hh"
/// \#include "float.hh"
/// \#include "test.hh"
///
/// TEST(zero_is_less_than_one) {
/// ASSERT(0.0 < 1.0);
/// }
///
#include <cstdio>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <set>
#include <string>
#include <iostream>
namespace {
struct Test;
typedef void (*testfunc_t)();
typedef void (*testfunc_t)(); ///< A unit-test function
std::vector<Test *> 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<UnitTest *> 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<std::string> &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<std::string> 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;
}

View file

@ -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 \<script> tag
void ConsoleCommands::restoreXml(const Element *el)
{
const List &list(el->getChildren());
List::const_iterator iter;
for(iter=list.begin();iter!=list.end();++iter) {
const Element *subel = *iter;
commands.push_back(subel->getContent());
}
pos = 0;
}
void FunctionTestCollection::clear(void)
{
dcp->clearArchitecture();
testList.clear();
console.reset();
}
/// Instantiate an Architecture object
void FunctionTestCollection::buildProgram(DocumentStorage &docStorage)
{
ArchitectureCapability *capa = ArchitectureCapability::getCapability("xml");
if (capa == (ArchitectureCapability *)0)
throw IfaceExecutionError("Missing XML architecture capability");
dcp->conf = capa->buildArchitecture("test", "", console.optr);
string errmsg;
bool iserror = false;
try {
dcp->conf->init(docStorage);
dcp->conf->readLoaderSymbols("::"); // Read in loader symbols
} catch(XmlError &err) {
errmsg = err.explain;
iserror = true;
} catch(LowlevelError &err) {
errmsg = err.explain;
iserror = true;
}
if (iserror)
throw IfaceExecutionError("Error during architecture initialization: " + errmsg);
}
/// Let each test initialize itself thru its startTest() method
void FunctionTestCollection::startTests(void) const
{
list<FunctionTestProperty>::const_iterator iter;
for(iter=testList.begin();iter!=testList.end();++iter) {
(*iter).startTest();
}
}
/// Each test gets a chance to process a line of output
/// \param line is the given line of output
void FunctionTestCollection::passLineToTests(const string &line) const
{
list<FunctionTestProperty>::const_iterator iter;
for(iter=testList.begin();iter!=testList.end();++iter) {
(*iter).processLine(line);
}
}
/// \brief Do the final evaluation of each test
///
/// This is called after each test has been fed all lines of output.
/// The result of each test is printed to the \e midStream, and then
/// failures are written to the lateStream in order to see a summary.
/// \param midStream is the stream write results to as the test is performed
/// \param lateStream collects failures to display as a summary
void FunctionTestCollection::evaluateTests(ostream &midStream,list<string> &lateStream) const
{
list<FunctionTestProperty>::const_iterator iter;
for(iter=testList.begin();iter!=testList.end();++iter) {
numTestsApplied += 1;
if ((*iter).endTest()) {
midStream << "Success -- " << (*iter).getName() << endl;
numTestsSucceeded += 1;
}
else {
midStream << "FAIL -- " << (*iter).getName() << endl;
lateStream.push_back((*iter).getName());
}
}
}
FunctionTestCollection::FunctionTestCollection(void)
{
dcp = (IfaceDecompData *)console.getData("decompile");
console.setErrorIsDone(true);
numTestsApplied = 0;
numTestsSucceeded = 0;
}
/// Load the architecture based on the discovered \<binaryimage> tag.
/// Collect the script commands and the specific tests.
/// \param filename is the XML file holding the test data
void FunctionTestCollection::loadTest(const string &filename)
{
fileName = filename;
DocumentStorage docStorage;
Document *doc = docStorage.openDocument(filename);
Element *el = doc->getRoot();
if (el->getName() == "decompilertest")
restoreXml(docStorage,el);
else if (el->getName() == "binaryimage")
restoreXmlOldForm(docStorage,el);
else
throw IfaceParseError("Test file " + filename + " has unrecognized XML tag: "+el->getName());
}
void FunctionTestCollection::restoreXml(DocumentStorage &store,const Element *el)
{
clear();
const List &list(el->getChildren());
List::const_iterator iter = list.begin();
bool sawScript = false;
bool sawTests = false;
bool sawProgram = false;
while(iter != list.end()) {
const Element *subel = *iter;
++iter;
if (subel->getName() == "script") {
sawScript = true;
console.restoreXml(subel);
}
else if (subel->getName() == "stringmatch") {
sawTests = true;
testList.emplace_back();
testList.back().restoreXml(subel);
}
else if (subel->getName() == "binaryimage") {
sawProgram = true;
store.registerTag(subel);
buildProgram(store);
}
else
throw IfaceParseError("Unknown tag in <decompiletest>: "+subel->getName());
}
if (!sawScript)
throw IfaceParseError("Did not see <script> tag in <decompiletest>");
if (!sawTests)
throw IfaceParseError("Did not see any <stringmatch> tags in <decompiletest>");
if (!sawProgram)
throw IfaceParseError("No <binaryimage> tag in <decompiletest>");
}
/// Pull the script and tests from a comment in \<binaryimage>
void FunctionTestCollection::restoreXmlOldForm(DocumentStorage &store,const Element *el)
{
clear();
throw IfaceParseError("Old format test not supported");
}
/// Run the script commands on the current program.
/// Collect any bulk output, and run tests over the output.
/// Report test failures back to the caller
/// \param midStream is the output stream to write to during the test
/// \param lateStream collects messages for a final summary
void FunctionTestCollection::runTests(ostream &midStream,list<string> &lateStream)
{
numTestsApplied = 0;
numTestsSucceeded = 0;
ostringstream midBuffer; // Collect command console output
console.optr = &midBuffer;
ostringstream bulkout;
console.fileoptr = &bulkout;
mainloop(&console);
console.optr = &midStream;
console.fileoptr = &midStream;
if (console.isInError()) {
midStream << "Error: Did not apply tests in " << fileName << endl;
midStream << midBuffer.str() << endl;
ostringstream fs;
fs << "Execution failed for " << fileName;
lateStream.push_back(fs.str());
return;
}
string result = bulkout.str();
if (result.size() == 0) {
ostringstream fs;
fs << "No output for " << fileName;
lateStream.push_back(fs.str());
return;
}
startTests();
string::size_type prevpos = 0;
string::size_type pos = result.find_first_of('\n');
while(pos != string::npos) {
string line = result.substr(prevpos,pos - prevpos);
passLineToTests(line);
prevpos = pos + 1;
pos = result.find_first_of('\n',prevpos);
}
if (prevpos != result.size()) {
string line = result.substr(prevpos); // Process final line without a newline char
passLineToTests(line);
}
evaluateTests(midStream, lateStream);
}
/// Run through all XML files in the given directory, processing each in turn.
/// \param dirname is a directory containing the XML test files
/// \param testNames (if not empty) specifies particular tests to run
void FunctionTestCollection::runTestCollections(const string &dirname,set<string> &testNames)
{
FileManage fileManage;
set<string> fullNames;
for(set<string>::iterator iter=testNames.begin();iter!=testNames.end();++iter) {
string val = dirname;
if (dirname.back() != '/')
val += '/';
val += *iter;
fullNames.insert(val);
}
fileManage.addDir2Path(dirname);
vector<string> testFiles;
fileManage.matchList(testFiles,".xml",true);
int4 totalTestsApplied = 0;
int4 totalTestsSucceeded = 0;
list<string> failures;
FunctionTestCollection testCollection;
for(int4 i=0;i<testFiles.size();++i) {
if (!fullNames.empty() && fullNames.find(testFiles[i]) == fullNames.end())
continue;
try {
testCollection.loadTest(testFiles[i]);
testCollection.runTests(cout, failures);
totalTestsApplied += testCollection.getTestsApplied();
totalTestsSucceeded += testCollection.getTestsSucceeded();
} catch(IfaceParseError &err) {
ostringstream fs;
fs << "Error parsing " << testFiles[i] << ": " << err.explain;
cout << fs.str() << endl;
failures.push_back(fs.str());
} catch(IfaceExecutionError &err) {
ostringstream fs;
fs << "Error executing " << testFiles[i] << ": " << err.explain;
cout << fs.str() << endl;
failures.push_back(fs.str());
}
}
cout << endl;
cout << "Total tests applied = " << totalTestsApplied << endl;
cout << "Total passing tests = " << totalTestsSucceeded << endl;
cout << endl;
if (!failures.empty()) {
cout << "Failures: " << endl;
list<string>::const_iterator iter = failures.begin();
for(int4 i=0;i<10;++i) {
cout << " " << *iter << endl;
++iter;
if (iter == failures.end()) break;
}
}
}

View file

@ -0,0 +1,88 @@
/* ###
* 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 testfunction.hh
/// \brief Framework for decompiler data driven single function tests
#ifndef __TESTFUNCTION__
#define __TESTFUNCTION__
#include "libdecomp.hh"
#include <iostream>
#include <regex>
/// \brief A single property to be searched for in the output of a function decompilation
///
/// This is generally a regular expression run over the characters in the
/// decompiled "source" form of the function.
/// The property may "match" more than once or not at all.
class FunctionTestProperty {
int4 minimumMatch; ///< Minimum number of times property is expected to match
int4 maximumMatch; ///< Maximum number of times property is expected to match
string name; ///< Name of the test, to be printed in test summaries
regex pattern; ///< Regular expression to match against a line of output
mutable uint4 count; ///< Number of times regular expression has been seen
public:
string getName(void) const { return name; } ///< Get the name of the property
void startTest(void) const; ///< Reset "state", counting number of matching lines
void processLine(const string &line) const; ///< Search thru \e line, update state if match found
bool endTest(void) const; ///< Return results of property search
void restoreXml(const Element *el); ///< Reconstruct the property from an XML tag
};
/// \brief A console command run as part of a test sequence
class ConsoleCommands : public IfaceStatus {
vector<string> commands; ///< Sequence of commands
uint4 pos; ///< Position of next command to execute
virtual void readLine(string &line);
public:
ConsoleCommands(void); ///< Constructor
void reset(void); ///< Reset console for a new program
virtual bool isStreamFinished(void) const { return pos == commands.size(); }
void restoreXml(const Element *el); ///< Reconstruct the command from an XML tag
};
/// \brief A collection of tests around a single program/function
///
/// The collection of tests is loaded from a single XML file via loadTest(),
/// and the tests are run by calling runTests().
/// An entire program is loaded and possibly annotated by a series of
/// console command lines. Decompiler output is also triggered by a command,
/// and then the output is scanned for by the test objects (FunctionTestProperty).
/// Results of passed/failed tests are collected. If the command line script
/// does not complete properly, this is considered a special kind of failure.
class FunctionTestCollection {
IfaceDecompData *dcp; ///< Program data for the test collection
string fileName; ///< Name of the file containing test data
list<FunctionTestProperty> testList; ///< List of tests for this collection
ConsoleCommands console; ///< Decompiler console for executing scripts
mutable int4 numTestsApplied; ///< Count of tests that were executed
mutable int4 numTestsSucceeded; ///< Count of tests that passed
void clear(void); ///< Clear any previous architecture and function
void buildProgram(DocumentStorage &store); ///< Build program (Architecture) from \<binaryimage> tag
void startTests(void) const; ///< Initialize each FunctionTestProperty
void passLineToTests(const string &line) const; ///< Let all tests analyze a line of the results
void evaluateTests(ostream &midStream,list<string> &lateStream) const;
public:
FunctionTestCollection(void); ///< Constructor
int4 getTestsApplied(void) const { return numTestsApplied; } ///< Get the number of tests executed
int4 getTestsSucceeded(void) const { return numTestsSucceeded; } ///< Get the number of tests that passed
void loadTest(const string &filename); ///< Load a test program, tests, and script
void restoreXml(DocumentStorage &store,const Element *el); ///< Load tests from a \<decompilertest> tag.
void restoreXmlOldForm(DocumentStorage &store,const Element *el); ///< Load tests from \<binaryimage> tag.
void runTests(ostream &midStream,list<string> &lateStream); ///< Run the script and perform the tests
static void runTestCollections(const string &dirname,set<string> &testNames); ///< Run test files in a whole directory
};
#endif

View file

@ -201,7 +201,7 @@ public:
bool isFloatExtension(void) const { return (pieces.size() == 1); } ///< Does this record extend a float varnode
const VarnodeData &getPiece(int4 i) const { return pieces[i]; } ///< Get the i-th piece
const VarnodeData &getUnified(void) const { return unified; } ///< Get the Varnode whole
Address getEquivalentAddress(uintb offset,int4 &pos) const; ///< Given offset in \join space, get equivalent address of piece
Address getEquivalentAddress(uintb offset,int4 &pos) const; ///< Given offset in \e join space, get equivalent address of piece
bool operator<(const JoinRecord &op2) const; ///< Compare records lexigraphically by pieces
};

View file

@ -318,7 +318,7 @@ void Datatype::saveXmlRef(ostream &s) const
/// A CPUI_PTRSUB must act on a pointer data-type where the given offset addresses a component.
/// Perform this check.
/// \param is the given offset
/// \param offset is the given offset
/// \return \b true if \b this is a suitable PTRSUB data-type
bool Datatype::isPtrsubMatching(uintb offset) const
@ -422,7 +422,7 @@ uint8 Datatype::hashName(const string &nm)
/// The hashing is reversible by feeding the output ID back into this function with the same size.
/// \param id is the given ID to (de)uniquify
/// \param size is the instance size of the structure
/// \param return the (de)uniquified id
/// \return the (de)uniquified id
uint8 Datatype::hashSize(uint8 id,int4 size)
{

View file

@ -123,6 +123,7 @@ public:
/// Given a specific language and PcodeOp, emit the expression rooted at the operation.
/// \param lng is the PrintLanguage to emit
/// \param op is the specific PcodeOp
/// \param readOp is the PcodeOp consuming the output (or null)
virtual void push(PrintLanguage *lng,const PcodeOp *op,const PcodeOp *readOp) const=0;
/// \brief Print (for debugging purposes) \b this specific PcodeOp to the stream

View file

@ -22,7 +22,7 @@
/// properly, in which case the union of the two ranges can exist without
/// destroying data-type information.
/// \param b is the range to reconcile with \b this
/// \param \b true if the data-type information can be reconciled
/// \return \b true if the data-type information can be reconciled
bool RangeHint::reconcile(const RangeHint *b) const
{

View file

@ -399,7 +399,7 @@ void Varnode::setSymbolEntry(SymbolEntry *entry)
/// This used when there is a constant address reference to the Symbol and the Varnode holds the
/// reference, not the actual value of the Symbol.
/// \param entry is a mapping to the given Symbol
/// \off is the byte offset into the Symbol of the reference
/// \param off is the byte offset into the Symbol of the reference
void Varnode::setSymbolReference(SymbolEntry *entry,int4 off)
{

View file

@ -0,0 +1,30 @@
<decompilertest>
<binaryimage arch="MIPS:LE:32:default:default">
<!--
Contrived example of a LOAD off of a pointer that eventually
collapses to an address in a volatile region. The value returned
by the LOAD is unused. This test makes sure the side-effects of
the volatile read are still present.
-->
<bytechunk space="ram" offset="0xbfc05558" readonly="true">
c1bf053c5cdfa58c
e8ffbd2701a0023c5491428c1400bfaf
120001240000a090
8618f00f00000000
0800e0038001bd27
</bytechunk>
<bytechunk space="ram" offset="0xbfc0df5c" readonly="true">
00100000
</bytechunk>
<symbol space="ram" offset="0xbfc05558" name="deadvolatile"/>
</binaryimage>
<script>
<com>option readonly on</com>
<com>volatile [ram,0x1000,16]</com>
<com>lo fu deadvolatile</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Dead Volatile #1" min="1" max="1">read_volatile.*0x1000</stringmatch>
</decompilertest>

View file

@ -0,0 +1,70 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
A contrived function with different float and double encodings.
-->
<bytechunk space="ram" offset="0x4004c7" readonly="true">
554889e5f30f100539
030000f30f1105550b2000f30f10052d
030000f30f1105490b2000f30f100521
030000f30f11053d0b2000f30f100515
030000f30f1105310b2000f30f100509
030000660fefc9f30f5ec1f30f11051d
0b2000660fefc0660fefc9f30f5ec1f3
0f11050d0b2000f30f1005e1020000f3
0f1105010b2000f20f1005d9020000f2
0f1105f90a2000f20f1005d1020000f2
0f1105f10a2000f20f1005c9020000f2
0f1105e90a2000f20f1005c1020000f2
0f1105e10a2000f20f1005b902000066
0fefc9f20f5ec1f20f1105d10a200066
0fefc0660fefc9f20f5ec1f20f1105c5
0a2000f20f100595020000f20f1105bd
0a2000905dc3
</bytechunk>
<bytechunk space="ram" offset="0x40080c" readonly="true">
abaaaa3e
000000406f1283babd3786350000803f
24d4523600000000555555555555e53f
0000000000000040fca9f1d24d6250bf
bbbdd7d9df7cdb3d000000000000f03f
7e7480d3845aca3e
</bytechunk>
<symbol space="ram" offset="0x4004c7" name="establish"/>
</binaryimage>
<script>
<com>map addr r0x601030 float4 floatv1</com>
<com>map addr r0x601034 float4 floatv2</com>
<com>map addr r0x601038 float4 floatv3</com>
<com>map addr r0x60103c float4 floatv4</com>
<com>map addr r0x601040 float4 floatv5</com>
<com>map addr r0x601044 float4 floatv6</com>
<com>map addr r0x601048 float4 floatv7</com>
<com>map addr r0x601050 float8 double1</com>
<com>map addr r0x601058 float8 double2</com>
<com>map addr r0x601060 float8 double3</com>
<com>map addr r0x601068 float8 double4</com>
<com>map addr r0x601070 float8 double5</com>
<com>map addr r0x601078 float8 double6</com>
<com>map addr r0x601080 float8 double7</com>
<com>option readonly on</com>
<com>lo fu establish</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Float print #1" min="1" max="1">floatv1 = 0.3333333;</stringmatch>
<stringmatch name="Float print #2" min="1" max="1">floatv2 = 2.0;</stringmatch>
<stringmatch name="Float print #3" min="1" max="1">floatv3 = -0.001;</stringmatch>
<stringmatch name="Float print #4" min="1" max="1">floatv4 = 1e-06;</stringmatch>
<stringmatch name="Float print #5" min="1" max="1">floatv5 = INFINITY;</stringmatch>
<stringmatch name="Float print #6" min="1" max="1">floatv6 = -NAN;</stringmatch>
<stringmatch name="Float print #7" min="1" max="1">floatv7 = 3.141592e-06;</stringmatch>
<stringmatch name="Float print #8" min="1" max="1">double1 = 0.6666666666666666;</stringmatch>
<stringmatch name="Float print #9" min="1" max="1">double2 = 2.0;</stringmatch>
<stringmatch name="Float print #10" min="1" max="1">double3 = -0.001;</stringmatch>
<stringmatch name="Float print #11" min="1" max="1">double4 = 1e-10;</stringmatch>
<stringmatch name="Float print #12" min="1" max="1">double5 = INFINITY;</stringmatch>
<stringmatch name="Float print #13" min="1" max="1">double6 = -NAN;</stringmatch>
<stringmatch name="Float print #14" min="1" max="1">double7 = 3.141592653589793e-06;</stringmatch>
</decompilertest>

View file

@ -0,0 +1,22 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
Basic for-loop. Contrived example.
-->
<bytechunk space="ram" offset="0x400517" readonly="true">
554889e5534883ec18
897decbb00000000eb0dbf20084000e8
fcfeffff83c3013b5dec7cee904883c4
185b5dc3
</bytechunk>
<symbol space="ram" offset="0x400517" name="forloop1"/>
</binaryimage>
<script>
<com>lo fu forloop1</com>
<com>parse line extern void forloop1(int4 max);</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="For-loop #1" min="1" max="1">for.*iVar1 = 0; iVar1 &lt; max; iVar1 = iVar1 \+ 1</stringmatch>match>
</decompilertest>

View file

@ -0,0 +1,31 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
For-loop where the iterator statement LOADs the next value. Contrived example.
-->
<bytechunk space="ram" offset="0x4005fb" readonly="true">
554889e548
83ec2048897de8c745fc00000000eb10
488b45e8488b4018488945e88345fc01
48837de80075e98b45fc89c6bf440840
00b800000000e805feffff90c9c3
</bytechunk>
<bytechunk space="ram" offset="0x400844" readonly="true">
436f756e74203d2025640a00
</bytechunk>
<symbol space="ram" offset="0x4005fb" name="forloop_loaditer"/>
<symbol space="ram" offset="0x400440" name="printf"/>
</binaryimage>
<script>
<com>parse line extern void printf(char *,...);</com>
<com>parse line extern void forloop_loaditer(int4 **ptr);</com>
<com>lo fu forloop_loaditer</com>
<com>map addr s0xfffffffffffffff4 int4 count</com>
<com>map addr s0xffffffffffffffe0 int4 **loopvar</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="For-loop iterator load #1" min="1" max="1">for \(loopvar = ptr; loopvar != \(int4 \*\*\)0x0; loopvar = \(int4 \*\*\)loopvar\[3\]\)</stringmatch>
<stringmatch name="For-loop iterator load #2" min="1" max="1">count = count \+ 1;</stringmatch>
</decompilertest>

View file

@ -0,0 +1,30 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
For loop where the increment statement must cross special operations. Contrived example.
-->
<bytechunk space="ram" offset="0x4005bb" readonly="true">
554889e553
4883ec18897dec488975e0bb03000000
eb1c83c301488b45e0c7000a00000048
8345e004bf38084000e842feffff3b5d
ec7cdf904883c4185b5dc3
</bytechunk>
<bytechunk space="ram" offset="0x400838" readonly="true">
4d616b6520612063616c6c00
</bytechunk>
<symbol space="ram" offset="0x4005bb" name="forloop_thruspecial"/>
<symbol space="ram" offset="0x400430" name="puts"/>
</binaryimage>
<script>
<com>parse line extern void puts(char *);</com>
<com>parse line extern void forloop_thruspecial(int4 max,int4 *ptr);</com>
<com>lo fu forloop_thruspecial</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="For-loop thru special #1" min="1" max="1">for.*iVar1 = 3; iVar1 &lt; max; iVar1 = iVar1 \+ 1</stringmatch>
<stringmatch name="For-loop thru special #2" min="1" max="1">\*piStack.* = 10;</stringmatch>
<stringmatch name="For-loop thru special #3" min="1" max="1">puts.*Make a call.*;</stringmatch>
</decompilertest>

View file

@ -0,0 +1,31 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
For-loop where the loop variable is used elsewhere in the loop body. Contrived example.
-->
<bytechunk space="ram" offset="0x400544" readonly="true">
554889e5534883ec18897dec
bb00000000eb2e89d883e00385c07511
bf26084000b800000000e8d1feffffeb
1189debf2e084000b800000000e8befe
ffff83c3013b5dec7ccd904883c4185b
5dc3
</bytechunk>
<bytechunk space="ram" offset="0x400826" readonly="true">
30206d6f6420340076616c203d2025640a00
</bytechunk>
<symbol space="ram" offset="0x400544" name="forloop_loopvarused"/>
<symbol space="ram" offset="0x400440" name="printf"/>
</binaryimage>
<script>
<com>parse line extern void printf(char *,...);</com>
<com>parse line extern void forloop_loopvarused(int4 max);</com>
<com>lo fu forloop_loopvarused</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="For-loop var used #1" min="1" max="1">for.*uVar1 = 0;.*uVar1 &lt; max; uVar1 = uVar1 \+ 1</stringmatch>
<stringmatch name="For-loop var used #2" min="1" max="1">if \(\(uVar1 &amp; 3\) == 0\)</stringmatch>
<stringmatch name="For-loop var used #3" min="1" max="1">val = %d.*uVar1</stringmatch>
</decompilertest>

View file

@ -0,0 +1,24 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
For-loop where the loop var may be additionally incremented in the loop body. Contrived example.
-->
<bytechunk space="ram" offset="0x400592" readonly="true">
554889e553897df4bb00000000eb
0c837df40a7e0383c30183c3018b057d
0a200039c37cea905b5dc3
</bytechunk>
<symbol space="ram" offset="0x400592" name="forloop_withskip"/>
</binaryimage>
<script>
<com>map addr r0x601030 int4 globvar</com>
<com>parse line extern void forloop_withskip(int4 val);</com>
<com>lo fu forloop_withskip</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="For-loop with skip #1" min="1" max="1">for.*iVar1 = 0; iVar1 &lt; globvar; iVar1 = iVar1 \+ 1</stringmatch>
<stringmatch name="For-loop with skip #2" min="1" max="1">if \(10 &lt; val\)</stringmatch>
<stringmatch name="For-loop with skip #3" min="1" max="1">iVar1 = iVar1 \+ 1;</stringmatch>
</decompilertest>

View file

@ -0,0 +1,47 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
A contrived function with different loops, where we put comments on instructions
comprising the loop conditions. The comments should get moved to a separate line
and not get printed in the middle of the condition.
-->
<bytechunk space="ram" offset="0x1006e4" readonly="true">
554889e54883ec20897dec89
75e88955e4894de08b45ec83f8097f1c
837de8647e168b45e43de8030000750c
488d3d0d020000e864feffffc745fc00
000000eb28488d3d04020000e84ffeff
ff488d45ec4889c7e88dffffff488d45
e04889c7e881ffffff8345fc018b45e0
3dc70000007f378b45ec83f8137f2f81
7dfcc70000007ebdeb24488d3dc80100
00e80afeffff488d45ec4889c7e848ff
ffff488d45e04889c7e83cffffff8b45
ec83f8630f9ec28b45e083f80a0f9fc0
21d084c075c48b45ec83c00a8945ec48
8d3d8e010000e8c5fdffff8b45ec89c6
488d3d85010000b800000000e8bffdff
ff488d45ec4889c7e8edfeffff488d45
e44889c7e8e1feffff8b45ec3dcf0700
007f088b45e483f81d7eab8b45ec89c6
488d3d49010000b800000000e87ffdff
ff90c9c3
</bytechunk>
<symbol space="ram" offset="0x1006e4" name="loopcomment"/>
</binaryimage>
<script>
<com>lo fu loopcomment</com>
<com>comment instr r0x100704 ifcomment</com>
<com>comment instr r0x10075d forcomment</com>
<com>comment instr r0x100791 whilecomment</com>
<com>comment instr r0x1007f9 dowhilecomment</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Loop comment #1" min="1" max="1">^ */\* ifcomment \*/ *$</stringmatch>
<stringmatch name="Loop comment #2" min="1" max="1">^ */\* forcomment \*/ *$</stringmatch>
<stringmatch name="Loop comment #3" min="1" max="1">^ */\* whilecomment \*/ *$</stringmatch>
<stringmatch name="Loop comment #4" min="1" max="1">^ */\* dowhilecomment \*/ *$</stringmatch>
<stringmatch name="Loop comment #5" min="4" max="4">.*/\*</stringmatch>
</decompilertest>

View file

@ -0,0 +1,29 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
A routine that uses multiple symbols with the same name. The decompiler should
detect the name collisions and append appropriate namespace tokens.
-->
<bytechunk space="ram" offset="0x400537" readonly="true">
554889e5897dfc8b45
fc83c0018905e60a20008b55fc89d0c1
e00201d001c08905d80a20008b15d60a
20008b45fc01d05dc3
</bytechunk>
<symbol space="ram" offset="0x400537" name="a::b::assign_vals"/>
</binaryimage>
<script>
<com>map addr r0x601030 int4 spam</com>
<com>map addr r0x601034 int4 a::spam</com>
<com>map addr r0x601038 int4 c::spam</com>
<com>parse line extern int4 a::b::assign_vals(int4 spam);</com>
<com>lo fu a::b::assign_vals</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Namespace #1" min="1" max="1">a::b::assign_vals\(int4 spam\)</stringmatch>
<stringmatch name="Namespace #2" min="1" max="1">^ ::spam = spam \+ 1;</stringmatch>
<stringmatch name="Namespace #3" min="1" max="1">^ a::spam = spam \* 10;</stringmatch>
<stringmatch name="Namespace #4" min="1" max="1">return spam \+ c::spam;</stringmatch>
</decompilertest>

View file

@ -0,0 +1,25 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
Hand coded routine where the offset into a structure to reach an array field
is contained inside the index expression for the array. The decompiler must
distribute the constant multiple scaling the array index to "see" the offset.
-->
<bytechunk space="ram" offset="0x400517" readonly="true">
554889e5488d441602
488d04878b005dc3
</bytechunk>
<symbol space="ram" offset="0x400517" name="readstruct"/>
</binaryimage>
<script>
<com>parse line struct twostruct { int4 field1; int4 array[5]; };</com>
<com>parse line extern int4 readstruct(twostruct *ptr,int8 a,int8 b);</com>
<com>lo fu readstruct</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Nested offset #1" min="1" max="1">return ptr-&gt;array\[b \+ a\];</stringmatch>
<stringmatch name="Nested offset #2" min="0" max="0">field</stringmatch>
<stringmatch name="Nested offset #3" min="0" max="0">\* 4</stringmatch>
</decompilertest>

View file

@ -0,0 +1,35 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
A loop that should not recover as a for-loop as the loop variable is potentially aliased after the iterate statement.
Contrived example.
-->
<bytechunk space="ram" offset="0x400690" readonly="true">
554889e54883ec20897decc745f00100
0000c745f400000000c745f802000000
c745fc03000000eb298b45f483c00189
45f4488d45f04889c7e8adffffff8b45
f489c6bf5d084000b800000000e85efd
ffff8b45f43945ec7fcf90c9c3
</bytechunk>
<bytechunk space="ram" offset="0x40085d" readonly="true">
56616c203d2025640a00
</bytechunk>
<symbol space="ram" offset="0x400690" name="noforloop_alias"/>
<symbol space="ram" offset="0x400440" name="printf"/>
<symbol space="ram" offset="0x40067b" name="might_change"/>
</binaryimage>
<script>
<com>parse line extern void printf(char *,...);</com>
<com>parse line extern void noforloop_alias(int4 max);</com>
<com>lo fu noforloop_alias</com>
<com>map addr s0xffffffffffffffe8 int4 i[4]</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="No for-loop alias #1" min="1" max="1">while \(i\[1\] &lt; max\)</stringmatch>
<stringmatch name="No for-loop alias #2" min="1" max="1">i\[1\] = i\[1\] \+ 1;</stringmatch>
<stringmatch name="No for-loop alias #3" min="4" max="4">i\[[0-3]\] = [0-3];</stringmatch>
<stringmatch name="No for-loop alias #4" min="1" max="1">might_change\(i\);</stringmatch>
</decompilertest>

View file

@ -0,0 +1,27 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
A loop that should not recover as a for-loop as the loop variable is global and may change
in a subfunction after the iteration statement.
-->
<bytechunk space="ram" offset="0x4006ed" readonly="true">
554889
e54883ec0848897df8c7052d09200000
000000eb1b8b052509200083c0018905
1c092000488b45f84889c7e85bffffff
8b050a09200083f8097eda90c9c3
</bytechunk>
<symbol space="ram" offset="0x4006ed" name="noforloop_globcall"/>
</binaryimage>
<script>
<com>parse line extern void noforloop_globcall(int4 *ptr);</com>
<com>map addr r0x601030 int4 globvar</com>
<com>lo fu noforloop_globcall</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="No for-loop global call #1" min="1" max="1">while \(globvar &lt; 10\)</stringmatch>
<stringmatch name="No for-loop global call #2" min="1" max="1">globvar = 0;</stringmatch>
<stringmatch name="No for-loop global call #3" min="1" max="1">globvar = globvar \+ 1;</stringmatch>
</decompilertest>

View file

@ -0,0 +1,32 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
A loop that should not recover as a for-loop as the loop variable is used after the iterate statement. Contrived example.
-->
<bytechunk space="ram" offset="0x40063e" readonly="true">
5548
89e5534883ec18897decbb0a000000eb
1d89debf50084000b800000000e8defd
ffff83c3016bc3648905c20920003b5d
ec7cde904883c4185b5dc3
</bytechunk>
<bytechunk space="ram" offset="0x400850" readonly="true">
4265666f7265203d2025640a00
</bytechunk>
<symbol space="ram" offset="0x40063e" name="noforloop_iterused"/>
<symbol space="ram" offset="0x400440" name="printf"/>
</binaryimage>
<script>
<com>parse line extern void printf(char *,...);</com>
<com>parse line extern void noforloop_iterused(int4 max);</com>
<com>map addr r0x601030 int4 globvar</com>
<com>lo fu noforloop_iterused</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="No for-loop iterator used #1" min="1" max="1">while \(.*uVar1 &lt; max\)</stringmatch>
<stringmatch name="No for-loop iterator used #2" min="1" max="1">uVar1 = 10;</stringmatch>
<stringmatch name="No for-loop iterator used #3" min="1" max="1">uVar1 = uVar1 \+ 1;</stringmatch>
<stringmatch name="No for-loop iterator used #4" min="1" max="1">globvar = uVar1 \* 100;</stringmatch>
</decompilertest>

View file

@ -0,0 +1,27 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
Contrived routine that indexes into an array as a subfield of a structure. The
original source code had a negative offset added to the index, which got folded into
the stack offset for the start of the array. The offset is easily confused with the
offset of the "firstfield" of the structure, but the decompiler should figure it out
because of the clear array indexing.
-->
<bytechunk space="ram" offset="0x100000" readonly="true">
534889fb4881ec900000004889e7e8ed
0f00008b049c4881c4900000005bc3
</bytechunk>
<symbol space="ram" offset="0x100000" name="access_array1"/>
<symbol space="ram" offset="0x101000" name="populate_mystruct"/>
</binaryimage>
<script>
<com>parse line struct mystruct { int4 firstfield; int4 array[32]; };</com>
<com>parse line extern void populate_mystruct(mystruct *ptr);</com>
<com>lo fu access_array1</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Offset array #1" min="1" max="1">return mStack.*array\[param_1 \+ -2\]</stringmatch>
<stringmatch name="Offset array #2" min="0" max="0">firstfield</stringmatch>
</decompilertest>

View file

@ -0,0 +1,20 @@
<decompilertest>
<binaryimage arch="x86:LE:32:default:gcc">
<bytechunk space="ram" offset="0x80662e0" readonly="true">
5589e58b55080fb60284c0750eeb17
</bytechunk>
<bytechunk space="ram" offset="0x80662f0" readonly="true">
0fb6420184c0740e83c20183e8303c09
76ee5d31c0c35db801000000c3
</bytechunk>
<symbol space="ram" offset="0x80662e0" name="promote_compare"/>
</binaryimage>
<script>
<com>lo fu promote_compare</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Promotion on compare #1" min="1" max="1">9.*uint1..cVar.*0x30</stringmatch>
<stringmatch name="Promotion on compare #2" min="1" max="1">uint1</stringmatch>
</decompilertest>

View file

@ -0,0 +1,24 @@
<decompilertest>
<binaryimage arch="68000:BE:32:MC68020:default">
<!--
Simple example of a read from a volatile region, where the "value" is not
used, but the read may have side-effects that the decompiler should see.
-->
<bytechunk space="ram" offset="0x484">
1028001e4200114000141140
000a114000141140000a1140000a4e73
</bytechunk>
<symbol space="ram" offset="0x484" name="iofunc"/>
</binaryimage>
<script>
<com>volatile [ram,0x210000,64]</com>
<com>map addr r0x210000 int1 NVRAM[32]</com>
<com>set track A0 0x210000 r0x484 r0x485</com>
<com>lo fu iofunc</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Read Volatile #1" min="1" max="1">read_volatile.*NVRAM.*30</stringmatch>
<stringmatch name="Read Volatile #2" min="5" max="5">write_volatile.*NVRAM</stringmatch>
</decompilertest>

View file

@ -0,0 +1,32 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
Contrived example of reading and writing to a two dimensional array.
-->
<bytechunk space="ram" offset="0x400590" readonly="true">
554889e5897dec8975e8c745fc140000
00eb758b05b73f2c008b55ec4863ca48
988b55fc4863d24869f0d00700004889
d048c1e0024801d04801c04801f04801
c88b0485601060008945f88b057f3f2c
008b55e84863ca48988b55fc4863d248
69f0d00700004889d048c1e0024801d0
4801c04801f0488d14088b45f8890495
601060008345fc01837dfc1d7e858b45
f85dc3
</bytechunk>
<symbol space="ram" offset="0x400590" name="array_access"/>
</binaryimage>
<script>
<com>map addr r0x6c4560 int4 globindex</com>
<com>map addr r0x601060 int4 myarray[100][200][10]</com>
<com>parse line extern int4 array_access(int4 valin,int4 valout);</com>
<com>lo fu array_access</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Three dimension #1" min="1" max="1">iStack16 = myarray\[globindex\]\[iStack12\]\[valin\];</stringmatch>
<stringmatch name="Three dimension #2" min="1" max="1">myarray\[globindex\]\[iStack12\]\[valout\] = iStack16;</stringmatch>
<stringmatch name="Three dimension #3" min="0" max="0"> \* </stringmatch>
</decompilertest>

View file

@ -0,0 +1,29 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
Contrived example of reading and writing to a two dimensional array.
-->
<bytechunk space="ram" offset="0x400568" readonly="true">
554889e5897dec89
75e88b05240b20008b55ec4863ca4863
d04889d048c1e0024801d04801c88b04
85601060008945fc8b05fe0a20008b55
fc8d4a0a8b55e84863f24863d04889d0
48c1e0024801d04801f0890c85601060
008b45fc5dc3
</bytechunk>
<symbol space="ram" offset="0x400568" name="array_access"/>
</binaryimage>
<script>
<com>map addr r0x60109c int4 globindex</com>
<com>map addr r0x601060 int4 myarray[3][5]</com>
<com>parse line extern int4 array_access(int4 valin,int4 valout);</com>
<com>lo fu array_access</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Two dimension #1" min="1" max="1">iVar. = myarray\[globindex\]\[valin\];</stringmatch>
<stringmatch name="Two dimension #2" min="1" max="1">myarray\[globindex\]\[valout\] = iVar. \+ 10;</stringmatch>
<stringmatch name="Two dimension #3" min="0" max="0"> \* </stringmatch>
</decompilertest>

View file

@ -0,0 +1,27 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
Contrived routine that indexes into an array as a subfield of a structure. The
original source code had a negative offset added to the index, which got folded into
the stack offset for the start of the array. The offset pulls the base reference
entirely out of the mapped structure, so the decompiler must search forward in
the local map to find it.
-->
<bytechunk space="ram" offset="0x100000" readonly="true">
534889fb4881ec900000004889e7e8ed
0f00008b449cf84881c4900000005bc3
</bytechunk>
<symbol space="ram" offset="0x100000" name="access_array1"/>
<symbol space="ram" offset="0x101000" name="populate_mystruct"/>
</binaryimage>
<script>
<com>parse line struct mystruct { int8 firstfield; int4 array[32]; };</com>
<com>parse line extern void populate_mystruct(mystruct *ptr);</com>
<com>lo fu access_array1</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Wayoff array #1" min="1" max="1">return mStack.*array\[param_1 \+ -4\]</stringmatch>
<stringmatch name="Wayoff array #2" min="0" max="0">firstfield</stringmatch>
</decompilertest>

View file

@ -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 <cmath>
#include <cstdint>
#include <cstring>
#include <limits>
#include <vector>
// 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> float_test_values{
-0.0f,
+0.0f,
-1.0f,
+1.0f,
-1.234f,
+1.234f,
-std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::min() - std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::min(),
std::numeric_limits<float>::min() + std::numeric_limits<float>::denorm_min(),
-std::numeric_limits<float>::min() + std::numeric_limits<float>::denorm_min(),
-std::numeric_limits<float>::min(),
-std::numeric_limits<float>::min() - std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::quiet_NaN(),
-std::numeric_limits<float>::infinity(),
std::numeric_limits<float>::infinity()
};
static std::vector<int> int_test_values = {
0, -1, 1, 1234, -1234, std::numeric_limits<int>::min(), std::numeric_limits<int>::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<float>::quiet_NaN());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::quiet_NaN());
}
TEST(double_encoding_nan) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::quiet_NaN());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::quiet_NaN());
}
TEST(float_encoding_subnormal) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::denorm_min());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::denorm_min());
}
TEST(double_encoding_subnormal) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::denorm_min());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::denorm_min());
}
TEST(float_encoding_min_normal) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::min());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::min());
}
TEST(double_encoding_min_normal) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::min());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::min());
}
TEST(float_encoding_infinity) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::infinity());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::infinity());
}
TEST(double_encoding_infinity) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::infinity());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::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<int>::max() || (int64_t)f < std::numeric_limits<int>::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<f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opLess(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opLessEqual) {
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.opLessEqual(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opAdd) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1+f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opAdd(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opDiv) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1/f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opDiv(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opMult) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1*f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opMult(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opSub) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1-f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opSub(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
// end generated

View file

@ -44,7 +44,7 @@ public class ClangTextField extends WrappingVerticalLayoutTextField {
*
* @param initialX The original x value passed to the constructor of this class
* @param lineNumberElement he line number element for this field from which we get a width
* @return
* @return the calculated offset
*/
private static int calculateXPositionWithLineNumberOffset(int initialX,
FieldElement lineNumberElement) {
@ -70,7 +70,7 @@ public class ClangTextField extends WrappingVerticalLayoutTextField {
FieldElement lineNumberFieldElement, int x, int width, HighlightFactory hlFactory) {
super(createSingleLineElement(fieldElements),
calculateXPositionWithLineNumberOffset(x, lineNumberFieldElement),
calculateWidthFromXPosition(x, lineNumberFieldElement, width), 30, hlFactory);
calculateWidthFromXPosition(x, lineNumberFieldElement, width), 30, hlFactory, false);
this.tokenList = tokenList;
this.lineNumberFieldElement = lineNumberFieldElement;
}
@ -157,8 +157,8 @@ public class ClangTextField extends WrappingVerticalLayoutTextField {
}
@Override
public void paint(JComponent c, Graphics g, PaintContext context,
Rectangle clip, FieldBackgroundColorManager selectionMap, RowColLocation cursorLoc, int rowHeight) {
public void paint(JComponent c, Graphics g, PaintContext context, Rectangle clip,
FieldBackgroundColorManager selectionMap, RowColLocation cursorLoc, int rowHeight) {
// Don't print line numbers; don't copy line numbers. We are assuming that the user only
// wants to copy code.

View file

@ -15,7 +15,9 @@
*/
package ghidra.app.plugin.core.decompile.actions;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import docking.widgets.EventTrigger;
import ghidra.app.services.GraphDisplayBroker;
@ -101,7 +103,11 @@ public class ASTGraphTask extends Task {
else {
createControlFlowGraph(graph, monitor);
}
GraphDisplay display = graphService.getDefaultGraphDisplay(!newGraph, monitor);
Map<String, String> 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 =

View file

@ -30,7 +30,7 @@ import utilities.util.ArrayUtilities;
public class CoffArchiveFileSystemFactory
implements GFileSystemFactoryFull<CoffArchiveFileSystem>, 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,

View file

@ -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

View file

@ -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')

View file

@ -213,7 +213,11 @@
<LI><A name="Expand_Selected">
<B>Expand Selected Vertices</B> - Any group vertices are reverted back to the vertices that it contains.</LI>
<LI><A name="Grow_Selection_To_Entire_Component">
<B>Grow Selection To Entire Component</B> - Adds to the selection all vertices that are reachable from the
currently selected vertices.</LI>
</UL>
<H3>Vertex Popup Actions</H3>

View file

@ -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
}

View file

@ -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
*
* <P>This graph uses the following properties:
* <UL>
* <LI>selectedVertexColor - hex color using '0x' or '#', with 6 digits
* </LI>
* <LI>selectedEdgeColor - hex color using '0x' or '#', with 6 digits
* </LI>
* <LI>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)}
* </LI>
* <LI>vertexLabelPosition - see {@link Position}
* </LI>
* <LI>initialLayoutAlgorithm - the name of the layout algorithm to be used for the initial
* graph layout
* </LI>
* </UL>
*
*/
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<String, String> displayProperties = new HashMap<>();
private Set<DockingActionIf> 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<String, String> 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<AttributedVertex, AttributedEdge> 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<AttributedVertex, AttributedEdge> 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<AttributedVertex> selected = viewer.getSelectedVertexState().getSelected();
Set<AttributedVertex> selected = viewer.getSelectedVertices();
Graph<AttributedVertex, AttributedEdge> 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<AttributedVertex> vertices) {
return viewer.getSelectedVertexState().getSelected().containsAll(vertices);
}
private Set<AttributedVertex> getTargetVerticesFromSelected() {
Set<AttributedVertex> targets = new HashSet<>();
Set<AttributedVertex> selectedVertices = getSelectedVertices();
selectedVertices.forEach(v -> {
Set<AttributedEdge> edges = graph.outgoingEdgesOf(v);
edges.forEach(e -> targets.add(graph.getEdgeTarget(e)));
});
return targets;
return viewer.getSelectedVertices().containsAll(vertices);
}
private Set<AttributedVertex> getSourceVerticesFromSelected() {
Set<AttributedVertex> sources = new HashSet<>();
Set<AttributedVertex> selectedVertices = getSelectedVertices();
selectedVertices.forEach(v -> {
for (AttributedVertex v : selectedVertices) {
Set<AttributedEdge> edges = graph.incomingEdgesOf(v);
edges.forEach(e -> sources.add(graph.getEdgeSource(e)));
});
}
return sources;
}
private Set<AttributedVertex> getUnselectedSourceVerticesFromSelected() {
MutableSelectedState<AttributedVertex> selectedVertexState =
viewer.getSelectedVertexState();
return getSourceVerticesFromSelected().stream()
.filter(v -> !selectedVertexState.isSelected(v))
.collect(Collectors.toSet());
}
private Set<AttributedVertex> getTargetVerticesFromSelected() {
Set<AttributedVertex> targets = new HashSet<>();
Set<AttributedVertex> selectedVertices = getSelectedVertices();
for (AttributedVertex v : selectedVertices) {
Set<AttributedEdge> edges = graph.outgoingEdgesOf(v);
edges.forEach(e -> targets.add(graph.getEdgeTarget(e)));
}
return targets;
}
private Set<AttributedVertex> getUnselectedTargetVerticesFromSelected() {
MutableSelectedState<AttributedVertex> selectedVertexState =
viewer.getSelectedVertexState();
return getTargetVerticesFromSelected().stream()
.filter(v -> !selectedVertexState.isSelected(v))
.collect(Collectors.toSet());
}
private Set<AttributedVertex> getAllDownstreamVerticesFromSelected() {
Set<AttributedVertex> downstream = new HashSet<>();
Set<AttributedVertex> targets = getUnselectedTargetVerticesFromSelected();
while (!targets.isEmpty()) {
downstream.addAll(targets);
growSelection(targets);
targets = getUnselectedTargetVerticesFromSelected();
}
return downstream;
}
private Set<AttributedVertex> getAllUpstreamVerticesFromSelected() {
Set<AttributedVertex> upstream = new HashSet<>();
Set<AttributedVertex> sources = getUnselectedSourceVerticesFromSelected();
while (!sources.isEmpty()) {
growSelection(sources);
upstream.addAll(sources);
sources = getUnselectedSourceVerticesFromSelected();
}
return upstream;
}
public Set<AttributedVertex> getAllComponentVerticesFromSelected() {
Set<AttributedVertex> componentVertices = getAllDownstreamVerticesFromSelected();
componentVertices.addAll(getAllUpstreamVerticesFromSelected());
return componentVertices;
}
private void invertSelection() {
switchableSelectionListener.setEnabled(false);
try {
MutableSelectedState<AttributedVertex> selectedVertexState =
viewer.getSelectedVertexState();
graph.vertexSet().forEach(v -> {
for (AttributedVertex v : graph.vertexSet()) {
if (selectedVertexState.isSelected(v)) {
selectedVertexState.deselect(v);
}
else {
selectedVertexState.select(v);
}
});
}
Set<AttributedVertex> 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<AttributedVertex> 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<AttributedVertex, AttributedEdge> createViewer() {
final VisualizationViewer<AttributedVertex, AttributedEdge> vv =
protected VisualizationViewer<AttributedVertex, AttributedEdge> createViewer() {
VisualizationViewer<AttributedVertex, AttributedEdge> vv =
VisualizationViewer.<AttributedVertex, AttributedEdge> builder()
.multiSelectionStrategySupplier(
() -> freeFormSelection ? MultiSelectionStrategy.arbitrary()
@ -1119,21 +1247,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
this.iconCache = new GhidraIconCache();
RenderContext<AttributedVertex, AttributedEdge> renderContext = vv.getRenderContext();
// set up the shape and color functions
IconShapeFunction<AttributedVertex> 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<AttributedVertex, AttributedEdge> modalRenderer = vv.getRenderer();
@ -1162,12 +1275,16 @@ public class DefaultGraphDisplay implements GraphDisplay {
Renderer.Vertex<AttributedVertex, AttributedEdge> vertexRenderer =
modalRenderer.getVertexRenderer(LIGHTWEIGHT);
// cause the lightweight (optimized) renderer to use the vertex shapes instead
// of using default shapes.
if (vertexRenderer instanceof LightweightVertexRenderer) {
Function<AttributedVertex, Shape> vertexShapeFunction =
renderContext.getVertexShapeFunction();
LightweightVertexRenderer<AttributedVertex, AttributedEdge> lightweightVertexRenderer =
(LightweightVertexRenderer<AttributedVertex, AttributedEdge>) 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<AttributedVertex, AttributedEdge> vv) {
RenderContext<AttributedVertex, AttributedEdge> renderContext = vv.getRenderContext();
String useIcons =
displayProperties.getOrDefault(DISPLAY_VERTICES_AS_ICONS, Boolean.TRUE.toString());
Function<Shape, Rectangle> toRectangle = s -> RectangleUtils.convert(s.getBounds2D());
if (Boolean.parseBoolean(useIcons)) {
// set up the shape and color functions
IconShapeFunction<AttributedVertex> 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<AttributedVertex> selectedVertices = getSelectedVertices();
notifySelectionChanged(new HashSet<AttributedVertex>(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<AttributedVertex> 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<AttributedVertex> 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<AttributedVertex, AttributedEdge> {
@ -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<AttributedVertex> selectedVertices = getSelectedVertices();
notifySelectionChanged(new HashSet<AttributedVertex>(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<AttributedVertex> selectedVertices = getSelectedVertices();
notifySelectionChanged(selectedVertices);
}
viewer.repaint();
}
void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
}

View file

@ -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();
}
}

View file

@ -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<String, String> 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;
}

View file

@ -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<AttributedEdge> {
private final Set<AttributedEdge> 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<String, Integer> 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<String> 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);
}
}

View file

@ -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<AttributedVertex, AttributedEdge> {
public GhidraGraphCollapser(VisualizationServer<AttributedVertex, AttributedEdge> vv) {
super(vv, null);
}
@Override
public AttributedVertex collapse(Collection<AttributedVertex> 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<AttributedVertex,
MutableSelectedState<AttributedEdge> selectedEState = vv.getSelectedEdgeState();
Collection<AttributedVertex> 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);

View file

@ -42,6 +42,7 @@ public class GhidraIconCache {
private final Map<AttributedVertex, Icon> 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;
}
}

View file

@ -98,8 +98,8 @@ class LayoutFunction
.<AttributedVertex> builder()
.verticalVertexSpacing(300);
case TREE:
return TreeLayoutAlgorithm
.builder();
return EdgeAwareTreeLayoutAlgorithm
.<AttributedVertex, AttributedEdge>edgeAwareBuilder();
case TIDIER_TREE:
default:
return TidierTreeLayoutAlgorithm

View file

@ -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<AttributedVertex> rootPredicate;
public static final List<String> 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<AttributedEdge> edgeComparator = (e1, e2) -> 0;
Comparator<AttributedEdge> 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<AttributedVertex>) 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<AttributedEdge>) 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<AttributedVertex>) initialLayoutAlgorithm)
.setVertexBoundsFunction(vertexBoundsFunction);
initialLayoutAlgorithm.setAfter(new PostProcessRunnable<>(
visualizationServer.getVisualizationModel().getLayoutModel()));
}
if (initialLayoutAlgorithm instanceof EdgeSorting) {
((EdgeSorting<AttributedEdge>) initialLayoutAlgorithm)

View file

@ -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 <V> vertex type
* @param <E> edge type
*/
public class PostProcessRunnable<V, E> implements Runnable {
LayoutModel<V> layoutModel;
public PostProcessRunnable(LayoutModel<V> layoutModel) {
this.layoutModel = layoutModel;
}
@Override
public void run() {
moveVerticesThatOverlapVerticalEdges(layoutModel);
}
protected int moveVerticesThatOverlapVerticalEdges(LayoutModel<V> layoutModel) {
int offset = 100;
int moved = 0;
Graph<V, E> graph = layoutModel.getGraph();
Map<Double, Set<E>> 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;
}
}

View file

@ -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<String, String> 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 "<html>" + String.join("<p>", Splitter.on('\n').split(name));
}

View file

@ -45,14 +45,6 @@ public abstract class AbstractJgtGraphMousePlugin<V, E>
protected V selectedVertex;
protected E selectedEdge;
public AbstractJgtGraphMousePlugin() {
this(InputEvent.BUTTON1_DOWN_MASK);
}
public AbstractJgtGraphMousePlugin(int selectionModifiers) {
super(selectionModifiers);
}
public VisualizationViewer<V, E> getViewer(MouseEvent e) {
VisualizationViewer<V, E> viewer = getGraphViewer(e);
return viewer;

View file

@ -33,10 +33,6 @@ import org.jungrapht.visualization.control.AbstractGraphMousePlugin;
public class JgtCursorRestoringPlugin<V, E> extends AbstractGraphMousePlugin
implements MouseMotionListener {
public JgtCursorRestoringPlugin() {
super(0);
}
@Override
public void mouseDragged(MouseEvent e) {
// don't care

View file

@ -34,10 +34,19 @@ import ghidra.graph.visualization.CenterAnimationJob;
*/
public class JgtEdgeNavigationPlugin<V, E> extends AbstractJgtGraphMousePlugin<V, E> {
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)) {

View file

@ -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<AttributedVertex, AttributedEdge> {
public class JgtGraphMouse extends DefaultGraphMouse<AttributedVertex, AttributedEdge> {
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.<AttributedVertex, AttributedEdge> builder());
public JgtGraphMouse(DefaultGraphDisplay graphDisplay) {
super(DefaultGraphMouse.builder());
this.graphDisplay = graphDisplay;
}
@ -47,24 +46,29 @@ public class JgtPluggableGraphMouse extends DefaultGraphMouse<AttributedVertex,
//
// edge
add(new JgtEdgeNavigationPlugin<AttributedVertex, AttributedEdge>());
add(new JgtEdgeNavigationPlugin<>(InputEvent.BUTTON1_DOWN_MASK));
add(new JgtVertexFocusingPlugin<AttributedVertex, AttributedEdge>(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<AttributedVertex, AttributedEdge>());
add(new TranslatingGraphMousePlugin(InputEvent.BUTTON1_DOWN_MASK));
add(new SelectingGraphMousePlugin<AttributedVertex, AttributedEdge>(
InputEvent.BUTTON1_DOWN_MASK,
0,
DockingUtils.CONTROL_KEY_MODIFIER_MASK));
// scaling
add(new ScalingGraphMousePlugin());
// cursor cleanup
add(new JgtCursorRestoringPlugin<AttributedVertex, AttributedEdge>());
add(new JgtCursorRestoringPlugin<>());
setPluginsLoaded();
}
}

View file

@ -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<AttributedVertex, AttributedEdge> {
@Override
public void loadPlugins() {
scalingPlugin =
new SatelliteScalingGraphMousePlugin(
new CrossoverScalingControl(),
scalingMask,
xAxisScalingMask,
yAxisScalingMask,
in,
out);
//
// JUNGRAPHT CHANGE 3
//
SelectingGraphMousePlugin<AttributedVertex, AttributedEdge> 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);
}
}

View file

@ -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<AttributedVertex, AttributedEdge> {
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<AttributedVertex, AttributedEdge> vv =
(VisualizationServer<AttributedVertex, AttributedEdge>) e.getSource();
GraphElementAccessor<AttributedVertex, AttributedEdge> pickSupport =
vv.getPickSupport();
MutableSelectedState<AttributedVertex> selectedVertexState =
vv.getSelectedVertexState();
LayoutModel<AttributedVertex> layoutModel = vv.getVisualizationModel().getLayoutModel();
if (pickSupport instanceof ShapePickSupport) {
ShapePickSupport<AttributedVertex, AttributedEdge> shapePickSupport =
(ShapePickSupport<AttributedVertex, AttributedEdge>) 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;
}
}

View file

@ -40,16 +40,21 @@ public class JgtTranslatingPlugin<V, E>
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);

View file

@ -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
*/

View file

@ -29,11 +29,18 @@ import ghidra.service.graph.AttributedVertex;
public class JgtVertexFocusingPlugin<V, E> extends AbstractJgtGraphMousePlugin<V, E> {
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)) {

View file

@ -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

View file

@ -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.
}

Some files were not shown because too many files have changed in this diff Show more