diff --git a/Ghidra/Features/BSim/ghidra_scripts/DumpBSimDebugSignaturesScript.py b/Ghidra/Features/BSim/ghidra_scripts/DumpBSimDebugSignaturesScript.py index 3d6221595a..430f4cf77e 100755 --- a/Ghidra/Features/BSim/ghidra_scripts/DumpBSimDebugSignaturesScript.py +++ b/Ghidra/Features/BSim/ghidra_scripts/DumpBSimDebugSignaturesScript.py @@ -16,6 +16,7 @@ # Generate the BSim signature for the function at the current address, # then dump the signature hashes and debug information to the console # @category: BSim.python +# @runtime Jython import ghidra.app.decompiler.DecompInterface as DecompInterface import ghidra.app.decompiler.DecompileOptions as DecompileOptions diff --git a/Ghidra/Features/BSim/ghidra_scripts/DumpBSimSignaturesScript.py b/Ghidra/Features/BSim/ghidra_scripts/DumpBSimSignaturesScript.py index c1189492cc..15114469f3 100755 --- a/Ghidra/Features/BSim/ghidra_scripts/DumpBSimSignaturesScript.py +++ b/Ghidra/Features/BSim/ghidra_scripts/DumpBSimSignaturesScript.py @@ -16,6 +16,7 @@ # Generate the BSim signature for the function at the current address, then dump the # signature hashes to the console # @category: BSim.python +# @runtime Jython import ghidra.app.decompiler.DecompInterface as DecompInterface import ghidra.app.decompiler.DecompileOptions as DecompileOptions diff --git a/Ghidra/Features/BSim/ghidra_scripts/ExampleOverviewQueryScript.py b/Ghidra/Features/BSim/ghidra_scripts/ExampleOverviewQueryScript.py index 0e3877d167..c92ee8603b 100755 --- a/Ghidra/Features/BSim/ghidra_scripts/ExampleOverviewQueryScript.py +++ b/Ghidra/Features/BSim/ghidra_scripts/ExampleOverviewQueryScript.py @@ -15,6 +15,7 @@ ## # Example of how to perform an overview query in a script # @category BSim.python +# @runtime Jython import ghidra.features.bsim.query.facade.SFOverviewInfo as SFOverviewInfo import ghidra.features.bsim.query.facade.SimilarFunctionQueryService as SimilarFunctionQueryService diff --git a/Ghidra/Features/BSim/ghidra_scripts/GenerateSignatures.py b/Ghidra/Features/BSim/ghidra_scripts/GenerateSignatures.py index 4cbc1edbbb..e518e776b7 100755 --- a/Ghidra/Features/BSim/ghidra_scripts/GenerateSignatures.py +++ b/Ghidra/Features/BSim/ghidra_scripts/GenerateSignatures.py @@ -15,6 +15,7 @@ ## #Generate signatures for every function in the current program and write them to an XML file in a user-specified directory #@category BSim.python +#@runtime Jython import java.lang.System as System import java.io.File as File diff --git a/Ghidra/Features/BSim/ghidra_scripts/QueryFunction.py b/Ghidra/Features/BSim/ghidra_scripts/QueryFunction.py index 9393b795dd..a57e4f9918 100755 --- a/Ghidra/Features/BSim/ghidra_scripts/QueryFunction.py +++ b/Ghidra/Features/BSim/ghidra_scripts/QueryFunction.py @@ -15,6 +15,7 @@ ## # Example of performing a BSim query on a single function # @category BSim.python +# @runtime Jython import ghidra.features.bsim.query.BSimClientFactory as BSimClientFactory import ghidra.features.bsim.query.GenSignatures as GenSignatures diff --git a/Ghidra/Features/Base/ghidra_scripts/LocateMemoryAddressesForFileOffset.py b/Ghidra/Features/Base/ghidra_scripts/LocateMemoryAddressesForFileOffset.py index 74cfec9e2d..19b25d1d46 100644 --- a/Ghidra/Features/Base/ghidra_scripts/LocateMemoryAddressesForFileOffset.py +++ b/Ghidra/Features/Base/ghidra_scripts/LocateMemoryAddressesForFileOffset.py @@ -19,6 +19,7 @@ #Print the file offset as a Ghidra comment at the memory address in the Ghidra Listing #If multiple addresses are located, then print the addresses to the console (do not set a Ghidra comment) # @category Examples +# @runtime Jython import sys from ghidra.program.model.address import Address diff --git a/Ghidra/Features/Base/ghidra_scripts/RecursiveStringFinder.py b/Ghidra/Features/Base/ghidra_scripts/RecursiveStringFinder.py index 91bd2c455b..efe9182bfe 100644 --- a/Ghidra/Features/Base/ghidra_scripts/RecursiveStringFinder.py +++ b/Ghidra/Features/Base/ghidra_scripts/RecursiveStringFinder.py @@ -15,6 +15,7 @@ ## #Given a function, find all strings used within all called funtions. # @category: Strings +# @runtime Jython # Handles only functions, not subroutines, as of now. Hopefully this will change later diff --git a/Ghidra/Features/Base/ghidra_scripts/RunYARAFromGhidra.py b/Ghidra/Features/Base/ghidra_scripts/RunYARAFromGhidra.py index 0977421ea6..5e142ba9fb 100644 --- a/Ghidra/Features/Base/ghidra_scripts/RunYARAFromGhidra.py +++ b/Ghidra/Features/Base/ghidra_scripts/RunYARAFromGhidra.py @@ -24,6 +24,7 @@ # generate the original bytes of the imported file and asks the user to provide a filename to store the bytes. YARA then runs on that file. #@category Memory.YARA +#@runtime Jython import os.path import sys diff --git a/Ghidra/Features/Base/ghidra_scripts/mark_in_out.py b/Ghidra/Features/Base/ghidra_scripts/mark_in_out.py index 8e9020ea18..40b2b3070e 100644 --- a/Ghidra/Features/Base/ghidra_scripts/mark_in_out.py +++ b/Ghidra/Features/Base/ghidra_scripts/mark_in_out.py @@ -15,6 +15,7 @@ ## # Sets up IOPORT IN/OUT references for the Program #@category Instructions +#@runtime Jython # Before running this script, you should have created an OVERLAY memory # space called IOMEM, starting at address 0, size 0x10000. # diff --git a/Ghidra/Features/Base/src/main/help/help/topics/GhidraScriptMgrPlugin/ScriptDevelopment.htm b/Ghidra/Features/Base/src/main/help/help/topics/GhidraScriptMgrPlugin/ScriptDevelopment.htm index 84eb0f34f1..7e524b0576 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/GhidraScriptMgrPlugin/ScriptDevelopment.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/GhidraScriptMgrPlugin/ScriptDevelopment.htm @@ -127,7 +127,7 @@

-

The tag indicates the top-level menu path. Path levels are delimited using the "." +

This tag indicates the top-level menu path. Path levels are delimited using the "." character. A mnemonic can be defined by adding an ampersand ("&") in front of the mnemonic key. Ampersands can be escaped by adding another ampersand ("&&").

@@ -149,7 +149,20 @@ then in the Ghidra installation. If the image does not exists, a toolbar button will be created using the default Ghidra image.

- For example, "@toolbar myScriptImage.gif".
+ For example, "@toolbar myScriptImage.gif".
+

+
+ +

@runtime

+ +
+

This tag indicates which Ghidra script runtime environment is required to execute the + script. It allows for greater control when more than one Ghidra script runtime environment + uses the same script file extension. If left unspecified, the first Ghidra script runtime + environment that matches the script's extension will be used.
+
+ For example, specify "@runtime Jython" if the script is targetted for a Jython 2 + runtime environment rather than a Python 3 runtime environment.

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java index 15fa34b459..b47ad3ab4c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java @@ -370,7 +370,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { } GhidraScriptProvider provider = GhidraScriptUtil.getProvider(script); - SaveDialog dialog = new SaveDialog(getComponent(), "Rename Script", this, script, + SaveDialog dialog = new SaveDialog(getComponent(), "Rename Script", this, script, provider, actionManager.getRenameHelpLocation()); if (dialog.isCancelled()) { plugin.getTool().setStatusInfo("User cancelled rename."); @@ -580,7 +580,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { ResourceFile newFile = GhidraScriptUtil.createNewScript(provider, new ResourceFile(userScriptsDir), getScriptDirectories()); SaveDialog dialog = new SaveNewScriptDialog(getComponent(), "New Script", this, newFile, - actionManager.getNewHelpLocation()); + provider, actionManager.getNewHelpLocation()); if (dialog.isCancelled()) { plugin.getTool().setStatusInfo("User cancelled creating a new script."); return; @@ -676,6 +676,11 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { private GhidraScript getScriptInstance(ResourceFile scriptFile, ConsoleService console) { String scriptName = scriptFile.getName(); GhidraScriptProvider provider = GhidraScriptUtil.getProvider(scriptFile); + if (provider == null) { + console.addErrorMessage("", + "Could not find a compatible script provider for: " + scriptName); + return null; + } try { return provider.getScriptInstance(scriptFile, console.getStdErr()); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java index 6ebee30d2e..cf3adad9eb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java @@ -561,8 +561,8 @@ public class GhidraScriptEditorComponentProvider extends ComponentProvider { private boolean saveAs() { HelpLocation help = new HelpLocation(plugin.getName(), saveAction.getName()); - SaveDialog dialog = - new SaveDialog(getComponent(), "Save Script", provider, scriptSourceFile, help); + SaveDialog dialog = new SaveDialog(getComponent(), "Save Script", provider, + scriptSourceFile, GhidraScriptUtil.getProvider(scriptSourceFile), help); if (dialog.isCancelled()) { return false; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptTableModel.java index ac44f948d6..606507fff2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptTableModel.java @@ -66,6 +66,8 @@ class GhidraScriptTableModel extends GDynamicColumnTableModel { + + private Comparator comparator = new CaseInsensitiveDuplicateStringComparator(); + + @Override + public Comparator getComparator() { + return comparator; + } + + @Override + public String getColumnName() { + return "Runtime"; + } + + @Override + public String getValue(ResourceFile rowObject, Settings settings, Object data, + ServiceProvider sp) throws IllegalArgumentException { + return infoManager.getExistingScriptInfo(rowObject).getRuntimeEnvironmentName(); + } + + @Override + public int getColumnPreferredWidth() { + return 100; + } + } + + private class ProviderColumn extends AbstractDynamicTableColumn { + + private Comparator comparator = new CaseInsensitiveDuplicateStringComparator(); + + @Override + public Comparator getComparator() { + return comparator; + } + + @Override + public String getColumnName() { + return "Runtime Provider"; + } + + @Override + public String getValue(ResourceFile rowObject, Settings settings, Object data, + ServiceProvider sp) throws IllegalArgumentException { + return infoManager.getExistingScriptInfo(rowObject) + .getProvider() + .getClass() + .getSimpleName(); + } + + @Override + public int getColumnPreferredWidth() { + return 100; + } + } + private class DateRenderer extends AbstractGColumnRenderer { @Override public Component getTableCellRendererComponent(GTableCellRenderingData data) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/SaveDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/SaveDialog.java index ae3b2ebd95..e73e92610f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/SaveDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/SaveDialog.java @@ -47,9 +47,9 @@ public class SaveDialog extends DialogComponentProvider implements ListSelection private boolean cancelled; SaveDialog(Component parent, String title, GhidraScriptComponentProvider componentProvider, - ResourceFile scriptFile, HelpLocation help) { + ResourceFile scriptFile, GhidraScriptProvider scriptProvider, HelpLocation help) { this(parent, title, componentProvider, componentProvider.getWritableScriptDirectories(), - scriptFile, help); + scriptFile, scriptProvider, help); } /** @@ -60,15 +60,16 @@ public class SaveDialog extends DialogComponentProvider implements ListSelection * @param componentProvider the provider * @param scriptDirs list of directories to give as options when saving * @param scriptFile the default save location + * @param scriptProvider the {@link GhidraScriptProvider} * @param help contextual help, e.g. for rename or save */ public SaveDialog(Component parent, String title, GhidraScriptComponentProvider componentProvider, List scriptDirs, - ResourceFile scriptFile, HelpLocation help) { + ResourceFile scriptFile, GhidraScriptProvider scriptProvider, HelpLocation help) { super(title, true, true, true, false); this.componentProvider = componentProvider; - this.provider = GhidraScriptUtil.getProvider(scriptFile); + this.provider = scriptProvider; this.scriptFile = scriptFile; this.paths = new ArrayList<>(scriptDirs); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/SaveNewScriptDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/SaveNewScriptDialog.java index 3551c56f40..0a38d46f82 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/SaveNewScriptDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/SaveNewScriptDialog.java @@ -19,14 +19,15 @@ import java.awt.Component; import java.io.File; import generic.jar.ResourceFile; +import ghidra.app.script.GhidraScriptProvider; import ghidra.util.HelpLocation; class SaveNewScriptDialog extends SaveDialog { SaveNewScriptDialog(Component parent, String title, GhidraScriptComponentProvider componentProvider, ResourceFile scriptFile, - HelpLocation help) { - super(parent, title, componentProvider, scriptFile, help); + GhidraScriptProvider scriptProvider, HelpLocation help) { + super(parent, title, componentProvider, scriptFile, scriptProvider, help); } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/AbstractPythonScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/AbstractPythonScriptProvider.java new file mode 100644 index 0000000000..4db3bc398d --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/AbstractPythonScriptProvider.java @@ -0,0 +1,99 @@ +/* ### + * 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.script; + +import java.io.*; +import java.util.regex.Pattern; + +import generic.jar.ResourceFile; + +/** + * An abstract {@link GhidraScriptProvider} used to provide common functionality to different + * types of Python script implementations + */ +public abstract class AbstractPythonScriptProvider extends GhidraScriptProvider { + + private static final Pattern BLOCK_COMMENT = Pattern.compile("'''"); + + @Override + public abstract String getDescription(); + + @Override + public abstract String getRuntimeEnvironmentName(); + + @Override + public abstract GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer) + throws GhidraScriptLoadException; + + @Override + public void createNewScript(ResourceFile newScript, String category) throws IOException { + try (PrintWriter writer = new PrintWriter(new FileWriter(newScript.getFile(false)))) { + writeHeader(writer, category); + writer.println(""); + writeBody(writer); + writer.println(""); + } + } + + /** + * {@inheritDoc} + *

+ * In Python this is a triple single quote sequence, "'''". + * + * @return the Pattern for Python block comment openings + */ + @Override + public Pattern getBlockCommentStart() { + return BLOCK_COMMENT; + } + + /** + * {@inheritDoc} + *

+ * In Python this is a triple single quote sequence, "'''". + * + * @return the Pattern for Python block comment openings + */ + @Override + public Pattern getBlockCommentEnd() { + return BLOCK_COMMENT; + } + + @Override + public String getCommentCharacter() { + return "#"; + } + + @Override + protected String getCertifyHeaderStart() { + return "## ###"; + } + + @Override + protected String getCertificationBodyPrefix() { + return "#"; + } + + @Override + protected String getCertifyHeaderEnd() { + return "##"; + } + + @Override + public String getExtension() { + return ".py"; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java index a68f214945..8d0a5cb1c9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java @@ -105,6 +105,19 @@ public abstract class GhidraScriptProvider public abstract void createNewScript(ResourceFile newScript, String category) throws IOException; + /** + * Returns an optional runtime environment name of a {@link GhidraScriptProvider} that scripts + * can specify they require to run under. Useful for when more than one + * {@link GhidraScriptProvider} uses the same file extension. + * + * @return an optional runtime environment name of a {@link GhidraScriptProvider} that scripts + * can specify they require to run under (could be null if there is no requirement) + * @see ScriptInfo#AT_RUNTIME + */ + public String getRuntimeEnvironmentName() { + return null; + } + /** * Returns a Pattern that matches block comment openings. * @@ -161,6 +174,9 @@ public abstract class GhidraScriptProvider if (metadataItem.equals(ScriptInfo.AT_CATEGORY)) { writer.print(category); } + else if (metadataItem.equals(ScriptInfo.AT_RUNTIME)) { + writer.print(getRuntimeEnvironmentName()); + } writer.println(""); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java index 542097febf..180b024e29 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java @@ -276,29 +276,31 @@ public class GhidraScriptUtil { } /** - * Returns a list of all Ghidra script providers + * Returns a list of all supported Ghidra script providers * - * @return a list of all Ghidra script providers + * @return a list of all supported Ghidra script providers */ // Note: this method is synchronized so that two threads do not try to create the list when null public static synchronized List getProviders() { if (providers == null) { - List newProviders = - new ArrayList<>(ClassSearcher.getInstances(GhidraScriptProvider.class)); - Collections.sort(newProviders); - providers = newProviders; + providers = ClassSearcher.getInstances(GhidraScriptProvider.class) + .stream() + .filter(p -> !(p instanceof UnsupportedScriptProvider)) + .sorted() + .toList(); } return providers; } /** - * Returns the corresponding Ghidra script providers - * for the specified script file. + * Returns the corresponding Ghidra script provider for the specified script file. + * * @param scriptFile the script file - * @return the Ghidra script provider + * @return the Ghidra script provider or {@link UnsupportedScriptProvider} if the script file + * does not exist or no provider matches */ public static GhidraScriptProvider getProvider(ResourceFile scriptFile) { - return findProvider(scriptFile.getName()); + return findProvider(scriptFile); } /** @@ -308,11 +310,46 @@ public class GhidraScriptUtil { * @return true if a provider exists that can process the specified file */ public static boolean hasScriptProvider(ResourceFile scriptFile) { - return findProvider(scriptFile.getName()) != null; + return findProvider(scriptFile) != null; } /** - * Find the provider whose extension matches the given filename extension. + * Find the first provider whose extension matches the given file's extension and whose + * {@link ScriptInfo#AT_RUNTIME} matches + * + * @param scriptFile the script file (not guaranteed to exist if this method is called because + * the script manager is creating a new script and all it has to go off of initially is + * the desired file extension...in this case there will not be a @runtime tag yet) + * @return the matching provider or null if no provider matches + */ + private static GhidraScriptProvider findProvider(ResourceFile scriptFile) { + GhidraScriptProvider baseProvider = null; + String fileName = scriptFile.getName().toLowerCase(); + for (GhidraScriptProvider provider : getProviders()) { + String extension = provider.getExtension().toLowerCase(); + if (fileName.endsWith(extension)) { + baseProvider = provider; + if (!scriptFile.exists()) { + // Use UnsupportedScriptProvider. The provider will be updated later when + // the file actually exists and we can properly look for an @runtime tag + // (or confirm that one is not defined) + break; + } + String runtime = new ScriptInfo(provider, scriptFile).getRuntimeEnvironmentName(); + if (runtime == null || + runtime.equalsIgnoreCase(provider.getRuntimeEnvironmentName())) { + return provider; + } + } + } + if (baseProvider != null) { + return new UnsupportedScriptProvider(baseProvider); + } + return null; + } + + /** + * Find the first provider whose extension matches the given filename extension. * * @param fileName name of script file * @return the first matching provider or null if no provider matches diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java index e66ee56237..809180208f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java @@ -69,6 +69,11 @@ public class JavaScriptProvider extends GhidraScriptProvider { return ".java"; } + @Override + public String getRuntimeEnvironmentName() { + return "Java"; + } + @Override public boolean deleteScript(ResourceFile sourceFile) { try { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java index 324a51bfb0..786666dd45 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java @@ -46,12 +46,13 @@ public class ScriptInfo { static final String AT_KEYBINDING = "@keybinding"; static final String AT_MENUPATH = "@menupath"; static final String AT_TOOLBAR = "@toolbar"; + static final String AT_RUNTIME = "@runtime"; // omit from METADATA to avoid pre-populating in new scripts private static final String AT_IMPORTPACKAGE = "@importpackage"; public static final String[] METADATA = - { AT_AUTHOR, AT_CATEGORY, AT_KEYBINDING, AT_MENUPATH, AT_TOOLBAR, }; + { AT_AUTHOR, AT_CATEGORY, AT_KEYBINDING, AT_MENUPATH, AT_TOOLBAR, AT_RUNTIME }; private GhidraScriptProvider provider; private ResourceFile sourceFile; @@ -68,6 +69,7 @@ public class ScriptInfo { private String toolbar; private ImageIcon toolbarImage; private String importpackage; + private String runtime; /** * Constructs a new script. @@ -94,6 +96,7 @@ public class ScriptInfo { toolbarImage = null; importpackage = null; keybindingErrorMessage = null; + runtime = null; } /** @@ -129,6 +132,26 @@ public class ScriptInfo { return author; } + /** + * Returns the name of the required runtime environment + * @return the name of the required runtime environment + * @see GhidraScriptProvider#getRuntimeEnvironmentName() + */ + public String getRuntimeEnvironmentName() { + parseHeader(); + return runtime; + } + + /** + * Returns the {@link GhidraScriptProvider} currently associated with the script + * @return The {@link GhidraScriptProvider} currently associated with the script + */ + public GhidraScriptProvider getProvider() { + parseHeader(); + provider = GhidraScriptUtil.getProvider(sourceFile); + return provider; + } + /** * Returns true if the script has compile errors. * @return true if the script has compile errors @@ -163,6 +186,16 @@ public class ScriptInfo { this.isDuplicate = isDuplicate; } + /** + * Returns true if this script has an {@link UnsupportedScriptProvider}. This will typically + * happen when a script defines a wrong {@link ScriptInfo#AT_RUNTIME} tag. + * + * @return True if this script has an {@link UnsupportedScriptProvider}; otherwise, false + */ + public boolean hasUnsupportedProvider() { + return provider instanceof UnsupportedScriptProvider; + } + /** * Returns the script description. * @return the script description @@ -329,6 +362,9 @@ public class ScriptInfo { else if (line.startsWith(AT_IMPORTPACKAGE)) { importpackage = getTagValue(AT_IMPORTPACKAGE, line); } + else if (line.startsWith(AT_RUNTIME)) { + runtime = getTagValue(AT_RUNTIME, line); + } } catch (Exception e) { Msg.debug(this, "Unexpected exception reading script metadata " + "line: " + line, e); @@ -514,6 +550,7 @@ public class ScriptInfo { String htmlCategory = bold("Category:") + space + escapeHTML(toString(category)); String htmlKeyBinding = bold("Key Binding:") + space + getKeybindingToolTip(); String htmlMenuPath = bold("Menu Path:") + space + escapeHTML(toString(menupath)); + String htmlRuntime = bold("Runtime Environment:") + space + escapeHTML(toString(runtime)); StringBuilder buffer = new StringBuilder(); buffer.append("

").append(space).append(escapeHTML(getName())).append("

"); @@ -529,6 +566,8 @@ public class ScriptInfo { buffer.append(HTML_NEW_LINE); buffer.append(space).append(htmlMenuPath); buffer.append(HTML_NEW_LINE); + buffer.append(space).append(htmlRuntime); + buffer.append(HTML_NEW_LINE); buffer.append(HTML_NEW_LINE); return wrapAsHTML(buffer.toString()); } @@ -560,7 +599,7 @@ public class ScriptInfo { * @return true if the script either has compiler errors, or is a duplicate */ public boolean hasErrors() { - return isCompileErrors() || isDuplicate(); + return isCompileErrors() || isDuplicate() || hasUnsupportedProvider(); } /** @@ -575,6 +614,10 @@ public class ScriptInfo { return "Script is a duplicate of another script"; } + if (hasUnsupportedProvider()) { + return "Script's @runtime tag specifies an unsupported runtime environment"; + } + return null; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/UnsupportedScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/UnsupportedScriptProvider.java new file mode 100644 index 0000000000..bf9720c20b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/UnsupportedScriptProvider.java @@ -0,0 +1,97 @@ +/* ### + * 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.script; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.regex.Pattern; + +import generic.jar.ResourceFile; + +/** + * A stub provider for unsupported scripts. These will typically be scripts with supported + * extensions but unsupported {@link ScriptInfo#AT_RUNTIME} tags. + */ +public class UnsupportedScriptProvider extends GhidraScriptProvider { + + private GhidraScriptProvider baseProvider; + + public UnsupportedScriptProvider() { + // Necessary for instantiation from the ClassSearcher + } + + /** + * Creates a new {@link UnsupportedScriptProvider} that is derived from the given base provider. + * The base provider is any provider with a compatible extension, but without the required + * {@link ScriptInfo#AT_RUNTIME} tag. + * + * @param baseProvider The base {@link GhidraScriptProvider} + */ + public UnsupportedScriptProvider(GhidraScriptProvider baseProvider) { + this.baseProvider = baseProvider; + } + + @Override + public String getDescription() { + return ""; + } + + @Override + public String getExtension() { + return baseProvider.getExtension(); + } + + @Override + public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer) + throws GhidraScriptLoadException { + throw new GhidraScriptLoadException("Script is not supported."); + } + + @Override + public void createNewScript(ResourceFile newScript, String category) throws IOException { + // Do nothing + } + + @Override + public String getCommentCharacter() { + return baseProvider.getCommentCharacter(); + } + + @Override + public Pattern getBlockCommentStart() { + return baseProvider.getBlockCommentStart(); + } + + @Override + public Pattern getBlockCommentEnd() { + return baseProvider.getBlockCommentEnd(); + } + + @Override + protected String getCertifyHeaderStart() { + return baseProvider.getCertifyHeaderStart(); + } + + @Override + protected String getCertificationBodyPrefix() { + return baseProvider.getCertificationBodyPrefix(); + } + + @Override + protected String getCertifyHeaderEnd() { + return baseProvider.getCertifyHeaderEnd(); + } +} diff --git a/Ghidra/Features/Jython/ghidra_scripts/AddCommentToProgramScriptPy.py b/Ghidra/Features/Jython/ghidra_scripts/AddCommentToProgramScriptPy.py index d85657ca4f..0d9f112a53 100644 --- a/Ghidra/Features/Jython/ghidra_scripts/AddCommentToProgramScriptPy.py +++ b/Ghidra/Features/Jython/ghidra_scripts/AddCommentToProgramScriptPy.py @@ -19,6 +19,7 @@ # use only. Please run the Java version in a production environment. #@category Examples.Python +#@runtime Jython from ghidra.program.model.address.Address import * diff --git a/Ghidra/Features/Jython/ghidra_scripts/AskScriptPy.py b/Ghidra/Features/Jython/ghidra_scripts/AskScriptPy.py index 61f79eb33f..9edac8366b 100644 --- a/Ghidra/Features/Jython/ghidra_scripts/AskScriptPy.py +++ b/Ghidra/Features/Jython/ghidra_scripts/AskScriptPy.py @@ -22,6 +22,7 @@ # use only. Please run the Java version in a production environment. #@category Examples.Python +#@runtime Jython from ghidra.framework.model import DomainFile from ghidra.framework.model import DomainFolder diff --git a/Ghidra/Features/Jython/ghidra_scripts/CallAnotherScriptForAllProgramsPy.py b/Ghidra/Features/Jython/ghidra_scripts/CallAnotherScriptForAllProgramsPy.py index 82c32790d2..e0225381f8 100644 --- a/Ghidra/Features/Jython/ghidra_scripts/CallAnotherScriptForAllProgramsPy.py +++ b/Ghidra/Features/Jython/ghidra_scripts/CallAnotherScriptForAllProgramsPy.py @@ -21,6 +21,7 @@ # NOTE: Script will only process unversioned and checked-out files. #@category Examples.Python +#@runtime Jython from ghidra.app.script import GhidraState from ghidra.framework.model import * diff --git a/Ghidra/Features/Jython/ghidra_scripts/CallAnotherScriptPy.py b/Ghidra/Features/Jython/ghidra_scripts/CallAnotherScriptPy.py index c0d423af61..088323c84b 100644 --- a/Ghidra/Features/Jython/ghidra_scripts/CallAnotherScriptPy.py +++ b/Ghidra/Features/Jython/ghidra_scripts/CallAnotherScriptPy.py @@ -19,6 +19,7 @@ # use only. Please run the Java version in a production environment. #@category Examples.Python +#@runtime Jython runScript("HelloWorldScript.java") runScript("HelloWorldPopupScript.java") diff --git a/Ghidra/Features/Jython/ghidra_scripts/ChooseDataTypeScriptPy.py b/Ghidra/Features/Jython/ghidra_scripts/ChooseDataTypeScriptPy.py index be36f8fdf9..9153659da4 100644 --- a/Ghidra/Features/Jython/ghidra_scripts/ChooseDataTypeScriptPy.py +++ b/Ghidra/Features/Jython/ghidra_scripts/ChooseDataTypeScriptPy.py @@ -19,6 +19,7 @@ # use only. Please run the Java version in a production environment. #@category Examples.Python +#@runtime Jython from ghidra.app.util.datatype import DataTypeSelectionDialog from ghidra.framework.plugintool import PluginTool diff --git a/Ghidra/Features/Jython/ghidra_scripts/ExampleColorScriptPy.py b/Ghidra/Features/Jython/ghidra_scripts/ExampleColorScriptPy.py index 39da09d7ce..41728659d7 100644 --- a/Ghidra/Features/Jython/ghidra_scripts/ExampleColorScriptPy.py +++ b/Ghidra/Features/Jython/ghidra_scripts/ExampleColorScriptPy.py @@ -19,6 +19,7 @@ # use only. Please run the Java version in a production environment. #@category Examples.Python +#@runtime Jython from ghidra.app.plugin.core.colorizer import ColorizingService from ghidra.app.script import GhidraScript diff --git a/Ghidra/Features/Jython/ghidra_scripts/FormatExampleScriptPy.py b/Ghidra/Features/Jython/ghidra_scripts/FormatExampleScriptPy.py index 01192093a3..6a38017e21 100644 --- a/Ghidra/Features/Jython/ghidra_scripts/FormatExampleScriptPy.py +++ b/Ghidra/Features/Jython/ghidra_scripts/FormatExampleScriptPy.py @@ -20,6 +20,7 @@ # use only. Please run the Java version in a production environment. #@category Examples.Python +#@runtime Jython from time import * import java.util.Calendar diff --git a/Ghidra/Features/Jython/ghidra_scripts/ImportSymbolsScript.py b/Ghidra/Features/Jython/ghidra_scripts/ImportSymbolsScript.py index 20802dcbeb..ae4a502483 100644 --- a/Ghidra/Features/Jython/ghidra_scripts/ImportSymbolsScript.py +++ b/Ghidra/Features/Jython/ghidra_scripts/ImportSymbolsScript.py @@ -24,6 +24,7 @@ # Omitting the address space or memory region specifier from the address will result in the function or label being created in the default address space. # @author unkown; edited by matedealer # @category Data +# @runtime Jython # from ghidra.program.model.symbol.SourceType import * diff --git a/Ghidra/Features/Jython/ghidra_scripts/PrintNonZeroPurge.py b/Ghidra/Features/Jython/ghidra_scripts/PrintNonZeroPurge.py index a92323676a..4b6d53d2d2 100644 --- a/Ghidra/Features/Jython/ghidra_scripts/PrintNonZeroPurge.py +++ b/Ghidra/Features/Jython/ghidra_scripts/PrintNonZeroPurge.py @@ -14,6 +14,7 @@ # limitations under the License. ## # Prints out all the functions in the program that have a non-zero stack purge size +# @runtime Jython for func in currentProgram.getFunctionManager().getFunctions(currentProgram.evaluateAddress("0"), 1): if func.getStackPurgeSize() != 0: diff --git a/Ghidra/Features/Jython/ghidra_scripts/ToolPropertiesExampleScriptPy.py b/Ghidra/Features/Jython/ghidra_scripts/ToolPropertiesExampleScriptPy.py index ff082b2ea6..6216ab354e 100644 --- a/Ghidra/Features/Jython/ghidra_scripts/ToolPropertiesExampleScriptPy.py +++ b/Ghidra/Features/Jython/ghidra_scripts/ToolPropertiesExampleScriptPy.py @@ -18,7 +18,8 @@ # DISCLAIMER: This is a recreation of a Java Ghidra script for example # use only. Please run the Java version in a production environment. -#@category Examples.Python +#@category Examples.Python +#@runtime Jython from ghidra.framework.options import Options from ghidra.framework.plugintool import PluginTool diff --git a/Ghidra/Features/Jython/ghidra_scripts/external_module_callee.py b/Ghidra/Features/Jython/ghidra_scripts/external_module_callee.py index 0444128956..89ad8757d5 100644 --- a/Ghidra/Features/Jython/ghidra_scripts/external_module_callee.py +++ b/Ghidra/Features/Jython/ghidra_scripts/external_module_callee.py @@ -15,6 +15,7 @@ ## # Example of being imported by a Ghidra Python script/module # @category: Examples.Python +# @runtime Jython # The following line will fail if this module is imported from external_module_caller.py, # because only the script that gets directly launched by Ghidra inherits fields and methods diff --git a/Ghidra/Features/Jython/ghidra_scripts/external_module_caller.py b/Ghidra/Features/Jython/ghidra_scripts/external_module_caller.py index cbe32a5b86..40b08e1681 100644 --- a/Ghidra/Features/Jython/ghidra_scripts/external_module_caller.py +++ b/Ghidra/Features/Jython/ghidra_scripts/external_module_caller.py @@ -15,6 +15,7 @@ ## # Example of importing an external Ghidra Python module # @category: Examples.Python +# @runtime Jython # Import the external module that wants to access the Ghidra scripting API. # NOTE: see external_module_callee.py for additional tips. diff --git a/Ghidra/Features/Jython/ghidra_scripts/ghidra_basics.py b/Ghidra/Features/Jython/ghidra_scripts/ghidra_basics.py index 8828e19c10..c120e8a7a7 100644 --- a/Ghidra/Features/Jython/ghidra_scripts/ghidra_basics.py +++ b/Ghidra/Features/Jython/ghidra_scripts/ghidra_basics.py @@ -15,6 +15,7 @@ ## # Examples of basic Ghidra scripting in Python # @category: Examples.Python +# @runtime Jython # Get info about the current program print diff --git a/Ghidra/Features/Jython/ghidra_scripts/jython_basics.py b/Ghidra/Features/Jython/ghidra_scripts/jython_basics.py index 3dc3cd8347..503fd2adad 100644 --- a/Ghidra/Features/Jython/ghidra_scripts/jython_basics.py +++ b/Ghidra/Features/Jython/ghidra_scripts/jython_basics.py @@ -15,6 +15,7 @@ ## # Examples of Jython-specific functionality # @category: Examples.Python +# @runtime Jython # Using Java data structures from Jython python_list = [1, 2, 3] diff --git a/Ghidra/Features/Jython/ghidra_scripts/python_basics.py b/Ghidra/Features/Jython/ghidra_scripts/python_basics.py index dff8d2f2b5..71ee658cd1 100644 --- a/Ghidra/Features/Jython/ghidra_scripts/python_basics.py +++ b/Ghidra/Features/Jython/ghidra_scripts/python_basics.py @@ -15,6 +15,7 @@ ## # Examples of basic Python # @category: Examples.Python +# @runtime Jython # Python data types my_int = 32 diff --git a/Ghidra/Features/Jython/src/main/java/ghidra/jython/JythonScriptProvider.java b/Ghidra/Features/Jython/src/main/java/ghidra/jython/JythonScriptProvider.java index f1b0cf43ae..c91041bd4b 100644 --- a/Ghidra/Features/Jython/src/main/java/ghidra/jython/JythonScriptProvider.java +++ b/Ghidra/Features/Jython/src/main/java/ghidra/jython/JythonScriptProvider.java @@ -15,71 +15,17 @@ */ package ghidra.jython; -import java.io.*; -import java.util.regex.Pattern; +import java.io.PrintWriter; import generic.jar.ResourceFile; import ghidra.app.script.*; +import ghidra.util.classfinder.ExtensionPointProperties; -public class JythonScriptProvider extends GhidraScriptProvider { - - private static final Pattern BLOCK_COMMENT = Pattern.compile("'''"); - - @Override - public void createNewScript(ResourceFile newScript, String category) throws IOException { - PrintWriter writer = new PrintWriter(new FileWriter(newScript.getFile(false))); - writeHeader(writer, category); - writer.println(""); - writeBody(writer); - writer.println(""); - writer.close(); - } - - /** - * {@inheritDoc} - * - *

- * In Jython this is a triple single quote sequence, "'''". - * - * @return the Pattern for Jython block comment openings - */ - @Override - public Pattern getBlockCommentStart() { - return BLOCK_COMMENT; - } - - /** - * {@inheritDoc} - * - *

- * In Jython this is a triple single quote sequence, "'''". - * - * @return the Pattern for Jython block comment openings - */ - @Override - public Pattern getBlockCommentEnd() { - return BLOCK_COMMENT; - } - - @Override - public String getCommentCharacter() { - return "#"; - } - - @Override - protected String getCertifyHeaderStart() { - return "## ###"; - } - - @Override - protected String getCertificationBodyPrefix() { - return "#"; - } - - @Override - protected String getCertifyHeaderEnd() { - return "##"; - } +/** + * A {@link GhidraScriptProvider} used to run Jython scripts + */ +@ExtensionPointProperties(priority = 1000) // Enforce high priority so Jython is the default Python provider +public class JythonScriptProvider extends AbstractPythonScriptProvider { @Override public String getDescription() { @@ -87,8 +33,8 @@ public class JythonScriptProvider extends GhidraScriptProvider { } @Override - public String getExtension() { - return ".py"; + public String getRuntimeEnvironmentName() { + return "Jython"; } @Override @@ -105,4 +51,5 @@ public class JythonScriptProvider extends GhidraScriptProvider { throw new GhidraScriptLoadException(e); } } + } diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java index 0101856efb..f7ccf0cc71 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java @@ -82,7 +82,7 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator scriptDirs.add(new ResourceFile("/User/home/ghidra_scripts")); SaveDialog dialog = new SaveDialog(tool.getToolFrame(), "Save Script", provider, - scriptDirs, scriptFile, helpLocation); + scriptDirs, scriptFile, new JavaScriptProvider(), helpLocation); tool.showDialog(dialog); }, false); @@ -208,7 +208,7 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator scriptDirs.add(new ResourceFile("/User/home/ghidra_scripts")); SaveDialog dialog = new SaveDialog(tool.getToolFrame(), "Rename Script", provider, - scriptDirs, scriptFile, helpLocation); + scriptDirs, scriptFile, new JavaScriptProvider(), helpLocation); tool.showDialog(dialog); }, false);