GP-707: GhidraDev updates

- New wizard to import a module source dir
- Ghidra won't launch if build dir is present
- Better validation when exporting extension
This commit is contained in:
Ryan Kurtz 2024-02-23 08:50:28 -05:00
parent fc0e3e1b6f
commit 91cb801521
20 changed files with 568 additions and 61 deletions

View File

@ -163,19 +163,19 @@
<setEntry value="ch.qos.logback.classic@default:default"/>
<setEntry value="ch.qos.logback.core@default:default"/>
<setEntry value="com.google.gson@default:default"/>
<setEntry value="com.google.guava*30.1.0.v20221112-0806@default:default"/>
<setEntry value="com.google.guava*32.1.2.jre@default:default"/>
<setEntry value="com.google.guava.failureaccess@default:default"/>
<setEntry value="com.google.guava@default:default"/>
<setEntry value="com.ibm.icu@default:default"/>
<setEntry value="com.python.pydev.analysis@default:default"/>
<setEntry value="com.python.pydev.debug@default:default"/>
<setEntry value="com.python.pydev.refactoring@default:default"/>
<setEntry value="com.sun.jna.platform@default:default"/>
<setEntry value="com.sun.jna@default:default"/>
<setEntry value="jakarta.servlet-api*4.0.0@default:default"/>
<setEntry value="jakarta.servlet-api*5.0.0@default:default"/>
<setEntry value="javax.annotation@default:default"/>
<setEntry value="javax.inject@default:default"/>
<setEntry value="jakarta.annotation-api*1.3.5@default:default"/>
<setEntry value="jakarta.annotation-api*2.1.1@default:default"/>
<setEntry value="jakarta.inject.jakarta.inject-api*1.0.5@default:default"/>
<setEntry value="jakarta.inject.jakarta.inject-api*2.0.1@default:default"/>
<setEntry value="jakarta.servlet-api@default:default"/>
<setEntry value="javax.xml@default:default"/>
<setEntry value="jaxen@default:default"/>
<setEntry value="org.apache.aries.spifly.dynamic.bundle@default:default"/>
@ -184,10 +184,10 @@
<setEntry value="org.apache.batik.i18n@default:default"/>
<setEntry value="org.apache.batik.util@default:default"/>
<setEntry value="org.apache.commons.cli@default:default"/>
<setEntry value="org.apache.commons.codec@default:default"/>
<setEntry value="org.apache.commons.commons-codec@default:default"/>
<setEntry value="org.apache.commons.commons-io@default:default"/>
<setEntry value="org.apache.commons.jxpath@default:default"/>
<setEntry value="org.apache.commons.lang3@default:default"/>
<setEntry value="org.apache.commons.logging@default:default"/>
<setEntry value="org.apache.felix.gogo.command@default:default"/>
<setEntry value="org.apache.felix.gogo.runtime@default:default"/>
@ -204,9 +204,8 @@
<setEntry value="org.eclipse.buildship.compat@default:default"/>
<setEntry value="org.eclipse.buildship.core@default:default"/>
<setEntry value="org.eclipse.buildship.ui@default:default"/>
<setEntry value="org.eclipse.cdt.core.macosx*5.3.0.201502131403@default:default"/>
<setEntry value="org.eclipse.cdt.core.native*5.7.0.201502131403@default:default"/>
<setEntry value="org.eclipse.cdt.core.win32*5.4.0.201502131403@default:false"/>
<setEntry value="org.eclipse.cdt.core.win32.x86_64*5.3.0.201502131403@default:false"/>
<setEntry value="org.eclipse.cdt.core@default:default"/>
<setEntry value="org.eclipse.cdt.ui@default:default"/>
<setEntry value="org.eclipse.compare.core@default:default"/>
@ -220,13 +219,10 @@
<setEntry value="org.eclipse.core.expressions@default:default"/>
<setEntry value="org.eclipse.core.externaltools@default:default"/>
<setEntry value="org.eclipse.core.filebuffers@default:default"/>
<setEntry value="org.eclipse.core.filesystem.win32.x86_64@default:false"/>
<setEntry value="org.eclipse.core.filesystem.macosx@default:default"/>
<setEntry value="org.eclipse.core.filesystem@default:default"/>
<setEntry value="org.eclipse.core.jobs@default:default"/>
<setEntry value="org.eclipse.core.net.win32.x86_64@default:false"/>
<setEntry value="org.eclipse.core.net.win32@default:false"/>
<setEntry value="org.eclipse.core.net@default:default"/>
<setEntry value="org.eclipse.core.resources.win32.x86_64@default:false"/>
<setEntry value="org.eclipse.core.resources@default:default"/>
<setEntry value="org.eclipse.core.runtime@default:true"/>
<setEntry value="org.eclipse.core.variables@default:default"/>
@ -250,9 +246,9 @@
<setEntry value="org.eclipse.e4.ui.model.workbench@default:default"/>
<setEntry value="org.eclipse.e4.ui.progress@default:default"/>
<setEntry value="org.eclipse.e4.ui.services@default:default"/>
<setEntry value="org.eclipse.e4.ui.swt.win32@default:false"/>
<setEntry value="org.eclipse.e4.ui.widgets@default:default"/>
<setEntry value="org.eclipse.e4.ui.workbench.addons.swt@default:default"/>
<setEntry value="org.eclipse.e4.ui.workbench.renderers.swt.cocoa@default:default"/>
<setEntry value="org.eclipse.e4.ui.workbench.renderers.swt@default:default"/>
<setEntry value="org.eclipse.e4.ui.workbench.swt@default:default"/>
<setEntry value="org.eclipse.e4.ui.workbench3@default:default"/>
@ -289,15 +285,15 @@
<setEntry value="org.eclipse.equinox.p2.ui@default:default"/>
<setEntry value="org.eclipse.equinox.preferences@default:default"/>
<setEntry value="org.eclipse.equinox.registry@default:default"/>
<setEntry value="org.eclipse.equinox.security.macosx@default:default"/>
<setEntry value="org.eclipse.equinox.security.ui@default:default"/>
<setEntry value="org.eclipse.equinox.security.win32.x86_64@default:false"/>
<setEntry value="org.eclipse.equinox.security@default:default"/>
<setEntry value="org.eclipse.equinox.simpleconfigurator.manipulator@default:default"/>
<setEntry value="org.eclipse.equinox.simpleconfigurator@1:true"/>
<setEntry value="org.eclipse.help.base@default:default"/>
<setEntry value="org.eclipse.help.ui@default:default"/>
<setEntry value="org.eclipse.help@default:default"/>
<setEntry value="org.eclipse.jdt.annotation*2.2.700.v20220826-1026@default:default"/>
<setEntry value="org.eclipse.jdt.annotation*2.2.800.v20231029-1039@default:default"/>
<setEntry value="org.eclipse.jdt.core.compiler.batch@default:default"/>
<setEntry value="org.eclipse.jdt.core.manipulation@default:default"/>
<setEntry value="org.eclipse.jdt.core@default:default"/>
@ -309,14 +305,18 @@
<setEntry value="org.eclipse.jdt.launching@default:default"/>
<setEntry value="org.eclipse.jdt.ui@default:default"/>
<setEntry value="org.eclipse.jem.util@default:default"/>
<setEntry value="org.eclipse.jetty.ee8.security@default:default"/>
<setEntry value="org.eclipse.jetty.ee8.server@default:default"/>
<setEntry value="org.eclipse.jetty.ee8.servlet@default:default"/>
<setEntry value="org.eclipse.jetty.ee8.webapp@default:default"/>
<setEntry value="org.eclipse.jetty.http@default:default"/>
<setEntry value="org.eclipse.jetty.io@default:default"/>
<setEntry value="org.eclipse.jetty.security@default:default"/>
<setEntry value="org.eclipse.jetty.server@default:default"/>
<setEntry value="org.eclipse.jetty.servlet@default:default"/>
<setEntry value="org.eclipse.jetty.servlet-api@default:default"/>
<setEntry value="org.eclipse.jetty.session@default:default"/>
<setEntry value="org.eclipse.jetty.util.ajax@default:default"/>
<setEntry value="org.eclipse.jetty.util@default:default"/>
<setEntry value="org.eclipse.jetty.webapp@default:default"/>
<setEntry value="org.eclipse.jetty.xml@default:default"/>
<setEntry value="org.eclipse.jface.databinding@default:default"/>
<setEntry value="org.eclipse.jface.notifications@default:default"/>
@ -324,30 +324,33 @@
<setEntry value="org.eclipse.jface@default:default"/>
<setEntry value="org.eclipse.ltk.core.refactoring@default:default"/>
<setEntry value="org.eclipse.ltk.ui.refactoring@default:default"/>
<setEntry value="org.eclipse.m2e.archetype.catalog@default:default"/>
<setEntry value="org.eclipse.m2e.archetype.catalog@default:false"/>
<setEntry value="org.eclipse.m2e.archetype.common@default:default"/>
<setEntry value="org.eclipse.m2e.archetype.descriptor@default:default"/>
<setEntry value="org.eclipse.m2e.archetype.maven-artifact-transfer@default:default"/>
<setEntry value="org.eclipse.m2e.archetype.descriptor@default:false"/>
<setEntry value="org.eclipse.m2e.archetype.maven-artifact-transfer@default:false"/>
<setEntry value="org.eclipse.m2e.core.ui@default:default"/>
<setEntry value="org.eclipse.m2e.core@default:default"/>
<setEntry value="org.eclipse.m2e.logback@default:false"/>
<setEntry value="org.eclipse.m2e.maven.runtime@default:default"/>
<setEntry value="org.eclipse.m2e.model.edit@default:default"/>
<setEntry value="org.eclipse.m2e.workspace.cli@default:default"/>
<setEntry value="org.eclipse.orbit.xml-apis-ext@default:default"/>
<setEntry value="org.eclipse.osgi.compatibility.state@default:false"/>
<setEntry value="org.eclipse.osgi.services@default:default"/>
<setEntry value="org.eclipse.osgi.util@default:default"/>
<setEntry value="org.eclipse.osgi@-1:true"/>
<setEntry value="org.eclipse.platform@default:default"/>
<setEntry value="org.eclipse.rap.tools.launch.rwt@default:default"/>
<setEntry value="org.eclipse.search.core@default:default"/>
<setEntry value="org.eclipse.search@default:default"/>
<setEntry value="org.eclipse.swt.win32.win32.x86_64@default:false"/>
<setEntry value="org.eclipse.swt.cocoa.macosx.aarch64@default:default"/>
<setEntry value="org.eclipse.swt@default:default"/>
<setEntry value="org.eclipse.team.core@default:default"/>
<setEntry value="org.eclipse.team.ui@default:default"/>
<setEntry value="org.eclipse.text@default:default"/>
<setEntry value="org.eclipse.ui.browser@default:default"/>
<setEntry value="org.eclipse.ui.cheatsheets@default:default"/>
<setEntry value="org.eclipse.ui.cocoa@default:default"/>
<setEntry value="org.eclipse.ui.console@default:default"/>
<setEntry value="org.eclipse.ui.editors@default:default"/>
<setEntry value="org.eclipse.ui.forms@default:default"/>
@ -361,7 +364,6 @@
<setEntry value="org.eclipse.ui.views.log@default:default"/>
<setEntry value="org.eclipse.ui.views.properties.tabbed@default:default"/>
<setEntry value="org.eclipse.ui.views@default:default"/>
<setEntry value="org.eclipse.ui.win32@default:false"/>
<setEntry value="org.eclipse.ui.workbench.texteditor@default:default"/>
<setEntry value="org.eclipse.ui.workbench@default:default"/>
<setEntry value="org.eclipse.ui@default:default"/>
@ -421,16 +423,12 @@
<setEntry value="org.sat4j.core@default:default"/>
<setEntry value="org.sat4j.pb@default:default"/>
<setEntry value="org.tukaani.xz@default:default"/>
<setEntry value="org.w3c.css.sac@default:default"/>
<setEntry value="org.w3c.dom.events@default:default"/>
<setEntry value="org.w3c.dom.smil@default:default"/>
<setEntry value="org.w3c.dom.svg@default:default"/>
<setEntry value="slf4j.api@default:default"/>
</setAttribute>
<setAttribute key="selected_workspace_bundles">
<setEntry value="ghidra.ghidradev@default:default"/>
</setAttribute>
<booleanAttribute key="show_selected_only" value="false"/>
<booleanAttribute key="show_selected_only" value="true"/>
<booleanAttribute key="tracing" value="false"/>
<booleanAttribute key="useCustomFeatures" value="false"/>
<booleanAttribute key="useDefaultConfig" value="true"/>

View File

@ -36,6 +36,7 @@ change with future releases.</p>
<li><a href="#NewGhidraScript">New Ghidra Script</a></li>
<li><a href="#NewGhidraScriptProject">New Ghidra Script Project</a></li>
<li><a href="#NewGhidraModuleProject">New Ghidra Module Project</a></li>
<li><a href="#ImportGhidraModuleSource">Import Ghidra Module Source</a></li>
<li><a href="#ExportGhidraModuleExtension">Export Ghidra Module Extension</a></li>
<li><a href="#Preferences">Preferences</a></li>
<li><a href="#LinkGhidra">Link Ghidra</a></li>
@ -59,6 +60,14 @@ change with future releases.</p>
GhidraDev has been upgraded to be compatible with Ghidra 11.1 and later. Older versions of
GhidraDev will report an error when trying to link against Ghidra 11.1 or later.
</li>
<li>
GhidraDev now supports importing a Ghidra module source directory. This will work best
with Ghidra module projects created from Ghidra 11.1 or later.
</li>
<li>
GhidraDev will now fail to launch Ghidra if a top-level <i>build</i> directory is detected.
Presence of this intermediate build artifact can cause Ghidra to have runtime/debugging issues.
</li>
</ul>
<p><u><b>3.0.2</b>:</u>
<ul>
@ -243,6 +252,13 @@ installed into Ghidra as an "extension".</p>
project can be initialized with optional template source files that provide a good starting
point for implementing advanced Ghidra features such as Analyzers, Plugins, Loaders, etc.
</li>
</ul>
<li>Import</li>
<ul>
<li>
<a name="ImportGhidraModuleSource"></a><b>Ghidra Module Source:</b> Opens a wizard that
imports a Ghidra module source directory as a new Ghidra module project.
</li>
</ul>
<li>Export</li>
<ul>

View File

@ -9,6 +9,7 @@ Require-Bundle: org.eclipse.ant.core;bundle-version="3.6.200",
org.eclipse.buildship.core;bundle-version="3.1.5",
org.eclipse.core.expressions;bundle-version="3.8.100",
org.eclipse.core.externaltools;bundle-version="1.2.100",
org.eclipse.core.resources;bundle-version="3.16.0",
org.eclipse.core.runtime;bundle-version="3.24.0",
org.eclipse.debug.ui;bundle-version="3.15.200",
org.eclipse.jdt.core;bundle-version="3.28.0",

View File

@ -38,6 +38,23 @@
project="true">
</wizard>
</extension>
<extension
point="org.eclipse.ui.importWizards">
<category
id="GhidraCategory"
name="Ghidra">
</category>
<wizard
category="GhidraCategory"
class="ghidradev.ghidraprojectcreator.wizards.ImportGhidraModuleSourceWizard"
icon="icons/brick_add.png"
id="ghidradev.ghidraprojectcreator.wizards.ImportGhidraModuleSourceWizard"
name="Ghidra Module Source">
<description>
Imports a Ghidra module source directory to a new project.
</description>
</wizard>
</extension>
<extension
point="org.eclipse.ui.exportWizards">
<category
@ -50,6 +67,9 @@
icon="icons/brick_go.png"
id="ghidradev.ghidraprojectcreator.wizards.ExportGhidraModuleWizard"
name="Ghidra Module Extension">
<description>
Exports a Ghidra module project to a zipped Ghidra extension.
</description>
</wizard>
</extension>
<extension
@ -146,6 +166,19 @@
</parameter>
</command>
</menu>
<menu
label="Import">
<command
commandId="org.eclipse.ui.file.import"
icon="icons/brick_add.png"
label="Ghidra Module Source"
style="push">
<parameter
name="importWizardId"
value="ghidradev.ghidraprojectcreator.wizards.ImportGhidraModuleWizard">
</parameter>
</command>
</menu>
<menu
label="Export">
<command

View File

@ -73,6 +73,15 @@ public class GhidraLaunchDelegate extends JavaLaunchDelegate {
return;
}
// Make sure there isn't a build/ directory present...it messes up the classpath.
// The build directory could exist if the user built an extension from the command line
// rather than from the Eclipse wizard
if (javaProject.getProject().getFolder("build").exists()) {
EclipseMessageUtils.showErrorDialog("Failed to launch project \"" + projectName +
"\".\nDelete top-level 'build' directory and try again.");
return;
}
// Set program arguments
String customProgramArgs =
configuration.getAttribute(GhidraLaunchUtils.ATTR_PROGAM_ARGUMENTS, "").trim();

View File

@ -44,6 +44,12 @@ public class GhidraProjectCreatorPreferences {
*/
static final String GHIDRA_LAST_PROJECT_ROOT_PATH = "ghidradev.ghidraLastProjectRootPath";
/**
* Path to the last used Ghidra module source directory.
*/
static final String GHIDRA_LAST_MODULE_SOURCE_DIR_PATH =
"ghidradev.ghidraLastModuleSourceDirPath";
/**
* The last used Gradle distribution.
*/
@ -123,6 +129,28 @@ public class GhidraProjectCreatorPreferences {
prefs.setValue(GHIDRA_LAST_PROJECT_ROOT_PATH, path);
}
/**
* Gets the last used Ghidra module source directory path that's defined in the preferences.
*
* @return The last used Ghidra module source directory path that's defined in the preferences.
* Could be the empty string.
*/
public static String getGhidraLastModuleSourceDirPath() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
return prefs.getString(GHIDRA_LAST_MODULE_SOURCE_DIR_PATH);
}
/**
* Sets the last used Ghidra module source directory path that's defined in the preferences.
*
* @param path The last used Ghidra module source directory path that's defined in the
* preferences.
*/
public static void setGhidraLastModuleSourceDirPath(String path) {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
prefs.setValue(GHIDRA_LAST_MODULE_SOURCE_DIR_PATH, path);
}
/**
* Gets the last used Ghidra Gradle distribution that's defined in the preferences.
*

View File

@ -32,6 +32,7 @@ import ghidra.GhidraLauncher;
/**
* Utility methods for working with Ghidra launchers in Eclipse.
*/
@SuppressWarnings("restriction")
public class GhidraLaunchUtils {
/**

View File

@ -94,6 +94,7 @@ public class GhidraModuleUtils {
sourceFolders.add(project.getFolder("src/main/java"));
sourceFolders.add(project.getFolder("src/main/help"));
sourceFolders.add(project.getFolder("src/main/resources"));
sourceFolders.add(project.getFolder("src/test/java"));
sourceFolders.add(project.getFolder("ghidra_scripts"));
for (IFolder sourceFolder : sourceFolders) {
GhidraProjectUtils.createFolder(sourceFolder, monitor);
@ -203,6 +204,76 @@ public class GhidraModuleUtils {
return null;
}
/**
* Imports a Ghidra module source directory to a new Ghidra module project with the given name.
*
* @param projectName The name of the project to create.
* @param moduleSourceDir The module source directory to import.
* @param createRunConfig Whether or not to create a new run configuration for the project.
* @param runConfigMemory The run configuration's desired memory. Could be null.
* @param ghidraLayout The Ghidra layout to link the project to.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param monitor The progress monitor to use during project creation.
* @return The imported project.
* @throws IOException If there was a file-related problem with creating the project.
* @throws ParseException If there was a parse-related problem with creating the project.
* @throws CoreException If there was an Eclipse-related problem with creating the project.
*/
public static IJavaProject importGhidraModuleSource(String projectName, File moduleSourceDir,
boolean createRunConfig, String runConfigMemory, GhidraApplicationLayout ghidraLayout,
String jythonInterpreterName, IProgressMonitor monitor)
throws IOException, ParseException, CoreException {
// Create empty Ghidra project
IJavaProject javaProject =
GhidraProjectUtils.createEmptyGhidraProject(projectName, moduleSourceDir,
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
IProject project = javaProject.getProject();
// Find source directory paths
List<IPath> sourcePaths = new ArrayList<>();
IFolder srcFolder = project.getFolder("src");
List<IFolder> srcSubFolders = getSubFolders(srcFolder);
if (!srcSubFolders.isEmpty()) {
for (IFolder srcSubFolder : srcSubFolders) {
List<IFolder> subSubFolders = getSubFolders(srcSubFolder);
if (!subSubFolders.isEmpty()) {
sourcePaths.addAll(subSubFolders.stream().map(e -> e.getFullPath()).toList());
}
else {
sourcePaths.add(srcSubFolder.getFullPath());
}
}
}
else {
sourcePaths.add(srcFolder.getFullPath());
}
// Find jar file paths
List<IPath> jarPaths = new ArrayList<>();
IFolder libFolder = project.getFolder("lib");
if (libFolder.exists()) {
for (IResource resource : libFolder.members()) {
if (resource.getType() == IResource.FILE &&
resource.getFileExtension().equals("jar")) {
jarPaths.add(resource.getFullPath());
}
}
}
// Put the source and jar paths in the project's classpath
List<IClasspathEntry> cp = new ArrayList<>();
cp.addAll(sourcePaths.stream().map(e -> JavaCore.newSourceEntry(e)).toList());
cp.addAll(jarPaths.stream().map(e -> JavaCore.newLibraryEntry(e, null, null)).toList());
GhidraProjectUtils.addToClasspath(javaProject, cp, monitor);
// Update language ant properties file
GhidraModuleUtils.writeAntProperties(project, ghidraLayout);
return javaProject;
}
/**
* Writes project-specific ant properties, which get imported by the module project's language
* build.xml file to allow building against a Ghidra that lives in an external location. If the
@ -278,4 +349,24 @@ public class GhidraModuleUtils {
Change change = refactoring.createChange(monitor);
change.perform(monitor);
}
/**
* Gets a {@link List} of sub-folders
*
* @param folder The folder to get the sub-folders of
* @return A {@link List} of
* @throws CoreException If there was an Eclipse-related problem getting the sub-folders
*/
private static List<IFolder> getSubFolders(IFolder folder) throws CoreException {
List<IFolder> subFolders = new ArrayList<>();
if (folder.exists()) {
for (IResource resource : folder.members()) {
if (resource.getType() == IResource.FOLDER) {
subFolders.add(folder.getFolder(resource.getName()));
}
}
}
return subFolders;
}
}

View File

@ -16,6 +16,7 @@
package ghidradev.ghidraprojectcreator.utils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.*;
@ -43,6 +44,7 @@ import utility.module.ModuleUtilities;
/**
* Utility methods for working with Eclipse Ghidra projects.
*/
@SuppressWarnings("restriction")
public class GhidraProjectUtils {
/**
@ -290,6 +292,9 @@ public class GhidraProjectUtils {
IJavaProject javaProject = JavaCore.create(project);
project.open(monitor);
// Set the project's default encoding
project.setDefaultCharset(StandardCharsets.UTF_8.displayName(), monitor);
// Clear the project's classpath
javaProject.setRawClasspath(new IClasspathEntry[0], monitor);

View File

@ -61,7 +61,7 @@ public class CreateGhidraModuleProjectWizard extends Wizard implements INewWizar
@Override
public void init(IWorkbench wb, IStructuredSelection selection) {
workbench = wb;
projectPage = new CreateGhidraProjectWizardPage();
projectPage = new CreateGhidraProjectWizardPage(true);
projectConfigPage = new ConfigureGhidraModuleProjectWizardPage();
ghidraInstallationPage = new ChooseGhidraInstallationWizardPage();
pythonPage = new EnablePythonWizardPage(ghidraInstallationPage);

View File

@ -54,7 +54,7 @@ public class CreateGhidraScriptProjectWizard extends Wizard implements INewWizar
@Override
public void init(IWorkbench wb, IStructuredSelection selection) {
projectPage = new CreateGhidraProjectWizardPage("GhidraScripts");
projectPage = new CreateGhidraProjectWizardPage("GhidraScripts", true);
projectConfigPage = new ConfigureGhidraScriptProjectWizardPage();
ghidraInstallationPage = new ChooseGhidraInstallationWizardPage();
pythonPage = new EnablePythonWizardPage(ghidraInstallationPage);

View File

@ -41,6 +41,7 @@ import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout;
import ghidra.launch.JavaConfig;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.wizards.pages.ChooseGhidraModuleProjectWizardPage;
import ghidradev.ghidraprojectcreator.wizards.pages.ConfigureGradleWizardPage;
@ -77,6 +78,10 @@ public class ExportGhidraModuleWizard extends Wizard implements INewWizard {
@Override
public boolean performFinish() {
if (!validate()) {
return false;
}
IJavaProject javaProject = projectPage.getGhidraModuleProject();
GradleDistribution gradleDist = gradlePage.getGradleDistribution();
try {
@ -169,4 +174,26 @@ public class ExportGhidraModuleWizard extends Wizard implements INewWizard {
monitor.done();
}
}
/**
* Validates the wizard pages. If they are invalid, an error popup will be displayed which
* will indicate the problem.
*
* @return True if the data returned from the wizard pages are valid; otherwise, false
*/
private boolean validate() {
String title = "Invalid Ghidra Module Extension";
IJavaProject javaProject = projectPage.getGhidraModuleProject();
if (!javaProject.getProject().getFile("extension.properties").exists()) {
EclipseMessageUtils.showErrorDialog(title,
"Cannot export extension because 'extension.properties' file does not exist.");
return false;
}
if (!javaProject.getProject().getFile("Module.manifest").exists()) {
EclipseMessageUtils.showErrorDialog(title,
"Cannot export extension because 'Module.manifest' file does not exist.");
return false;
}
return true;
}
}

View File

@ -0,0 +1,150 @@
/* ###
* 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 ghidradev.ghidraprojectcreator.wizards;
import static ghidradev.EclipseMessageUtils.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.IImportWizard;
import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils;
import ghidradev.ghidraprojectcreator.wizards.pages.*;
import utilities.util.FileUtilities;
/**
* Wizard for importing Ghidra module source to a new Ghidra module project.
*/
public class ImportGhidraModuleSourceWizard extends Wizard implements IImportWizard {
private ChooseGhidraModuleSourceWizardPage sourcePage;
private CreateGhidraProjectWizardPage projectPage;
private ChooseGhidraInstallationWizardPage ghidraInstallationPage;
private EnablePythonWizardPage pythonPage;
public ImportGhidraModuleSourceWizard() {
super();
}
@Override
public void init(IWorkbench wb, IStructuredSelection selection) {
sourcePage = new ChooseGhidraModuleSourceWizardPage();
projectPage = new CreateGhidraProjectWizardPage(false);
ghidraInstallationPage = new ChooseGhidraInstallationWizardPage();
pythonPage = new EnablePythonWizardPage(ghidraInstallationPage);
}
@Override
public void addPages() {
addPage(sourcePage);
addPage(projectPage);
addPage(ghidraInstallationPage);
addPage(pythonPage);
}
@Override
public boolean performFinish() {
if (!validate()) {
return false;
}
File moduleSourceDir = sourcePage.getSourceDir();
File ghidraInstallDir = ghidraInstallationPage.getGhidraInstallDir();
String projectName = projectPage.getProjectName();
boolean createRunConfig = projectPage.shouldCreateRunConfig();
String runConfigMemory = projectPage.getRunConfigMemory();
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
try {
getContainer().run(true, false,
monitor -> importModuleSource(ghidraInstallDir, projectName, moduleSourceDir,
createRunConfig, runConfigMemory, jythonInterpreterName, monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
catch (InvocationTargetException e) {
error(showWizardErrorDialog(getShell(), e), e);
return false;
}
return true;
}
/**
* Imports a Ghidra module source directory to a new Ghidra module project.
*
* @param ghidraInstallDir The Ghidra installation directory to use.
* @param projectName The name of the project to create.
* @param moduleSourceDir The module source directory to import.
* @param createRunConfig Whether or not to create a new run configuration for the project.
* @param runConfigMemory The run configuration's desired memory. Could be null.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param monitor The monitor to use during project creation.
* @throws InvocationTargetException if an error occurred during project creation.
*/
private void importModuleSource(File ghidraInstallDir, String projectName, File moduleSourceDir,
boolean createRunConfig, String runConfigMemory, String jythonInterpreterName,
IProgressMonitor monitor) throws InvocationTargetException {
try {
info("Importing " + projectName + " at " + moduleSourceDir);
monitor.beginTask("Importing " + projectName, 2);
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraInstallDir);
monitor.worked(1);
GhidraModuleUtils.importGhidraModuleSource(projectName, moduleSourceDir,
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
monitor.worked(1);
info("Finished importing " + projectName);
}
catch (IOException | ParseException | CoreException e) {
throw new InvocationTargetException(e);
}
finally {
monitor.done();
}
}
/**
* Validates the wizard pages. If they are invalid, an error popup will be displayed which
* will indicate the problem.
*
* @return True if the data returned from the wizard pages are valid; otherwise, false
*/
private boolean validate() {
if (FileUtilities.isPathContainedWithin(ghidraInstallationPage.getGhidraInstallDir(),
sourcePage.getSourceDir())) {
EclipseMessageUtils.showErrorDialog("Invalid Module Source Directory",
"Module source directory cannot reside inside of the selected Ghidra installation directory.");
return false;
}
return true;
}
}

View File

@ -0,0 +1,124 @@
/* ###
* 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 ghidradev.ghidraprojectcreator.wizards.pages;
import java.io.File;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferences;
/**
* A wizard page that lets the user choose a Ghidra module source directory.
*/
public class ChooseGhidraModuleSourceWizardPage extends WizardPage {
private Text sourceDirText;
private Button sourceDirButton;
/**
* Creates a new Ghidra module source chooser wizard page.
*/
public ChooseGhidraModuleSourceWizardPage() {
super("ChooseGhidraModuleSourceWizardPage");
setTitle("Choose Ghidra Module Source");
setDescription("Choose a Ghidra module source directory.");
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(3, false));
// Source directory
Label sourceDirLabel = new Label(container, SWT.NULL);
String sourceDirToolTip = "The Ghidra module source directory.";
sourceDirLabel.setText("Source directory:");
sourceDirLabel.setToolTipText(sourceDirToolTip);
sourceDirText = new Text(container, SWT.BORDER | SWT.SINGLE);
sourceDirText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
sourceDirText.setText(GhidraProjectCreatorPreferences.getGhidraLastModuleSourceDirPath());
sourceDirText.addModifyListener(evt -> validate());
sourceDirText.setToolTipText(sourceDirToolTip);
sourceDirButton = new Button(container, SWT.BUTTON1);
sourceDirButton.setText("...");
sourceDirButton.setToolTipText("Browse to select source directory");
sourceDirButton.addListener(SWT.Selection, evt -> {
DirectoryDialog dialog = new DirectoryDialog(container.getShell());
String path = dialog.open();
if (path != null) {
sourceDirText.setText(path);
}
});
validate();
setControl(container);
}
/**
* Gets the module source directory.
*
* @return The module source directory. Could be null if unspecified, however, the page will not
* be valid until the module source directory is valid, so it should never be null when called
* by other classes.
*/
public File getSourceDir() {
if (sourceDirText.getText().isEmpty()) {
return null;
}
return new File(sourceDirText.getText());
}
/**
* Validates the fields on the page and updates the page's status.
* Should be called every time a field on the page changes.
*/
private void validate() {
String message = null;
File sourceDir = new File(sourceDirText.getText());
if (!sourceDir.isAbsolute()) {
message = "Source directory must be an absolute path";
}
else if (!sourceDir.isDirectory()) {
message = "Source directory does not exist";
}
else if (!new File(sourceDir, "Module.manifest").exists()) {
message = "Source directory does not contain a Module.manifest file";
}
else if (!new File(sourceDir, "build.gradle").exists()) {
message = "Source directory does not contain a build.gradle file";
}
else if (new File(sourceDir, ".project").exists()) {
message = "Source directory already contains a .project file";
}
else if (new File(sourceDir, ".classpath").exists()) {
message = "Source directory already contains a .classpath file";
}
setErrorMessage(message);
setPageComplete(message == null);
if (message == null) {
GhidraProjectCreatorPreferences
.setGhidraLastModuleSourceDirPath(sourceDirText.getText());
}
}
}

View File

@ -39,6 +39,7 @@ import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
public class CreateGhidraProjectWizardPage extends WizardPage {
private String suggestedProjectName;
private boolean showProjectDir;
private Text projectNameText;
private Text projectRootDirText;
@ -50,19 +51,25 @@ public class CreateGhidraProjectWizardPage extends WizardPage {
* Creates a Ghidra new project wizard page with the given suggested project name.
*
* @param suggestedProjectName The suggested project name.
* @param showProjectDir True to show a component for selecting the root project directory;
* otherwise, false
*/
public CreateGhidraProjectWizardPage(String suggestedProjectName) {
public CreateGhidraProjectWizardPage(String suggestedProjectName, boolean showProjectDir) {
super("CreateGhidraProjectWizardPage");
setTitle("Create Ghidra Project");
setDescription("Create a new Ghidra project.");
this.suggestedProjectName = suggestedProjectName;
this.showProjectDir = showProjectDir;
}
/**
* Creates a Ghidra new project wizard page.
*
* @param showProjectDir True to show a component for selecting the root project directory;
* otherwise, false
*/
public CreateGhidraProjectWizardPage() {
this("");
public CreateGhidraProjectWizardPage(boolean showProjectDir) {
this("", showProjectDir);
}
@Override
@ -81,25 +88,28 @@ public class CreateGhidraProjectWizardPage extends WizardPage {
new Label(container, SWT.NONE).setText(""); // empty grid cell
// Project directory
Label projectDirLabel = new Label(container, SWT.NULL);
String projectDirToolTip = "The directory where this project will be created.";
projectDirLabel.setText("Project root directory:");
projectDirLabel.setToolTipText(projectDirToolTip);
projectRootDirText = new Text(container, SWT.BORDER | SWT.SINGLE);
projectRootDirText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
projectRootDirText.setText(GhidraProjectCreatorPreferences.getGhidraLastProjectRootPath());
projectRootDirText.addModifyListener(evt -> validate());
projectRootDirText.setToolTipText(projectDirToolTip);
projectDirButton = new Button(container, SWT.BUTTON1);
projectDirButton.setText("...");
projectDirButton.setToolTipText("Browse to select project root directory");
projectDirButton.addListener(SWT.Selection, evt -> {
DirectoryDialog dialog = new DirectoryDialog(container.getShell());
String path = dialog.open();
if (path != null) {
projectRootDirText.setText(path);
}
});
if (showProjectDir) {
Label projectDirLabel = new Label(container, SWT.NULL);
String projectDirToolTip = "The directory where this project will be created.";
projectDirLabel.setText("Project root directory:");
projectDirLabel.setToolTipText(projectDirToolTip);
projectRootDirText = new Text(container, SWT.BORDER | SWT.SINGLE);
projectRootDirText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
projectRootDirText
.setText(GhidraProjectCreatorPreferences.getGhidraLastProjectRootPath());
projectRootDirText.addModifyListener(evt -> validate());
projectRootDirText.setToolTipText(projectDirToolTip);
projectDirButton = new Button(container, SWT.BUTTON1);
projectDirButton.setText("...");
projectDirButton.setToolTipText("Browse to select project root directory");
projectDirButton.addListener(SWT.Selection, evt -> {
DirectoryDialog dialog = new DirectoryDialog(container.getShell());
String path = dialog.open();
if (path != null) {
projectRootDirText.setText(path);
}
});
}
// Create run configuration checkbox
createRunConfigCheckboxButton = new Button(container, SWT.CHECK);
@ -153,14 +163,13 @@ public class CreateGhidraProjectWizardPage extends WizardPage {
* Gets the project directory. This is the directory where the .project file should live.
*
* @return The project directory. This is the directory where the .project file should live.
* Could be null if unspecified, however, the page will not be valid until the project
* directory is valid, so it should never be null when called by other classes.
* Could be null if unspecified.
*/
public File getProjectDir() {
if (projectNameText.getText().isEmpty()) {
return null;
}
if (projectRootDirText.getText().isEmpty()) {
if (projectRootDirText == null || projectRootDirText.getText().isEmpty()) {
return null;
}
return new File(projectRootDirText.getText(), getProjectName());
@ -227,10 +236,10 @@ public class CreateGhidraProjectWizardPage extends WizardPage {
else if (BAD.chars().anyMatch(ch -> projectName.indexOf(ch) != -1)) {
message = "Project name cannot contain invalid characters:\n " + BAD;
}
else if (projectDir == null) {
else if (showProjectDir && projectDir == null) {
message = "Project root directory must be specified";
}
else if (projectDir.exists()) {
else if (showProjectDir && projectDir.exists()) {
message = "Project already exists at: " + projectDir.getAbsolutePath();
}
else if (ResourcesPlugin.getWorkspace().getRoot().getProject(projectName).exists()) {
@ -250,8 +259,10 @@ public class CreateGhidraProjectWizardPage extends WizardPage {
setErrorMessage(message);
setPageComplete(message == null);
if (message == null) {
GhidraProjectCreatorPreferences.setGhidraLastProjectRootPath(
projectRootDirText.getText());
if (projectRootDirText != null) {
GhidraProjectCreatorPreferences
.setGhidraLastProjectRootPath(projectRootDirText.getText());
}
}
}
}

View File

@ -34,6 +34,7 @@ import org.eclipse.ui.ide.IDE;
import ghidradev.EclipseMessageUtils;
@SuppressWarnings("restriction")
public class OpenDeclarations {
private IProject project;

View File

@ -37,6 +37,7 @@ rootProject.assembleDistribution {
exclude '.project'
exclude 'build.gradle'
rename "buildTemplate.gradle", "build.gradle"
rename "gitignore", ".gitignore"
into "Extensions/Ghidra/Skeleton"
}
}

View File

@ -11,6 +11,7 @@ data/languages/skel.slaspec||GHIDRA||||END|
data/sleighArgs.txt||GHIDRA||||END|
extension.properties||GHIDRA||||END|
ghidra_scripts/README.txt||GHIDRA||||END|
gitignore||GHIDRA||||END|
lib/README.txt||GHIDRA||||END|
os/linux_x86_64/README.txt||GHIDRA||||END|
os/mac_x86_64/README.txt||GHIDRA||||END|

View File

@ -0,0 +1,10 @@
.project
.pyproject
.classpath
.settings/
.gradle/
bin/
build/
dist/
*.swp
.antProperties.xml

View File

@ -41,7 +41,7 @@ public class SkeletonAnalyzer extends AbstractAnalyzer {
// TODO: Return true if analyzer should be enabled by default
return false;
return true;
}
@Override
@ -50,7 +50,7 @@ public class SkeletonAnalyzer extends AbstractAnalyzer {
// TODO: Examine 'program' to determine of this analyzer should analyze it. Return true
// if it can.
return false;
return true;
}
@Override