diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/app/plugin/core/datamgr/actions/DataTypeWriterTask.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/app/plugin/core/datamgr/actions/DataTypeWriterTask.java new file mode 100644 index 0000000000..56aeb1d088 --- /dev/null +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/app/plugin/core/datamgr/actions/DataTypeWriterTask.java @@ -0,0 +1,71 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.datamgr.actions; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; + +import com.google.gson.JsonObject; + +import docking.widgets.tree.GTree; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.ISF.IsfDataTypeWriter; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.Task; +import ghidra.util.task.TaskMonitor; + +public class DataTypeWriterTask extends Task { + + private final DataTypeManager programDataTypeMgr; + private final List dataTypeList; + private final File file; + private final GTree gTree; + + public DataTypeWriterTask(GTree gTree, DataTypeManager programDataTypeMgr, List dataTypeList, File file) { + super("Export Data Types", true, false, true); + this.gTree = gTree; + this.programDataTypeMgr = programDataTypeMgr; + this.dataTypeList = dataTypeList; + this.file = file; + } + + @Override + public void run(TaskMonitor monitor) { + try { + //monitor.setMessage("Export to " + file.getName() + "..."); + FileWriter baseWriter = file == null ? null : new FileWriter(file); + IsfDataTypeWriter dataTypeWriter = new IsfDataTypeWriter(programDataTypeMgr, dataTypeList, baseWriter); + + try { + JsonObject object = dataTypeWriter.getRootObject(monitor); + if (file != null) { + dataTypeWriter.write(object); + } + } finally { + dataTypeWriter.close(); + } + } catch (CancelledException e) { + // user cancelled; ignore + } catch (IOException e) { + Msg.showError(getClass(), gTree, "Export Data Types Failed", "Error exporting Data Types: " + e); + return; + } + } +} diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/app/plugin/core/datamgr/actions/ExportToIsfAction.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/app/plugin/core/datamgr/actions/ExportToIsfAction.java index 893caf3bea..61be0bd957 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/app/plugin/core/datamgr/actions/ExportToIsfAction.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/app/plugin/core/datamgr/actions/ExportToIsfAction.java @@ -188,14 +188,14 @@ public class ExportToIsfAction extends DockingAction { fileChooser.dispose(); } - private class DataTypeWriterTask extends Task { + public class DataTypeWriterTask extends Task { private final DataTypeManager programDataTypeMgr; private final List dataTypeList; private final File file; private final GTree gTree; - DataTypeWriterTask(GTree gTree, DataTypeManager programDataTypeMgr, + public DataTypeWriterTask(GTree gTree, DataTypeManager programDataTypeMgr, List dataTypeList, File file) { super("Export Data Types", true, false, true); this.gTree = gTree; @@ -209,12 +209,9 @@ public class ExportToIsfAction extends DockingAction { try { monitor.setMessage("Export to " + file.getName() + "..."); IsfDataTypeWriter dataTypeWriter = - new IsfDataTypeWriter(programDataTypeMgr, new FileWriter(file)); + new IsfDataTypeWriter(programDataTypeMgr, dataTypeList, new FileWriter(file)); try { - for (DataType dataType : dataTypeList) { - dataTypeWriter.requestType(dataType); - } JsonObject object = dataTypeWriter.getRootObject(monitor); dataTypeWriter.write(object); } diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/dbg/isf/IsfClientHandler.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/dbg/isf/IsfClientHandler.java index 05f1b64011..c2a747a7ed 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/dbg/isf/IsfClientHandler.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/dbg/isf/IsfClientHandler.java @@ -130,7 +130,7 @@ public class IsfClientHandler { private String lookType(String ns, String key) throws IOException { IsfDataTypeWriter isfWriter = createDataTypeWriter(server.getDataTypeManager(ns)); isfWriter.setSkipSymbols(true); - isfWriter.requestType(key); + //isfWriter.requestType(key); return writeFrom(isfWriter); } @@ -198,7 +198,7 @@ public class IsfClientHandler { private IsfDataTypeWriter createDataTypeWriter(DataTypeManager dtm) throws IOException { StringWriter out = new StringWriter(); - return new IsfDataTypeWriter(dtm, out); + return new IsfDataTypeWriter(dtm, null, out); } private String writeFrom(IsfDataTypeWriter dataTypeWriter) throws IOException { diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/AbstractIsfObject.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/AbstractIsfObject.java new file mode 100644 index 0000000000..e793e7d3eb --- /dev/null +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/AbstractIsfObject.java @@ -0,0 +1,60 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.program.model.data.ISF; + +import java.util.ArrayList; +import java.util.List; + +import ghidra.docking.settings.Settings; +import ghidra.docking.settings.SettingsDefinition; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.ISF.AbstractIsfWriter.Exclude; + +public abstract class AbstractIsfObject implements IsfObject { + + @Exclude + public String name; + @Exclude + public String location; + @Exclude + public List settings; + + public AbstractIsfObject(DataType dt) { + if (dt != null) { + name = dt.getName(); + location = dt.getCategoryPath().getPath(); + Settings defaultSettings = dt.getDefaultSettings(); + processSettings(dt, defaultSettings); + } + } + + protected void processSettings(DataType dt, Settings defaultSettings) { + SettingsDefinition[] settingsDefinitions = dt.getSettingsDefinitions(); + for (SettingsDefinition def : settingsDefinitions) { + if (def.hasValue(defaultSettings)) { + settings = new ArrayList<>(); + String[] names = defaultSettings.getNames(); + for (String n : names) { + Object value = defaultSettings.getValue(n); + if (value != null) { + IsfSetting setting = new IsfSetting(n, value); + settings.add(setting); + } + } + } + } + } +} diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/AbstractIsfWriter.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/AbstractIsfWriter.java new file mode 100644 index 0000000000..9afd82d11e --- /dev/null +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/AbstractIsfWriter.java @@ -0,0 +1,108 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.program.model.data.ISF; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Writer; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.stream.JsonWriter; + +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public abstract class AbstractIsfWriter implements Closeable { + + protected JsonWriter writer; + protected Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + protected JsonObject root = new JsonObject(); + protected JsonArray objects = new JsonArray(); + + public AbstractIsfWriter(Writer baseWriter) throws IOException { + if (writer != null) { + this.writer = new JsonWriter(baseWriter); + writer.setIndent(" "); + } + this.gson = new GsonBuilder().addSerializationExclusionStrategy(strategy).setPrettyPrinting().create(); + } + + protected abstract void genRoot(TaskMonitor monitor) throws CancelledException, IOException; + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface Exclude { + // EMPTY + } + + // Am setting this as the default, but it's possible we may want more latitude + // in the future + protected boolean STRICT = true; + + // @Exclude used for properties that might be desirable for a non-STRICT + // implementation. + ExclusionStrategy strategy = new ExclusionStrategy() { + @Override + public boolean shouldSkipClass(Class clazz) { + return false; + } + + @Override + public boolean shouldSkipField(FieldAttributes field) { + return STRICT && field.getAnnotation(Exclude.class) != null; + } + }; + + public JsonObject getRootObject(TaskMonitor monitor) throws CancelledException, IOException { + genRoot(monitor); + return root; + } + + public JsonArray getResults() { + return objects; + } + + public JsonElement getTree(Object obj) { + return gson.toJsonTree(obj); + } + + public Object getObject(JsonElement element, Class clazz) { + return gson.fromJson(element, clazz); + } + + public void write(JsonObject object) { + gson.toJson(object, writer); + } + + public void close() throws IOException { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + +} diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfBuiltIn.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfBuiltIn.java index b4311bd6ee..394d19f544 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfBuiltIn.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfBuiltIn.java @@ -17,16 +17,15 @@ package ghidra.program.model.data.ISF; import ghidra.program.model.data.BuiltInDataType; -public class IsfBuiltIn implements IsfObject { +public class IsfBuiltIn extends AbstractIsfObject { public Integer size; - public Boolean signed; public String kind; public String endian; public IsfBuiltIn(BuiltInDataType builtin) { + super(builtin); size = IsfUtilities.getLength(builtin); - signed = IsfUtilities.getSigned(builtin); kind = IsfUtilities.getBuiltInKind(builtin); endian = IsfUtilities.getEndianness(builtin); } diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfComponent.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfComponent.java index 1c5f07b49c..87bd7cd8a0 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfComponent.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfComponent.java @@ -16,9 +16,9 @@ package ghidra.program.model.data.ISF; import ghidra.program.model.data.DataTypeComponent; -import ghidra.program.model.data.ISF.IsfDataTypeWriter.Exclude; +import ghidra.program.model.data.ISF.AbstractIsfWriter.Exclude; -public class IsfComponent implements IsfObject { +public class IsfComponent extends AbstractIsfObject { public Integer offset; public IsfObject type; @@ -30,16 +30,25 @@ public class IsfComponent implements IsfObject { @Exclude public String field_name; @Exclude + public Boolean noFieldName; + @Exclude public String comment; + public IsfComponent(DataTypeComponent component, IsfObject typeObj) { + super(component.getDataType()); offset = component.getOffset(); type = typeObj; field_name = component.getFieldName(); + if (field_name == null || field_name.equals("")) { + noFieldName = true; + } ordinal = component.getOrdinal(); length = component.getLength(); comment = component.getComment(); + + processSettings(component.getDataType(), component.getDefaultSettings()); } } diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfComposite.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfComposite.java index 26d91e92d7..fe8cfd17a6 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfComposite.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfComposite.java @@ -15,52 +15,50 @@ */ package ghidra.program.model.data.ISF; -import java.util.*; - import com.google.gson.JsonObject; -import ghidra.program.model.data.*; -import ghidra.program.model.data.ISF.IsfDataTypeWriter.Exclude; +import ghidra.program.model.data.Composite; +import ghidra.program.model.data.DataTypeComponent; +import ghidra.program.model.data.Structure; import ghidra.util.task.TaskMonitor; -public class IsfComposite implements IsfObject { +public class IsfComposite extends AbstractIsfObject { public String kind; public Integer size; public JsonObject fields; - - @Exclude - public int alignment; - + public IsfComposite(Composite composite, IsfDataTypeWriter writer, TaskMonitor monitor) { + super(composite); size = composite.getLength(); kind = composite instanceof Structure ? "struct" : "union"; - alignment = composite.getAlignment(); DataTypeComponent[] components = composite.getComponents(); - Map comps = new HashMap<>(); - for (DataTypeComponent component : components) { - String key = component.getFieldName(); - if (key == null) { - key = component.getDefaultFieldName(); - } - comps.put(key, component); + if (components.length == 0) { + // NB: composite.getLength always returns > 0 + size = 0; } - ArrayList keylist = new ArrayList<>(comps.keySet()); - Collections.sort(keylist); - fields = new JsonObject(); - for (String key : keylist) { + for (DataTypeComponent component : components) { if (monitor.isCancelled()) { break; } - DataTypeComponent component = comps.get(key); IsfObject type = writer.getObjectTypeDeclaration(component); - IsfComponent cobj = new IsfComponent(component, type); + IsfComponent cobj = getComponent(component, type); + String key = component.getFieldName(); + if (key == null) { + key = DataTypeComponent.DEFAULT_FIELD_NAME_PREFIX + component.getOrdinal(); + if (component.getParent() instanceof Structure) { + key += "_0x" + Integer.toHexString(component.getOffset()); + } + } fields.add(key, writer.getTree(cobj)); } + } + protected IsfComponent getComponent(DataTypeComponent component, IsfObject type) { + return new IsfComponent(component, type); } } diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeArray.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeArray.java index 5478d3b574..7551629b82 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeArray.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeArray.java @@ -17,13 +17,14 @@ package ghidra.program.model.data.ISF; import ghidra.program.model.data.Array; -public class IsfDataTypeArray implements IsfObject { +public class IsfDataTypeArray extends AbstractIsfObject { public String kind; public Integer count; public IsfObject subtype; public IsfDataTypeArray(Array arr, IsfObject typeObj) { + super(arr); kind = IsfUtilities.getKind(arr); count = arr.getNumElements(); subtype = typeObj; diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeBitField.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeBitField.java index 9ee584f02e..ff74ec0819 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeBitField.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeBitField.java @@ -16,9 +16,9 @@ package ghidra.program.model.data.ISF; import ghidra.program.model.data.BitFieldDataType; -import ghidra.program.model.data.ISF.IsfDataTypeWriter.Exclude; +import ghidra.program.model.data.ISF.AbstractIsfWriter.Exclude; -public class IsfDataTypeBitField implements IsfObject { +public class IsfDataTypeBitField extends AbstractIsfObject { public String kind; public Integer bit_length; @@ -31,6 +31,7 @@ public class IsfDataTypeBitField implements IsfObject { private int storage_size; public IsfDataTypeBitField(BitFieldDataType bf, int componentOffset, IsfObject typeObj) { + super(bf); kind = IsfUtilities.getKind(bf); bit_length = bf.getBitSize(); bit_offset = bf.getBitOffset(); diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeDefault.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeDefault.java index 2fb90b2912..67bd6ae636 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeDefault.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeDefault.java @@ -17,14 +17,15 @@ package ghidra.program.model.data.ISF; import ghidra.program.model.data.DataType; -public class IsfDataTypeDefault implements IsfObject { +public class IsfDataTypeDefault extends AbstractIsfObject { public String kind; - public String name; + int size; public IsfDataTypeDefault(DataType dt) { + super(dt); kind = IsfUtilities.getKind(dt); - name = dt.getName(); + size = dt.getLength(); } } diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeTypeDef.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeTypeDef.java index 82e5b32857..c37fff6355 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeTypeDef.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeTypeDef.java @@ -17,12 +17,13 @@ package ghidra.program.model.data.ISF; import ghidra.program.model.data.DataType; -public class IsfDataTypeTypeDef implements IsfObject { +public class IsfDataTypeTypeDef extends AbstractIsfObject { public String kind; public IsfObject subtype; public IsfDataTypeTypeDef(DataType dt, IsfObject typeObj) { + super(dt); kind = IsfUtilities.getKind(dt); subtype = typeObj; } diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeWriter.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeWriter.java index de19527332..e12ebf42a1 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeWriter.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDataTypeWriter.java @@ -17,20 +17,43 @@ package ghidra.program.model.data.ISF; import java.io.IOException; import java.io.Writer; -import java.lang.annotation.*; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; -import com.google.gson.*; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.stream.JsonWriter; import ghidra.program.database.data.ProgramDataTypeManager; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressFormatException; -import ghidra.program.model.data.*; +import ghidra.program.model.data.Array; +import ghidra.program.model.data.BitFieldDataType; +import ghidra.program.model.data.BuiltInDataType; +import ghidra.program.model.data.Composite; +import ghidra.program.model.data.DataOrganization; +import ghidra.program.model.data.DataOrganizationImpl; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeComponent; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.Dynamic; import ghidra.program.model.data.Enum; +import ghidra.program.model.data.FactoryDataType; +import ghidra.program.model.data.FunctionDefinition; +import ghidra.program.model.data.Pointer; +import ghidra.program.model.data.TypeDef; import ghidra.program.model.listing.Program; -import ghidra.program.model.symbol.*; +import ghidra.program.model.symbol.Reference; +import ghidra.program.model.symbol.ReferenceIterator; +import ghidra.program.model.symbol.ReferenceManager; +import ghidra.program.model.symbol.Symbol; +import ghidra.program.model.symbol.SymbolIterator; +import ghidra.program.model.symbol.SymbolTable; import ghidra.util.Msg; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -40,29 +63,28 @@ import ghidra.util.task.TaskMonitor; * * The ISF JSON should be valid for Volatility is STRICT==true. */ -public class IsfDataTypeWriter { +public class IsfDataTypeWriter extends AbstractIsfWriter { - private Map resolved = new HashMap<>(); + protected Map resolved = new HashMap<>(); private Map resolvedTypeMap = new HashMap<>(); - private List deferredKeys = new ArrayList<>(); + public List deferredKeys = new ArrayList<>(); private Writer baseWriter; - private JsonWriter writer; - private Gson gson = new GsonBuilder().setPrettyPrinting().create(); - private DataTypeManager dtm; + protected DataTypeManager dtm; private DataOrganization dataOrganization; - private JsonObject data = new JsonObject(); - private JsonObject metadata = new JsonObject(); - private JsonObject baseTypes = new JsonObject(); - private JsonObject userTypes = new JsonObject(); - private JsonObject enums = new JsonObject(); - private JsonObject symbols = new JsonObject(); + protected JsonObject data = new JsonObject(); + protected JsonElement metadata; + protected JsonElement baseTypes; + protected JsonElement userTypes; + protected JsonElement enums; + protected JsonElement functions; + protected JsonElement symbols; private List
requestedAddresses = new ArrayList<>(); private List requestedSymbols = new ArrayList<>(); - private List requestedTypes = new ArrayList<>(); + // private List requestedTypes = new ArrayList<>(); private List requestedDataTypes = new ArrayList<>(); private boolean skipSymbols = false; private boolean skipTypes = false; @@ -70,11 +92,13 @@ public class IsfDataTypeWriter { /** * Constructs a new instance of this class using the given writer * - * @param dtm data-type manager corresponding to target program or null for default + * @param dtm data-type manager corresponding to target program or null + * for default * @param baseWriter the writer to use when writing data types * @throws IOException if there is an exception writing the output */ - public IsfDataTypeWriter(DataTypeManager dtm, Writer baseWriter) throws IOException { + public IsfDataTypeWriter(DataTypeManager dtm, List target, Writer baseWriter) throws IOException { + super(baseWriter); this.dtm = dtm; if (dtm != null) { dataOrganization = dtm.getDataOrganization(); @@ -82,66 +106,43 @@ public class IsfDataTypeWriter { if (dataOrganization == null) { dataOrganization = DataOrganizationImpl.getDefaultOrganization(); } - this.baseWriter = baseWriter; - this.writer = new JsonWriter(baseWriter); - writer.setIndent(" "); - this.gson = new GsonBuilder() - .addSerializationExclusionStrategy(strategy) - .setPrettyPrinting() - .create(); + + metadata = new JsonObject(); + baseTypes = new JsonObject(); + userTypes = new JsonObject(); + enums = new JsonObject(); + functions = new JsonObject(); + symbols = new JsonObject(); + requestedDataTypes = target; + STRICT = true; } - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - public @interface Exclude { - //EMPTY - } - - // Am setting this as the default, but it's possible we may want more latitude in the future - private boolean STRICT = true; - - // @Exclude used for properties that might be desirable for a non-STRICT implementation. - ExclusionStrategy strategy = new ExclusionStrategy() { - @Override - public boolean shouldSkipClass(Class clazz) { - return false; - } - - @Override - public boolean shouldSkipField(FieldAttributes field) { - return STRICT && field.getAnnotation(Exclude.class) != null; - } - }; - - /** - * Exports all data types in the list as ISF JSON. - * - * @param monitor the task monitor - * @return the resultant JSON object - * @throws IOException if there is an exception writing the output - * @throws CancelledException if the action is cancelled by the user - */ - public JsonObject getRootObject(TaskMonitor monitor) - throws IOException, CancelledException { - + @Override + protected void genRoot(TaskMonitor monitor) throws CancelledException, IOException { genMetadata(); genTypes(monitor); - genSymbols(); - genRoot(); + genSymbols(monitor); - return data; - } - - private void genRoot() { data.add("metadata", metadata); data.add("base_types", baseTypes); data.add("user_types", userTypes); data.add("enums", enums); - // Would be nice to support this in the futere, but Volatility does not - //data.add("typedefs", typedefs); + // Would be nice to support this in the future, but Volatility does not + // data.add("functions", functions); data.add("symbols", symbols); } + public void add(JsonElement parent, String optKey, JsonElement child) { + if (parent instanceof JsonObject) { + JsonObject p = (JsonObject) parent; + p.add(optKey, child); + } + if (parent instanceof JsonArray) { + JsonArray p = (JsonArray) parent; + p.add(child); + } + } + private void genMetadata() { String oskey = "UNKNOWN"; if (dtm instanceof ProgramDataTypeManager) { @@ -154,20 +155,21 @@ public class IsfDataTypeWriter { oskey = metaData.get("Compiler ID"); if (metaData.containsKey("PDB Loaded")) { os = gson.toJsonTree(new IsfWinOS(metaData)); - } - else if (metaData.containsKey("Executable Format")) { + } else if (metaData.containsKey("Executable Format")) { if (metaData.get("Executable Format").contains("ELF")) { oskey = "linux"; os = gson.toJsonTree(new IsfLinuxOS(gson, metaData)); } } - metadata.addProperty("format", "6.2.0"); - metadata.add("producer", producer); - metadata.add(oskey, os); + if (metadata instanceof JsonObject) { + ((JsonObject) metadata).addProperty("format", "6.2.0"); + } + add(metadata, "producer", producer); + add(metadata, oskey, os); } } - private void genSymbols() { + private void genSymbols(TaskMonitor monitor) { if (!skipSymbols && dtm instanceof ProgramDataTypeManager) { ProgramDataTypeManager pgmDtm = (ProgramDataTypeManager) dtm; Program program = pgmDtm.getProgram(); @@ -194,18 +196,15 @@ public class IsfDataTypeWriter { Symbol symbol = iterator.next(); symbolToJson(imageBase, symbolTable, linkages, map, symbol); } - } - else { + } else { for (Address addr : requestedAddresses) { - Symbol[] symsFromAddr = - symbolTable.getSymbols(addr.add(imageBase.getOffset())); + Symbol[] symsFromAddr = symbolTable.getSymbols(addr.add(imageBase.getOffset())); for (Symbol symbol : symsFromAddr) { symbolToJson(imageBase, symbolTable, linkages, map, symbol); } } } - } - else { + } else { for (String key : requestedSymbols) { SymbolIterator iter = symbolTable.getSymbols(key); while (iter.hasNext()) { @@ -215,33 +214,29 @@ public class IsfDataTypeWriter { } } for (Entry entry : map.entrySet()) { - symbols.add(entry.getKey(), entry.getValue()); + add(symbols, entry.getKey(), entry.getValue()); } for (Entry entry : map.entrySet()) { if (entry.getKey().startsWith("_")) { String nu = entry.getKey().substring(1); - if (symbols.get(nu) == null) { - symbols.add(nu, entry.getValue()); - } + add(symbols, nu, entry.getValue()); } } } } - private void genTypes(TaskMonitor monitor) - throws CancelledException, IOException { + private void genTypes(TaskMonitor monitor) throws CancelledException, IOException { if (skipTypes) { return; } Map map = new HashMap<>(); if (requestedDataTypes.isEmpty()) { dtm.getAllDataTypes(requestedDataTypes); - baseTypes.add("pointer", getTree(new IsfTypedefPointer())); - baseTypes.add("undefined", getTree(new IsfTypedefPointer())); + addSingletons(); } monitor.initialize(requestedDataTypes.size()); for (DataType dataType : requestedDataTypes) { - String key = dataType.getName(); + String key = dataType.getPathName(); map.put(key, dataType); } @@ -260,10 +255,9 @@ public class IsfDataTypeWriter { private void processMap(Map map, List keylist, TaskMonitor monitor) throws CancelledException, IOException { JsonObject obj = new JsonObject(); - int cnt = 0; + monitor.setMaximum(keylist.size()); for (String key : keylist) { DataType dataType = map.get(key); - monitor.checkCancelled(); if (key.contains(".conflict")) { continue; } @@ -272,36 +266,29 @@ public class IsfDataTypeWriter { continue; } if (dataType instanceof FunctionDefinition) { - // Would be nice to support this in the futere, but Volatility does not - //typedefs.add(dataType.getName(), obj); - } - else if (IsfUtilities.isBaseDataType(dataType)) { - baseTypes.add(dataType.getName(), obj); - } - else if (dataType instanceof TypeDef) { + // Would be nice to support this in the future, but Volatility does not + add(functions, dataType.getPathName(), obj); + } else if (IsfUtilities.isBaseDataType(dataType)) { + add(baseTypes, dataType.getPathName(), obj); + } else if (dataType instanceof TypeDef) { DataType baseDataType = ((TypeDef) dataType).getBaseDataType(); if (IsfUtilities.isBaseDataType(baseDataType)) { - baseTypes.add(dataType.getName(), obj); - } - else if (baseDataType instanceof Enum) { - enums.add(dataType.getName(), obj); - } - else { - userTypes.add(dataType.getName(), obj); + add(baseTypes, dataType.getPathName(), obj); + } else if (baseDataType instanceof Enum) { + add(enums, dataType.getPathName(), obj); + } else { + add(userTypes, dataType.getPathName(), obj); } + } else if (dataType instanceof Enum) { + add(enums, dataType.getPathName(), obj); + } else if (dataType instanceof Composite) { + add(userTypes, dataType.getPathName(), obj); } - else if (dataType instanceof Enum) { - enums.add(dataType.getName(), obj); - } - else if (dataType instanceof Composite) { - userTypes.add(dataType.getName(), obj); - } - monitor.setProgress(++cnt); + monitor.increment(); } } - private void symbolToJson(Address imageBase, SymbolTable symbolTable, - Map linkages, + private void symbolToJson(Address imageBase, SymbolTable symbolTable, Map linkages, Map map, Symbol symbol) { String key = symbol.getName(); Address address = symbol.getAddress(); @@ -313,9 +300,12 @@ public class IsfDataTypeWriter { sym.addProperty("linkage_name", linkage.getName()); sym.addProperty("address", linkage.getAddress().getOffset()); } - } - else { - sym.addProperty("address", address.subtract(imageBase)); + } else { + if (address.getAddressSpace().equals(imageBase.getAddressSpace())) { + sym.addProperty("address", address.subtract(imageBase)); + } else { + sym.addProperty("address", address.getOffset()); + } } map.put(symbol.getName(), sym); if (!symbol.isPrimary()) { @@ -332,8 +322,12 @@ public class IsfDataTypeWriter { gson.toJson(obj, writer); } - JsonObject getObjectForDataType(DataType dt, TaskMonitor monitor) - throws IOException, CancelledException { + protected void addSingletons() { + add(baseTypes, "pointer", getTree(newTypedefPointer(null))); + add(baseTypes, "undefined", getTree(newTypedefPointer(null))); + } + + protected JsonObject getObjectForDataType(DataType dt, TaskMonitor monitor) throws IOException, CancelledException { IsfObject isf = getIsfObject(dt, monitor); if (isf != null) { JsonObject jobj = (JsonObject) getTree(isf); @@ -344,26 +338,27 @@ public class IsfDataTypeWriter { } /** - * Writes the data type as ISF JSON using the underlying writer. For now, ignoring top-level - * bit-fields and function defs as unsupported by ISF. Typedefs really deserve their own - * category, but again unsupported. + * Writes the data type as ISF JSON using the underlying writer. For now, + * ignoring top-level bit-fields and function defs as unsupported by ISF. + * Typedefs really deserve their own category, but again unsupported. * - * @param dt the data type to write as ISF JSON + * @param dt the data type to write as ISF JSON * @param monitor the task monitor * @throws IOException if there is an exception writing the output */ - private IsfObject getIsfObject(DataType dt, TaskMonitor monitor) - throws IOException, CancelledException { + protected IsfObject getIsfObject(DataType dt, TaskMonitor monitor) throws IOException, CancelledException { if (dt == null) { - Msg.error(this, "Shouldn't get here - null datatype passed"); - return null; + throw new IOException("Null datatype passed to getIsfObject"); } if (dt instanceof FactoryDataType) { Msg.error(this, "Factory data types may not be written - type: " + dt); } - if (dt instanceof Pointer || dt instanceof Array || dt instanceof BitFieldDataType) { + if (dt instanceof BitFieldDataType) { + Msg.error(this, "BitField data types may not be written - type: " + dt); + } + if (dt instanceof Pointer || dt instanceof Array) { IsfObject type = getObjectDataType(IsfUtilities.getBaseDataType(dt)); - IsfObject obj = new IsfTypedObject(dt, type); + IsfObject obj = newTypedObject(dt, type); return obj; } @@ -377,41 +372,33 @@ public class IsfDataTypeWriter { if (dt instanceof Dynamic dynamic) { DataType rep = dynamic.getReplacementBaseType(); return rep == null ? null : getIsfObject(rep, monitor); - } - else if (dt instanceof TypeDef typedef) { + } else if (dt instanceof TypeDef typedef) { return getObjectTypeDef(typedef, monitor); - } - else if (dt instanceof Composite composite) { + } else if (dt instanceof Composite composite) { return new IsfComposite(composite, this, monitor); - } - else if (dt instanceof Enum enumm) { + } else if (dt instanceof Enum enumm) { return new IsfEnum(enumm); - } - else if (dt instanceof BuiltInDataType builtin) { + } else if (dt instanceof BuiltInDataType builtin) { return new IsfBuiltIn(builtin); - } - else if (dt instanceof BitFieldDataType) { + } else if (dt instanceof BitFieldDataType) { // skip - not hit - } - else if (dt instanceof FunctionDefinition) { ///FAIL + } else if (dt instanceof FunctionDefinition) { /// FAIL // skip - not hit - } - else if (dt.equals(DataType.DEFAULT)) { + } else if (dt.equals(DataType.DEFAULT)) { // skip - not hit - } - else { + } else { Msg.warn(this, "Unable to write datatype. Type unrecognized: " + dt.getClass()); } return null; } - private IsfObject resolve(DataType dt) { + public IsfObject resolve(DataType dt) { if (resolved.containsKey(dt)) { return resolved.get(dt); } - DataType resolvedType = resolvedTypeMap.get(dt.getName()); + DataType resolvedType = resolvedTypeMap.get(dt.getPathName()); if (resolvedType != null) { if (resolvedType.isEquivalent(dt)) { return resolved.get(dt); // skip equivalent type with same name as a resolved type @@ -425,31 +412,32 @@ public class IsfDataTypeWriter { } } } - Msg.warn(this, "WARNING! conflicting data type names: " + dt.getPathName() + - " - " + resolvedType.getPathName()); + Msg.warn(this, + "WARNING! conflicting data type names: " + dt.getPathName() + " - " + resolvedType.getPathName()); return resolved.get(dt); } - resolvedTypeMap.put(dt.getName(), dt); + resolvedTypeMap.put(dt.getPathName(), dt); return null; } private void clearResolve(String typedefName, DataType baseType) { if (baseType instanceof Composite || baseType instanceof Enum) { // auto-typedef generated with composite and enum - if (typedefName.equals(baseType.getName())) { + if (typedefName.equals(baseType.getPathName())) { resolvedTypeMap.remove(typedefName); return; } } - // Inherited from DataTypeWriter (logic lost to time): - // A comment explaining the special 'P' case would be helpful!! Smells like fish. + // Inherited from DataTypeWriter (logic lost to time): + // A comment explaining the special 'P' case would be helpful!! Smells like + // fish. else if (baseType instanceof Pointer && typedefName.startsWith("P")) { DataType dt = ((Pointer) baseType).getDataType(); if (dt instanceof TypeDef) { dt = ((TypeDef) dt).getBaseDataType(); } - if (dt instanceof Composite && dt.getName().equals(typedefName.substring(1))) { + if (dt instanceof Composite && dt.getPathName().equals(typedefName.substring(1))) { // auto-pointer-typedef generated with composite resolvedTypeMap.remove(typedefName); return; @@ -469,13 +457,11 @@ public class IsfDataTypeWriter { int elementLen = replacementBaseType.getLength(); if (elementLen > 0) { int elementCnt = (component.getLength() + elementLen - 1) / elementLen; - return new IsfDynamicComponent(dynamic, type, elementCnt); + return newIsfDynamicComponent(dynamic, type, elementCnt); } - Msg.error(this, - dynamic.getClass().getSimpleName() + - " returned bad replacementBaseType: " + - replacementBaseType.getClass().getSimpleName()); + Msg.error(this, dynamic.getClass().getSimpleName() + " returned bad replacementBaseType: " + + replacementBaseType.getClass().getSimpleName()); } } return null; @@ -492,7 +478,7 @@ public class IsfDataTypeWriter { return getObjectDataType(dataType, -1); } - private IsfObject getObjectDataType(DataType dataType, int componentOffset) { + public IsfObject getObjectDataType(DataType dataType, int componentOffset) { if (dataType == null) { return new IsfDataTypeNull(); } @@ -509,9 +495,9 @@ public class IsfDataTypeWriter { IsfObject baseObject = getObjectDataType(IsfUtilities.getBaseDataType(dataType)); return new IsfDataTypeTypeDef(dataType, baseObject); } - if (dataType.getName().contains(".conflict")) { - if (!deferredKeys.contains(dataType.getName())) { - deferredKeys.add(dataType.getName()); + if (dataType.getPathName().contains(".conflict")) { + if (!deferredKeys.contains(dataType.getPathName())) { + deferredKeys.add(dataType.getPathName()); } } return new IsfDataTypeDefault(dataType); @@ -522,27 +508,21 @@ public class IsfDataTypeWriter { * * @throws CancelledException if the action is cancelled by the user */ - private IsfObject getObjectTypeDef(TypeDef typeDef, TaskMonitor monitor) - throws CancelledException { - //UNVERIFIED + protected IsfObject getObjectTypeDef(TypeDef typeDef, TaskMonitor monitor) throws CancelledException { DataType dataType = typeDef.getDataType(); - String typedefName = typeDef.getDisplayName(); - String dataTypeName = dataType.getDisplayName(); - if (IsfUtilities.isIntegral(typedefName, dataTypeName)) { - return new IsfTypedefIntegral(typeDef); - } + String typedefName = typeDef.getPathName(); - DataType baseType = typeDef.getBaseDataType(); + DataType baseType = typeDef.getDataType(); try { if (baseType instanceof BuiltInDataType builtin) { - return new IsfTypedefBase(typeDef); + return newTypedefBase(typeDef); } if (!(baseType instanceof Pointer)) { - return getIsfObject(dataType, monitor); + IsfObject isfObject = getIsfObject(dataType, monitor); + return newTypedefUser(typeDef, isfObject); } - return new IsfTypedefPointer(); - } - catch (Exception e) { + return newTypedefPointer(typeDef); + } catch (Exception e) { Msg.error(this, "TypeDef error: " + e); } clearResolve(typedefName, baseType); @@ -550,11 +530,7 @@ public class IsfDataTypeWriter { return null; } - public JsonElement getTree(Object obj) { - return gson.toJsonTree(obj); - } - - public void requestAddress(String key) { + public void requestAddress(String key) throws IOException { if (dtm instanceof ProgramDataTypeManager pgmDtm) { try { Address address = pgmDtm.getProgram().getMinAddress().getAddress(key); @@ -563,9 +539,8 @@ public class IsfDataTypeWriter { return; } requestedAddresses.add(address); - } - catch (AddressFormatException e) { - e.printStackTrace(); + } catch (AddressFormatException e) { + throw new IOException("Bad address format: " + key); } } } @@ -578,24 +553,6 @@ public class IsfDataTypeWriter { requestedSymbols.add(symbol); } - public void requestType(String path) { - requestedTypes.add(path); - DataType dataType = dtm.getDataType(path); - if (dataType == null) { - Msg.error(this, path + " not found"); - return; - } - requestedDataTypes.add(dataType); - } - - public void requestType(DataType dataType) { - if (dataType == null) { - Msg.error(this, dataType + " not found"); - return; - } - requestedDataTypes.add(dataType); - } - public JsonWriter getWriter() { return writer; } @@ -605,16 +562,6 @@ public class IsfDataTypeWriter { return baseWriter.toString(); } - public void close() { - try { - writer.flush(); - writer.close(); - } - catch (IOException e) { - e.printStackTrace(); - } - } - public void setSkipSymbols(boolean val) { skipSymbols = val; } @@ -623,7 +570,28 @@ public class IsfDataTypeWriter { skipTypes = val; } - public void setStrict(boolean val) { - STRICT = val; + public IsfTypedefBase newTypedefBase(TypeDef typeDef) { + return new IsfTypedefBase(typeDef); } + +// public IsfTypedefIntegral newTypedefIntegral(TypeDef typeDef) { +// return new IsfTypedefIntegral(typeDef); +// } + + public IsfTypedefPointer newTypedefPointer(TypeDef typeDef) { + return new IsfTypedefPointer(typeDef); + } + + public IsfObject newTypedefUser(TypeDef typeDef, IsfObject object) { + return object; + } + + public IsfTypedObject newTypedObject(DataType dt, IsfObject type) { + return new IsfTypedObject(dt, type); + } + + public IsfObject newIsfDynamicComponent(Dynamic dynamic, IsfObject type, int elementCnt) { + return new IsfDynamicComponent(dynamic, type, elementCnt); + } + } diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDynamicComponent.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDynamicComponent.java index d25a0afe0e..fcad5d72ce 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDynamicComponent.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfDynamicComponent.java @@ -17,13 +17,14 @@ package ghidra.program.model.data.ISF; import ghidra.program.model.data.Dynamic; -public class IsfDynamicComponent implements IsfObject { +public class IsfDynamicComponent extends AbstractIsfObject { public String kind; public Integer count; public IsfObject subtype; public IsfDynamicComponent(Dynamic dynamicType, IsfObject type, int elementCnt) { + super(dynamicType); kind = "array"; subtype = type; count = elementCnt; diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfEnum.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfEnum.java index 801a7620bb..0c450a6899 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfEnum.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfEnum.java @@ -19,13 +19,14 @@ import com.google.gson.JsonObject; import ghidra.program.model.data.Enum; -public class IsfEnum implements IsfObject { +public class IsfEnum extends AbstractIsfObject { public Integer size; public String base; public JsonObject constants = new JsonObject(); public IsfEnum(Enum enumm) { + super(enumm); size = enumm.getLength(); base = "int"; String[] names = enumm.getNames(); diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfFunction.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfFunction.java index 4af99c709b..bcd0f2aded 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfFunction.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfFunction.java @@ -15,11 +15,14 @@ */ package ghidra.program.model.data.ISF; -public class IsfFunction implements IsfObject { +import ghidra.program.model.data.FunctionDefinition; + +public class IsfFunction extends AbstractIsfObject { public String kind; - public IsfFunction() { + public IsfFunction(FunctionDefinition def) { + super(def); kind = "function"; } diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfFunctionPointer.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfFunctionPointer.java index 74183df1a2..8660301a0e 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfFunctionPointer.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfFunctionPointer.java @@ -18,14 +18,15 @@ package ghidra.program.model.data.ISF; import ghidra.program.model.data.DataType; import ghidra.program.model.data.FunctionDefinition; -public class IsfFunctionPointer implements IsfObject { +public class IsfFunctionPointer extends AbstractIsfObject { public String kind; public IsfObject subtype; public IsfFunctionPointer(FunctionDefinition def, DataType dt) { + super(def); kind = "pointer"; - subtype = new IsfFunction(); + subtype = new IsfFunction(def); //TODO? } diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfObject.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfObject.java index fbecf2737c..e6ec8b5638 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfObject.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfObject.java @@ -17,6 +17,6 @@ package ghidra.program.model.data.ISF; public interface IsfObject { - // EMPTY by design - + // EMPTY + } diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfPointer.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfPointer.java new file mode 100644 index 0000000000..02119eb5d9 --- /dev/null +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfPointer.java @@ -0,0 +1,32 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.program.model.data.ISF; + +import ghidra.program.model.data.Pointer; + +public class IsfPointer implements IsfObject { + + public Integer size; + public String kind; + public String endian; + + public IsfPointer(Pointer ptr) { + size = ptr.hasLanguageDependantLength() ? -1 : IsfUtilities.getLength(ptr); + kind = "pointer"; + endian = IsfUtilities.getEndianness(ptr); + } + +} diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfSetting.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfSetting.java new file mode 100644 index 0000000000..ac41d8a61b --- /dev/null +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfSetting.java @@ -0,0 +1,30 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.program.model.data.ISF; + +public class IsfSetting implements IsfObject { + + public String name; + public String kind; + public String value; + + public IsfSetting(String name, Object value) { + this.name = name; + this.value = value.toString(); + this.kind = value instanceof String ? "string" : "long"; + } + +} diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedObject.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedObject.java index 9f7ee6c1d4..bd8aa66044 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedObject.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedObject.java @@ -17,15 +17,16 @@ package ghidra.program.model.data.ISF; import ghidra.program.model.data.DataType; -public class IsfTypedObject implements IsfObject { +public class IsfTypedObject extends AbstractIsfObject { public String kind; public Integer size; public IsfObject type; public IsfTypedObject(DataType dt, IsfObject typeObj) { + super(dt); kind = IsfUtilities.getKind(dt); - size = dt.getLength(); + size = dt.hasLanguageDependantLength() ? -1 : dt.getLength(); type = typeObj; } diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedefBase.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedefBase.java index 03fae5baac..6309010245 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedefBase.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedefBase.java @@ -18,18 +18,17 @@ package ghidra.program.model.data.ISF; import ghidra.program.model.data.BuiltInDataType; import ghidra.program.model.data.TypeDef; -public class IsfTypedefBase implements IsfObject { +public class IsfTypedefBase extends AbstractIsfObject { public Integer size; public String kind; - public Boolean signed; public String endian; public IsfTypedefBase(TypeDef typeDef) { + super(typeDef); BuiltInDataType builtin = (BuiltInDataType) typeDef.getBaseDataType(); size = typeDef.getLength(); kind = IsfUtilities.getBuiltInKind(builtin); - signed = IsfUtilities.getSigned(typeDef); endian = IsfUtilities.getEndianness(typeDef); } diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedefIntegral.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedefIntegral.java index 8ebdb46d77..a63107ca1f 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedefIntegral.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedefIntegral.java @@ -17,11 +17,12 @@ package ghidra.program.model.data.ISF; import ghidra.program.model.data.TypeDef; -public class IsfTypedefIntegral implements IsfObject { +public class IsfTypedefIntegral extends AbstractIsfObject { public Integer size; public IsfTypedefIntegral(TypeDef td) { + super(td); size = td.getLength(); } diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedefPointer.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedefPointer.java index 573292042a..6e029fd6ea 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedefPointer.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedefPointer.java @@ -15,21 +15,30 @@ */ package ghidra.program.model.data.ISF; +import ghidra.program.model.data.Pointer; import ghidra.program.model.data.PointerDataType; +import ghidra.program.model.data.TypeDef; -public class IsfTypedefPointer implements IsfObject { +public class IsfTypedefPointer extends AbstractIsfObject { public Integer size; - public Boolean signed; public String kind; public String endian; + public IsfObject type; - public IsfTypedefPointer() { - PointerDataType ptr = new PointerDataType(); - size = ptr.getLength(); - signed = false; //IsfUtilities.getSigned(ptr); - kind = IsfUtilities.getBuiltInKind(ptr); + public IsfTypedefPointer(TypeDef typeDef) { + super(typeDef); + Pointer ptr; + if (typeDef != null) { + ptr = (Pointer) typeDef.getBaseDataType(); + } + else { + ptr = new PointerDataType(); + } + size = ptr.hasLanguageDependantLength() ? -1 : ptr.getLength(); + kind = "typedef"; endian = IsfUtilities.getEndianness(ptr); + type = new IsfPointer(ptr); } } diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedefUser.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedefUser.java index 84c664d5a5..4c0d73f634 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedefUser.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfTypedefUser.java @@ -15,19 +15,19 @@ */ package ghidra.program.model.data.ISF; -import ghidra.program.model.data.DataType; import ghidra.program.model.data.TypeDef; -public class IsfTypedefUser implements IsfObject { +public class IsfTypedefUser extends AbstractIsfObject { public Integer size; public String kind; public IsfObject type; public IsfTypedefUser(TypeDef typeDef, IsfObject typeObj) { - DataType baseType = typeDef.getBaseDataType(); + super(typeDef); size = typeDef.getLength(); - kind = IsfUtilities.getKind(baseType); + kind = "typedef"; + //kind = IsfUtilities.getKind(baseType); type = typeObj; } diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfUtilities.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfUtilities.java index 531a5ac070..202bcdd407 100644 --- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfUtilities.java +++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/program/model/data/ISF/IsfUtilities.java @@ -20,46 +20,6 @@ import ghidra.program.model.data.Enum; public class IsfUtilities { - // list of Ghidra built-in type names which correspond to C primitive types - private static String[] INTEGRAL_TYPES = { "char", "short", "int", "long", "long long", - "__int64", "float", "double", "long double", "void" }; - - private static String[] INTEGRAL_MODIFIERS = - { "signed", "unsigned", "const", "static", "volatile", "mutable", }; - - public static boolean isIntegral(String typedefName, String basetypeName) { - for (String type : INTEGRAL_TYPES) { - if (typedefName.equals(type)) { - return true; - } - } - - boolean endsWithIntegralType = false; - for (String type : INTEGRAL_TYPES) { - if (typedefName.endsWith(" " + type)) { - endsWithIntegralType = true; - break; - } - } - boolean containsIntegralModifier = false; - for (String modifier : INTEGRAL_MODIFIERS) { - if (typedefName.indexOf(modifier + " ") >= 0 || - typedefName.indexOf(" " + modifier) >= 0) { - return true; - } - } - - if (endsWithIntegralType && containsIntegralModifier) { - return true; - } - - if (typedefName.endsWith(" " + basetypeName)) { - return containsIntegralModifier; - } - - return false; - } - public static DataType getBaseDataType(DataType dt) { while (dt != null) { if (dt instanceof Array) { @@ -117,7 +77,7 @@ public class IsfUtilities { return "enum"; } if (dt instanceof TypeDef) { - return "base"; //"typedef"; + return "typedef"; } if (dt instanceof FunctionDefinition) { return "function"; @@ -133,7 +93,7 @@ public class IsfUtilities { public static String getBuiltInKind(BuiltInDataType dt) { if (dt instanceof AbstractIntegerDataType) { - return dt.getLength() == 1 ? "char" : "int"; + return dt.getName(); } if (dt instanceof AbstractFloatDataType) { return "float"; @@ -145,7 +105,7 @@ public class IsfUtilities { return "char"; // "string"; } if (dt instanceof PointerDataType) { - return "void"; //"pointer"; + return "pointer"; } if (dt instanceof VoidDataType) { return "void"; @@ -185,11 +145,48 @@ public class IsfUtilities { return dt.getLength(); } - public static Boolean getSigned(DataType dt) { - return dt.getDataOrganization().isSignedChar(); - } - public static String getEndianness(DataType dt) { return dt.getDataOrganization().isBigEndian() ? "big" : "little"; } + +// // list of Ghidra built-in type names which correspond to C primitive types +// private static String[] INTEGRAL_TYPES = { "char", "short", "int", "long", "long long", +// "__int64", "float", "double", "long double", "void" }; +// +// private static String[] INTEGRAL_MODIFIERS = +// { "signed", "unsigned", "const", "static", "volatile", "mutable", }; +// +// public static boolean isIntegral(String typedefName, String basetypeName) { +// for (String type : INTEGRAL_TYPES) { +// if (typedefName.equals(type)) { +// return true; +// } +// } +// +// boolean endsWithIntegralType = false; +// for (String type : INTEGRAL_TYPES) { +// if (typedefName.endsWith(" " + type)) { +// endsWithIntegralType = true; +// break; +// } +// } +// boolean containsIntegralModifier = false; +// for (String modifier : INTEGRAL_MODIFIERS) { +// if (typedefName.indexOf(modifier + " ") >= 0 || +// typedefName.indexOf(" " + modifier) >= 0) { +// return true; +// } +// } +// +// if (endsWithIntegralType && containsIntegralModifier) { +// return true; +// } +// +// if (typedefName.endsWith(" " + basetypeName)) { +// return containsIntegralModifier; +// } +// +// return false; +// } + } diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 608491ab2a..9b63f669c3 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -389,7 +389,6 @@ src/main/help/help/topics/MemoryMapPlugin/images/MoveMemory.png||GHIDRA||||END| src/main/help/help/topics/MemoryMapPlugin/images/SetImageBaseDialog.png||GHIDRA||||END| src/main/help/help/topics/MemoryMapPlugin/images/SplitMemoryBlock.png||GHIDRA||||END| src/main/help/help/topics/Misc/Appendix.htm||GHIDRA||||END| -src/main/help/help/topics/Misc/Tips.htm||NONE||||END| src/main/help/help/topics/Misc/Welcome_to_Ghidra_Help.htm||GHIDRA||||END| src/main/help/help/topics/Navigation/Navigation.htm||GHIDRA||||END| src/main/help/help/topics/Navigation/images/GoToDialog.png||GHIDRA||||END| diff --git a/Ghidra/Features/Base/src/main/help/help/topics/ExporterPlugin/exporter.htm b/Ghidra/Features/Base/src/main/help/help/topics/ExporterPlugin/exporter.htm index f6bbdec7bd..2242bc1a0e 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/ExporterPlugin/exporter.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/ExporterPlugin/exporter.htm @@ -37,6 +37,8 @@
  • Raw Bytes
  • XML Export Format
  • + +
  • SARIF Export Format
  • Export Action

    @@ -414,6 +416,18 @@ XML Options are identical the XML Importer Options.

    + +

    SARIF

    + +
    +

    The SARIF Exporter creates SARIF files that conform to Ghidra's Program DTD. You can + re-import files in this format using the SARIF Importer.

    + +

    The + SARIF Options are identical the SARIF Importer Options.

    +

    Related Topics:

    diff --git a/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/importer.htm b/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/importer.htm index fb439d9c46..7761843a34 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/importer.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/importer.htm @@ -51,6 +51,7 @@
  • Raw Binary
  • Relocatable Object Module Format (OMF)
  • XML Input Format
  • +
  • SARIF Input Format
  • @@ -680,6 +681,16 @@ Names.

    + +

    SARIF Options

    + +
    +

    The SARIF format is used to load from a SARIF formatted file. The options are simply + switches for which types of program information to import and are identical to the options + specified above for XML.

    +
    + +

    Library Search Path

    diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterDialog.java index a7c2764abd..2846aa1915 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterDialog.java @@ -64,8 +64,12 @@ import ghidra.util.task.*; public class ExporterDialog extends DialogComponentProvider implements AddressFactoryService { private static final String XML_WARNING = - " Warning: XML is lossy and intended only for transfering data to external tools. " + - "GZF is the recommended format for saving and sharing program data."; + " Warning: XML is lossy and intended only for transfering data to external tools. " + + "GZF is the recommended format for saving and sharing program data."; + + private static final String SARIF_WARNING = + " Warning: SARIF is lossy and intended only for transfering data to external tools. " + + "GZF is the recommended format for saving and sharing program data."; private static String lastUsedExporterName = GzfExporter.NAME; // default to GZF first time @@ -394,6 +398,9 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa if (getSelectedExporter().getName().contains("XML")) { setStatusText(XML_WARNING); } + if (getSelectedExporter().getName().contains("SARIF")) { + setStatusText(SARIF_WARNING); + } setOkEnabled(true); } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/program/database/code/CodeManagerTest.java b/Ghidra/Features/Base/src/test/java/ghidra/program/database/code/CodeManagerTest.java index 0239959224..9fc22790f5 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/program/database/code/CodeManagerTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/program/database/code/CodeManagerTest.java @@ -470,7 +470,7 @@ public class CodeManagerTest extends AbstractGenericTest { Instruction inst = listing.getInstructionAt(addr(0x1100)); inst.setProperty("Numbers", 12); - PropertyMap map = listing.getPropertyMap("Numbers"); + PropertyMap map = listing.getPropertyMap("Numbers"); assertNotNull(map); inst.setProperty("FavoriteColor", new SaveableColor(Palette.RED)); diff --git a/Ghidra/Features/Sarif/Module.manifest b/Ghidra/Features/Sarif/Module.manifest new file mode 100644 index 0000000000..1d58d92fad --- /dev/null +++ b/Ghidra/Features/Sarif/Module.manifest @@ -0,0 +1 @@ +MODULE FILE LICENSE: lib/java-sarif-2.1.jar MIT diff --git a/Ghidra/Features/Sarif/build.gradle b/Ghidra/Features/Sarif/build.gradle new file mode 100644 index 0000000000..9a3162a450 --- /dev/null +++ b/Ghidra/Features/Sarif/build.gradle @@ -0,0 +1,36 @@ +/* ### + * 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. + */ +apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle" +apply from: "$rootProject.projectDir/gradle/javaProject.gradle" +apply from: "$rootProject.projectDir/gradle/jacocoProject.gradle" +apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle" +apply plugin: 'eclipse' + +eclipse.project.name = 'Features Sarif' + +dependencies { + api project(':Base') + api project(':Debugger-isf') + //api "javax.activation:activation-1.1.1" + api "com.contrastsecurity.sarif:java-sarif-2.1" +} + +test { + // temporary to prevent test from running when building + // specify a pattern that doesn't match any test files. + include "dontruntests" +} + diff --git a/Ghidra/Features/Sarif/certification.manifest b/Ghidra/Features/Sarif/certification.manifest new file mode 100644 index 0000000000..c54edb4baf --- /dev/null +++ b/Ghidra/Features/Sarif/certification.manifest @@ -0,0 +1,6 @@ +##VERSION: 2.0 +Module.manifest||GHIDRA||||END| +data/ExtensionPoint.manifest||GHIDRA||||END| +src/main/help/help/TOC_Source.xml||GHIDRA||||END| +src/main/help/help/topics/Sarif/SARIF.htm||GHIDRA||||END| +src/main/java/sarif/DEVNOTES.txt||GHIDRA||||END| diff --git a/Ghidra/Features/Sarif/data/ExtensionPoint.manifest b/Ghidra/Features/Sarif/data/ExtensionPoint.manifest new file mode 100644 index 0000000000..10d0b4ff57 --- /dev/null +++ b/Ghidra/Features/Sarif/data/ExtensionPoint.manifest @@ -0,0 +1,2 @@ +RunHandler +ResultHandler diff --git a/Ghidra/Features/Sarif/src/main/help/help/TOC_Source.xml b/Ghidra/Features/Sarif/src/main/help/help/TOC_Source.xml new file mode 100644 index 0000000000..5e2d0e0008 --- /dev/null +++ b/Ghidra/Features/Sarif/src/main/help/help/TOC_Source.xml @@ -0,0 +1,56 @@ + + + + + + + + + + diff --git a/Ghidra/Features/Sarif/src/main/help/help/topics/Sarif/SARIF.htm b/Ghidra/Features/Sarif/src/main/help/help/topics/Sarif/SARIF.htm new file mode 100644 index 0000000000..da6bed2120 --- /dev/null +++ b/Ghidra/Features/Sarif/src/main/help/help/topics/Sarif/SARIF.htm @@ -0,0 +1,81 @@ + + + + + + + Static Analysis Results Interchange Format (SARIF) + + + + +

    Using SARIF Files

    + +

    SARIF is an OASIS standard for exchanging the results of static analysis performed against + a target executable. While not a perfect match for exchanging Ghidra program analysis results, + it has at least some level of community acceptance and support within the reverse engineering + communities at large. SARIF can be created in any number of ways, but, within Ghidra, SARIF + creation is done by means of any exporter. As with all of our exporters, a dialog appears when + you request an export, with an options panel for selecting which features you would like to export. + Currently, we support bookmarks, code, comments, data types, defined data, entry points, equates, + external libraries, functions, memory maps, properties, references, registers, relocations, + symbols, and program trees.

    + +

    SARIF files can be re-ingested in two ways. A matching importer uses the same options to + create a new program or add features to an existing one. The process is probably lossy, + although efforts have been made to re-create exported programs with some fidelity. Known issues + are documented in the DEVNOTES file. The second way to ingest SARIF files is to configure a tool + with the SarifPlugin. The plugin provides a single "Read File" toolbar action. After selecting + a file, the SARIF results are displayed in table format, with associated ingest actions applicable + to a table selection. The features provide by the SARIF importer and exporter all share the action + "Add To Program".

    + +

    Writing Custom SARIF Handlers

    + +

    While the main SarifProgramResultHandler class handles the majority of program-derived + information used on import/export, there are many occasions where you might want to apply the + results of some external analysis to a Ghidra program. The "Features Sarif" project contains + templates (in the form of abstract classes) for custom handlers: SarifResultHandler.java and + SarifRunHandler.java. Both handler types extend ExtensionPoint and are discovered automatically + when the a file is read by the SarifPlugin.

    + +

    Per-result handlers are used to populate a table displayed to the user and to associate + actions with a column in the table. The "getKey" method returns the name for a column, and the + "getActionName" a name for the action to be taken. When the user selects that action (in this case, + "Commit"), the table provider executes the program task specified by "getTask". In this example, + the task grabs the selected rows from the table and, for each row, applies the return type in the + column "return_type" to the address in the column "address".

    + +

    A more complicated use case might involve the "parse" method, which is called for every result. + The default "handle" method takes anything returned by the "parse" method, creates key-value pairs + (stored as a map, but...) using "getKey" and the value returned, and adds them to a list of results + stored in the data frame. These can be retrieved programmatically later or processed immediately. + For example, the SarifPropertyResultHandler looks for results with "AdditionalProperties" labelled + either "viewer/table/xxx" or "listing/[comment, highlight, bookmark]". Items in the second category + are applied immediately to the current program. Items in the first category are added to the table + under column "xxx".

    + +

    A third and significantly more complicated example is the ProgramResultHandler, which overrides + the "handle" method. This handler converts the set of "AdditionalProperties" into a key-value map. + The map is added to a list of results for the data frame, but also converted to a map of lists based + on the result's "RuleId". The frame ultimately accrues a map of list of maps, keyed on "RuleId". + Its "Add to Program" task takes each per-rule list and applies them to the program using the values + stored in the per-result maps.

    + +

    A final example, the SarifGraphRunHandler, is a per-run handler and is generally called only once + (unless the SARIF files contains multiple runs). This example retrieves a SARIF Graph from the run, + converts it to a Ghidra graph object, and displays it. Controls for how and when SARIF graphs are + displayed are stored under Edit->Tool Options->Graph->Sarif.

    + + +

    Related Topics:

    + + + +

     

    + + diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/DEVNOTES.txt b/Ghidra/Features/Sarif/src/main/java/sarif/DEVNOTES.txt new file mode 100644 index 0000000000..24af15197d --- /dev/null +++ b/Ghidra/Features/Sarif/src/main/java/sarif/DEVNOTES.txt @@ -0,0 +1,13 @@ +Random notes: + +1. Symbols require a pre- and post-function pass, pre- to guarantee the correct naming of globals + including functions and libraries and post- to guarantee namespaces have already been created + for locals + +Code differences from export/re-import: + +1. There may be missing parameter datatypes for parameters in a FunctionDefinition used inside a structure or union. + +2. Datatypes like "RTTIBaseDescriptor *32 _((image-base-relative)) *32 _((image-base-relative))" are not handled correctly. + +3. Modified/multiple ProgramTrees will not be imported correctly. Appears to be no way to fix this with the current API. \ No newline at end of file diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/SarifController.java b/Ghidra/Features/Sarif/src/main/java/sarif/SarifController.java new file mode 100644 index 0000000000..b5b260c2af --- /dev/null +++ b/Ghidra/Features/Sarif/src/main/java/sarif/SarifController.java @@ -0,0 +1,289 @@ +/* ### + * 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 sarif; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.contrastsecurity.sarif.Location; +import com.contrastsecurity.sarif.LogicalLocation; +import com.contrastsecurity.sarif.Result; +import com.contrastsecurity.sarif.SarifSchema210; + +import docking.widgets.table.ObjectSelectedListener; +import ghidra.app.plugin.core.colorizer.ColorizingService; +import ghidra.app.services.GraphDisplayBroker; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.BookmarkManager; +import ghidra.program.model.listing.CodeUnit; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.service.graph.AttributedGraph; +import ghidra.service.graph.EmptyGraphType; +import ghidra.service.graph.GraphDisplay; +import ghidra.service.graph.GraphDisplayOptions; +import ghidra.util.Msg; +import ghidra.util.classfinder.ClassSearcher; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.GraphException; +import resources.ResourceManager; +import sarif.handlers.SarifResultHandler; +import sarif.handlers.SarifRunHandler; +import sarif.managers.ProgramSarifMgr; +import sarif.model.SarifDataFrame; +import sarif.view.ImageArtifactDisplay; +import sarif.view.SarifResultsTableProvider; + +/** + * Controller for handling interactions between the SARIF log file and Ghidra + */ +public class SarifController implements ObjectSelectedListener> { + + private SarifPlugin plugin; + private Program program; + + private ColorizingService coloringService; + private BookmarkManager bookmarkManager; + private ProgramSarifMgr programManager; + + private Set providers = new HashSet<>(); + public Set artifacts = new HashSet<>(); + public Set graphs = new HashSet<>(); + + public Set getSarifResultHandlers() { + Set set = new HashSet<>(); + set.addAll(ClassSearcher.getInstances(SarifResultHandler.class)); + return set; + } + + public Set getSarifRunHandlers() { + Set set = new HashSet<>(); + set.addAll(ClassSearcher.getInstances(SarifRunHandler.class)); + return set; + } + + public SarifController(Program program, SarifPlugin plugin) { + this.program = program; + this.plugin = plugin; + this.coloringService = plugin.getTool().getService(ColorizingService.class); + this.programManager = new ProgramSarifMgr(program); + } + + public SarifController(ProgramSarifMgr manager) { + this.program = null; + this.plugin = null; + this.coloringService = null; // plugin.getTool().getService(ColorizingService.class); + this.programManager = manager; + } + + public void dispose() { + Set copyProviders = new HashSet<>(); + copyProviders.addAll(providers); + for (SarifResultsTableProvider p : copyProviders) { + p.dispose(); + } + Set copyArtifacts = new HashSet<>(); + copyArtifacts.addAll(artifacts); + for (ImageArtifactDisplay a : copyArtifacts) { + a.dispose(); + } + for (GraphDisplay g : graphs) { + g.close(); + } + } + + public void showTable(boolean makeVisible) { + for (SarifResultsTableProvider p : providers) { + p.setVisible(makeVisible); + } + } + + public void showTable(String logName, SarifSchema210 sarif) { + SarifDataFrame df = new SarifDataFrame(sarif, this, false); + SarifResultsTableProvider provider = new SarifResultsTableProvider(logName, this.plugin, this, df); + provider.filterTable.addSelectionListener(this); + provider.addToTool(); + provider.setVisible(true); + provider.setTitle(logName); + if (!providers.contains(provider)) { + providers.add(provider); + } + } + + public void showImage(String key, BufferedImage img) { + if (plugin.displayArtifacts()) { + ImageArtifactDisplay display = new ImageArtifactDisplay(plugin.getTool(), key, "Sarif Parse", img); + display.setVisible(true); + artifacts.add(display); + } + } + + public void showGraph(AttributedGraph graph) { + try { + GraphDisplayBroker service = this.plugin.getTool().getService(GraphDisplayBroker.class); + boolean append = plugin.appendToGraph(); + GraphDisplay display = service.getDefaultGraphDisplay(append, null); + GraphDisplayOptions graphOptions = new GraphDisplayOptions(new EmptyGraphType()); + graphOptions.setMaxNodeCount(plugin.getGraphSize()); + + if (plugin.displayGraphs()) { + display.setGraph(graph, graphOptions, graph.getDescription(), append, null); + graphs.add(display); + } + } catch (GraphException | CancelledException e) { + Msg.error(this, "showGraph failed " + e.getMessage()); + } + } + + /** + * If a results has "listing/" in a SARIF result, this handles + * defining our custom API for those + * + * @param log + * @param result + * @param key + * @param value + */ + public void handleListingAction(Result result, String key, Object value) { + List
    addrs = getListingAddresses(result); + for (Address addr : addrs) { + switch (key) { + case "comment": + /* @formatter:off + * docs/GhidraAPI_javadoc/api/constant-values.html#ghidra.program.model.listing.CodeUnit + * EOL_COMMENT 0 + * PRE_COMMENT 1 + * POST_COMMENT 2 + * PLATE_COMMENT 3 + * REPEATABLE_COMMENT 4 + * @formatter:on + */ + String comment = (String) value; + getProgram().getListing().setComment(addr, CodeUnit.PLATE_COMMENT, comment); + break; + case "highlight": + Color color = Color.decode((String) value); + coloringService.setBackgroundColor(addr, addr, color); + break; + case "bookmark": + String bookmark = (String) value; + getProgram().getBookmarkManager().setBookmark(addr, "Sarif", result.getRuleId(), bookmark); + break; + } + } + } + + public void colorBackground(AddressSetView set, Color color) { + coloringService.setBackgroundColor(set, color); + } + + public void colorBackground(Address addr, Color color) { + coloringService.setBackgroundColor(addr, addr, color); + } + + public Address longToAddress(Object lval) { + if (lval instanceof Long) { + return getProgram().getAddressFactory().getDefaultAddressSpace().getAddress((Long) lval); + } + return getProgram().getAddressFactory().getDefaultAddressSpace().getAddress((Integer) lval); + } + + /** + * Get listing addresses associated with a result + * + * @param result + * @return + */ + public List
    getListingAddresses(Result result) { + List
    addrs = new ArrayList<>(); + if (result.getLocations() != null && result.getLocations().size() > 0) { + List locations = result.getLocations(); + for (Location loc : locations) { + Address addr = locationToAddress(loc); + if (addr != null) { + addrs.add(addr); + } + } + } + return addrs; + } + + public Address locationToAddress(Location loc) { + if (loc.getPhysicalLocation() != null) { + return longToAddress(loc.getPhysicalLocation().getAddress().getAbsoluteAddress()); + } + if (loc.getLogicalLocations() != null) { + Set logicalLocations = loc.getLogicalLocations(); + for (LogicalLocation logLoc : logicalLocations) { + switch (logLoc.getKind()) { + case "function": + String fname = logLoc.getName(); + for (Function func : getProgram().getFunctionManager().getFunctions(true)) { + if (fname.equals(func.getName())) { + return func.getEntryPoint(); + } + } + break; + default: + Msg.error(this, "Unknown logical location to handle: " + logLoc.toString()); + } + } + } + return null; + } + + @SuppressWarnings("unchecked") + @Override + public void objectSelected(Map row) { + if (row != null) { + if (row.containsKey("CodeFlows")) { + for (List
    flow : (List>) row.get("CodeFlows")) { + this.plugin.makeSelection(flow); + } + } + if (row.containsKey("Graphs")) { + for (AttributedGraph graph : (List) row.get("Graphs")) { + this.showGraph(graph); + } + } + } + } + + public void removeProvider(SarifResultsTableProvider provider) { + providers.remove(provider); + } + + public ProgramSarifMgr getProgramSarifMgr() { + return programManager; + } + + public Program getProgram() { + return program; + } + + public void setProgram(Program program) { + this.program = program; + this.bookmarkManager = program.getBookmarkManager(); + bookmarkManager.defineType("Sarif", ResourceManager.loadImage("images/peach_16.png"), Color.pink, 0); + } + +} diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/SarifLoader.java b/Ghidra/Features/Sarif/src/main/java/sarif/SarifLoader.java new file mode 100644 index 0000000000..6a2ef8cfb1 --- /dev/null +++ b/Ghidra/Features/Sarif/src/main/java/sarif/SarifLoader.java @@ -0,0 +1,350 @@ +/* ### + * 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 sarif; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import ghidra.app.plugin.core.analysis.AnalysisWorker; +import ghidra.app.plugin.core.analysis.AutoAnalysisManager; +import ghidra.app.util.Option; +import ghidra.app.util.OptionException; +import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.opinion.AbstractProgramLoader; +import ghidra.app.util.opinion.LoadException; +import ghidra.app.util.opinion.LoadSpec; +import ghidra.app.util.opinion.Loaded; +import ghidra.app.util.opinion.LoaderTier; +import ghidra.framework.model.DomainObject; +import ghidra.framework.model.Project; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.CompilerSpec; +import ghidra.program.model.lang.CompilerSpecDescription; +import ghidra.program.model.lang.CompilerSpecNotFoundException; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.lang.ExternalLanguageCompilerSpecQuery; +import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.LanguageCompilerSpecPair; +import ghidra.program.model.lang.LanguageDescription; +import ghidra.program.model.lang.LanguageNotFoundException; +import ghidra.program.model.listing.Program; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; +import sarif.export.SarifObject; +import sarif.managers.ProgramInfo; +import sarif.managers.ProgramSarifMgr; + +public class SarifLoader extends AbstractProgramLoader { + + private static final String FILE_EXTENSION = SarifObject.SARIF ? ".sarif" : ".json"; + public final static String SARIF_SRC_NAME = "SARIF Input Format"; + + @Override + public LoaderTier getTier() { + return LoaderTier.SPECIALIZED_TARGET_LOADER; + } + + @Override + public int getTierPriority() { + return 50; + } + + @Override + public boolean supportsLoadIntoProgram() { + return true; + } + + @Override + public Collection findSupportedLoadSpecs(ByteProvider provider) throws IOException { + + List loadSpecs = new ArrayList<>(); + + // + // Unusual Code Alert!: the parse() method below uses Processor to + // location processors + // by name when reading SARIF. The Processor class is not fully + // populated until the languages have been loaded. + // + getLanguageService(); + + ParseResult result = parse(provider); + + ProgramInfo info = result.lastInfo; + if (info == null) { + return loadSpecs; + } + + if (info.languageID != null) {// non-external language + // got a language ID, good... + try { + LanguageDescription languageDescription = + getLanguageService().getLanguageDescription(info.languageID); + + boolean preferred = false; + if (info.compilerSpecID == null) { + // no compiler spec ID, try to pick "default" (embedded + // magic string!!! BAD) + for (CompilerSpecDescription csd : languageDescription.getCompatibleCompilerSpecDescriptions()) { + LanguageCompilerSpecPair pair = new LanguageCompilerSpecPair( + languageDescription.getLanguageID(), csd.getCompilerSpecID()); + loadSpecs.add(new LoadSpec(this, 0, pair, preferred)); + } + } + else { + // test existence; throw exception on failure + languageDescription.getCompilerSpecDescriptionByID(info.compilerSpecID); + // good, we know exactly what this is (make it preferred) + LanguageCompilerSpecPair pair = + new LanguageCompilerSpecPair(info.languageID, info.compilerSpecID); + preferred = true; + loadSpecs.add(new LoadSpec(this, 0, pair, preferred)); + } + } + catch (CompilerSpecNotFoundException | LanguageNotFoundException lnfe) { + // ignore + // should fall into loadSpecs.isEmpty() case below + } + + } + else if (info.processorName != null) {// external language + // no ID, look by processor/possibly endian + Integer size = extractSize(info.addressModel); + Endian endian = Endian.toEndian(info.endian); + ExternalLanguageCompilerSpecQuery broadQuery = + new ExternalLanguageCompilerSpecQuery(info.processorName, + info.getNormalizedExternalToolName(), endian, size, info.compilerSpecID); + List pairs = + getLanguageService().getLanguageCompilerSpecPairs(broadQuery); + + if (!pairs.isEmpty()) { + boolean preferred = false; + if (pairs.size() == 1) { + preferred = true; + } + for (LanguageCompilerSpecPair pair : pairs) { + loadSpecs.add(new LoadSpec(this, 0, pair, preferred)); + } + } + } + + if (loadSpecs.isEmpty() && provider.getName().endsWith(FILE_EXTENSION)) { + // just put 'em all in (give endianess preference) + List languageDescriptions = + getLanguageService().getLanguageDescriptions(false); + for (LanguageDescription languageDescription : languageDescriptions) { + Collection compilerSpecDescriptions = + languageDescription.getCompatibleCompilerSpecDescriptions(); + for (CompilerSpecDescription compilerSpecDescription : compilerSpecDescriptions) { + LanguageCompilerSpecPair pair = + new LanguageCompilerSpecPair(languageDescription.getLanguageID(), + compilerSpecDescription.getCompilerSpecID()); + loadSpecs.add(new LoadSpec(this, 0, pair, false)); + } + } + } + return loadSpecs; + } + + private static Pattern ADDRESS_MODEL_PATTERN = Pattern.compile("(\\d+)-bit"); + + private Integer extractSize(String addressModel) { + if (addressModel != null) { + Matcher matcher = ADDRESS_MODEL_PATTERN.matcher(addressModel); + if (matcher.find()) { + return Integer.parseInt(matcher.group(1)); + } + } + return null; + } + + @Override + public String getPreferredFileName(ByteProvider provider) { + String name = provider.getName(); + if (name.toLowerCase().endsWith(FILE_EXTENSION)) { + return name.substring(0, name.length() - FILE_EXTENSION.length()); + } + return name; + } + + @Override + protected List> loadProgram(ByteProvider provider, String programName, + Project project, String programFolderPath, LoadSpec loadSpec, List