diff --git a/Ghidra/Features/Base/ghidra_scripts/ExportFunctionInfoScript.java b/Ghidra/Features/Base/ghidra_scripts/ExportFunctionInfoScript.java index 719da0315f..528786f7e9 100644 --- a/Ghidra/Features/Base/ghidra_scripts/ExportFunctionInfoScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/ExportFunctionInfoScript.java @@ -13,56 +13,51 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -// List function names and entry point addresses to a file +// List function names and entry point addresses to a file in JSON format //@category Functions -import ghidra.app.plugin.core.script.Ingredient; -import ghidra.app.plugin.core.script.IngredientDescription; -import ghidra.app.script.GatherParamPanel; +import java.io.File; +import java.io.FileWriter; + +import com.google.gson.*; +import com.google.gson.stream.JsonWriter; + import ghidra.app.script.GhidraScript; import ghidra.program.model.address.Address; import ghidra.program.model.listing.*; -import java.io.*; +public class ExportFunctionInfoScript extends GhidraScript { -public class ExportFunctionInfoScript extends GhidraScript implements Ingredient { + private static final String NAME = "name"; + private static final String ENTRY = "entry"; @Override public void run() throws Exception { - IngredientDescription[] ingredients = getIngredientDescriptions(); - for (int i = 0; i < ingredients.length; i++) { - state.addParameter(ingredients[i].getID(), ingredients[i].getLabel(), - ingredients[i].getType(), ingredients[i].getDefaultValue()); - } - if (!state.displayParameterGatherer("Script Options")) { - return; - } - File outputNameFile = (File) state.getEnvironmentVar("FunctionNameOutputFile"); - PrintWriter pWriter = new PrintWriter(new FileOutputStream(outputNameFile)); + + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + File outputFile = askFile("Please Select Output File", "Choose"); + JsonWriter jsonWriter = new JsonWriter(new FileWriter(outputFile)); + jsonWriter.beginArray(); + Listing listing = currentProgram.getListing(); FunctionIterator iter = listing.getFunctions(true); while (iter.hasNext() && !monitor.isCancelled()) { Function f = iter.next(); - String fName = f.getName(); + + String name = f.getName(); Address entry = f.getEntryPoint(); - if (entry == null) { - pWriter.println("/* FUNCTION_NAME_ " + fName + " FUNCTION_ADDR_ " + - "NO_ENTRY_POINT" + " */"); - println("WARNING: no entry point for " + fName); - } - else { - pWriter.println("/* FUNCTION_NAME_ " + fName + " FUNCTION_ADDR_ " + entry + " */"); - } + + JsonObject json = new JsonObject(); + json.addProperty(NAME, name); + json.addProperty(ENTRY, entry.toString()); + + gson.toJson(json, jsonWriter); } - pWriter.close(); - } - @Override - public IngredientDescription[] getIngredientDescriptions() { - IngredientDescription[] retVal = - new IngredientDescription[] { new IngredientDescription("FunctionNameOutputFile", - "Output Function Name File", GatherParamPanel.FILE, "") }; - return retVal; - } + jsonWriter.endArray(); + jsonWriter.close(); + println("Wrote functions to " + outputFile); + } } diff --git a/Ghidra/Features/Base/ghidra_scripts/FindEmptySpaceScript.java b/Ghidra/Features/Base/ghidra_scripts/FindEmptySpaceScript.java deleted file mode 100644 index 46dfc2c77b..0000000000 --- a/Ghidra/Features/Base/ghidra_scripts/FindEmptySpaceScript.java +++ /dev/null @@ -1,153 +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. - */ -//searches for pre-defined patterns and free space in code images - -import java.util.List; -import java.util.stream.Collectors; - -import ghidra.app.cmd.label.AddLabelCmd; -import ghidra.app.plugin.core.script.Ingredient; -import ghidra.app.plugin.core.script.IngredientDescription; -import ghidra.app.plugin.core.searchmem.RegExSearchData; -import ghidra.app.script.GatherParamPanel; -import ghidra.app.script.GhidraScript; -import ghidra.program.model.address.*; -import ghidra.program.model.listing.CodeUnit; -import ghidra.program.model.symbol.SourceType; -import ghidra.util.datastruct.ListAccumulator; -import ghidra.util.search.memory.*; - -public class FindEmptySpaceScript extends GhidraScript implements Ingredient { - - @Override - public void run() throws Exception { - IngredientDescription[] ingredients = getIngredientDescriptions(); - for (IngredientDescription ingredient : ingredients) { - state.addParameter(ingredient.getID(), ingredient.getLabel(), ingredient.getType(), - ingredient.getDefaultValue()); - } - if (!state.displayParameterGatherer("Empty Area Finder Options")) { - return; - } - - String emptyArea = (String) state.getEnvironmentVar("EmptyAreaData"); - Integer threshold = (Integer) state.getEnvironmentVar("Threshold"); - Integer align = (Integer) state.getEnvironmentVar("Alignment"); - String stem = (String) state.getEnvironmentVar("NameStem"); - - findEmptyAreas(emptyArea, threshold, align, stem); - } - - protected void findEmptyAreas(String emptyArea, Integer threshold, Integer align, String stem) - throws Exception { - String emptyAreaPlusThreshold = emptyArea + "{" + threshold + ",}"; - if (align < currentProgram.getLanguage().getInstructionAlignment()) { - align = currentProgram.getLanguage().getInstructionAlignment(); - println( - " Adjusting alignment to minimum instruction alignment for this processor; new alignment is " + - align + " bytes"); - } - else if ((align % currentProgram.getLanguage().getInstructionAlignment()) != 0) { - align = align + (align % currentProgram.getLanguage().getInstructionAlignment()); - println( - " Adjusting alignment to match processor instruction alignment; new alignment is " + - align + " bytes"); - } - - println(" Searching initialized memory for " + emptyAreaPlusThreshold + - "; minimum size = " + threshold + " bytes ; alignment = " + align + - " bytes; search limited to first 1000 matches"); - - AddressSetView addrs = currentProgram.getMemory().getLoadedAndInitializedAddressSet(); - - SearchInfo searchInfo = new SearchInfo(new RegExSearchData(emptyAreaPlusThreshold), 1000, - false, true, align, true, null); - RegExMemSearcherAlgorithm searcher = - new RegExMemSearcherAlgorithm(searchInfo, addrs, currentProgram, true); - - ListAccumulator accumulator = new ListAccumulator<>(); - searcher.search(accumulator, monitor); - List results = accumulator.asList(); - List
addresses = - results.stream().map(r -> r.getAddress()).collect(Collectors.toList()); - - int numMatches = 0; - long maxLen = 0; - if (results.isEmpty()) { - println(" FAILURE: Could not find any empty areas with regexp = " + - emptyAreaPlusThreshold + "and alignment = " + align + " bytes"); - return; - } - - //put matches into an address set, thereby coalescing ranges - AddressSet addrSet = new AddressSet(); - for (MemSearchResult result : results) { - Address match = result.getAddress(); - int len = result.getLength(); - addrSet.addRange(match, match.addNoWrap(len)); - } - - //iterate over the set items that matched - for (AddressRange range : addrSet) { - long len = range.getLength(); - addLabelAndExportSym(range.getMinAddress(), len, stem, "emptyArea", "size = " + len + - " bytes (alignment = " + align + " bytes; min size = " + threshold + " bytes)"); - numMatches++; - if (len > maxLen) { - maxLen = len; - } - } - - println(" Found " + numMatches + - " empty areas meeting size and alignment requirements; maximum length found = " + - maxLen + " bytes"); - - } - - protected void addLabelAndExportSym(Address matchAddr, long len, String stem, String tag, - String optComment) { - String label = stem + "_" + matchAddr + "_" + len; - label = label.replaceAll(":", "_"); - String comment = "{@exportsym " + tag + " " + optComment + "}"; - CodeUnit cd = currentProgram.getListing().getCodeUnitAt(matchAddr); - if (cd == null) { - return; - } - AddLabelCmd lcmd = new AddLabelCmd(matchAddr, label, false, SourceType.USER_DEFINED); - lcmd.applyTo(currentProgram); - String commentThere = cd.getComment(CodeUnit.EOL_COMMENT); - if (commentThere != null) { - comment = commentThere + "\n" + comment; - } - cd.setComment(CodeUnit.EOL_COMMENT, comment); - } - - @Override - public IngredientDescription[] getIngredientDescriptions() { - - IngredientDescription[] retVal = new IngredientDescription[] { - new IngredientDescription("EmptyAreaData", "Regular Expression Data Pattern", - GatherParamPanel.STRING, "\\xff"), - new IngredientDescription("Threshold", "Minimum Size (decimal bytes)", - GatherParamPanel.INTEGER, ""), - new IngredientDescription("Alignment", "Alignment (decimal bytes)", - GatherParamPanel.INTEGER, ""), - new IngredientDescription("NameStem", "Optional Label Stem", GatherParamPanel.STRING, - "EMPTY") }; - return retVal; - } - -} diff --git a/Ghidra/Features/Base/ghidra_scripts/OverlayHeadersScript.java b/Ghidra/Features/Base/ghidra_scripts/OverlayHeadersScript.java deleted file mode 100644 index 5fa734b32c..0000000000 --- a/Ghidra/Features/Base/ghidra_scripts/OverlayHeadersScript.java +++ /dev/null @@ -1,97 +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. - */ -//Places header structure on overlay segments - -import java.io.File; - -import ghidra.app.plugin.core.script.Ingredient; -import ghidra.app.plugin.core.script.IngredientDescription; -import ghidra.app.script.GatherParamPanel; -import ghidra.app.script.GhidraScript; -import ghidra.program.model.address.*; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.FileDataTypeManager; -import ghidra.program.model.util.CodeUnitInsertionException; - -public class OverlayHeadersScript extends GhidraScript implements Ingredient { - - @Override - public void run() throws Exception { - // Get our configuration info and save for other scripts to use - IngredientDescription[] ingredients = getIngredientDescriptions(); - for (IngredientDescription ingredient : ingredients) { - state.addParameter(ingredient.getID(), ingredient.getLabel(), ingredient.getType(), - ingredient.getDefaultValue()); - } - if (!state.displayParameterGatherer("Script Options")) { - return; - } - - // Get our parameters for use here - String overlayName = (String) state.getEnvironmentVar("OverlayName"); - File dataTypeArchive = (File) state.getEnvironmentVar("OverlayHeaderArchive"); - String dataTypeName = (String) state.getEnvironmentVar("OverlayHeaderName"); // must include datatype category - - // Create our history logger - Address histAddr = currentProgram.getMemory().getMinAddress(); - String tmpString = "\nScript: OverlayHeaders()\n"; - tmpString = tmpString + " Add " + dataTypeName + " structure\n from " + - dataTypeArchive.toString(); - - // Get the datatype that we want to place on the overlays - FileDataTypeManager dataTypeFileManager = openDataTypeArchive(dataTypeArchive, true); - DataType dataType = dataTypeFileManager.getDataType(dataTypeName); - dataTypeFileManager.close(); - if (dataType == null) { - println("Can't find data type " + dataTypeName + " in " + dataTypeArchive.toString()); - throw new Exception( - "Can't find data type " + dataTypeName + "\n in " + dataTypeArchive.toString()); - } - - // Now iterate over overlays the lay down structure - AddressSetView searchSet = currentProgram.getMemory(); - AddressRangeIterator addressRanges = searchSet.getAddressRanges(true); - monitor.initialize(searchSet.getNumAddresses()); - int progressCount = 0; - while (addressRanges.hasNext() && !monitor.isCancelled()) { - AddressRange range = addressRanges.next(); - Address startAddr = range.getMinAddress(); - String rangeName = startAddr.toString(); - if (rangeName.startsWith(overlayName)) { - try { - createData(startAddr, dataType); - } - catch (CodeUnitInsertionException ex) { - println("Error creating data type: " + ex); - } - } - progressCount += range.getLength(); - monitor.setProgress(progressCount); - } - } - - @Override - public IngredientDescription[] getIngredientDescriptions() { - IngredientDescription[] retVal = new IngredientDescription[] { - new IngredientDescription("OverlayName", "Overlay Name", GatherParamPanel.STRING, "ov"), - new IngredientDescription("OverlayHeaderArchive", "Overlay Header Archive", - GatherParamPanel.FILE, ""), - new IngredientDescription("OverlayHeaderName", "Overlay Header Name", - GatherParamPanel.STRING, "/overlay_header") }; - return retVal; - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/Ingredient.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/Ingredient.java deleted file mode 100644 index 1e45b56ee4..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/Ingredient.java +++ /dev/null @@ -1,20 +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.app.plugin.core.script; - -public interface Ingredient { - IngredientDescription [] getIngredientDescriptions(); -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/IngredientDescription.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/IngredientDescription.java deleted file mode 100644 index 4530dd6d66..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/IngredientDescription.java +++ /dev/null @@ -1,54 +0,0 @@ -/* ### - * IP: GHIDRA - * REVIEWED: YES - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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.script; - -public class IngredientDescription { - private boolean visited; - private String id; - private String label; - private int type; - private Object defaultValue; - - public IngredientDescription(String id, String label, int type, Object defaultValue) { - this.id = id; - this.label = label; - this.type = type; - this.defaultValue = defaultValue; - visited = false; - } - - public boolean wasVisited() { - return visited; - } - - public String getLabel() { - return label; - } - - public String getID() { - return id; - } - - public int getType() { - return type; - } - - public Object getDefaultValue() { - return defaultValue; - } - -} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/ExportFunctionInfoScriptTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/ExportFunctionInfoScriptTest.java new file mode 100644 index 0000000000..4f70cd7450 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/ExportFunctionInfoScriptTest.java @@ -0,0 +1,156 @@ +/* ### + * 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.script; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import com.google.gson.Gson; +import com.google.gson.stream.JsonReader; + +import docking.widgets.filechooser.GhidraFileChooser; +import generic.json.Json; +import ghidra.framework.Application; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.test.*; + +/** + * Tests the {@code ExportFunctionInfoScript}, which writes Ghidra function object info in JSON + * form for the entire program + */ +public class ExportFunctionInfoScriptTest extends AbstractGhidraHeadedIntegrationTest { + + private TestEnv env; + private File script; + + private Program program; + private Function f1; + private Function f2; + + @Before + public void setUp() throws Exception { + + program = buildProgram(); + + env = new TestEnv(); + env.launchDefaultTool(program); + + String scriptPath = "ghidra_scripts/ExportFunctionInfoScript.java"; + script = Application.getModuleFile("Base", scriptPath).getFile(true); + } + + private Program buildProgram() throws Exception { + ToyProgramBuilder builder = new ToyProgramBuilder("Test", true, this); + builder.createMemory(".text", "0x1001000", 0x40); + + f1 = builder.createFunction("0x1001000"); + f2 = builder.createFunction("0x1001020"); + + return builder.getProgram(); + } + + @Test + public void testScript() throws Exception { + + File outputFile = createTempFileForTest(); + + ScriptTaskListener listener = env.runScript(script); + + chooseFile(outputFile); + + waitForScriptCompletion(listener, 20000); + + assertFunctionsInFile(outputFile, f1, f2); + } + + private void assertFunctionsInFile(File file, Function... functions) + throws Exception { + + List testFunctions = new ArrayList<>(List.of(f1, f2)); + + List jsons = readFromJson(file); + + jsons.forEach(jsonFunction -> assertFunction(jsonFunction, testFunctions)); + assertThat("Not all program functions written to json file", + testFunctions, is(empty())); + } + + private List readFromJson(File file) throws Exception { + + List results = new ArrayList<>(); + Gson gson = new Gson(); + BufferedReader br = new BufferedReader(new FileReader(file)); + JsonReader reader = new JsonReader(br); + + // the file is an array of objects + reader.beginArray(); + while (reader.hasNext()) { + TestJsonFunction function = gson.fromJson(reader, TestJsonFunction.class); + results.add(function); + } + reader.endArray(); + reader.close(); + + return results; + } + + private void assertFunction(TestJsonFunction function, List testFunctions) { + Function match = null; + for (Function expected : testFunctions) { + if (function.matches(expected)) { + match = expected; + break; + } + } + + assertNotNull("Unexpected function written to file", match); + testFunctions.remove(match); + } + + private void chooseFile(File file) throws Exception { + + GhidraFileChooser chooser = waitForDialogComponent(GhidraFileChooser.class); + runSwing(() -> chooser.setSelectedFile(file)); + waitForUpdateOnChooser(chooser); + pressButtonByText(chooser.getComponent(), "Choose"); + waitForSwing(); + } + + private class TestJsonFunction { + private String name; + private String entry; + + boolean matches(Function expected) { + return name.equals(expected.getName()) && + entry.equals(expected.getEntryPoint().toString()); + } + + @Override + public String toString() { + // this is only for debug; not required + return Json.toString(this); + } + + } +} diff --git a/Ghidra/Features/Decompiler/ghidra_scripts/Decompile.java b/Ghidra/Features/Decompiler/ghidra_scripts/Decompile.java deleted file mode 100644 index f84a2e5911..0000000000 --- a/Ghidra/Features/Decompiler/ghidra_scripts/Decompile.java +++ /dev/null @@ -1,57 +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. - */ -//Decompile an entire program - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import ghidra.app.plugin.core.script.Ingredient; -import ghidra.app.plugin.core.script.IngredientDescription; -import ghidra.app.script.GatherParamPanel; -import ghidra.app.script.GhidraScript; -import ghidra.app.util.Option; -import ghidra.app.util.exporter.CppExporter; - -public class Decompile extends GhidraScript implements Ingredient { - - @Override - public void run() throws Exception { - IngredientDescription[] ingredients = getIngredientDescriptions(); - for (IngredientDescription ingredient : ingredients) { - state.addParameter(ingredient.getID(), ingredient.getLabel(), ingredient.getType(), - ingredient.getDefaultValue()); - } - if (!state.displayParameterGatherer("Script Options")) { - return; - } - File outputFile = (File) state.getEnvironmentVar("COutputFile"); - CppExporter cppExporter = new CppExporter(); - List