diff --git a/Ghidra/Extensions/bundle_examples/Module.manifest b/Ghidra/Extensions/bundle_examples/Module.manifest new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Ghidra/Extensions/bundle_examples/build.gradle b/Ghidra/Extensions/bundle_examples/build.gradle new file mode 100644 index 0000000000..2121c39fed --- /dev/null +++ b/Ghidra/Extensions/bundle_examples/build.gradle @@ -0,0 +1,116 @@ +/* This extension is different from the others. It produces a zip containing + * directories of source bundles and jar bundles. + * - Each source directory is added as a sourceset so that the eclipse plugin + * can add them to the generated project. + * - the source destined to be included as jars are compiled. + */ + +apply from: "$rootProject.projectDir/gradle/javaProject.gradle" +apply plugin: 'eclipse' + +// there is no main jar +jar.enabled=false + +eclipse.project.name = 'Xtra Bundle Examples' + + +dependencies { + compile project(':Base') +} + + +def srcDirs = [] +file(project.projectDir).eachDirMatch(~/.*scripts_.*/) { srcDirs << it.name } + +srcDirs.each {dirName -> + sourceSets.create(dirName) { + java { + srcDir { + dirName + } + } + } +} + +// create and return a jar task for the given source directory +def makeJarTask(dirName) { + return tasks.create("build${dirName}", Jar) { + archiveBaseName = dirName + archiveFileName = "${dirName}.jar" + ext.dirName=dirName + + + from(sourceSets[dirName].output) { + include "**" + } + manifest { + def manifestFile=file("${dirName}/META-INF/MANIFEST.MF") + // if there is a source manifest, use it + if(manifestFile.exists()) + from manifestFile + else // otherwise, use a default manifest + attributes \ + "Bundle-Name": dirName, + "Bundle-SymbolicName": dirName + } + } +} + + +def jarTasks=[ + makeJarTask("scripts_jar1"), + makeJarTask("scripts_jar2") +] + +eclipse { + classpath { + // jar1 and jar2 implement the same classes (with different OSGi package versions) + // adding both as source directories would cause errors in eclipse, so remove jar2. + sourceSets-=[sourceSets.scripts_jar2] + } +} + + + +// we need a alternative to the zipExtensions task from +// "$rootProject.projectDir/gradle/support/extensionCommon.gradle" +task zipExtensions(type: Zip, dependsOn:jarTasks) { + def p = this.project + archiveFileName = "${rootProject.ext.ZIP_NAME_PREFIX}_${p.name}.zip" + destinationDir rootProject.ext.DISTRIBUTION_DIR + + duplicatesStrategy 'exclude' + + from '.' + + srcDirs.each { f -> + include f + '/**' + } + + include "scripts_*.jar" + + for(jarTask in jarTasks) { + from relativePath(jarTask.archiveFile) + exclude jarTask.dirName + } + + into p.name +} + +// Registratino with rootProject.createInstallationZip is ususally done in +// "$rootProject.projectDir/gradle/distributableGhidraExtension.gradle", but +// since we define a custom zipExtensions task (and can't overwrite it), we do +// the registration here. +rootProject.createInstallationZip { + from (this.project.zipExtensions) { + into { + ZIP_DIR_PREFIX + "/Extensions/Ghidra" + } + } + doLast { + this.project.zipExtensions.outputs.each { + delete it + } + } +} + diff --git a/Ghidra/Extensions/bundle_examples/certification.manifest b/Ghidra/Extensions/bundle_examples/certification.manifest new file mode 100644 index 0000000000..b951f42732 --- /dev/null +++ b/Ghidra/Extensions/bundle_examples/certification.manifest @@ -0,0 +1,7 @@ +##VERSION: 2.0 +Module.manifest||GHIDRA||||END| +build.gradle||GHIDRA||||END| +extension.properties||GHIDRA||||END| +scripts_jar1/META-INF/MANIFEST.MF||GHIDRA||||END| +scripts_jar2/META-INF/MANIFEST.MF||GHIDRA||||END| +scripts_with_manifest/META-INF/MANIFEST.MF||GHIDRA||||END| diff --git a/Ghidra/Extensions/bundle_examples/extension.properties b/Ghidra/Extensions/bundle_examples/extension.properties new file mode 100644 index 0000000000..93bc8f1c31 --- /dev/null +++ b/Ghidra/Extensions/bundle_examples/extension.properties @@ -0,0 +1,5 @@ +name=Bundle Examples +description=This zip contains example script directories and jar bundles that demonstrate dynamic modularity (OSGi) features in Ghidra scripting. From a code browser menu, select Window -> Bundle Manager. Then select the plus (+) to add bundles. Navigate to the install path for this extension zip, and add all of the directories and jars from the root. In the script manager, select the category Examples/Bundle - each script demonstrates a different feature. +author=Ghidra Team +createdOn=10/14/2020 +version=@extversion@ diff --git a/Ghidra/Extensions/bundle_examples/scripts_jar1/META-INF/MANIFEST.MF b/Ghidra/Extensions/bundle_examples/scripts_jar1/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..6f835094a9 --- /dev/null +++ b/Ghidra/Extensions/bundle_examples/scripts_jar1/META-INF/MANIFEST.MF @@ -0,0 +1,7 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=11))" +Bundle-SymbolicName: org.jarlib +Bundle-Version: 1.0.0 +Bundle-Name: Example library +Export-Package: org.jarlib;version=1.0.0 diff --git a/Ghidra/Extensions/bundle_examples/scripts_jar1/org/jarlib/JarUtil.java b/Ghidra/Extensions/bundle_examples/scripts_jar1/org/jarlib/JarUtil.java new file mode 100644 index 0000000000..9e8b1e36f1 --- /dev/null +++ b/Ghidra/Extensions/bundle_examples/scripts_jar1/org/jarlib/JarUtil.java @@ -0,0 +1,22 @@ +/* ### + * 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 org.jarlib; + +public class JarUtil { + public static String getVersion() { + return "1.0"; + } +} diff --git a/Ghidra/Extensions/bundle_examples/scripts_jar2/META-INF/MANIFEST.MF b/Ghidra/Extensions/bundle_examples/scripts_jar2/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..d83ea11155 --- /dev/null +++ b/Ghidra/Extensions/bundle_examples/scripts_jar2/META-INF/MANIFEST.MF @@ -0,0 +1,7 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=11))" +Bundle-SymbolicName: org.jarlib +Bundle-Version: 2.0.0 +Bundle-Name: Example library +Export-Package: org.jarlib;version=2.0.0 diff --git a/Ghidra/Extensions/bundle_examples/scripts_jar2/org/jarlib/JarUtil.java b/Ghidra/Extensions/bundle_examples/scripts_jar2/org/jarlib/JarUtil.java new file mode 100644 index 0000000000..40873fe517 --- /dev/null +++ b/Ghidra/Extensions/bundle_examples/scripts_jar2/org/jarlib/JarUtil.java @@ -0,0 +1,22 @@ +/* ### + * 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 org.jarlib; + +public class JarUtil { + public static String getVersion() { + return "2.0"; + } +} diff --git a/Ghidra/Extensions/bundle_examples/scripts_lib/IntraBundleExampleScript.java b/Ghidra/Extensions/bundle_examples/scripts_lib/IntraBundleExampleScript.java new file mode 100644 index 0000000000..f319288d31 --- /dev/null +++ b/Ghidra/Extensions/bundle_examples/scripts_lib/IntraBundleExampleScript.java @@ -0,0 +1,31 @@ +/* ### + * 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. + */ +// Intra-bundle dependency example. +//@category Examples.Bundle + +import org.other.lib.Util; // defined in this bundle, no need to @importpackage + +import ghidra.app.script.GhidraScript; + +public class IntraBundleExampleScript extends GhidraScript { + @Override + public void run() throws Exception { + println("This script shows the use of " + Util.class.getCanonicalName() + + " from within the same bundle."); + + Util.doStuff(this); + } +} diff --git a/Ghidra/Extensions/bundle_examples/scripts_lib/org/other/lib/Util.java b/Ghidra/Extensions/bundle_examples/scripts_lib/org/other/lib/Util.java new file mode 100644 index 0000000000..af59174b2d --- /dev/null +++ b/Ghidra/Extensions/bundle_examples/scripts_lib/org/other/lib/Util.java @@ -0,0 +1,29 @@ +/* ### + * 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 org.other.lib; + +import ghidra.app.script.GhidraScript; + +public class Util { + /** + * An example of a utility function that scripts might want to use. + * + * @param script the calling script + */ + public static void doStuff(GhidraScript script) { + script.println("in " + Util.class.getCanonicalName()); + } +} diff --git a/Ghidra/Extensions/bundle_examples/scripts_lib_user/InterBundleExampleScript.java b/Ghidra/Extensions/bundle_examples/scripts_lib_user/InterBundleExampleScript.java new file mode 100644 index 0000000000..6d35da769d --- /dev/null +++ b/Ghidra/Extensions/bundle_examples/scripts_lib_user/InterBundleExampleScript.java @@ -0,0 +1,34 @@ +/* ### + * 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. + */ +// Inter-bundle dependency example. +//@category Examples.Bundle +//@importpackage org.other.lib + +import org.other.lib.Util; // from another bundle, use @importpackage + +import ghidra.app.script.GhidraScript; + +public class InterBundleExampleScript extends GhidraScript { + @Override + public void run() throws Exception { + println("This script shows the use of " + Util.class.getCanonicalName() + + " from a different bundle."); + println( + "In this case, the dependency is declared with the metadata comment \"//@importpackage org.other.lib\""); + + Util.doStuff(this); + } +} diff --git a/Ghidra/Extensions/bundle_examples/scripts_uses_jar/UsesJarExampleScript.java b/Ghidra/Extensions/bundle_examples/scripts_uses_jar/UsesJarExampleScript.java new file mode 100644 index 0000000000..dbcd8f6f8f --- /dev/null +++ b/Ghidra/Extensions/bundle_examples/scripts_uses_jar/UsesJarExampleScript.java @@ -0,0 +1,37 @@ +/* ### + * 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. + */ +// Example use of jar bundle +//@category Examples.Bundle +//@importpackage org.jarlib + +import org.jarlib.JarUtil; + +import ghidra.app.script.GhidraScript; + +public class UsesJarExampleScript extends GhidraScript { + + @Override + protected void run() throws Exception { + println("This script shows the use of " + JarUtil.class.getCanonicalName() + "."); + println(" a class defined in an external jar bundle."); + println("There are two versions of the jar in the bundle examples directory,"); + println(" since \"@importpackage\" declaration doesn't specify a version, either"); + println(" of the jar bundles, scripts_jar1.jar or scripts_jar2.jar works."); + println(" Try enabling only one of the \"scripts_jar*\" bundles and rerun this script."); + + println("Currently using JarUtil version " + JarUtil.getVersion()); + } +} diff --git a/Ghidra/Extensions/bundle_examples/scripts_uses_jar_version/UsesJarByVersionExampleScript.java b/Ghidra/Extensions/bundle_examples/scripts_uses_jar_version/UsesJarByVersionExampleScript.java new file mode 100644 index 0000000000..a5c452734a --- /dev/null +++ b/Ghidra/Extensions/bundle_examples/scripts_uses_jar_version/UsesJarByVersionExampleScript.java @@ -0,0 +1,37 @@ +/* ### + * 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. + */ +// Example use of jar bundle with a version constraint +//@category Examples.Bundle +//@importpackage org.jarlib;version="[2,3)" + +import org.jarlib.JarUtil; + +import ghidra.app.script.GhidraScript; + +public class UsesJarByVersionExampleScript extends GhidraScript { + + @Override + protected void run() throws Exception { + println("This script shows the use of " + JarUtil.class.getCanonicalName() + "."); + println(" a class defined in an external jar bundle."); + println("There are two versions of the jar in the bundle examples directory,"); + println(" since \"@importpackage\" declaration doesn't specify a version, either"); + println(" of the jar bundles, scripts_jar1.jar or scripts_jar2.jar works."); + println(" Try enabling only one of the \"scripts_jar*\" bundles and rerun this script."); + + println("Currently using JarUtil version " + JarUtil.getVersion()); + } +} diff --git a/Ghidra/Extensions/bundle_examples/scripts_with_activator/ActivatorExampleScript.java b/Ghidra/Extensions/bundle_examples/scripts_with_activator/ActivatorExampleScript.java new file mode 100644 index 0000000000..756186ae17 --- /dev/null +++ b/Ghidra/Extensions/bundle_examples/scripts_with_activator/ActivatorExampleScript.java @@ -0,0 +1,55 @@ +/* ### + * 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. + */ +// Example script that cleans up actions with a bundle activator. +//@category Examples.Bundle + +import docking.ActionContext; +import docking.action.*; +import ghidra.app.script.GhidraScript; +import ghidra.app.services.CodeViewerService; +import ghidra.app.services.ConsoleService; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.util.ProgramLocation; +import internal.MyActivator; +import resources.Icons; + +public class ActivatorExampleScript extends GhidraScript { + + @Override + protected void run() throws Exception { + PluginTool tool = state.getTool(); + println("This script is from a bundle with a custom activator class, " + + MyActivator.class.getCanonicalName() + "."); + println("When the bundle is activated, its start method is called."); + println("When deactivated, the activator's stop method is called."); + println("To demonstrate how it can be useful, this script adds an action to the toolbar,"); + println(" it's a gear icon with tooltip \"Added by script!!\"."); + println("The activator will remove the action if this bundle is deactivated,"); + println(" e.g. if this script is modified and the bundle needs to be reloaded."); + + DockingAction action = new DockingAction("Added by script!!", null, false) { + @Override + public void actionPerformed(ActionContext context) { + ConsoleService console = tool.getService(ConsoleService.class); + CodeViewerService codeviewer = tool.getService(CodeViewerService.class); + ProgramLocation loc = codeviewer.getCurrentLocation(); + console.getStdOut().printf("Current location is %s\n", loc); + } + }; + action.setToolBarData(new ToolBarData(Icons.CONFIGURE_FILTER_ICON)); + MyActivator.addAction(tool, action); + } +} diff --git a/Ghidra/Extensions/bundle_examples/scripts_with_activator/internal/MyActivator.java b/Ghidra/Extensions/bundle_examples/scripts_with_activator/internal/MyActivator.java new file mode 100644 index 0000000000..a00bbe5bc8 --- /dev/null +++ b/Ghidra/Extensions/bundle_examples/scripts_with_activator/internal/MyActivator.java @@ -0,0 +1,88 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package internal; + +import org.osgi.framework.BundleContext; + +import docking.action.DockingAction; +import ghidra.app.plugin.core.osgi.GhidraBundleActivator; +import ghidra.framework.plugintool.PluginTool; +import ghidra.util.Msg; +import ghidra.util.Swing; + +/** + * An example BundleActivator that manages a {@link DockingAction}. + */ +public class MyActivator extends GhidraBundleActivator { + + private static PluginTool storedTool; + private static DockingAction storedAction; + + /** + * add {@code action} to {@code tool} and store both to remove later. + * + * Note: this can be used exactly once per bundle lifecycle, i.e. between start & stop. + * + * @param tool the tool to add {@code action} to + * @param action the action to add to {@code tool} + * @return false if this add operation has already been performed + */ + public static boolean addAction(PluginTool tool, DockingAction action) { + if (storedTool != null || storedAction != null) { + return false; + } + storedTool = tool; + storedAction = action; + Swing.runNow(() -> { + storedTool.addAction(storedAction); + }); + return true; + } + + /** + * Called by Ghidra when bundle is activated. + * + * @param bundleContext the context for this bundle + * @param api placeholder for future Ghidra API + */ + @Override + protected void start(BundleContext bundleContext, Object api) { + if (storedAction != null) { + Msg.showError(this, null, "Activator error!", "storedAction non-null on bundle start!"); + } + } + + /** + * Called by Ghidra when bundle is deactivated. + * + * @param bundleContext the context for this bundle + * @param api placeholder for future Ghidra API + */ + @Override + protected void stop(BundleContext bundleContext, Object api) { + if (storedAction != null) { + storedAction.dispose(); + if (storedTool == null) { + Msg.showError(this, null, "Activator error!", "storedTool is null!"); + } + else { + storedTool.removeAction(storedAction); + } + storedTool = null; + storedAction = null; + } + } +} diff --git a/Ghidra/Extensions/bundle_examples/scripts_with_manifest/InterBundleManifestExampleScript.java b/Ghidra/Extensions/bundle_examples/scripts_with_manifest/InterBundleManifestExampleScript.java new file mode 100644 index 0000000000..728a147dcc --- /dev/null +++ b/Ghidra/Extensions/bundle_examples/scripts_with_manifest/InterBundleManifestExampleScript.java @@ -0,0 +1,34 @@ +/* ### + * 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. + */ +// Inter-bundle dependency example where import is done with bundle manifest. +//@category Examples.Bundle + +import org.other.lib.Util; // from another bundle, import done in META-INF/MANIFEST.MF + +import ghidra.app.script.GhidraScript; + +public class InterBundleManifestExampleScript extends GhidraScript { + @Override + public void run() throws Exception { + println("This script shows the use of " + Util.class.getCanonicalName() + + " from a different bundle."); + println( + "In this case, the dependency is declared in the source file, \"META-INF/MANIFEST.MF\""); + println(" see the line starting \"Import-Package\""); + + Util.doStuff(this); + } +} diff --git a/Ghidra/Extensions/bundle_examples/scripts_with_manifest/META-INF/MANIFEST.MF b/Ghidra/Extensions/bundle_examples/scripts_with_manifest/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..b84265e9d0 --- /dev/null +++ b/Ghidra/Extensions/bundle_examples/scripts_with_manifest/META-INF/MANIFEST.MF @@ -0,0 +1,8 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Export-Package: com.mystuff.exports +Import-Package: org.other.lib,ghidra.app.script,ghidra.app.plugin.core.osgi +Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=11))" +Bundle-SymbolicName: examples.scripts_with_manifest +Bundle-Name: Example Ghidra script directory with manifest. +Bundle-Version: 1.0