mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-08-28 05:20:21 +00:00
Merge remote-tracking branch 'origin/master' into debugger
This commit is contained in:
commit
f5ec74f2c3
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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 $@.$$$$
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
{
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
{
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"; }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
345
Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.cc
Normal file
345
Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.cc
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
88
Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.hh
Normal file
88
Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.hh
Normal 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
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
||||
{
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 < max; iVar1 = iVar1 \+ 1</stringmatch>match>
|
||||
</decompilertest>
|
|
@ -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>
|
|
@ -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 < 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>
|
|
@ -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 < max; uVar1 = uVar1 \+ 1</stringmatch>
|
||||
<stringmatch name="For-loop var used #2" min="1" max="1">if \(\(uVar1 & 3\) == 0\)</stringmatch>
|
||||
<stringmatch name="For-loop var used #3" min="1" max="1">val = %d.*uVar1</stringmatch>
|
||||
</decompilertest>
|
|
@ -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 < globvar; iVar1 = iVar1 \+ 1</stringmatch>
|
||||
<stringmatch name="For-loop with skip #2" min="1" max="1">if \(10 < val\)</stringmatch>
|
||||
<stringmatch name="For-loop with skip #3" min="1" max="1">iVar1 = iVar1 \+ 1;</stringmatch>
|
||||
</decompilertest>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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->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>
|
|
@ -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\] < 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>
|
|
@ -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 < 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>
|
|
@ -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 < 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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue