Candidate release of source code.

This commit is contained in:
Dan 2019-03-26 13:45:32 -04:00
parent db81e6b3b0
commit 79d8f164f8
12449 changed files with 2800756 additions and 16 deletions

View file

@ -0,0 +1,130 @@
/* ###
* IP: GHIDRA
* NOTE: eclipse plugin code
*
* 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;
import java.io.*;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
/**
* The activator class controls the plug-in life cycle.
*
* NOTE: Plugins declaring extensions or extension points must be implemented as a singleton.
*/
public class Activator extends AbstractUIPlugin {
private Set<Closeable> closeSet = new HashSet<>();
/**
* The plug-in ID.
*/
public static final String PLUGIN_ID = "GhidraDev";
/**
* A system property that Ghidra sets when launching Eclipse.
*/
private static final String GHIDRA_INSTALL_DIR_PROPERTY = "ghidra.install.dir";
/**
* The shared instance.
*/
private static Activator plugin;
/**
* Returns the shared instance.
*
* @return the shared instance. Could be null if the plugin is not started.
*/
public static Activator getDefault() {
return plugin;
}
/**
* Registers an object to be closed when the plugin shuts down.
*
* @param c The object to register for close on shutdown.
*/
public synchronized void registerCloseable(Closeable c) {
closeSet.add(c);
}
/**
* Unregisters an object to be closed when the plugin shuts down.
*
* @param c The object to unregister for close on shutdown.
*/
public synchronized void unregisterCloseable(Closeable c) {
closeSet.remove(c);
}
/**
* Gets the installation directory of the Ghidra that launched Eclipse.
*
* @return The installation directory of the Ghidra that launched Eclipse. Could be null if
* Ghidra did not launch Eclipse, or Ghidra did not pass along it's installation directory to
* Eclipse properly.
*/
public File getGhidraInstallDir() {
String ghidraInstallDirProperty = System.getProperty(GHIDRA_INSTALL_DIR_PROPERTY);
if (ghidraInstallDirProperty != null && !ghidraInstallDirProperty.isEmpty()) {
return new File(ghidraInstallDirProperty);
}
return null;
}
/**
* Checks to see if Eclipse was launched by Ghidra.
*
* @return True if Eclipse was launched by Ghidra; otherwise, false.
*/
public boolean isLaunchedByGhidra() {
return System.getProperty(GHIDRA_INSTALL_DIR_PROPERTY) != null;
}
@Override
public void start(BundleContext context) throws Exception {
super.start(context);
plugin = this;
EclipseMessageUtils.info("Starting " + PLUGIN_ID + " plugin");
}
@Override
public void stop(BundleContext context) throws Exception {
plugin = null;
super.stop(context);
EclipseMessageUtils.info("Stopping " + PLUGIN_ID + " plugin");
// Close registered items
synchronized (this) {
for (Closeable c : closeSet) {
if (c != null) {
EclipseMessageUtils.info("Closing " + c);
try {
c.close();
}
catch (IOException e) {
EclipseMessageUtils.info("Failed to close " + c);
}
}
}
closeSet.clear();
}
}
}

View file

@ -0,0 +1,242 @@
/* ###
* 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;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.*;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.statushandlers.StatusManager;
public class EclipseMessageUtils {
private static IWorkbenchPage workbenchPage = getWorkbenchPage();
private static Shell shell = workbenchPage.getWorkbenchWindow().getShell();
/**
* Prevent instantiation.
*/
private EclipseMessageUtils() {
// Nothing to do
}
/**
* Shows an information dialog with a custom title and message.
*
* @param title The title of the information dialog.
* @param message The message of the information dialog.
*/
public static void showInfoDialog(final String title, final String message) {
Display.getDefault().syncExec(() -> {
shell.forceActive();
MessageDialog.openInformation(shell, title, message);
});
}
/**
* Shows a confirmation dialog with a custom title and message.
*
* @param title The title of the confirmation dialog.
* @param message The message of the confirmation dialog.
* @return True of ok was pressed; false if canceled was pressed.
*/
public static boolean showConfirmDialog(final String title, final String message) {
AtomicBoolean okPressed = new AtomicBoolean();
Display.getDefault().syncExec(() -> {
shell.forceActive();
okPressed.set(MessageDialog.openConfirm(shell, title, message));
});
return okPressed.get();
}
/**
* Shows a question dialog with a custom title and message.
*
* @param title The title of the question dialog.
* @param message The message of the question dialog.
* @return True of yes was pressed; false if no was pressed.
*/
public static boolean showQuestionDialog(final String title, final String message) {
AtomicBoolean yesPressed = new AtomicBoolean();
Display.getDefault().syncExec(() -> {
shell.forceActive();
yesPressed.set(MessageDialog.openQuestion(shell, title, message));
});
return yesPressed.get();
}
/**
* Shows an error dialog with a custom title and message.
*
* @param title The title of the error dialog.
* @param message The message of the error dialog.
*/
public static void showErrorDialog(final String title, final String message) {
Display.getDefault().syncExec(() -> {
shell.forceActive();
MessageDialog.openError(shell, title, message);
});
}
/**
* Shows an error dialog with a default title and custom message.
*
* @param message The message of the error dialog.
*/
public static void showErrorDialog(final String message) {
showErrorDialog(Activator.PLUGIN_ID + " error", message);
}
/**
* Shows a warning dialog with a custom title and custom message.
*
* @param title The title of the warning dialog.
* @param message The message of the error dialog.
*/
public static void showWarnDialog(final String title, final String message) {
Display.getDefault().syncExec(() -> {
shell.forceActive();
MessageDialog.openWarning(shell, title, message);
});
}
/**
* Shows an error dialog that a wizard can use to display information about an exception that
* occurred while wizard'ing.
*
* @param wizardShell The wizard's shell.
* @param e The exception that occurred.
* @return The displayed message.
*/
public static String showWizardErrorDialog(Shell wizardShell, InvocationTargetException e) {
String message = null;
Throwable cause = e.getCause();
if (cause != null) {
message = cause.getClass().getSimpleName();
if (cause.getMessage() != null && !cause.getMessage().isEmpty()) {
message += ": " + cause.getMessage();
}
}
else {
message = e.getClass().getSimpleName();
}
MessageDialog.openError(wizardShell, "Error", message);
return message;
}
/**
* Logs an info message.
*
* @param message The message to display.
*/
public static void info(String message) {
StatusManager.getManager().handle(new Status(IStatus.INFO, Activator.PLUGIN_ID, message));
}
/**
* Logs an error message.
*
* @param message The message to display.
*/
public static void error(String message) {
StatusManager.getManager().handle(new Status(IStatus.ERROR, Activator.PLUGIN_ID, message));
}
/**
* Logs an error message with the responsible error included.
*
* @param message The message to display.
* @param t The responsible throwable.
*/
public static void error(String message, Throwable t) {
StatusManager.getManager().handle(
new Status(IStatus.ERROR, Activator.PLUGIN_ID, message, t));
}
public static IWorkbenchPage getWorkbenchPage() {
IWorkbench workbench = PlatformUI.getWorkbench();
IWorkbenchWindow workbenchWindow = getWorkbenchWindow(workbench);
if (workbenchWindow == null) {
error("Couldn't get workbench window");
return null;
}
IWorkbenchPage wbp = getPage(workbenchWindow);
if (wbp == null) {
error("Couldn't get workbench page");
return null;
}
return wbp;
}
private static IWorkbenchWindow getWorkbenchWindow(IWorkbench workbench) {
IWorkbenchWindow workbenchWindow = workbench.getActiveWorkbenchWindow();
if (workbenchWindow != null) {
return workbenchWindow;
}
IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
for (IWorkbenchWindow window : windows) {
if (window != null) {
return window;
}
}
return null;
}
private static IWorkbenchPage getPage(IWorkbenchWindow workbenchWindow) {
IWorkbenchPage wbp = workbenchWindow.getActivePage();
if (wbp != null) {
return wbp;
}
IWorkbenchPage[] pages = workbenchWindow.getPages();
for (IWorkbenchPage page : pages) {
if (page != null) {
return page;
}
}
return null;
}
/**
* Displays the given file in an editor using the Java perspective.
* If something goes wrong, this method has no effect.
*
* @param file The file to display.
* @param workbench The workbench.
*/
public static void displayInEditor(IFile file, IWorkbench workbench) {
new UIJob("Display in editor") {
@Override
public IStatus runInUIThread(IProgressMonitor m) {
try {
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
IDE.openEditor(window.getActivePage(), file);
workbench.showPerspective("org.eclipse.jdt.ui.JavaPerspective", window);
return Status.OK_STATUS;
}
catch (NullPointerException | WorkbenchException e) {
return Status.CANCEL_STATUS;
}
}
}.schedule();
}
}

View file

@ -0,0 +1,79 @@
/* ###
* 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;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.*;
import org.eclipse.ui.intro.IIntroManager;
import org.eclipse.ui.intro.IIntroPart;
import ghidradev.ghidrascripteditor.ScriptEditorInitializer;
import ghidradev.ghidrasymbollookup.SymbolLookupInitializer;
/**
* When Eclipse starts, initializes the plugin's subcomponents.
*/
public class GhidraDevStartup implements IStartup {
@Override
public void earlyStartup() {
Job job = new Job(Activator.PLUGIN_ID + " startup") {
@Override
protected IStatus run(IProgressMonitor monitor) {
monitor.beginTask("Initializing " + Activator.PLUGIN_ID, 2);
// If we were launched from Ghidra, close the Eclipse welcome screen if present,
// and make it so it never shows up again.
if (Activator.getDefault().isLaunchedByGhidra()) {
IIntroManager introManager = PlatformUI.getWorkbench().getIntroManager();
IIntroPart intro = introManager.getIntro();
if (intro != null) {
Display.getDefault().syncExec(() -> introManager.closeIntro(intro));
}
PlatformUI.getPreferenceStore().setValue(
IWorkbenchPreferenceConstants.SHOW_INTRO, false);
}
// Ask the user (only once) for consent before listening on any ports
boolean firstTimeConsent = false;
if (!GhidraRootPreferences.requestedConsentToOpenPorts()) {
firstTimeConsent = EclipseMessageUtils.showQuestionDialog(
Activator.PLUGIN_ID + "User Consent",
Activator.PLUGIN_ID + " opens ports to enable communication with Ghidra " +
"for various features such as initiating script editing and symbol " +
"lookup from Ghidra.\n\nDo you consent to the ports being opened?\n\n" +
"If you do not consent now, you can enable these features at any " +
"time in the " + Activator.PLUGIN_ID + " preferences.");
GhidraRootPreferences.setOpenPortConsentRequest(true);
}
// Initialize the script editor
ScriptEditorInitializer.init(firstTimeConsent);
monitor.worked(1);
// Initialize symbol lookup
SymbolLookupInitializer.init(firstTimeConsent);
monitor.worked(1);
monitor.done();
return Status.OK_STATUS;
}
};
job.schedule();
}
}

View file

@ -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.
*/
package ghidradev;
import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
import org.eclipse.jface.preference.IPreferenceStore;
/**
* Class used to initialize default preference values.
*/
public class GhidraRootPreferenceInitializer extends AbstractPreferenceInitializer {
@Override
public void initializeDefaultPreferences() {
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
store.setDefault(GhidraRootPreferences.GHIDRA_REQUESTED_OPEN_PORT_CONSENT, false);
}
}

View file

@ -0,0 +1,38 @@
/* ###
* 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;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
public class GhidraRootPreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
public GhidraRootPreferencePage() {
noDefaultAndApplyButton();
}
public void init(IWorkbench workbench) {
setDescription("Please see subcategories for Ghidra preferences.");
}
@Override
protected Control createContents(Composite parent) {
return null;
}
}

View file

@ -0,0 +1,49 @@
/* ###
* 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;
import org.eclipse.jface.preference.IPreferenceStore;
/**
* General preference definitions and related utility methods.
*/
public class GhidraRootPreferences {
/**
* Whether or not we have requested consent to open network ports.
*/
static final String GHIDRA_REQUESTED_OPEN_PORT_CONSENT = "ghidradev.requestedOpenPortConsent";
/**
* Gets whether or not consent was requested to open network ports.
*
* @return True if consent was requested to open network ports; otherwise, false.
*/
public static boolean requestedConsentToOpenPorts() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
return prefs.getBoolean(GHIDRA_REQUESTED_OPEN_PORT_CONSENT);
}
/**
* Sets whether or not the user has given consent to open network ports.
*
* @param requested True if consent was requested to open network ports; otherwise, false.
*/
public static void setOpenPortConsentRequest(boolean requested) {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
prefs.setValue(GHIDRA_REQUESTED_OPEN_PORT_CONSENT, requested);
}
}

View file

@ -0,0 +1,38 @@
/* ###
* 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.commands;
import org.eclipse.core.commands.*;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.Version;
import ghidradev.EclipseMessageUtils;
/**
* Pops up a dialog with information about the plugin.
*/
public class AboutCommand extends AbstractHandler {
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
Version version = FrameworkUtil.getBundle(getClass()).getVersion();
StringBuilder message = new StringBuilder();
message.append("GhidraDev " + version.toString() + "\n\n");
message.append("Ghidra Development support for Eclipse");
EclipseMessageUtils.showInfoDialog("About", message.toString());
return null;
}
}

View file

@ -0,0 +1,40 @@
/* ###
* 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.commands;
import org.eclipse.core.commands.*;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.handlers.HandlerUtil;
import ghidradev.ghidraprojectcreator.wizards.LinkGhidraWizard;
/**
* Launches the {@link LinkGhidraWizard}.
*/
public class LinkGhidraCommand extends AbstractHandler {
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindow(event);
ISelection selection = window.getSelectionService().getSelection();
WizardDialog dialog =
new WizardDialog(window.getShell(), new LinkGhidraWizard(selection));
dialog.open();
return null;
}
}

View file

@ -0,0 +1,108 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.launchers;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.*;
import org.eclipse.debug.core.*;
import org.eclipse.debug.ui.ILaunchShortcut;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import ghidradev.Activator;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.testers.GhidraProjectPropertyTester;
import ghidradev.ghidraprojectcreator.utils.GhidraLaunchUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
/**
* Ghidra launch shortcut actions. These shortcuts appear when you right click on a
* Ghidra project or file and select "Run As" or "Debug As".
* <p>
* The {@link GhidraProjectPropertyTester} is used to determine whether or not the shortcuts appear.
*/
public abstract class AbstractGhidraLaunchShortcut implements ILaunchShortcut {
private String launchConfigTypeId;
private String launchConfigNameSuffix;
/**
* Creates a new Ghidra launch shortcut associated with the given launch configuration type ID.
*
* @param launchConfigTypeId The launch configuration type ID of this Ghidra launch shortcut.
* @param launchConfigNameSuffix A string to append to the name of the launch configuration.
*/
protected AbstractGhidraLaunchShortcut(String launchConfigTypeId,
String launchConfigNameSuffix) {
this.launchConfigTypeId = launchConfigTypeId;
this.launchConfigNameSuffix = launchConfigNameSuffix;
}
@Override
public void launch(ISelection selection, String mode) {
IProject project = GhidraProjectUtils.getSelectedProject(selection);
if (project != null) {
launch(JavaCore.create(project), mode);
}
}
@Override
public void launch(IEditorPart editor, String mode) {
IEditorInput input = editor.getEditorInput();
IResource resource = input.getAdapter(IResource.class);
if (resource != null) {
launch(JavaCore.create(resource.getProject()), mode);
}
}
/**
* Launches the given Java project in the given mode with a Ghidra launcher.
*
* @param javaProject The Java project to launch.
* @param mode The mode to launch in (run/debug).
*/
private void launch(IJavaProject javaProject, String mode) {
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
ILaunchConfigurationType launchType =
launchManager.getLaunchConfigurationType(launchConfigTypeId);
String launchConfigName = javaProject.getProject().getName() + launchConfigNameSuffix;
try {
ILaunchConfiguration lc = GhidraLaunchUtils.getLaunchConfig(launchConfigName);
ILaunchConfigurationWorkingCopy wc = null;
if (lc == null) {
wc = GhidraLaunchUtils.createLaunchConfig(javaProject, launchConfigTypeId,
launchConfigName, null);
}
else if (lc.getType().equals(launchType)) {
wc = lc.getWorkingCopy();
}
else {
throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
IStatus.ERROR, "Failed to launch. Run configuration with name \"" +
launchConfigName + "\" already exists.",
null));
}
wc.doSave().launch(mode, null);
}
catch (CoreException e) {
EclipseMessageUtils.showErrorDialog(e.getMessage());
}
}
}

View file

@ -0,0 +1,33 @@
/* ###
* 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.launchers;
import ghidradev.ghidraprojectcreator.utils.GhidraLaunchUtils;
/**
* The Ghidra GUI launch shortcut actions.
*
* @see AbstractGhidraLaunchShortcut
*/
public class GhidraGuiLaunchShortcut extends AbstractGhidraLaunchShortcut {
/**
* Creates a new Ghidra GUI launch shortcut.
*/
public GhidraGuiLaunchShortcut() {
super(GhidraLaunchUtils.GUI_LAUNCH, "");
}
}

View file

@ -0,0 +1,33 @@
/* ###
* 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.launchers;
import ghidradev.ghidraprojectcreator.utils.GhidraLaunchUtils;
/**
* The Ghidra headless launch shortcut actions.
*
* @see AbstractGhidraLaunchShortcut
*/
public class GhidraHeadlessLaunchShortcut extends AbstractGhidraLaunchShortcut {
/**
* Creates a new Ghidra headless launch shortcut.
*/
public GhidraHeadlessLaunchShortcut() {
super(GhidraLaunchUtils.HEADLESS_LAUNCH, " (Headless)");
}
}

View file

@ -0,0 +1,169 @@
/* ###
* 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.launchers;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import javax.naming.OperationNotSupportedException;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.debug.core.*;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.launching.JavaLaunchDelegate;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IPerspectiveDescriptor;
import org.eclipse.ui.PlatformUI;
import ghidra.launch.JavaConfig;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.*;
/**
* The Ghidra Launch delegate handles the final launch of Ghidra.
* We can do any extra custom launch behavior here.
*/
public class GhidraLaunchDelegate extends JavaLaunchDelegate {
@Override
public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch,
IProgressMonitor monitor) throws CoreException {
boolean isHeadless =
configuration.getType().getIdentifier().equals(GhidraLaunchUtils.HEADLESS_LAUNCH);
ILaunchConfigurationWorkingCopy wc = configuration.getWorkingCopy();
// Get the launch properties associated with the version of Ghidra that is trying to launch
String projectName =
wc.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, "");
IJavaProject javaProject = GhidraProjectUtils.getGhidraProject(projectName);
if (javaProject == null) {
EclipseMessageUtils.showErrorDialog("Failed to launch project \"" + projectName +
"\".\nDoes not appear to be a Ghidra project.");
return;
}
IFolder ghidraFolder =
javaProject.getProject().getFolder(GhidraProjectUtils.GHIDRA_FOLDER_NAME);
JavaConfig javaConfig;
String ghidraInstallPath = ghidraFolder.getLocation().toOSString();
try {
javaConfig = new JavaConfig(new File(ghidraInstallPath));
}
catch (ParseException | IOException e) {
EclipseMessageUtils.showErrorDialog(
"Failed to launch project \"" + projectName + "\".\n" + e.getMessage());
return;
}
// Set program arguments
String customProgramArgs =
configuration.getAttribute(GhidraLaunchUtils.ATTR_PROGAM_ARGUMENTS, "").trim();
String programArgs =
isHeadless ? "ghidra.app.util.headless.AnalyzeHeadless" : "ghidra.GhidraRun";
programArgs += " " + customProgramArgs;
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, programArgs);
if (isHeadless && customProgramArgs.isEmpty()) {
EclipseMessageUtils.showInfoDialog("Ghidra Run Configuration",
"Headless launch is being performed without any command line arguments!\n\n" +
"Edit the \"" + configuration.getName() +
"\" run configuration's program arguments to customize headless behavior. " +
"See support/analyzeHeadlessREADME.html for more information.");
}
// Set VM arguments
String vmArgs = javaConfig.getLaunchProperties().getVmArgs();
vmArgs += " " + configuration.getAttribute(GhidraLaunchUtils.ATTR_VM_ARGUMENTS, "").trim();
vmArgs += " " + "-Declipse.install.dir=\"" +
Platform.getInstallLocation().getURL().getFile() + "\"";
vmArgs += " " + "-Declipse.workspace.dir=\"" +
ResourcesPlugin.getWorkspace().getRoot().getLocation() + "\"";
vmArgs += " " + "-Declipse.project.dir=\"" + javaProject.getProject().getLocation() + "\"";
vmArgs += " " + "-Declipse.project.dependencies=\"" +
getProjectDependencyDirs(javaProject) + "\"";
File pyDevSrcDir = PyDevUtils.getPyDevSrcDir();
if (pyDevSrcDir != null) {
vmArgs += " " + "-Declipse.pysrc.dir=\"" + pyDevSrcDir + "\"";
}
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, vmArgs);
// Handle special debug mode tasks
if (mode.equals("debug")) {
handleDebugMode();
}
super.launch(wc.doSave(), mode, launch, monitor);
}
/**
* For the given Java project, gets all of its classpath dependencies that are themselves
* projects. The result is formatted as a string of paths separated by
* {@link File#pathSeparator}.
*
* @param javaProject The Java project whose project dependencies we are getting.
* @return A string of paths separated by {@link File#pathSeparator} that represents the given
* Java project's dependencies that are projects. Could be empty if there are no
* dependencies.
* @throws CoreException if there was an Eclipse-related problem with getting the dependencies.
*/
private static String getProjectDependencyDirs(IJavaProject javaProject) throws CoreException {
String paths = "";
for (IClasspathEntry entry : javaProject.getRawClasspath()) {
if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
if (!paths.isEmpty()) {
paths += File.pathSeparator;
}
IResource resource =
ResourcesPlugin.getWorkspace().getRoot().findMember(entry.getPath());
if (resource != null) {
paths += resource.getLocation();
}
}
}
return paths;
}
/**
* Handles extra things that should happen when we are launching in debug mode.
*/
private static void handleDebugMode() {
Display.getDefault().asyncExec(() -> {
// Switch to debug perspective
if (PlatformUI.getWorkbench() != null) {
IPerspectiveDescriptor descriptor =
PlatformUI.getWorkbench().getPerspectiveRegistry().findPerspectiveWithId(
IDebugUIConstants.ID_DEBUG_PERSPECTIVE);
EclipseMessageUtils.getWorkbenchPage().setPerspective(descriptor);
}
// Start PyDev debugger
if (PyDevUtils.isSupportedPyDevInstalled()) {
try {
PyDevUtils.startPyDevRemoteDebugger();
}
catch (OperationNotSupportedException e) {
EclipseMessageUtils.error(
"Failed to start the PyDev remote debugger. PyDev version is not supported.");
}
}
});
}
}

View file

@ -0,0 +1,192 @@
/* ###
* 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.launchers;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.ui.*;
import org.eclipse.jdt.debug.ui.launchConfigurations.JavaClasspathTab;
import org.eclipse.jdt.debug.ui.launchConfigurations.JavaMainTab;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraLaunchUtils;
/**
* The Ghidra launcher tab group with default values needed for running/debugging
* Ghidra. Some Java tabs are hidden here because we don't want the user changing their properties.
* These properties are set in {@link GhidraLaunchDelegate}, which occurs right before
* the launch.
*/
public class GhidraLaunchTabGroup extends AbstractLaunchConfigurationTabGroup {
@Override
public void createTabs(ILaunchConfigurationDialog dialog, String mode) {
// Create the tabs
List<ILaunchConfigurationTab> tabs = new ArrayList<>();
tabs.add(getJavaMainTab());
tabs.add(getUserDefinedArgumentsTab());
tabs.add(new JavaClasspathTab());
tabs.add(getCommonTab());
// Set the tabs
setTabs(tabs.toArray(new ILaunchConfigurationTab[tabs.size()]));
}
/**
* Gets the {@link JavaMainTab} to use, with Ghidra's main method pre-configured in.
*
* @return The {@link JavaMainTab} to use, with Ghidra's main method pre-configured in.
*/
private JavaMainTab getJavaMainTab() {
return new JavaMainTab() {
@Override
public void initializeFrom(ILaunchConfiguration config) {
try {
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
GhidraLaunchUtils.setMainTypeName(wc);
super.initializeFrom(wc.doSave());
}
catch (CoreException e) {
EclipseMessageUtils.error("Failed to initialize the java main tab.", e);
}
}
};
}
/**
* Gets the user-defined arguments to use. These will be appended to Ghidra's required
* launch arguments, which are hidden from the tab group.
*
* @return The user-defined arguments to use.
*/
private AbstractLaunchConfigurationTab getUserDefinedArgumentsTab() {
return new AbstractLaunchConfigurationTab() {
private Text programArgsText;
private Text vmArgsText;
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NONE);
container.setLayout(new GridLayout(1, true));
GridData gd = new GridData(GridData.FILL_BOTH);
container.setLayoutData(gd);
// Program arguments
Group group = new Group(container, SWT.NONE);
group.setLayout(new GridLayout());
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setText("Program arguments:");
programArgsText = new Text(group, SWT.MULTI | SWT.WRAP | SWT.BORDER | SWT.V_SCROLL);
gd = new GridData(GridData.FILL_BOTH);
gd.heightHint = 40;
gd.widthHint = 100;
programArgsText.setLayoutData(gd);
programArgsText.addModifyListener(evt -> scheduleUpdateJob());
// VM arguments
group = new Group(container, SWT.NONE);
group.setLayout(new GridLayout());
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setText("VM arguments (appended to arguments defined in launch.properties):");
vmArgsText = new Text(group, SWT.MULTI | SWT.WRAP | SWT.BORDER | SWT.V_SCROLL);
gd = new GridData(GridData.FILL_BOTH);
gd.heightHint = 40;
gd.widthHint = 100;
vmArgsText.setLayoutData(gd);
vmArgsText.addModifyListener(evt -> scheduleUpdateJob());
setControl(container);
}
@Override
public void setDefaults(ILaunchConfigurationWorkingCopy config) {
try {
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
wc.setAttribute(GhidraLaunchUtils.ATTR_PROGAM_ARGUMENTS, "");
wc.setAttribute(GhidraLaunchUtils.ATTR_VM_ARGUMENTS, "");
wc.doSave();
}
catch (CoreException e) {
EclipseMessageUtils.error("Failed to set argument defaults.", e);
}
}
@Override
public void initializeFrom(ILaunchConfiguration config) {
try {
programArgsText.setText(
config.getAttribute(GhidraLaunchUtils.ATTR_PROGAM_ARGUMENTS, ""));
vmArgsText.setText(
config.getAttribute(GhidraLaunchUtils.ATTR_VM_ARGUMENTS, ""));
}
catch (CoreException e) {
EclipseMessageUtils.error("Failed to initialize the arguments.", e);
}
}
@Override
public void performApply(ILaunchConfigurationWorkingCopy config) {
try {
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
wc.setAttribute(GhidraLaunchUtils.ATTR_PROGAM_ARGUMENTS,
programArgsText.getText());
wc.setAttribute(GhidraLaunchUtils.ATTR_VM_ARGUMENTS, vmArgsText.getText());
wc.doSave();
}
catch (CoreException e) {
EclipseMessageUtils.error("Failed to apply the arguments.", e);
}
}
@Override
public String getName() {
return "Arguments";
}
};
}
/**
* Gets the {@link CommonTab} to use, with the new launch configuration added to the favorites.
*
* @return The {@link CommonTab} to use, with the new launch configuration added to the
* favorites.
*/
private CommonTab getCommonTab() {
return new CommonTab() {
@Override
public void initializeFrom(ILaunchConfiguration config) {
try {
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
GhidraLaunchUtils.setFavorites(wc);
super.initializeFrom(wc.doSave());
}
catch (CoreException e) {
EclipseMessageUtils.error("Failed to initialize the common tab.", e);
}
}
};
}
}

View file

@ -0,0 +1,56 @@
/* ###
* 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.preferences;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
import org.eclipse.jface.preference.IPreferenceStore;
import ghidradev.Activator;
/**
* Class used to initialize default preference values.
*/
public class GhidraProjectCreatorPreferenceInitializer extends AbstractPreferenceInitializer {
@Override
public void initializeDefaultPreferences() {
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
store.setDefault(GhidraProjectCreatorPreferences.GHIDRA_INSTALL_PATHS, "");
store.setDefault(GhidraProjectCreatorPreferences.GHIDRA_DEFAULT_INSTALL_PATH, "");
store.setDefault(GhidraProjectCreatorPreferences.GHIDRA_LAST_PROJECT_ROOT_PATH,
ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString());
store.setDefault(GhidraProjectCreatorPreferences.GHIDRA_LAST_GRADLE_DISTRIBUTION, "");
// If Ghidra launched Eclipse, automatically add in that Ghidra's location (if it doesn't
// already exist) as a convenience to the user.
File ghidraInstallDir = Activator.getDefault().getGhidraInstallDir();
if (ghidraInstallDir != null) {
Set<File> dirs = new HashSet<>(GhidraProjectCreatorPreferences.getGhidraInstallDirs());
if (!dirs.contains(ghidraInstallDir)) {
if (dirs.isEmpty()) {
GhidraProjectCreatorPreferences.setDefaultGhidraInstallDir(ghidraInstallDir);
}
dirs.add(ghidraInstallDir);
GhidraProjectCreatorPreferences.setGhidraInstallDirs(dirs);
}
}
}
}

View file

@ -0,0 +1,237 @@
/* ###
* 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.preferences;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.stream.Collectors;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import ghidra.GhidraApplicationLayout;
import ghidra.framework.ApplicationProperties;
import ghidra.framework.ApplicationVersion;
import ghidradev.Activator;
import ghidradev.EclipseMessageUtils;
import utility.application.ApplicationLayout;
/**
* Page for Ghidra project creator preferences.
*/
public class GhidraProjectCreatorPreferencePage extends PreferencePage
implements IWorkbenchPreferencePage {
private static ApplicationVersion MIN_GHIDRA_VERSION = new ApplicationVersion("9.0");
private Table table;
private Button addButton;
private Button removeButton;
public GhidraProjectCreatorPreferencePage() {
super();
}
@Override
public void init(IWorkbench workbench) {
// Nothing to do
}
@Override
protected IPreferenceStore doGetPreferenceStore() {
return Activator.getDefault().getPreferenceStore();
}
@Override
protected Control createContents(Composite parent) {
noDefaultButton();
FontData fontData = parent.getFont().getFontData()[0];
Font bold = new Font(parent.getDisplay(), fontData.getName(), fontData.getHeight(), SWT.BOLD);
Composite container = new Composite(parent, SWT.None);
container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
container.setLayout(new GridLayout(2, false));
// Description label
Label descriptionLabel = new Label(container, SWT.NULL);
descriptionLabel.setText("Add or remove Ghidra installations.\n" +
"The checked Ghidra installation is the default used when creating new projects.\n" +
"Red entries correspond to invalid Ghidra installations.");
new Label(container, SWT.NONE).setText(""); // filler
// Ghidra installations table
table = new Table(container, SWT.CHECK | SWT.BORDER | SWT.FULL_SELECTION);
table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
table.setHeaderVisible(true);
table.setLinesVisible(true);
table.addListener(SWT.Selection, evt -> {
if (evt.detail == SWT.CHECK) {
for (TableItem item : table.getItems()) {
item.setChecked(item.equals(evt.item));
item.setFont(item.equals(evt.item) ? bold : parent.getFont());
}
}
});
TableColumn col = new TableColumn(table, SWT.FILL);
col.setText("Ghidra installation directories");
col.setWidth(400);
for (File dir : GhidraProjectCreatorPreferences.getGhidraInstallDirs()) {
TableItem item = new TableItem(table, SWT.NONE);
item.setText(dir.getAbsolutePath());
try {
validateGhidraInstallation(dir);
item.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_BLACK));
}
catch (IOException e) {
item.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_RED));
}
if (dir.equals(GhidraProjectCreatorPreferences.getGhidraDefaultInstallDir())) {
item.setFont(bold);
item.setChecked(true);
}
}
if (table.getItemCount() > 0) {
col.pack();
}
// Buttons
Composite buttons = new Composite(container, SWT.None);
buttons.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING));
buttons.setLayout(new FillLayout(SWT.VERTICAL));
// Add button
addButton = new Button(buttons, SWT.PUSH);
addButton.setText("Add...");
addButton.addListener(SWT.Selection, evt -> {
DirectoryDialog dialog = new DirectoryDialog(container.getShell());
String path = dialog.open();
if (path == null) {
return;
}
if (Arrays.stream(table.getItems()).anyMatch(item -> item.getText().equals(path))) {
EclipseMessageUtils.showErrorDialog("Ghidra installation already specified.");
return;
}
try {
validateGhidraInstallation(new File(path));
TableItem item = new TableItem(table, SWT.NONE);
item.setText(path);
item.setChecked(table.getItemCount() == 1);
item.setFont(item.getChecked() ? bold : parent.getFont());
}
catch (IOException e) {
EclipseMessageUtils.showErrorDialog(e.getMessage());
}
});
// Remove button
removeButton = new Button(buttons, SWT.PUSH);
removeButton.setText("Remove");
removeButton.addListener(SWT.Selection, evt -> {
int selectionIndex = table.getSelectionIndex();
if (selectionIndex == -1) {
return;
}
boolean wasDefault = table.getItem(selectionIndex).getChecked();
table.remove(selectionIndex);
if (table.getItemCount() > 0) {
if (selectionIndex < table.getItemCount()) {
table.select(selectionIndex);
}
else {
table.select(selectionIndex - 1);
}
if (wasDefault) {
TableItem item = table.getItem(table.getSelectionIndex());
item.setChecked(true);
item.setFont(bold);
}
}
});
return parent;
}
@Override
public boolean performOk() {
super.performOk();
//@formatter:off
GhidraProjectCreatorPreferences.setGhidraInstallDirs(
Arrays.stream(table.getItems())
.map(item -> new File(item.getText()))
.collect(Collectors.toSet())
);
GhidraProjectCreatorPreferences.setDefaultGhidraInstallDir(
Arrays.stream(table.getItems())
.filter(TableItem::getChecked)
.findFirst()
.map(item -> new File(item.getText()))
.orElse(null)
);
//@formatter:on
return true;
}
/**
* Validates the given Ghidra installation directory.
*
* @param ghidraInstallDir The Ghidra installation directory to validate.
* @throws IOException If the given Ghidra installation directory is not valid. The exception's
* message has more detailed information on why it was not valid.
*/
public static void validateGhidraInstallation(File ghidraInstallDir) throws IOException {
ApplicationLayout layout;
try {
layout = new GhidraApplicationLayout(ghidraInstallDir);
}
catch (IOException e) {
throw new IOException("Not a valid Ghidra installation.");
}
ApplicationProperties applicationProperties = layout.getApplicationProperties();
ApplicationVersion version;
try {
version = new ApplicationVersion(applicationProperties.getApplicationVersion());
}
catch (IllegalArgumentException e) {
throw new IOException("Error parsing application version. " + e.getMessage() + ".");
}
if (version.compareTo(MIN_GHIDRA_VERSION) < 0) {
throw new IOException(
"Ghidra installation must be version " + MIN_GHIDRA_VERSION + " or later.");
}
String layoutVersion = applicationProperties.getProperty(
ApplicationProperties.APPLICATION_LAYOUT_VERSION_PROPERTY);
if (layoutVersion == null || !layoutVersion.equals("1")) {
// We can be smarter about this check and what we support later, once the layout version
// actually changes.
throw new IOException(
"Ghidra application layout is not supported. Please upgrade " +
Activator.PLUGIN_ID + " to use this version of Ghidra.");
}
}
}

View file

@ -0,0 +1,161 @@
/* ###
* 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.preferences;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
import org.eclipse.buildship.core.GradleDistribution;
import org.eclipse.jface.preference.IPreferenceStore;
import ghidradev.Activator;
/**
* Ghidra project creator preference definitions and related utility methods.
*/
public class GhidraProjectCreatorPreferences {
/**
* Paths to the Ghidra installation directories.
*/
static final String GHIDRA_INSTALL_PATHS = "ghidradev.ghidraInstallPaths";
/**
* Path to the default Ghidra installation directory.
*/
static final String GHIDRA_DEFAULT_INSTALL_PATH = "ghidradev.ghidraDefaultInstallPath";
/**
* Path to the last used Ghidra project root directory.
*/
static final String GHIDRA_LAST_PROJECT_ROOT_PATH = "ghidradev.ghidraLastProjectRootPath";
/**
* The last used Gradle distribution.
*/
static final String GHIDRA_LAST_GRADLE_DISTRIBUTION = "ghidradev.ghidraLastGradleDistribution";
/**
* Gets the set of Ghidra installation directories that's defined in the preferences.
*
* @return The set of Ghidra installation directories that's defined in the preferences.
*/
public static Set<File> getGhidraInstallDirs() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
String ghidraInstallDirPaths = prefs.getString(GHIDRA_INSTALL_PATHS);
if (ghidraInstallDirPaths.isEmpty()) {
return Collections.emptySet();
}
return Arrays.stream(ghidraInstallDirPaths.split(File.pathSeparator)).map(
p -> new File(p)).collect(Collectors.toSet());
}
/**
* Sets the set of Ghidra installation directories that's defined in the preferences.
*
* @param dirs The set of Ghidra installation directories that's defined in the preferences.
*/
public static void setGhidraInstallDirs(Set<File> dirs) {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
String paths = dirs.stream().map(dir -> dir.getAbsolutePath()).collect(
Collectors.joining(File.pathSeparator));
prefs.setValue(GHIDRA_INSTALL_PATHS, paths);
}
/**
* Gets the default Ghidra installation directory that's defined in the preferences.
*
* @return The default Ghidra installation directory that's defined in the preferences.
* Could be null if a default is not defined.
*/
public static File getGhidraDefaultInstallDir() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
String ghidraDefaultInstallDirPath = prefs.getString(GHIDRA_DEFAULT_INSTALL_PATH);
if (ghidraDefaultInstallDirPath.isEmpty()) {
return null;
}
return new File(ghidraDefaultInstallDirPath);
}
/**
* Sets the default Ghidra installation directory that's defined in the preferences.
*
* @param dir The default Ghidra installation directory that's defined in the preferences.
* Could be null if there is no default.
*/
public static void setDefaultGhidraInstallDir(File dir) {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
prefs.setValue(GHIDRA_DEFAULT_INSTALL_PATH, dir != null ? dir.getAbsolutePath() : "");
}
/**
* Gets the last used Ghidra project root path that's defined in the preferences.
*
* @return The last used Ghidra project root path that's defined in the preferences.
* Could be the empty string.
*/
public static String getGhidraLastProjectRootPath() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
return prefs.getString(GHIDRA_LAST_PROJECT_ROOT_PATH);
}
/**
* Sets the last used Ghidra project root path that's defined in the preferences.
*
* @param path The last used Ghidra project root path that's defined in the preferences.
*/
public static void setGhidraLastProjectRootPath(String path) {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
prefs.setValue(GHIDRA_LAST_PROJECT_ROOT_PATH, path);
}
/**
* Gets the last used Ghidra Gradle distribution that's defined in the preferences.
*
* @return The last used Ghidra Gradle distribution that's defined in the preferences.
* Could be null if there is no last used distribution.
*/
public static GradleDistribution getGhidraLastGradleDistribution() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
String pref = prefs.getString(GHIDRA_LAST_GRADLE_DISTRIBUTION);
if (pref != null && !pref.isEmpty()) {
try {
return GradleDistribution.fromString(pref);
}
catch (Exception e) {
// Failed to parse the string for some reason. Fall through to null.
}
}
return null;
}
/**
* Sets the last used Ghidra Gradle distribution that's defined in the preferences.
*
* @param gradleDistribution The last used Ghidra Gradle distribution that's defined in the
* preferences. Could be null if the preference should be set to the default.
*/
public static void setGhidraLastGradleDistribution(GradleDistribution gradleDistribution) {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
if (gradleDistribution != null) {
prefs.setValue(GHIDRA_LAST_GRADLE_DISTRIBUTION, gradleDistribution.toString());
}
else {
prefs.setToDefault(GHIDRA_LAST_GRADLE_DISTRIBUTION);
}
}
}

View file

@ -0,0 +1,33 @@
/* ###
* 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.testers;
import org.eclipse.core.expressions.PropertyTester;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
/**
* A {@link PropertyTester} used to determine if a given Eclipse resource is part
* of a Ghidra module project.
*/
public class GhidraModuleProjectPropertyTester extends PropertyTester {
@Override
public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
return GhidraProjectUtils.isGhidraModuleProject(
GhidraProjectUtils.getEnclosingProject(receiver));
}
}

View file

@ -0,0 +1,32 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.testers;
import org.eclipse.core.expressions.PropertyTester;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
/**
* A {@link PropertyTester} used to determine if a given Eclipse resource is part
* of a Ghidra project.
*/
public class GhidraProjectPropertyTester extends PropertyTester {
@Override
public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
return GhidraProjectUtils.isGhidraProject(GhidraProjectUtils.getEnclosingProject(receiver));
}
}

View file

@ -0,0 +1,32 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.testers;
import org.eclipse.core.expressions.PropertyTester;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
/**
* A {@link PropertyTester} used to determine if a given Eclipse resource is part
* of a Java project.
*/
public class JavaProjectPropertyTester extends PropertyTester {
@Override
public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
return GhidraProjectUtils.isJavaProject(GhidraProjectUtils.getEnclosingProject(receiver));
}
}

View file

@ -0,0 +1,41 @@
/* ###
* 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.testers;
import java.util.List;
import org.eclipse.core.expressions.PropertyTester;
import org.eclipse.jdt.core.IPackageFragmentRoot;
/**
* A {@link PropertyTester} used to determine if a given Eclipse resource is a Java package
* fragment root (which is basically a Java source folder on the build path).
*/
public class PackageFragmentRootPropertyTester extends PropertyTester {
@Override
public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
if (receiver instanceof List) {
List<?> list = (List<?>) receiver;
if (list.size() == 1) {
receiver = list.iterator().next();
}
}
return receiver instanceof IPackageFragmentRoot;
}
}

View file

@ -0,0 +1,200 @@
/* ###
* 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.utils;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.*;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.internal.ui.launchConfigurations.LaunchConfigurationManager;
import org.eclipse.debug.internal.ui.launchConfigurations.LaunchHistory;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import ghidra.GhidraLauncher;
/**
* Utility methods for working with Ghidra launchers in Eclipse.
*/
public class GhidraLaunchUtils {
/**
* Launch configuration ID for a Ghidra GUI launch. Must match corresponding value in
* plugin.xml.
*/
public static final String GUI_LAUNCH = "GhidraGuiLaunchConfigurationType";
/**
* Launch configuration ID for a Ghidra Headless launch. Must match corresponding value in
* plugin.xml.
*/
public static final String HEADLESS_LAUNCH = "GhidraHeadlessLaunchConfigurationType";
/**
* Program arguments that will get passed to the launched Ghidra. These will be appended
* to the required program arguments that are required to launch Ghidra, which are hidden
* from the user.
*/
public static final String ATTR_PROGAM_ARGUMENTS = "ghidradev.ghidraProgramArguments";
/**
* VM arguments that will get passed to the launched Ghidra. These will be appended
* to the required VM arguments that are required to launch Ghidra, which are hidden
* from the user.
*/
public static final String ATTR_VM_ARGUMENTS = "ghidradev.ghidraVmArguments";
/**
* Creates a new launch configuration for the given Java project.
*
* @param javaProject The Java project to create a launch configuration for.
* @param launchConfigTypeId The type of launch configuration.
* @param launchConfigName The name of the launch configuration.
* @param runConfigMemory The run configuration's desired memory. Could be null.
* @return A launch configuration working copy.
* @throws CoreException If there was an Eclipse-related problem with creating the launch
* configuration.
*/
public static ILaunchConfigurationWorkingCopy createLaunchConfig(IJavaProject javaProject,
String launchConfigTypeId, String launchConfigName, String runConfigMemory)
throws CoreException {
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
ILaunchConfigurationType launchType =
launchManager.getLaunchConfigurationType(launchConfigTypeId);
ILaunchConfigurationWorkingCopy wc = launchType.newInstance(null, launchConfigName);
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
javaProject.getProject().getName());
setMainTypeName(wc);
setMemory(wc, runConfigMemory);
setFavorites(wc);
return wc;
}
/**
* Gets the launch configuration with the given name.
*
* @param name The name of the launch configuration to get.
* @return The launch configuration with the given name, or null if it doesn't exist.
* @throws CoreException If there was an Eclipse-related problem with getting the launch
* configuration.
*/
public static ILaunchConfiguration getLaunchConfig(String name) throws CoreException {
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
for (ILaunchConfiguration lc : launchManager.getLaunchConfigurations()) {
if (lc.getName().equals(name)) {
return lc;
}
}
return null;
}
/**
* Gets the launch configuration with the given name and the given type ID.
*
* @param name The name of the launch configuration to get.
* @param id The launch configuration type id of the launch configuration to get.
* @return The launch configuration with the given name and type, or null if it doesn't exist.
* @throws CoreException If there was an Eclipse-related problem with getting the launch
* configuration.
*/
public static ILaunchConfiguration getLaunchConfig(String name, String id)
throws CoreException {
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
ILaunchConfigurationType type = launchManager.getLaunchConfigurationType(id);
if (type != null) {
for (ILaunchConfiguration lc : launchManager.getLaunchConfigurations(type)) {
if (lc.getName().equals(name)) {
return lc;
}
}
}
return null;
}
/**
* Sets the main type name attribute in the provided working copy. For Ghidra projects, this
* should be {@link GhidraLauncher}.
*
* @param wc The launch configuration working copy to modify.
* @return The modified working copy.
*/
public static ILaunchConfigurationWorkingCopy setMainTypeName(
ILaunchConfigurationWorkingCopy wc) {
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
GhidraLauncher.class.getName());
return wc;
}
/**
* Appends the maximum Java heap size (-Xmx) to the VM arguments in the provided working copy.
*
* @param memory The desired maximum Java heap size. Could be null if the default is to be
* used.
* @param wc The launch configuration working copy to modify.
* @return The modified working copy.
* @throws CoreException if there was an Eclipse-related issue appending the VM argument.
*
* @see #ATTR_VM_ARGUMENTS
*/
public static ILaunchConfigurationWorkingCopy setMemory(ILaunchConfigurationWorkingCopy wc,
String memory) throws CoreException {
if (memory != null) {
String vmArgs = wc.getAttribute(ATTR_VM_ARGUMENTS, "");
if (!vmArgs.isEmpty()) {
vmArgs += " ";
}
wc.setAttribute(ATTR_VM_ARGUMENTS, vmArgs + "-Xmx" + memory);
}
return wc;
}
/**
* Sets the favorites attribute in the provided working copy to include the launcher in both
* the run and debug launch groups.
*
* @param wc The launch configuration working copy to modify.
* @return The modified working copy.
* @throws CoreException If there was an Eclipse-related problem with setting the favorites
* attribute.
*/
public static ILaunchConfigurationWorkingCopy setFavorites(ILaunchConfigurationWorkingCopy wc)
throws CoreException {
List<String> list =
wc.getAttribute(IDebugUIConstants.ATTR_FAVORITE_GROUPS, new ArrayList<>());
list.add(IDebugUIConstants.ID_DEBUG_LAUNCH_GROUP);
list.add(IDebugUIConstants.ID_RUN_LAUNCH_GROUP);
wc.setAttribute(IDebugUIConstants.ATTR_FAVORITE_GROUPS, list);
return wc;
}
/**
* Adds the given launch configuration to the GUI's favorites list. This is useful to do if
* you create a launch configuration and want it to appear in the favorites list before ever
* launching it.
*
* @param launchConfig The launch configuration to add.
*/
public static void addToFavorites(ILaunchConfiguration launchConfig) {
LaunchConfigurationManager mgr = DebugUIPlugin.getDefault().getLaunchConfigurationManager();
LaunchHistory runHistory = mgr.getLaunchHistory(IDebugUIConstants.ID_RUN_LAUNCH_GROUP);
LaunchHistory debugHistory = mgr.getLaunchHistory(IDebugUIConstants.ID_DEBUG_LAUNCH_GROUP);
runHistory.addFavorite(launchConfig);
debugHistory.addFavorite(launchConfig);
}
}

View file

@ -0,0 +1,273 @@
/* ###
* 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.utils;
import java.io.*;
import java.nio.file.Files;
import java.text.ParseException;
import java.util.*;
import java.util.regex.Pattern;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.refactoring.IJavaRefactorings;
import org.eclipse.jdt.core.refactoring.descriptors.RenameJavaElementDescriptor;
import org.eclipse.ltk.core.refactoring.*;
import ghidra.GhidraApplicationLayout;
import ghidra.util.exception.CancelledException;
import utilities.util.FileUtilities;
/**
* Utility methods for working with Ghidra modules in Eclipse.
*/
public class GhidraModuleUtils {
public enum ModuleTemplateType {
ANALYZER("Analyzer", "Extends Ghidra analysis"),
PLUGIN("Plugin", "Extends the Ghidra user interface"),
LOADER("Loader", "Loads/imports a binary file format into Ghidra"),
FILESYSTEM("FileSystem", "Opens a file system format for browsing or batch import"),
EXPORTER("Exporter", "Exports/saves a Ghidra program to a specific file format");
private String name;
private String description;
private ModuleTemplateType(String name, String description) {
this.name = name;
this.description = description;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
}
/**
* Creates a new Ghidra module project with the given name.
*
* @param projectName The name of the project to create.
* @param projectDir The directory the project will be created in.
* @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 created 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 createGhidraModuleProject(String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory, GhidraApplicationLayout ghidraLayout,
String jythonInterpreterName, IProgressMonitor monitor)
throws IOException, ParseException, CoreException {
// Create empty Ghidra project
IJavaProject javaProject =
GhidraProjectUtils.createEmptyGhidraProject(projectName, projectDir, createRunConfig,
runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
IProject project = javaProject.getProject();
// Create source directories
List<IFolder> sourceFolders = new ArrayList<>();
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("ghidra_scripts"));
for (IFolder sourceFolder : sourceFolders) {
GhidraProjectUtils.createFolder(sourceFolder, monitor);
}
// Put the source directories in the project's classpath
List<IClasspathEntry> classpathEntries = new LinkedList<>();
for (IFolder sourceFolder : sourceFolders) {
classpathEntries.add(JavaCore.newSourceEntry(sourceFolder.getFullPath()));
}
GhidraProjectUtils.addToClasspath(javaProject, classpathEntries, monitor);
return javaProject;
}
/**
* Manually add in the source from the Skeleton (which should exist in the Ghidra installation
* directory), and then look at what's in the templates set to know what to keep and what to
* discard.
*
* @param javaProject The project whose source is to be configured.
* @param projectDir The project's directory.
* @param ghidraLayout The Ghidra layout the project is linked to.
* @param moduleTemplateTypes The templates to include in the source.
* @param monitor The progress monitor to use during source configuration.
* @return The primary module source file (which could be opened in an editor by default).
* @throws IOException If there was a file-related problem with configuring the source.
* @throws CoreException If there was an Eclipse-related problem with configuring the source.
*/
public static IFile configureModuleSource(IJavaProject javaProject, File projectDir,
GhidraApplicationLayout ghidraLayout, Set<ModuleTemplateType> moduleTemplateTypes,
IProgressMonitor monitor) throws CoreException, IOException {
final String SKELETON_PKG = "skeleton";
final String SKELETON_CLASS = "Skeleton";
IProject project = javaProject.getProject();
// Create a list of files to exclude. Use the provided templates list to know what
// source files should be included in the project.
List<String> excludeRegexes = new ArrayList<>();
for (ModuleTemplateType moduleTemplateType : ModuleTemplateType.values()) {
if (!moduleTemplateTypes.contains(moduleTemplateType)) {
excludeRegexes.add(SKELETON_CLASS + moduleTemplateType.getName() + "\\.java");
}
}
// Copy the skeleton files
File ghidraInstallDir = ghidraLayout.getApplicationInstallationDir().getFile(false);
File skeletonDir = Files.find(ghidraInstallDir.toPath(), 4, (path, attrs) -> {
return attrs.isDirectory() && path.getFileName().toString().equals("Skeleton");
}).map(p -> p.toFile()).findFirst().orElse(null);
if (skeletonDir == null) {
throw new IOException("Failed to find skeleton directory.");
}
try {
FileUtilities.copyDir(skeletonDir, projectDir, f -> {
return excludeRegexes.stream().map(r -> Pattern.compile(r)).noneMatch(
p -> p.matcher(f.getName()).matches());
}, null);
}
catch (CancelledException | IOException e) {
throw new IOException("Failed to copy skeleton directory: " + projectDir);
}
// Refresh project so it sees the new files
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
// Update language ant properties file
GhidraModuleUtils.writeAntProperties(project, ghidraLayout);
// Refactor/rename the source files, package, and help files
String packageName = project.getName().toLowerCase();
for (ModuleTemplateType moduleTemplateType : moduleTemplateTypes) {
IType skeletonClass = javaProject.findType(
SKELETON_PKG + "." + SKELETON_CLASS + moduleTemplateType.getName(), monitor);
if (skeletonClass != null) {
renameJavaElement(skeletonClass.getCompilationUnit(),
project.getName() + moduleTemplateType.getName(), monitor);
}
}
IJavaElement skeletonPackage = javaProject.findElement(new Path(SKELETON_PKG));
if (skeletonPackage != null) {
renameJavaElement(skeletonPackage, packageName, monitor);
}
IJavaElement helpTopic = javaProject.findElement(new Path("help/topics/skeleton"));
if (helpTopic != null) {
renameJavaElement(helpTopic, "help.topics." + packageName, monitor);
}
// Return the primary source file in the project (the first java file we see in the package)
IFolder packageFolder = project.getFolder("/src/main/java").getFolder(packageName);
if (packageFolder.exists()) {
for (IResource resource : packageFolder.members()) {
if (resource instanceof IFile && resource.getName().endsWith(".java")) {
return (IFile) resource;
}
}
}
return null;
}
/**
* 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
* given project is not a Ghidra module project, or if the Ghidra module project does not have a
* language build.xml ant file, this method has no effect.
*
* @param project The project to receive the ant properties.
* @param ghidraLayout The layout that contains the Ghidra installation directory that the project
* is currently linked against.
* @throws IOException if there was a problem writing the ant properties file.
*/
public static void writeAntProperties(IProject project, GhidraApplicationLayout ghidraLayout)
throws IOException {
if (!GhidraProjectUtils.isGhidraModuleProject(project)) {
return;
}
IFolder dataFolder = project.getFolder("data");
if (!dataFolder.exists()) {
return;
}
IFile buildXmlFile = dataFolder.getFile("build.xml");
if (!buildXmlFile.exists()) {
return;
}
File ghidraInstallDir = ghidraLayout.getApplicationInstallationDir().getFile(false);
File antFile = new File(project.getRawLocation().toFile(), ".antProperties.xml"); // hidden
try (PrintWriter writer = new PrintWriter(new FileWriter(antFile))) {
writer.println(
"<!-- This file is generated on each \"Link Ghidra\" command. Do not modify. -->");
writer.println();
writer.println("<project>");
writer.println(" <property name=\"ghidra.install.dir\" value=\"" +
ghidraInstallDir.getAbsolutePath() + "\" />");
writer.println("</project>");
}
}
/**
* Renames the given Java element to the given new name. Currently only supports renaming
* packages and compilation units.
*
* @param element The Java element to rename.
* @param newName The desired new name of the element.
* @param monitor The progress monitor.
* @throws CoreException If there is an Eclipse-related problem with the rename.
* @throws IllegalArgumentException If the given Java element is not a package or compilation unit.
*/
private static void renameJavaElement(IJavaElement element, String newName,
IProgressMonitor monitor) throws CoreException, IllegalArgumentException {
String id;
if (element.getElementType() == IJavaElement.PACKAGE_FRAGMENT) {
id = IJavaRefactorings.RENAME_PACKAGE;
}
else if (element.getElementType() == IJavaElement.COMPILATION_UNIT) {
id = IJavaRefactorings.RENAME_COMPILATION_UNIT;
}
else {
throw new IllegalArgumentException("Can only rename packages and compilation units!");
}
RefactoringContribution contribution = RefactoringCore.getRefactoringContribution(id);
RenameJavaElementDescriptor descriptor =
(RenameJavaElementDescriptor) contribution.createDescriptor();
descriptor.setProject(element.getResource().getProject().getName());
descriptor.setNewName(newName);
descriptor.setJavaElement(element);
RefactoringStatus status = new RefactoringStatus();
Refactoring refactoring = descriptor.createRefactoring(status);
refactoring.checkInitialConditions(monitor);
refactoring.checkFinalConditions(monitor);
Change change = refactoring.createChange(monitor);
change.perform(monitor);
}
}

View file

@ -0,0 +1,633 @@
/* ###
* 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.utils;
import java.io.*;
import java.text.ParseException;
import java.util.*;
import javax.naming.OperationNotSupportedException;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.internal.launching.StandardVMType;
import org.eclipse.jdt.launching.*;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.part.FileEditorInput;
import ghidra.GhidraApplicationLayout;
import ghidra.framework.GModule;
import ghidra.launch.JavaConfig;
import ghidradev.Activator;
import ghidradev.EclipseMessageUtils;
import utility.module.ModuleUtilities;
/**
* Utility methods for working with Eclipse Ghidra projects.
*/
public class GhidraProjectUtils {
/**
* The name of the linked project folder that points to the installation of
* Ghidra that it is using.
*/
public static final String GHIDRA_FOLDER_NAME = "Ghidra";
/**
* Characters that we are not going to allow in our Ghidra project and file.
*/
public static final String ILLEGAL_FILENAME_CHARS = " `~!@#$%^&*()-+=[]{}\\|;:'\"<>,./?";
/**
* Characters that we are not going to allow to start our Ghidra project and file names.
*/
public static final String ILLEGAL_FILENAME_START_CHARS = "0123456789";
/**
* Gets all of the open Java projects in the workspace.
*
* @return A collection of the open Java projects in the workspace.
*/
public static Collection<IJavaProject> getJavaProjects() {
List<IJavaProject> javaProjects = new ArrayList<>();
for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
if (project.isOpen() && isJavaProject(project)) {
javaProjects.add(JavaCore.create(project));
}
}
return javaProjects;
}
/**
* Checks to see if the given project is a Java project.
*
* @param project The project to check.
* @return True if the given project is a Java project; otherwise, false.
*/
public static boolean isJavaProject(IProject project) {
try {
return project != null && project.hasNature(JavaCore.NATURE_ID);
}
catch (CoreException e) {
EclipseMessageUtils.error("Java project check failed", e);
return false;
}
}
/**
* Checks to see if the given project is a Ghidra project.
*
* @param project The project to check.
* @return True if the given project is a Ghidra project; otherwise, false.
*/
public static boolean isGhidraProject(IProject project) {
return isJavaProject(project) && project.getFolder(GHIDRA_FOLDER_NAME).exists();
}
/**
* Checks to see if the given Java project is a Ghidra module project.
*
* @param project The project to check.
* @return True if the given project is a Ghidra module project; otherwise, false.
*/
public static boolean isGhidraModuleProject(IProject project) {
return isGhidraProject(project) &&
project.getFile(ModuleUtilities.MANIFEST_FILE_NAME).exists();
}
/**
* Gets all of the open Ghidra projects in the workspace.
*
* @return A collection of the open Ghidra projects in the workspace.
*/
public static Collection<IJavaProject> getGhidraProjects() {
List<IJavaProject> ghidraProjects = new ArrayList<>();
for (IJavaProject javaProject : getJavaProjects()) {
if (isGhidraProject(javaProject.getProject())) {
ghidraProjects.add(javaProject);
}
}
return ghidraProjects;
}
/**
* Gets the open Ghidra project with the given name.
*
* @param name The name of the project to get.
* @return The open Ghidra project with the given name, or null if it doesn't exist.
*/
public static IJavaProject getGhidraProject(String name) {
for (IJavaProject javaProject : getGhidraProjects()) {
if (javaProject.getProject().getName().equals(name)) {
return javaProject;
}
}
return null;
}
/**
* Gets the selected project, or null if a project is not selected. If multiple things
* are selected, only the first selected item is considered.
*
* @param selection A selection from which to get a project.
* @return The selected project, or null if a project is not selected.
*/
public static IProject getSelectedProject(ISelection selection) {
IProject project = null;
if (selection instanceof IStructuredSelection) {
IStructuredSelection structuredSelection = (IStructuredSelection) selection;
Object firstElement = structuredSelection.getFirstElement();
if (firstElement instanceof IResource) {
project = ((IResource) (firstElement)).getProject();
}
else if (firstElement instanceof IJavaElement) {
project = ((IJavaElement) (firstElement)).getResource().getProject();
}
}
return project;
}
/**
* Tries to get the given project object's enclosing project.
*
* @param projectObj The project object to get the enclosing project of.
* @return The given project object's enclosing project. Could be null if it could not be
* determined.
*/
public static IProject getEnclosingProject(Object projectObj) {
IProject project = null;
if (projectObj instanceof List) {
List<?> list = (List<?>) projectObj;
if (list.size() == 1) {
projectObj = list.iterator().next();
}
}
if (projectObj instanceof FileEditorInput) {
FileEditorInput fileEditorInput = (FileEditorInput) projectObj;
project = fileEditorInput.getFile().getProject();
}
else if (projectObj instanceof IResource) {
IResource resource = (IResource) projectObj;
project = resource.getProject();
}
else if (projectObj instanceof IJavaElement) {
IJavaElement javaElement = (IJavaElement) projectObj;
IResource resource = javaElement.getResource();
if (resource != null) {
project = resource.getProject();
}
}
return project;
}
/**
* Creates the given folder, including any necessary but nonexistent parent directories.
*
* @param folder The folder to create.
* @param monitor The progress monitor to use during folder creation.
* @throws CoreException If there was an Eclipse-related problem with creating the folder.
*/
public static void createFolder(IFolder folder, IProgressMonitor monitor) throws CoreException {
IContainer parent = folder.getParent();
if (parent instanceof IFolder) {
createFolder((IFolder) parent, monitor);
}
if (!folder.exists()) {
folder.create(false, true, monitor);
}
}
/**
* Updates the Java project's classpath to include the given list of classpath entries.
*
* @param javaProject The Java project that will get the new classpath entries.
* @param classpathEntries A list of classpath entries to add to the Java project's classpath.
* @param monitor The progress monitor.
* @throws JavaModelException If there was an issue adding to the Java project's classpath.
*/
public static void addToClasspath(IJavaProject javaProject,
List<IClasspathEntry> classpathEntries, IProgressMonitor monitor)
throws JavaModelException {
for (IClasspathEntry entry : javaProject.getRawClasspath()) {
classpathEntries.add(entry);
}
javaProject.setRawClasspath(
classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]), monitor);
}
/**
* Updates the Java project's classpath to include the given classpath entry.
*
* @param javaProject The Java project that will get the new classpath entries.
* @param classpathEntry The classpath entry to add to the Java project's classpath.
* @param monitor The progress monitor.
* @throws JavaModelException If there was an issue adding to the Java project's classpath.
*/
public static void addToClasspath(IJavaProject javaProject, IClasspathEntry classpathEntry,
IProgressMonitor monitor) throws JavaModelException {
List<IClasspathEntry> entryList = new ArrayList<>();
entryList.add(classpathEntry);
addToClasspath(javaProject, entryList, monitor);
}
/**
* Creates a new empty Ghidra project with the given name.
*
* @param projectName The name of the project to create.
* @param projectDir The directory the project will be created in.
* @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 created 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 createEmptyGhidraProject(String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory, GhidraApplicationLayout ghidraLayout,
String jythonInterpreterName, IProgressMonitor monitor)
throws IOException, ParseException, CoreException {
// Get Ghidra's Java configuration
JavaConfig javaConfig =
new JavaConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
// Make new Java project
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IProject project = workspace.getRoot().getProject(projectName);
IProjectDescription projectDescription = workspace.newProjectDescription(projectName);
projectDescription.setLocation(new Path(projectDir.getAbsolutePath()));
projectDescription.setNatureIds(new String[] { JavaCore.NATURE_ID });
ICommand command = projectDescription.newCommand();
command.setBuilderName(JavaCore.BUILDER_ID);
projectDescription.setBuildSpec(new ICommand[] { command });
project.create(projectDescription, monitor);
IJavaProject javaProject = JavaCore.create(project);
project.open(monitor);
// Clear the project's classpath
javaProject.setRawClasspath(new IClasspathEntry[0], monitor);
// Configure Java compiler for the project
configureJavaCompiler(javaProject, javaConfig);
// Setup bin folder
IFolder binFolder = project.getFolder("bin");
javaProject.setOutputLocation(binFolder.getFullPath(), monitor);
// Link in Ghidra to the project
linkGhidraToProject(javaProject, ghidraLayout, javaConfig, jythonInterpreterName, monitor);
// Create run configuration (if necessary)
if (createRunConfig) {
try {
ILaunchConfiguration launchConfig =
GhidraLaunchUtils.createLaunchConfig(javaProject, GhidraLaunchUtils.GUI_LAUNCH,
project.getName(), runConfigMemory).doSave();
GhidraLaunchUtils.addToFavorites(launchConfig);
}
catch (CoreException e) {
EclipseMessageUtils.showErrorDialog(
"Failed to create a Ghidra run configuration for the new project. Please do it manually.");
}
}
return javaProject;
}
/**
* Links the Ghidra layout to the given Java project. This effectively makes the project's
* build path "Ghidra aware."
* <p>
* If the project already has a Ghidra installation directory linked to it, that link is deleted
* and the new Ghidra installation directory is freshly linked back in.
*
* @param javaProject The Java project to link.
* @param ghidraLayout The Ghidra layout to link the project to.
* @param javaConfig Ghidra's Java configuration.
* @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 used during link.
* @throws IOException If there was a file-related problem with linking in Ghidra.
* @throws CoreException If there was an Eclipse-related problem with linking in Ghidra.
*/
public static void linkGhidraToProject(IJavaProject javaProject,
GhidraApplicationLayout ghidraLayout, JavaConfig javaConfig,
String jythonInterpreterName, IProgressMonitor monitor)
throws CoreException, IOException {
// Gets the Ghidra installation directory to link to from the Ghidra layout
File ghidraInstallDir = ghidraLayout.getApplicationInstallationDir().getFile(false);
// Get the Java VM used to launch the Ghidra to link to
IVMInstall vm = getGhidraVm(javaConfig);
IPath vmPath =
new Path(JavaRuntime.JRE_CONTAINER).append(vm.getVMInstallType().getId()).append(
vm.getName());
// Get the project's existing linked Ghidra installation folder and path (it may not exist)
IFolder ghidraFolder =
javaProject.getProject().getFolder(GhidraProjectUtils.GHIDRA_FOLDER_NAME);
IPath oldGhidraInstallPath = ghidraFolder.exists()
? new Path(ghidraFolder.getRawLocation().toFile().getAbsolutePath())
: null;
// Loop through the project's existing classpath to decide what to keep (things that aren't
// related to Ghidra), and things to not keep (things that will be added fresh from the new
// Ghidra).
IClasspathEntry vmEntryCandidate = null;
List<IClasspathEntry> classpathEntriesToKeep = new ArrayList<>();
for (IClasspathEntry entry : javaProject.getRawClasspath()) {
// If the project is not linked to an old Ghidra, save off the project's existing VM.
// We'll decide whether or not to keep it later.
if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER &&
entry.getPath().toString().startsWith(JavaRuntime.JRE_CONTAINER)) {
if (oldGhidraInstallPath == null) {
vmEntryCandidate = entry;
}
}
else if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
// Keep all project dependencies
classpathEntriesToKeep.add(entry);
}
else {
// If the project is linked to an old Ghidra, keep the list of source folders that are
// linked to the Ghidra installation (after updating their paths to point to the new
// Ghidra installation).
IFolder entryFolder =
ResourcesPlugin.getWorkspace().getRoot().getFolder(entry.getPath());
if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE && entryFolder.isLinked() &&
oldGhidraInstallPath != null &&
oldGhidraInstallPath.isPrefixOf(entryFolder.getLocation())) {
String origPath = entryFolder.getLocation().toString();
String newPath = ghidraInstallDir.getAbsolutePath() +
origPath.substring(oldGhidraInstallPath.toString().length());
entryFolder.createLink(new Path(newPath), IResource.REPLACE, monitor);
classpathEntriesToKeep.add(JavaCore.newSourceEntry(entryFolder.getFullPath()));
}
// If it's anything else that doesn't live in the old Ghidra installation, keep it.
else if (oldGhidraInstallPath == null ||
!oldGhidraInstallPath.isPrefixOf(entry.getPath())) {
classpathEntriesToKeep.add(entry);
}
}
}
// If we detected a VM to potentially keep, we should ask the user if they are OK with using
// the VM that Ghidra wants to use. Changing it automatically might cause problems for their
// existing Java project.
if (vmEntryCandidate == null || (!vmEntryCandidate.getPath().equals(vmPath) &&
EclipseMessageUtils.showConfirmDialog("Java Conflict",
"Current Java: " + JavaRuntime.getVMInstall(javaProject).getName() +
"\nGhidra Java: " + vm.getName() +
"\n\nPress OK to use Ghidra's Java, or cancel to keep current Java."))) {
classpathEntriesToKeep.add(JavaCore.newContainerEntry(vmPath));
}
else {
classpathEntriesToKeep.add(vmEntryCandidate);
}
// Add the Ghidra libraries from the new Ghidra installation directory to the classpath
List<IClasspathEntry> libraryClasspathEntries =
getGhidraLibraryClasspathEntries(ghidraLayout);
classpathEntriesToKeep.addAll(libraryClasspathEntries);
// Set classpath
javaProject.setRawClasspath(
classpathEntriesToKeep.toArray(new IClasspathEntry[classpathEntriesToKeep.size()]),
null);
// Update link to the Ghidra installation directory
ghidraFolder.createLink(new Path(ghidraInstallDir.getAbsolutePath()), IResource.REPLACE,
monitor);
// Update language ant properties file, if applicable
GhidraModuleUtils.writeAntProperties(javaProject.getProject(), ghidraLayout);
// Setup Python for the project
if (PyDevUtils.isSupportedPyDevInstalled()) {
try {
PyDevUtils.setupPythonForProject(javaProject, libraryClasspathEntries,
jythonInterpreterName, monitor);
}
catch (OperationNotSupportedException e) {
EclipseMessageUtils.showErrorDialog("PyDev error",
"Failed to setup Python for the project. PyDev version is not supported.");
}
}
}
/**
* Gets the appropriate classpath attribute for Ghidra's javadoc in the provided layout.
*
* @param ghidraLayout The Ghidra layout that contains the javadoc to get.
* @return The appropriate classpath attribute for Ghidra's javadoc in the provided layout.
* @throws FileNotFoundException If the javadoc was not found in the provided layout.
*/
private static IClasspathAttribute getGhidraJavadoc(GhidraApplicationLayout ghidraLayout)
throws FileNotFoundException {
File ghidraInstallDir = ghidraLayout.getApplicationInstallationDir().getFile(false);
File ghidraJavadocFile = new File(ghidraInstallDir, "docs/GhidraAPI_javadoc.zip");
if (!ghidraJavadocFile.isFile()) {
throw new FileNotFoundException("Ghidra javadoc file does not exist!");
}
String ghidraJavadocPath = String.format("jar:file:%s%s!/api/",
(ghidraJavadocFile.getAbsolutePath().startsWith("/") ? "" : "/"),
ghidraJavadocFile.getAbsolutePath());
return JavaCore.newClasspathAttribute("javadoc_location", ghidraJavadocPath);
}
/**
* Gets a list of classpath entries for Ghidra's module libraries.
*
* @param ghidraLayout The Ghidra layout that contains the classpath entries to get.
* @return A list of classpath entries for Ghidra's module libraries.
* @throws FileNotFoundException If the javadoc was not found in the provided layout.
*/
private static List<IClasspathEntry> getGhidraLibraryClasspathEntries(
GhidraApplicationLayout ghidraLayout) throws FileNotFoundException {
IClasspathAttribute ghidraJavadocAttr = getGhidraJavadoc(ghidraLayout);
List<IClasspathEntry> classpathEntries = new ArrayList<>();
for (GModule module : ghidraLayout.getModules().values()) {
File moduleDir = module.getModuleRoot().getFile(false);
File libDir = new File(moduleDir, "lib");
if (!libDir.isDirectory()) {
continue;
}
for (File f : libDir.listFiles()) { // assuming no relevant subdirs exist in lib/
String name = f.getName();
if (!name.endsWith(".jar")) {
continue;
}
IPath jarPath = new Path(f.getAbsolutePath());
String baseJarName = name.substring(0, name.length() - 4);
File srcZipFile = new File(libDir, baseJarName + "-src.zip");
IPath srcPath = srcZipFile.exists() ? new Path(srcZipFile.getAbsolutePath()) : null;
classpathEntries.add(JavaCore.newLibraryEntry(jarPath, srcPath, null, null,
new IClasspathAttribute[] { ghidraJavadocAttr }, false));
}
}
return classpathEntries;
}
/**
* Gets the required VM used to build and run the Ghidra defined by the given layout.
*
* @param javaConfig Ghidra's Java configuration.
* @return The required VM used to build and run the Ghidra defined by the given layout.
* @throws IOException If there was a file-related problem with getting the VM.
* @throws CoreException If there was an Eclipse-related problem with creating the project.
*/
private static IVMInstall getGhidraVm(JavaConfig javaConfig) throws IOException, CoreException {
File requiredJavaHomeDir = javaConfig.getSavedJavaHome(); // safe to assume it's valid
// First look for a matching VM in Eclipse's existing list.
// NOTE: Mac has its own VM type, so be sure to check it for VM matches too.
IVMInstall vm = null;
IVMInstallType standardType =
JavaRuntime.getVMInstallType(StandardVMType.ID_STANDARD_VM_TYPE);
IVMInstallType macType =
JavaRuntime.getVMInstallType("org.eclipse.jdt.internal.launching.macosx.MacOSXType");
if (standardType == null) {
throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, IStatus.ERROR,
"Failed to find the standard Java VM type.", null));
}
for (IVMInstall existingVm : standardType.getVMInstalls()) {
if (requiredJavaHomeDir.equals(existingVm.getInstallLocation())) {
vm = existingVm;
break;
}
}
if (macType != null && vm == null) {
for (IVMInstall existingVm : macType.getVMInstalls()) {
if (requiredJavaHomeDir.equals(existingVm.getInstallLocation())) {
vm = existingVm;
break;
}
}
}
// If we didn't find a match, create a new standard type entry
if (vm == null) {
long unique = System.currentTimeMillis(); // This loop seems to be the accepted way to get a unique VM id
while (standardType.findVMInstall(String.valueOf(unique)) != null) {
unique++;
}
VMStandin vmStandin = new VMStandin(standardType, String.valueOf(unique));
String dirName = requiredJavaHomeDir.getName();
if (requiredJavaHomeDir.getAbsolutePath().contains("Contents/Home")) {
dirName = requiredJavaHomeDir.getParentFile().getParentFile().getName();
}
vmStandin.setName(Activator.PLUGIN_ID + "_" + dirName);
vmStandin.setInstallLocation(requiredJavaHomeDir);
vm = vmStandin.convertToRealVM();
}
return vm;
}
/**
* Configures the default Java compiler behavior for the given java project.
*
* @param jp The Java project to configure.
* @param javaConfig Ghidra's Java configuration.
*/
private static void configureJavaCompiler(IJavaProject jp, JavaConfig javaConfig) {
final String WARNING = JavaCore.WARNING;
final String IGNORE = JavaCore.IGNORE;
final String ERROR = JavaCore.ERROR;
// Compliance
jp.setOption(JavaCore.COMPILER_SOURCE, javaConfig.getCompilerComplianceLevel());
jp.setOption(JavaCore.COMPILER_COMPLIANCE, javaConfig.getCompilerComplianceLevel());
jp.setOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM,
javaConfig.getCompilerComplianceLevel());
// Code style
jp.setOption(JavaCore.COMPILER_PB_STATIC_ACCESS_RECEIVER, WARNING);
jp.setOption(JavaCore.COMPILER_PB_INDIRECT_STATIC_ACCESS, WARNING);
jp.setOption(JavaCore.COMPILER_PB_UNQUALIFIED_FIELD_ACCESS, IGNORE);
jp.setOption(JavaCore.COMPILER_PB_UNDOCUMENTED_EMPTY_BLOCK, IGNORE);
jp.setOption(JavaCore.COMPILER_PB_SYNTHETIC_ACCESS_EMULATION, IGNORE);
jp.setOption(JavaCore.COMPILER_PB_METHOD_WITH_CONSTRUCTOR_NAME, WARNING);
jp.setOption(JavaCore.COMPILER_PB_PARAMETER_ASSIGNMENT, IGNORE);
jp.setOption(JavaCore.COMPILER_PB_NON_NLS_STRING_LITERAL, IGNORE);
// Potential programming problems
jp.setOption(JavaCore.COMPILER_PB_MISSING_SERIAL_VERSION, IGNORE);
jp.setOption(JavaCore.COMPILER_PB_NO_EFFECT_ASSIGNMENT, WARNING);
jp.setOption(JavaCore.COMPILER_PB_POSSIBLE_ACCIDENTAL_BOOLEAN_ASSIGNMENT, WARNING);
jp.setOption(JavaCore.COMPILER_PB_FINALLY_BLOCK_NOT_COMPLETING, WARNING);
jp.setOption(JavaCore.COMPILER_PB_EMPTY_STATEMENT, WARNING);
jp.setOption(JavaCore.COMPILER_PB_HIDDEN_CATCH_BLOCK, ERROR);
jp.setOption(JavaCore.COMPILER_PB_VARARGS_ARGUMENT_NEED_CAST, WARNING);
jp.setOption(JavaCore.COMPILER_PB_AUTOBOXING, IGNORE);
jp.setOption(JavaCore.COMPILER_PB_INCOMPLETE_ENUM_SWITCH, WARNING);
jp.setOption(JavaCore.COMPILER_PB_FALLTHROUGH_CASE, IGNORE);
jp.setOption(JavaCore.COMPILER_PB_NULL_REFERENCE, WARNING);
// Name shadowing and conflicts
jp.setOption(JavaCore.COMPILER_PB_FIELD_HIDING, WARNING);
jp.setOption(JavaCore.COMPILER_PB_LOCAL_VARIABLE_HIDING, WARNING);
jp.setOption(JavaCore.COMPILER_PB_TYPE_PARAMETER_HIDING, WARNING);
jp.setOption(JavaCore.COMPILER_PB_OVERRIDING_PACKAGE_DEFAULT_METHOD, WARNING);
jp.setOption(JavaCore.COMPILER_PB_INCOMPATIBLE_NON_INHERITED_INTERFACE_METHOD, ERROR);
// Deprecated and restricted API
jp.setOption(JavaCore.COMPILER_PB_DEPRECATION, WARNING);
jp.setOption(JavaCore.COMPILER_PB_FORBIDDEN_REFERENCE, ERROR);
jp.setOption(JavaCore.COMPILER_PB_DISCOURAGED_REFERENCE, WARNING);
// Unnecessary code
jp.setOption(JavaCore.COMPILER_PB_UNUSED_LOCAL, WARNING);
jp.setOption(JavaCore.COMPILER_PB_UNUSED_PARAMETER, IGNORE);
jp.setOption(JavaCore.COMPILER_PB_UNUSED_IMPORT, WARNING);
jp.setOption(JavaCore.COMPILER_PB_UNUSED_PRIVATE_MEMBER, WARNING);
jp.setOption(JavaCore.COMPILER_PB_UNNECESSARY_ELSE, WARNING);
jp.setOption(JavaCore.COMPILER_PB_UNNECESSARY_TYPE_CHECK, WARNING);
jp.setOption(JavaCore.COMPILER_PB_UNUSED_DECLARED_THROWN_EXCEPTION, WARNING);
jp.setOption(JavaCore.COMPILER_PB_UNUSED_LABEL, WARNING);
// Generic types
jp.setOption(JavaCore.COMPILER_PB_UNCHECKED_TYPE_OPERATION, WARNING);
jp.setOption(JavaCore.COMPILER_PB_RAW_TYPE_REFERENCE, WARNING);
jp.setOption(JavaCore.COMPILER_PB_FINAL_PARAMETER_BOUND, WARNING);
// Annotations
jp.setOption(JavaCore.COMPILER_PB_MISSING_OVERRIDE_ANNOTATION, IGNORE);
jp.setOption(JavaCore.COMPILER_PB_MISSING_DEPRECATED_ANNOTATION, IGNORE);
jp.setOption(JavaCore.COMPILER_PB_ANNOTATION_SUPER_INTERFACE, ERROR);
jp.setOption(JavaCore.COMPILER_PB_UNHANDLED_WARNING_TOKEN, WARNING);
jp.setOption(JavaCore.COMPILER_PB_SUPPRESS_WARNINGS, JavaCore.ENABLED);
}
}

View file

@ -0,0 +1,173 @@
/* ###
* 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.utils;
import java.io.*;
import java.text.ParseException;
import java.util.*;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.*;
import ghidra.GhidraApplicationLayout;
import ghidra.framework.GModule;
/**
* Utility methods for working with Ghidra scripts in Eclipse.
*/
public class GhidraScriptUtils {
public static File userScriptsDir =
new File(System.getProperty("user.home") + "/ghidra_scripts");
/**
* Creates a new Ghidra script project with the given name.
*
* @param projectName The name of the project to create.
* @param projectDir The directory the project will be created in.
* @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 linkUserScripts Whether or not to link in the user scripts directory.
* @param linkSystemScripts Whether or not to link in the system scripts directories.
* @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 created 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 createGhidraScriptProject(String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory, boolean linkUserScripts,
boolean linkSystemScripts, GhidraApplicationLayout ghidraLayout,
String jythonInterpreterName, IProgressMonitor monitor)
throws IOException, ParseException, CoreException {
List<IClasspathEntry> classpathEntries = new ArrayList<>();
// Create empty Ghidra project
IJavaProject javaProject = GhidraProjectUtils.createEmptyGhidraProject(projectName,
projectDir, createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName,
monitor);
// Link each module's ghidra_scripts directory to the project
if (linkSystemScripts) {
for (GModule module : ghidraLayout.getModules().values()) {
File moduleDir = module.getModuleRoot().getFile(false);
File moduleScriptsDir = new File(moduleDir, "ghidra_scripts");
if (!moduleScriptsDir.exists()) {
continue;
}
IPath moduleScriptsDirPath = new Path(moduleScriptsDir.getAbsolutePath());
String moduleName = moduleDir.getName();
String scriptsDirName = "Ghidra " + moduleName + " scripts";
IFolder link = javaProject.getProject().getFolder(scriptsDirName);
link.createLink(moduleScriptsDirPath, IResource.NONE, monitor);
classpathEntries.add(JavaCore.newSourceEntry(link.getFullPath()));
}
}
// Link in the user's personal ghidra_scripts directory
if (linkUserScripts) {
IFolder link = javaProject.getProject().getFolder("Home scripts");
link.createLink(new Path(userScriptsDir.getAbsolutePath()), IResource.NONE, monitor);
classpathEntries.add(JavaCore.newSourceEntry(link.getFullPath()));
}
// Update the project's classpath
GhidraProjectUtils.addToClasspath(javaProject, classpathEntries, monitor);
return javaProject;
}
/**
* Create a Ghidra script with the given name in the in the user's ghidra_scripts, and link it in
* to the provided project.
*
* @param scriptFolder The folder to create the script in.
* @param scriptName The name of the script to create.
* @param scriptAuthor The script's author.
* @param scriptCategory The script's category.
* @param scriptDescription The script's description lines.
* @param monitor The progress monitor to use during script creation.
* @return The script file (which could be opened in an editor by default).
* @throws IOException If there was a file-related problem with creating the script.
* @throws CoreException If there was an Eclipse-related problem with creating the script.
*/
public static IFile createGhidraScript(IFolder scriptFolder, String scriptName,
String scriptAuthor, String scriptCategory, String[] scriptDescription,
IProgressMonitor monitor) throws CoreException, IOException {
// Create the scripts folder directory, if necessary
if (!scriptFolder.exists()) {
GhidraProjectUtils.createFolder(scriptFolder, monitor);
}
IFile scriptFile = scriptFolder.getFile(scriptName);
// Does the script exist already? If so, it's a problem.
if (scriptFile.exists()) {
throw new IOException("File already exists: " + scriptFile);
}
// Create the script file, and fill in a useful entry point
try (PrintWriter writer =
new PrintWriter(new FileWriter(scriptFile.getLocation().toFile()))) {
if (scriptName.endsWith(".java")) {
Arrays.stream(scriptDescription).forEach(line -> writer.println("//" + line));
writer.println("//@author " + scriptAuthor);
writer.println("//@category " + scriptCategory);
writer.println("//@keybinding");
writer.println("//@menupath");
writer.println("//@toolbar");
writer.println();
writer.println("import ghidra.app.script.GhidraScript;");
writer.println();
writer.println("public class " + scriptName.substring(0, scriptName.length() - 5) +
" extends GhidraScript {");
writer.println();
writer.println("\t@Override");
writer.println("\tprotected void run() throws Exception {");
writer.println("\t\t//TODO: Add script code here");
writer.println("\t}");
writer.println("}");
}
else if (scriptName.endsWith(".py")) {
Arrays.stream(scriptDescription).forEach(line -> writer.println("#" + line));
writer.println("#@author " + scriptAuthor);
writer.println("#@category " + scriptCategory);
writer.println("#@keybinding");
writer.println("#@menupath");
writer.println("#@toolbar");
writer.println();
writer.println("#TODO: Add script code here");
}
}
catch (IOException e) {
throw new IOException("Failed to create: " + scriptFile);
}
// Refresh project to it sees the new script
scriptFile.getProject().refreshLocal(IResource.DEPTH_INFINITE, monitor);
return scriptFile;
}
}

View file

@ -0,0 +1,94 @@
/* ###
* 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.utils;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.ui.JavaElementLabelProvider;
import org.eclipse.jdt.ui.StandardJavaElementContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
import org.eclipse.ui.dialogs.ISelectionStatusValidator;
import ghidradev.Activator;
/**
* A dialog that lets you choose a Java package fragment root, which is basically a top-level
* source folder.
*/
public class PackageFragmentRootSelectionDialog extends ElementTreeSelectionDialog {
/**
* Creates a new package fragment root selection dialog.
*
* @param shell The parent shell for the dialog.
* @param title The title of the dialog.
* @param message The message of the dialog.
* @param errorMessage the error message to display if an invalid selection is made.
*/
public PackageFragmentRootSelectionDialog(Shell shell, String title, String message,
String errorMessage) {
super(shell, new JavaElementLabelProvider(JavaElementLabelProvider.SHOW_DEFAULT),
new StandardJavaElementContentProvider());
setTitle(title);
setMessage(message);
setAllowMultiple(false);
setInput(JavaCore.create(ResourcesPlugin.getWorkspace().getRoot()));
setValidator(new ISelectionStatusValidator() {
@Override
public IStatus validate(Object[] sel) {
if (sel.length == 1 && sel[0] instanceof IPackageFragmentRoot) {
return new Status(IStatus.OK, Activator.PLUGIN_ID, IStatus.OK, "", null);
}
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, IStatus.ERROR, errorMessage,
null);
}
});
addFilter(new ViewerFilter() {
@Override
public boolean select(Viewer viewer, Object parentObject, Object element) {
if (element instanceof IPackageFragmentRoot) {
IPackageFragmentRoot packageFragmentRoot = (IPackageFragmentRoot) element;
return !packageFragmentRoot.isArchive() && !packageFragmentRoot.isExternal();
}
return element instanceof IJavaModel || element instanceof IJavaProject;
}
});
}
/**
* Gets the selected package fragment root.
*
* @return The selected package fragment root. Could be null if there is not a valid
* selection.
*/
public IPackageFragmentRoot getPackageFragmentRoot() {
Object[] result = getResult();
if (result.length == 1 && result[0] instanceof IPackageFragmentRoot) {
return (IPackageFragmentRoot) result[0];
}
return null;
}
}

View file

@ -0,0 +1,162 @@
/* ###
* 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.utils;
import java.io.File;
import java.util.List;
import javax.naming.OperationNotSupportedException;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
/**
* Utility methods for interacting with PyDev.
*/
public class PyDevUtils {
public final static String MIN_SUPPORTED_VERSION = "6.3.1";
/**
* Checks to see if a supported version of PyDev is installed.
*
* @return True if a supported version of PyDev is installed; otherwise, false.
*/
public static boolean isSupportedPyDevInstalled() {
try {
if (PyDevUtilsInternal.isPyDevInstalled()) {
// Make sure the installed version of PyDev is new enough to support the following
// operation.
getJython27InterpreterNames();
return true;
}
}
catch (OperationNotSupportedException | NoClassDefFoundError e) {
// Fall through to return false
}
return false;
}
/**
* Gets a list of discovered Jython 2.7 interpreter names.
*
* @return a list of discovered Jython 2.7 interpreter names.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static List<String> getJython27InterpreterNames() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getJython27InterpreterNames();
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Adds the given Jython interpreter to PyDev.
*
* @param interpreterName The name of the interpreter to add.
* @param interpreterFile The interpreter file to add.
* @param interpreterLibDir The interpreter library directory to add.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static void addJythonInterpreter(String interpreterName, File interpreterFile,
File interpreterLibDir)
throws OperationNotSupportedException {
try {
PyDevUtilsInternal.addJythonInterpreter(interpreterName, interpreterFile,
interpreterLibDir);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Sets up Python for the given Java project.
*
* @param javaProject The Java project to enable Python for.
* @param classpathEntries The classpath entries to add to the Python path.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* If this is null, Python support will be removed from the project.
* @param monitor The progress monitor used during link.
* @throws CoreException if there was an Eclipse-related problem with enabling Python for the
* project.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static void setupPythonForProject(IJavaProject javaProject,
List<IClasspathEntry> classpathEntries, String jythonInterpreterName,
IProgressMonitor monitor) throws CoreException, OperationNotSupportedException {
try {
PyDevUtilsInternal.setupPythonForProject(javaProject, classpathEntries,
jythonInterpreterName, monitor);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Starts the PyDev remote debugger.
*
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static void startPyDevRemoteDebugger() throws OperationNotSupportedException {
try {
PyDevUtilsInternal.startPyDevRemoteDebugger();
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev Jython preference page ID.
*
* @return the PyDev Jython preference page ID.
*/
public static String getJythonPreferencePageId() {
return "org.python.pydev.ui.pythonpathconf.interpreterPreferencesPageJython";
}
/**
* Gets The PyDev source directory.
*
* @return The PyDev source directory, or null if it was not found.
*/
public static File getPyDevSrcDir() {
String eclipsePath = Platform.getInstallLocation().getURL().getFile();
File pluginsDir = new File(eclipsePath, "plugins");
File[] pluginSubDirs = pluginsDir.listFiles(File::isDirectory);
if (pluginSubDirs != null) {
for (File dir : pluginSubDirs) {
if (dir.getName().startsWith("org.python.pydev")) {
File pysrcDir = new File(dir, "pysrc");
if (pysrcDir.isDirectory()) {
return pysrcDir;
}
}
}
}
return null;
}
}

View file

@ -0,0 +1,154 @@
/* ###
* 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.utils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.python.pydev.ast.interpreter_managers.InterpreterInfo;
import org.python.pydev.ast.interpreter_managers.InterpreterManagersAPI;
import org.python.pydev.core.*;
import org.python.pydev.plugin.nature.PythonNature;
import com.python.pydev.debug.remote.client_api.PydevRemoteDebuggerServer;
import ghidradev.EclipseMessageUtils;
import utilities.util.ArrayUtilities;
/**
* Utility methods for interacting with PyDev.
* <p>
* NOTE: Since PyDev may not be installed, all PyDev interactions must be done in this
* class, and every method must throw {@link NoClassDefFoundError} so caller's can
* handle PyDev being absent. People wanting to interact with PyDev should go through the
* public-facing version of this class...{@link PyDevUtils}.
*/
class PyDevUtilsInternal {
/**
* Checks to see if PyDev is installed.
*
* @return True if PyDev is installed; otherwise, false.
* @throws NoClassDefFoundError if PyDev is not installed.
*/
public static boolean isPyDevInstalled() throws NoClassDefFoundError {
for (Bundle bundle : FrameworkUtil.getBundle(
PyDevUtilsInternal.class).getBundleContext().getBundles()) {
if (bundle.getSymbolicName().contains("pydev")) {
return true;
}
}
return false;
}
/**
* Gets a list of discovered Jython 2.7 interpreter names.
*
* @return a list of discovered Jython 2.7 interpreter names.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
*/
public static List<String> getJython27InterpreterNames()
throws NoClassDefFoundError, NoSuchMethodError {
List<String> interpreters = new ArrayList<>();
IInterpreterManager iMan = InterpreterManagersAPI.getJythonInterpreterManager(true);
for (IInterpreterInfo info : iMan.getInterpreterInfos()) {
if (info.getInterpreterType() == IPythonNature.INTERPRETER_TYPE_JYTHON && info.getVersion().equals("2.7")) {
interpreters.add(info.getName());
}
}
return interpreters;
}
/**
* Adds the given Jython interpreter to PyDev.
*
* @param interpreterName The name of the interpreter to add.
* @param interpreterFile The interpreter to add.
* @param interpreterLibDir The interpreter library directory to add.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
*/
public static void addJythonInterpreter(String interpreterName, File interpreterFile,
File interpreterLibDir) throws NoClassDefFoundError, NoSuchMethodError {
IProgressMonitor monitor = new NullProgressMonitor();
IInterpreterManager iMan = InterpreterManagersAPI.getJythonInterpreterManager(true);
IInterpreterInfo iInfo =
iMan.createInterpreterInfo(interpreterFile.getAbsolutePath(), monitor, false);
iInfo.setName(interpreterName);
if (iInfo instanceof InterpreterInfo) {
InterpreterInfo info = (InterpreterInfo) iInfo;
info.libs.add(interpreterLibDir.getAbsolutePath());
info.libs.add(new File(interpreterLibDir, "site-packages").getAbsolutePath());
}
else {
EclipseMessageUtils.error("Failed to add Jython Lib directory to python path");
}
iMan.setInfos(ArrayUtilities.copyAndAppend(iMan.getInterpreterInfos(), iInfo), null,
monitor);
}
/**
* Sets up Python for the given Java project.
*
* @param javaProject The Java project to setup Python for.
* @param classpathEntries The classpath entries to add to the Python path.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* If this is null, Python support will be removed from the project.
* @param monitor The progress monitor used during link.
* @throws CoreException If there was an Eclipse-related problem with enabling Python for the project.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
*/
public static void setupPythonForProject(IJavaProject javaProject,
List<IClasspathEntry> classpathEntries, String jythonInterpreterName,
IProgressMonitor monitor)
throws CoreException, NoClassDefFoundError, NoSuchMethodError {
PythonNature.removeNature(javaProject.getProject(), monitor);
if (jythonInterpreterName != null) {
String libs = classpathEntries.stream().map(e -> e.getPath().toOSString()).collect(
Collectors.joining("|"));
PythonNature.addNature(javaProject.getProject(), monitor,
IPythonNature.JYTHON_VERSION_2_7, null, libs, jythonInterpreterName, null);
}
}
/**
* Starts the PyDev remote debugger.
*
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
*/
public static void startPyDevRemoteDebugger() throws NoClassDefFoundError, NoSuchMethodError {
PydevRemoteDebuggerServer.startServer();
}
private PyDevUtilsInternal() throws NoClassDefFoundError {
// Prevent instantiation
}
}

View file

@ -0,0 +1,151 @@
/* ###
* 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 java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils.ModuleTemplateType;
import ghidradev.ghidraprojectcreator.wizards.pages.*;
/**
* Wizard to create a new Ghidra module project.
*/
public class CreateGhidraModuleProjectWizard extends Wizard implements INewWizard {
private IWorkbench workbench;
private CreateGhidraProjectWizardPage projectPage;
private ConfigureGhidraModuleProjectWizardPage projectConfigPage;
private ChooseGhidraInstallationWizardPage ghidraInstallationPage;
private EnablePythonWizardPage pythonPage;
/**
* Creates a new Ghidra module project wizard.
*/
public CreateGhidraModuleProjectWizard() {
setNeedsProgressMonitor(true);
}
@Override
public void init(IWorkbench wb, IStructuredSelection selection) {
workbench = wb;
projectPage = new CreateGhidraProjectWizardPage();
projectConfigPage = new ConfigureGhidraModuleProjectWizardPage();
ghidraInstallationPage = new ChooseGhidraInstallationWizardPage();
pythonPage = new EnablePythonWizardPage(ghidraInstallationPage);
}
@Override
public void addPages() {
addPage(projectPage);
addPage(projectConfigPage);
addPage(ghidraInstallationPage);
addPage(pythonPage);
}
@Override
public boolean performFinish() {
File ghidraInstallDir = ghidraInstallationPage.getGhidraInstallDir();
String projectName = projectPage.getProjectName();
boolean createRunConfig = projectPage.shouldCreateRunConfig();
String runConfigMemory = projectPage.getRunConfigMemory();
File projectDir = projectPage.getProjectDir();
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
Set<ModuleTemplateType> moduleTemplateTypes = projectConfigPage.getModuleTemplateTypes();
try {
getContainer().run(true, false,
monitor -> create(ghidraInstallDir, projectName, projectDir, createRunConfig,
runConfigMemory, moduleTemplateTypes, jythonInterpreterName, monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
catch (InvocationTargetException e) {
error(showWizardErrorDialog(getShell(), e), e);
return false;
}
return true;
}
/**
* Creates a Ghidra module project.
*
* @param ghidraInstallDir The Ghidra installation directory to use.
* @param projectName The name of the project to create.
* @param projectDir The project's directory.
* @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 moduleTemplateTypes The desired module template types.
* @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 create(File ghidraInstallDir, String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory,
Set<ModuleTemplateType> moduleTemplateTypes, String jythonInterpreterName,
IProgressMonitor monitor) throws InvocationTargetException {
try {
info("Creating " + projectName + " at " + projectDir);
monitor.beginTask("Creating " + projectName, 3);
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraInstallDir);
monitor.worked(1);
IJavaProject javaProject =
GhidraModuleUtils.createGhidraModuleProject(projectName, projectDir,
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
monitor.worked(1);
IFile sourceFile = GhidraModuleUtils.configureModuleSource(javaProject,
projectDir, ghidraLayout, moduleTemplateTypes, monitor);
monitor.worked(1);
if (sourceFile != null) {
EclipseMessageUtils.displayInEditor(sourceFile, workbench);
}
info("Finished creating " + projectName);
}
catch (IOException | ParseException | CoreException e) {
throw new InvocationTargetException(e);
}
finally {
monitor.done();
}
}
}

View file

@ -0,0 +1,137 @@
/* ###
* 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.INewWizard;
import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout;
import ghidradev.ghidraprojectcreator.utils.GhidraScriptUtils;
import ghidradev.ghidraprojectcreator.wizards.pages.*;
/**
* Wizard to create a new Ghidra scripting project.
*/
public class CreateGhidraScriptProjectWizard extends Wizard implements INewWizard {
private CreateGhidraProjectWizardPage projectPage;
private ConfigureGhidraScriptProjectWizardPage projectConfigPage;
private ChooseGhidraInstallationWizardPage ghidraInstallationPage;
private EnablePythonWizardPage pythonPage;
/**
* Creates a new Ghidra scripting project wizard.
*/
public CreateGhidraScriptProjectWizard() {
setNeedsProgressMonitor(true);
}
@Override
public void init(IWorkbench wb, IStructuredSelection selection) {
projectPage = new CreateGhidraProjectWizardPage("GhidraScripts");
projectConfigPage = new ConfigureGhidraScriptProjectWizardPage();
ghidraInstallationPage = new ChooseGhidraInstallationWizardPage();
pythonPage = new EnablePythonWizardPage(ghidraInstallationPage);
}
@Override
public void addPages() {
addPage(projectPage);
addPage(projectConfigPage);
addPage(ghidraInstallationPage);
addPage(pythonPage);
}
@Override
public boolean performFinish() {
File ghidraInstallDir = ghidraInstallationPage.getGhidraInstallDir();
String projectName = projectPage.getProjectName();
File projectDir = projectPage.getProjectDir();
boolean createRunConfig = projectPage.shouldCreateRunConfig();
String runConfigMemory = projectPage.getRunConfigMemory();
boolean linkUserScripts = projectConfigPage.shouldLinkUsersScripts();
boolean linkSystemScripts = projectConfigPage.shouldLinkSystemScripts();
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
try {
getContainer().run(true, false,
monitor -> create(ghidraInstallDir, projectName, projectDir, createRunConfig,
runConfigMemory, linkUserScripts, linkSystemScripts, jythonInterpreterName,
monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
catch (InvocationTargetException e) {
error(showWizardErrorDialog(getShell(), e), e);
return false;
}
return true;
}
/**
* Creates a Ghidra script project.
*
* @param ghidraInstallDir The Ghidra installation directory to use.
* @param projectName The name of the project to create.
* @param projectDir The project's directory.
* @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 linkUserScripts Whether or not to link in the user scripts directory.
* @param linkSystemScripts Whether or not to link in the system scripts directories.
* @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 create(File ghidraInstallDir, String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory, boolean linkUserScripts,
boolean linkSystemScripts, String jythonInterpreterName, IProgressMonitor monitor)
throws InvocationTargetException {
try {
info("Creating " + projectName + " at " + projectDir);
monitor.beginTask("Creating " + projectName, 2);
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraInstallDir);
monitor.worked(1);
GhidraScriptUtils.createGhidraScriptProject(projectName, projectDir, createRunConfig,
runConfigMemory, linkUserScripts, linkSystemScripts, ghidraLayout,
jythonInterpreterName, monitor);
monitor.worked(1);
info("Finished creating " + projectName);
}
catch (IOException | ParseException | CoreException e) {
throw new InvocationTargetException(e);
}
finally {
monitor.done();
}
}
}

View file

@ -0,0 +1,128 @@
/* ###
* 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.IOException;
import java.lang.reflect.InvocationTargetException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraScriptUtils;
import ghidradev.ghidraprojectcreator.wizards.pages.CreateGhidraScriptWizardPage;
/**
* Wizard to create a new Ghidra script file in an existing project.
*/
public class CreateGhidraScriptWizard extends Wizard implements INewWizard {
private IWorkbench workbench;
private CreateGhidraScriptWizardPage scriptPage;
/**
* Creates a new Ghidra script wizard.
*/
public CreateGhidraScriptWizard() {
setNeedsProgressMonitor(true);
}
@Override
public void init(IWorkbench wb, IStructuredSelection selection) {
workbench = wb;
IPackageFragmentRoot selectedPackageFragmentRoot = null;
Object firstElement = selection.getFirstElement();
if (firstElement instanceof IPackageFragmentRoot) {
selectedPackageFragmentRoot = (IPackageFragmentRoot) firstElement;
}
scriptPage = new CreateGhidraScriptWizardPage(selectedPackageFragmentRoot);
}
@Override
public void addPages() {
addPage(scriptPage);
}
@Override
public boolean performFinish() {
IFolder scriptFolder = scriptPage.getScriptFolder();
String scriptName = scriptPage.getScriptName();
String scriptAuthor = scriptPage.getScriptAuthor();
String scriptCategory = scriptPage.getScriptCategory();
String[] scriptDescription = scriptPage.getScriptDescription();
try {
getContainer().run(true, false, monitor -> create(scriptFolder, scriptName,
scriptAuthor, scriptCategory, scriptDescription, monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
catch (InvocationTargetException e) {
error(showWizardErrorDialog(getShell(), e), e);
return false;
}
return true;
}
/**
* Creates a Ghidra script.
*
* @param scriptFolder The folder to create the script in.
* @param scriptName The name of the script to create.
* @param scriptAuthor The script's author.
* @param scriptCategory The script's category.
* @param scriptDescription The script's description lines.
* @param monitor The monitor to use during project/script creation.
* @throws InvocationTargetException if an error occurred during project/script creation.
*/
private void create(IFolder scriptFolder, String scriptName, String scriptAuthor,
String scriptCategory, String[] scriptDescription, IProgressMonitor monitor)
throws InvocationTargetException {
try {
info("Creating " + scriptName + " in " + scriptFolder.toString());
monitor.beginTask("Creating " + scriptName + " in " + scriptFolder.toString(), 1);
IFile scriptFile = GhidraScriptUtils.createGhidraScript(scriptFolder, scriptName,
scriptAuthor, scriptCategory, scriptDescription, monitor);
monitor.worked(1);
if (scriptFile != null) {
EclipseMessageUtils.displayInEditor(scriptFile, workbench);
}
info("Finished creating " + scriptName);
}
catch (IOException | CoreException e) {
throw new InvocationTargetException(e);
}
finally {
monitor.done();
}
}
}

View file

@ -0,0 +1,166 @@
/* ###
* 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 java.util.*;
import org.eclipse.buildship.core.GradleDistribution;
import org.eclipse.buildship.core.internal.CorePlugin;
import org.eclipse.buildship.core.internal.launch.GradleLaunchConfigurationManager;
import org.eclipse.buildship.core.internal.launch.GradleRunConfigurationAttributes;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout;
import ghidra.launch.JavaConfig;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.wizards.pages.ChooseGhidraModuleProjectWizardPage;
import ghidradev.ghidraprojectcreator.wizards.pages.ConfigureGradleWizardPage;
/**
* Wizard for exporting a Ghidra module project to a releasable extension zip bundle.
*/
@SuppressWarnings("restriction")
public class ExportGhidraModuleWizard extends Wizard implements INewWizard {
private ChooseGhidraModuleProjectWizardPage projectPage;
private ConfigureGradleWizardPage gradlePage;
/**
* Creates a new Ghidra module export wizard.
*/
public ExportGhidraModuleWizard() {
setNeedsProgressMonitor(true);
}
@Override
public void init(IWorkbench wb, IStructuredSelection selection) {
projectPage = new ChooseGhidraModuleProjectWizardPage(
GhidraProjectUtils.getSelectedProject(selection));
gradlePage = new ConfigureGradleWizardPage(projectPage);
}
@Override
public void addPages() {
addPage(projectPage);
addPage(gradlePage);
}
@Override
public boolean performFinish() {
IJavaProject javaProject = projectPage.getGhidraModuleProject();
GradleDistribution gradleDist = gradlePage.getGradleDistribution();
try {
getContainer().run(true, false, monitor -> export(javaProject, gradleDist, monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
catch (InvocationTargetException e) {
error(showWizardErrorDialog(getShell(), e), e);
return false;
}
return true;
}
/**
* Exports the given Ghidra module project to an extension zip file.
*
* @param javaProject The Ghidra module project to export.
* @param gradleDistribution The Gradle distribution to use to export.
* @param monitor The monitor to use during export.
* @throws InvocationTargetException if an error occurred during export.
*/
private void export(IJavaProject javaProject, GradleDistribution gradleDistribution,
IProgressMonitor monitor)
throws InvocationTargetException {
try {
IProject project = javaProject.getProject();
info("Exporting " + project.getName());
monitor.beginTask("Exporting " + project.getName(), 2);
// Get path to Ghidra installation directory
String ghidraInstallDirPath = project.getFolder(
GhidraProjectUtils.GHIDRA_FOLDER_NAME).getRawLocation().toOSString();
// Get project's java. Gradle should use the same version.
// TODO: It's more correct to get this from the project's classpath, since Ghidra's
// saved Java home can change from launch to launch.
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(new File(ghidraInstallDirPath));
File javaHomeDir = new JavaConfig(
ghidraLayout.getApplicationInstallationDir().getFile(false)).getSavedJavaHome();
if(javaHomeDir == null) {
throw new IOException("Failed to get the Java home associated with the project. " +
"Perform a \"Link Ghidra\" operation on the project and try again.");
}
// Setup the Gradle build attributes
List<String> tasks = new ArrayList<>();
String workingDir = project.getRawLocation().toOSString();
String gradleDist = gradleDistribution.toString();
String gradleUserHome = "";
String javaHome = javaHomeDir.getAbsolutePath();
List<String> jvmArgs = new ArrayList<>();
List<String> gradleArgs =
Arrays.asList(new String[] { "-PGHIDRA_INSTALL_DIR=" + ghidraInstallDirPath });
boolean showExecutionView = false;
boolean showConsoleView = true;
boolean overrideWorkspaceSettings = true;
boolean isOffline = true;
boolean isBuildScansEnabled = false;
GradleRunConfigurationAttributes gradleAttributes =
new GradleRunConfigurationAttributes(tasks, workingDir, gradleDist, gradleUserHome,
javaHome, jvmArgs, gradleArgs, showExecutionView, showConsoleView,
overrideWorkspaceSettings, isOffline, isBuildScansEnabled);
// Launch Gradle
GradleLaunchConfigurationManager lm = CorePlugin.gradleLaunchConfigurationManager();
ILaunchConfiguration lc = lm.getOrCreateRunConfiguration(gradleAttributes);
lc.launch(ILaunchManager.RUN_MODE, monitor, true, true);
lc.delete();
monitor.worked(1);
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
monitor.worked(1);
info("Finished exporting " + project.getName());
}
catch (IOException | ParseException | CoreException e) {
throw new InvocationTargetException(e);
}
finally {
monitor.done();
}
}
}

View file

@ -0,0 +1,119 @@
/* ###
* 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.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.wizard.Wizard;
import ghidra.GhidraApplicationLayout;
import ghidra.launch.JavaConfig;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.wizards.pages.*;
/**
* Wizard for linking a Java project's classpath and external links to a Ghidra installation directory.
*/
public class LinkGhidraWizard extends Wizard {
private ChooseGhidraInstallationWizardPage ghidraInstallationPage;
private ChooseJavaProjectWizardPage projectPage;
private EnablePythonWizardPage pythonPage;
public LinkGhidraWizard(ISelection selection) {
setNeedsProgressMonitor(true);
this.ghidraInstallationPage = new ChooseGhidraInstallationWizardPage();
this.projectPage =
new ChooseJavaProjectWizardPage((GhidraProjectUtils.getSelectedProject(selection)));
this.pythonPage = new EnablePythonWizardPage(ghidraInstallationPage);
}
@Override
public void addPages() {
addPage(ghidraInstallationPage);
addPage(projectPage);
addPage(pythonPage);
}
@Override
public boolean performFinish() {
File ghidraInstallDir = ghidraInstallationPage.getGhidraInstallDir();
IJavaProject javaProject = projectPage.getJavaProject();
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
try {
getContainer().run(true, false,
monitor -> link(ghidraInstallDir, javaProject, jythonInterpreterName, monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
catch (InvocationTargetException e) {
error(showWizardErrorDialog(getShell(), e), e);
return false;
}
return true;
}
/**
* Links a Java project's classpath and external links to a Ghidra installation directory.
*
* @param ghidraInstallDir The Ghidra installation directory to use.
* @param javaProject The Java project to link.
* @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 link.
* @throws InvocationTargetException if an error occurred during link.
*/
private void link(File ghidraInstallDir, IJavaProject javaProject, String jythonInterpreterName,
IProgressMonitor monitor) throws InvocationTargetException {
IProject project = javaProject.getProject();
try {
info("Linking " + project.getName());
monitor.beginTask("Linking " + project.getName(), 2);
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraInstallDir);
JavaConfig javaConfig =
new JavaConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
GhidraProjectUtils.linkGhidraToProject(javaProject, ghidraLayout, javaConfig,
jythonInterpreterName, monitor);
monitor.worked(1);
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
monitor.worked(1);
info("Finished linking " + project.getName());
}
catch (IOException | ParseException | CoreException e) {
throw new InvocationTargetException(e);
}
finally {
monitor.done();
}
}
}

View file

@ -0,0 +1,143 @@
/* ###
* 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 java.io.IOException;
import java.text.ParseException;
import org.eclipse.jface.preference.PreferenceDialog;
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 org.eclipse.ui.dialogs.PreferencesUtil;
import ghidra.launch.JavaConfig;
import ghidra.launch.JavaFinder.JavaFilter;
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferencePage;
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferences;
/**
* A wizard page that lets the user choose a Ghidra installation.
*/
public class ChooseGhidraInstallationWizardPage extends WizardPage {
private Combo ghidraInstallDirCombo;
private Button addGhidraInstallDirButton;
/**
* Creates a new Ghidra installation wizard page.
*/
public ChooseGhidraInstallationWizardPage() {
super("ChooseGhidraInstallationWizardPage");
setTitle("Choose a Ghidra Installation");
setDescription("Choose the Ghidra installation to use.");
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(3, false));
Label ghidraInstallDirLabel = new Label(container, SWT.NULL);
ghidraInstallDirLabel.setText("Ghidra installation:");
ghidraInstallDirCombo = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
ghidraInstallDirCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
populateGhidraInstallationCombo();
ghidraInstallDirCombo.addModifyListener(evt -> validate());
ghidraInstallDirCombo.setToolTipText("The wizard requires a Ghidra installation to be " +
"selected. Click the + button to add or manage Ghidra installations.");
addGhidraInstallDirButton = new Button(container, SWT.BUTTON1);
addGhidraInstallDirButton.setText("+");
addGhidraInstallDirButton.setToolTipText("Adds/manages Ghidra installations.");
addGhidraInstallDirButton.addListener(SWT.Selection, evt -> {
PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(null,
GhidraProjectCreatorPreferencePage.class.getName(), null, null);
dialog.open();
populateGhidraInstallationCombo();
validate();
});
validate();
setControl(container);
}
/**
* Gets the Ghidra installation directory.
*
* @return The Ghidra installation directory.
* Could be null if unspecified, however, the page will not be valid until the Ghidra
* installation directory is valid, so it should never be null when called by other
* classes.
*/
public File getGhidraInstallDir() {
return new File(ghidraInstallDirCombo.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;
if (GhidraProjectCreatorPreferences.getGhidraInstallDirs().isEmpty()) {
message = "No Ghidra installations found. Click the + button to add one.";
}
else if (ghidraInstallDirCombo.getText().isEmpty()) {
message = "Ghidra installation must be specified.";
}
else {
try {
File ghidraInstallDir = new File(ghidraInstallDirCombo.getText());
GhidraProjectCreatorPreferencePage.validateGhidraInstallation(ghidraInstallDir);
try {
JavaConfig javaConfig = new JavaConfig(ghidraInstallDir);
if (!javaConfig.isSupportedJavaHomeDir(javaConfig.getSavedJavaHome(),
JavaFilter.JDK_ONLY)) {
message = "A supported JDK is not associated with this Ghidra " +
"installation. Please run this Ghidra and try again.";
}
}
catch (ParseException | IOException e) {
message = "Failed to determine Ghidra's JDK version. " + e.getMessage();
}
}
catch (IOException e) {
message = e.getMessage();
}
}
setErrorMessage(message);
setPageComplete(message == null);
}
/**
* Populates the Ghidra installations combo box with the values found in preferences.
*/
private void populateGhidraInstallationCombo() {
ghidraInstallDirCombo.removeAll();
for (File dir : GhidraProjectCreatorPreferences.getGhidraInstallDirs()) {
ghidraInstallDirCombo.add(dir.getAbsolutePath());
if (dir.equals(GhidraProjectCreatorPreferences.getGhidraDefaultInstallDir())) {
ghidraInstallDirCombo.setText(dir.getAbsolutePath());
}
}
}
}

View file

@ -0,0 +1,101 @@
/* ###
* 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 org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.IJavaProject;
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.utils.GhidraProjectUtils;
/**
* A wizard page that lets the user choose an open Ghidra module project.
*/
public class ChooseGhidraModuleProjectWizardPage extends WizardPage {
private IProject selectedProject;
private Combo projectCombo;
/**
* Creates a new Ghidra module project chooser wizard page.
*
* @param selectedProject The currently selected project in the project explorer.
*/
public ChooseGhidraModuleProjectWizardPage(IProject selectedProject) {
super("ChooseGhidraModuleProjectWizardPage");
setTitle("Choose Ghidra Module Project");
setDescription("Choose an open Ghidra module project.");
this.selectedProject = selectedProject;
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(2, false));
Label projectNameLabel = new Label(container, SWT.NULL);
projectNameLabel.setText("Ghida module project:");
projectCombo = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
projectCombo.setLayoutData(gd);
projectCombo.addModifyListener(evt -> validate());
for (IJavaProject javaProject : GhidraProjectUtils.getGhidraProjects()) {
if (GhidraProjectUtils.isGhidraModuleProject(javaProject.getProject())) {
IProject project = javaProject.getProject();
projectCombo.add(project.getName());
if (project.equals(selectedProject)) {
projectCombo.setText(project.getName());
}
}
}
validate();
setControl(container);
}
/**
* Gets the Java project.
*
* @return The chosen Ghidra module project. Only valid when the page is complete.
* Could be null if unspecified, however, the page will not be valid until the project
* is valid, so it should never be null when called by other classes.
*/
public IJavaProject getGhidraModuleProject() {
return GhidraProjectUtils.getGhidraProject(projectCombo.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;
String projectName = projectCombo.getText();
if (projectName.isEmpty()) {
message = "Project name must be specified";
}
setErrorMessage(message);
setPageComplete(message == null);
}
}

View file

@ -0,0 +1,105 @@
/* ###
* 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 org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.IJavaProject;
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.utils.GhidraProjectUtils;
/**
* A wizard page that lets the user choose an open Java project.
*/
public class ChooseJavaProjectWizardPage extends WizardPage {
private IProject selectedProject;
private Combo projectCombo;
/**
* Creates a new Java project chooser wizard page.
*
* @param selectedProject The currently selected project in the project explorer.
*/
public ChooseJavaProjectWizardPage(IProject selectedProject) {
super("ChooseJavaProjectWizardPage");
setTitle("Choose Java Project");
setDescription("Choose an existing Java project.");
this.selectedProject = selectedProject;
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(2, false));
Label projectNameLabel = new Label(container, SWT.NULL);
projectNameLabel.setText("Java project:");
projectCombo = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
projectCombo.setLayoutData(gd);
projectCombo.addModifyListener(evt -> validate());
for (IJavaProject javaProject : GhidraProjectUtils.getJavaProjects()) {
IProject project = javaProject.getProject();
projectCombo.add(project.getName());
if (project.equals(selectedProject)) {
projectCombo.setText(project.getName());
}
}
validate();
setControl(container);
}
/**
* Gets the Java project.
*
* @return The chosen Java project. Only valid when the page is complete.
* Could be null if unspecified, however, the page will not be valid until the project
* is valid, so it should never be null when called by other classes.
*/
public IJavaProject getJavaProject() {
for (IJavaProject javaProject : GhidraProjectUtils.getJavaProjects()) {
IProject project = javaProject.getProject();
if (project.getName().equals(projectCombo.getText())) {
return javaProject;
}
}
return null;
}
/**
* 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;
String projectName = projectCombo.getText();
if (projectName.isEmpty()) {
message = "Existing Java project must be specified";
}
setErrorMessage(message);
setPageComplete(message == null);
}
}

View file

@ -0,0 +1,108 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.wizards.pages;
import java.util.*;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.*;
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils.ModuleTemplateType;
/**
* A wizard page that lets the user configure a new Ghidra module project.
*/
public class ConfigureGhidraModuleProjectWizardPage extends WizardPage {
private Map<Button, ModuleTemplateType> moduleTemplateCheckboxMap;
/**
* Creates a new Ghidra module project configuration wizard page.
*/
public ConfigureGhidraModuleProjectWizardPage() {
super("ConfigureGhidraModuleProjectWizardPage");
setTitle("Configure Ghidra Module Project");
setDescription("Configure a new Ghidra module project.");
moduleTemplateCheckboxMap = new HashMap<>();
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(2, false));
Label moduleTemplateLabel = new Label(container, SWT.NULL);
moduleTemplateLabel.setText("Module template:");
Group moduleTemplateGroup = new Group(container, SWT.SHADOW_ETCHED_OUT);
moduleTemplateGroup.setLayout(new RowLayout(SWT.HORIZONTAL));
SelectionListener selectionListener = new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent evt) {
validate();
}
@Override
public void widgetDefaultSelected(SelectionEvent evt) {
validate();
}
};
for (ModuleTemplateType moduleTemplateType : ModuleTemplateType.values()) {
Button checkboxButton = new Button(moduleTemplateGroup, SWT.CHECK);
checkboxButton.setSelection(true);
checkboxButton.setText(moduleTemplateType.getName());
checkboxButton.setToolTipText(moduleTemplateType.getDescription());
checkboxButton.addSelectionListener(selectionListener);
moduleTemplateCheckboxMap.put(checkboxButton, moduleTemplateType);
}
validate();
setControl(container);
}
/**
* Gets the selected module template types.
*
* @return The selected module template types.
*/
public Set<ModuleTemplateType> getModuleTemplateTypes() {
Set<ModuleTemplateType> moduleTemplateTypes = new HashSet<>();
for (Button checkboxButton : moduleTemplateCheckboxMap.keySet()) {
if (checkboxButton.isEnabled() && checkboxButton.getSelection()) {
moduleTemplateTypes.add(moduleTemplateCheckboxMap.get(checkboxButton));
}
}
return moduleTemplateTypes;
}
/**
* 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() {
setErrorMessage(null);
setPageComplete(true);
}
}

View file

@ -0,0 +1,81 @@
/* ###
* 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 org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import ghidradev.ghidraprojectcreator.utils.GhidraScriptUtils;
public class ConfigureGhidraScriptProjectWizardPage extends WizardPage {
private Button userScriptsCheckboxButton;
private Button systemScriptsCheckboxButton;
/**
* Creates a new Ghidra script project configuration wizard page.
*/
public ConfigureGhidraScriptProjectWizardPage() {
super("ConfigureGhidraScriptProjectWizardPage");
setTitle("Configure Ghidra Script Project");
setDescription("Configure a new Ghidra script project.");
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(1, false));
userScriptsCheckboxButton = new Button(container, SWT.CHECK);
userScriptsCheckboxButton.setText(
"Link user home script directory (" + GhidraScriptUtils.userScriptsDir + ")");
userScriptsCheckboxButton.setToolTipText("Automatically links Ghidra's default user home " +
"script directory to the new project. This is recommended.");
userScriptsCheckboxButton.setSelection(true);
systemScriptsCheckboxButton = new Button(container, SWT.CHECK);
systemScriptsCheckboxButton.setText("Link Ghidra installation script directories");
systemScriptsCheckboxButton.setToolTipText("Automatically links any script directories " +
"found in the Ghidra installation to the new project, which serve as good examples.");
systemScriptsCheckboxButton.setSelection(true);
setErrorMessage(null);
setPageComplete(true);
setControl(container);
}
/**
* Checks whether or not the user scripts directory should be linked into the project.
*
* @return True if the user scripts directory should be linked into the project; otherwise, false.
*/
public boolean shouldLinkUsersScripts() {
return userScriptsCheckboxButton.getSelection();
}
/**
* Checks whether or not the system scripts directory should be linked into the project.
*
* @return True if the system scripts directory should be linked into the project; otherwise, false.
*/
public boolean shouldLinkSystemScripts() {
return systemScriptsCheckboxButton.getSelection();
}
}

View file

@ -0,0 +1,219 @@
/* ###
* 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 java.io.IOException;
import org.eclipse.buildship.core.*;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import ghidra.GhidraApplicationLayout;
import ghidra.framework.ApplicationProperties;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferences;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
public class ConfigureGradleWizardPage extends WizardPage {
private Button gradleWrapperChoiceButton;
private Button gradleLocalChoiceButton;
private Text gradleLocalDirText;
private Button gradlLocalDirButton;
private ChooseGhidraModuleProjectWizardPage projectPage;
private String gradleVersion;
/**
* Creates a new Gradle configuration wizard page.
*
* @param projectPage A {@link ChooseGhidraModuleProjectWizardPage} to get the project from.
*/
public ConfigureGradleWizardPage(ChooseGhidraModuleProjectWizardPage projectPage) {
super("ConfigureGradleWizardPage");
setTitle("Configure Gradle");
setDescription("Configure Gradle.");
this.projectPage = projectPage;
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(4, false));
SelectionListener selectionListener = new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent evt) {
validate();
}
@Override
public void widgetDefaultSelected(SelectionEvent evt) {
validate();
}
};
// Local Gradle
String gradleLocalTooltip = "Use a local installation of Gradle. For best results, " +
"ensure that the version of this local Gradle matches the version specified on this wizard page's description.";
gradleLocalChoiceButton = new Button(container, SWT.RADIO);
gradleLocalChoiceButton.addSelectionListener(selectionListener);
gradleLocalChoiceButton.setToolTipText(gradleLocalTooltip);
Label gradleLocalDirLabel = new Label(container, SWT.NULL);
gradleLocalDirLabel.setText("Local installation directory:");
gradleLocalDirText = new Text(container, SWT.BORDER | SWT.SINGLE);
gradleLocalDirText.setToolTipText(gradleLocalTooltip);
gradleLocalDirText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
gradleLocalDirText.addModifyListener(evt -> validate());
gradlLocalDirButton = new Button(container, SWT.BUTTON1);
gradlLocalDirButton.setText("...");
gradlLocalDirButton.addListener(SWT.Selection, evt -> {
DirectoryDialog dialog = new DirectoryDialog(container.getShell());
String path = dialog.open();
if (path != null) {
gradleLocalDirText.setText(path);
}
validate();
});
// Gradle Wrapper
String gradleWrapperTooltip = "Use the Gradle Wrapper, which will automatically download " +
"the correct version of Gradle to use from the Internet.";
gradleWrapperChoiceButton = new Button(container, SWT.RADIO);
gradleWrapperChoiceButton.addSelectionListener(selectionListener);
gradleWrapperChoiceButton.setToolTipText(gradleWrapperTooltip);
Label gradleWrapperDirLabel = new Label(container, SWT.NULL);
gradleWrapperDirLabel.setText("Gradle Wrapper");
Label internetLabel = new Label(container, SWT.NONE);
internetLabel.setText("INTERNET CONNECTION REQUIRED");
internetLabel.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_RED));
internetLabel.setToolTipText(gradleWrapperTooltip);
new Label(container, SWT.NONE).setText(""); // empty grid cell
// Set default value from preferences
GradleDistribution lastGradleDistribution =
GhidraProjectCreatorPreferences.getGhidraLastGradleDistribution();
if (lastGradleDistribution instanceof LocalGradleDistribution) {
gradleLocalChoiceButton.setSelection(true);
LocalGradleDistribution localGradleDistribution =
(LocalGradleDistribution) lastGradleDistribution;
if (localGradleDistribution.getLocation() != null) {
gradleLocalDirText.setText(localGradleDistribution.getLocation().getAbsolutePath());
}
}
else if (lastGradleDistribution instanceof WrapperGradleDistribution ||
lastGradleDistribution instanceof FixedVersionGradleDistribution) {
gradleWrapperChoiceButton.setSelection(true);
}
else {
gradleLocalChoiceButton.setSelection(true);
}
validate();
setControl(container);
}
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
// Update the page's description to reference the version of Gradle that should be used.
if (visible) {
IProject project = projectPage.getGhidraModuleProject().getProject();
IFolder ghidraFolder = project.getFolder(GhidraProjectUtils.GHIDRA_FOLDER_NAME);
File ghidraDir = ghidraFolder.getRawLocation().toFile();
try {
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraDir);
ApplicationProperties props = ghidraLayout.getApplicationProperties();
gradleVersion =
props.getProperty(ApplicationProperties.APPLICATION_GRADLE_VERSION_PROPERTY);
if (gradleVersion != null && !gradleVersion.isEmpty()) {
setDescription("Configure Gradle. Version " + gradleVersion + " is expected.");
}
}
catch (IOException e) {
EclipseMessageUtils.error("Unable to determine required Gradle version.");
}
}
}
/**
* Gets the Gradle distribution to use.
*
* @return The gradle distribution to use.
*/
public GradleDistribution getGradleDistribution() {
if (gradleLocalChoiceButton.getSelection()) {
return GradleDistribution.forLocalInstallation(new File(gradleLocalDirText.getText()));
}
else if (gradleVersion != null) {
return GradleDistribution.forVersion(gradleVersion);
}
else {
// This case should only happen if someone deleted the Gradle version from
// application.properties. In that case, we'll just try the standard wrapper and hope
// for the best.
return GradleDistribution.fromBuild();
}
}
/**
* 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;
if (gradleLocalChoiceButton.getSelection()) {
String path = gradleLocalDirText.getText().trim();
File dir = new File(path);
if (path.isEmpty()) {
message = "Path to local Gradle installation must be specified.";
}
else if (!dir.exists()) {
message = "Path to local Gradle installation does not exist.";
}
else if (!dir.isDirectory()) {
message = "Path to local Gradle installation is not a directory.";
}
else if (!new File(dir, "bin/gradle").exists()) {
message =
"Path to local Gradle installation appears invalid. Missing gradle binary.";
}
}
if (message == null) {
GhidraProjectCreatorPreferences.setGhidraLastGradleDistribution(
getGradleDistribution());
}
else {
GhidraProjectCreatorPreferences.setGhidraLastGradleDistribution(null);
}
setErrorMessage(message);
setPageComplete(message == null);
}
}

View file

@ -0,0 +1,257 @@
/* ###
* 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 java.util.regex.Pattern;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferences;
import ghidradev.ghidraprojectcreator.utils.GhidraLaunchUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
/**
* A wizard page that lets the user create a new Ghidra project.
*/
public class CreateGhidraProjectWizardPage extends WizardPage {
private String suggestedProjectName;
private Text projectNameText;
private Text projectRootDirText;
private Button projectDirButton;
private Button createRunConfigCheckboxButton;
private Text runConfigMemoryText;
/**
* Creates a Ghidra new project wizard page with the given suggested project name.
*
* @param suggestedProjectName The suggested project name.
*/
public CreateGhidraProjectWizardPage(String suggestedProjectName) {
super("CreateGhidraProjectWizardPage");
setTitle("Create Ghidra Project");
setDescription("Create a new Ghidra project.");
this.suggestedProjectName = suggestedProjectName;
}
/**
* Creates a Ghidra new project wizard page.
*/
public CreateGhidraProjectWizardPage() {
this("");
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(3, false));
// Project name
Label projectNameLabel = new Label(container, SWT.NULL);
projectNameLabel.setText("Project name:");
projectNameText = new Text(container, SWT.BORDER | SWT.SINGLE);
projectNameText.setText(suggestedProjectName);
projectNameText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
projectNameText.addModifyListener(evt -> validate());
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);
}
});
// Create run configuration checkbox
createRunConfigCheckboxButton = new Button(container, SWT.CHECK);
createRunConfigCheckboxButton.setText("Create run configuration");
createRunConfigCheckboxButton.setToolTipText("Automatically create a Ghidra run " +
"configuration that can be used to launch and debug this project in Ghidra. Run " +
"configurations can be created later and modified in the \"Run --> Run " +
"Configurations\" menu.");
createRunConfigCheckboxButton.setSelection(true);
createRunConfigCheckboxButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent evt) {
validate();
}
@Override
public void widgetDefaultSelected(SelectionEvent evt) {
validate();
}
});
new Label(container, SWT.NONE).setText(""); // empty grid cell
new Label(container, SWT.NONE).setText(""); // empty grid cell
// Run configuration memory
Label runConfigMemoryLabel = new Label(container, SWT.NULL);
String runConfigMemoryToolTip = "The maximum Java heap size (-Xmx) Ghidra will use when " +
"launched with the created run configuration (ex: 4G, 1500m, etc). If left blank, " +
"Java's default heap size will be used, which is determined by your system's memory " +
"capacity.";
runConfigMemoryLabel.setText("Run configuration memory:");
runConfigMemoryLabel.setToolTipText(runConfigMemoryToolTip);
runConfigMemoryText = new Text(container, SWT.BORDER | SWT.SINGLE);
runConfigMemoryText.addModifyListener(evt -> validate());
runConfigMemoryText.setToolTipText(runConfigMemoryToolTip);
new Label(container, SWT.NONE).setText(""); // empty grid cell
validate();
setControl(container);
}
/**
* Gets the name of the project.
*
* @return The name of the project.
*/
public String getProjectName() {
return projectNameText.getText();
}
/**
* 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.
*/
public File getProjectDir() {
if (projectNameText.getText().isEmpty()) {
return null;
}
if (projectRootDirText.getText().isEmpty()) {
return null;
}
return new File(projectRootDirText.getText(), getProjectName());
}
/**
* Checks to see whether or not a run configuration for the new project should be automatically
* created.
*
* @return True if a run configuration for the new project should be automatically created;
* otherwise, false.
*/
public boolean shouldCreateRunConfig() {
return createRunConfigCheckboxButton.getSelection();
}
/**
* Gets the run configuration's desired memory.
*
* @return The run configuration's desired memory. Could be null if unspecified.
*/
public String getRunConfigMemory() {
if (!createRunConfigCheckboxButton.getSelection() ||
runConfigMemoryText.getText().isEmpty()) {
return null;
}
return runConfigMemoryText.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() {
final String BAD = GhidraProjectUtils.ILLEGAL_FILENAME_CHARS;
final String BAD_START = GhidraProjectUtils.ILLEGAL_FILENAME_START_CHARS;
runConfigMemoryText.setEnabled(createRunConfigCheckboxButton.getSelection());
runConfigMemoryText.setMessage(runConfigMemoryText.isEnabled() ? "default" : "");
String message = null;
String projectName = getProjectName();
File projectDir = getProjectDir();
boolean launchConfigExists = false;
try {
ILaunchConfiguration launchConfig = GhidraLaunchUtils.getLaunchConfig(projectName);
if (launchConfig != null) {
String launchConfigTypeId = launchConfig.getType().getIdentifier();
launchConfigExists = !launchConfigTypeId.equals(GhidraLaunchUtils.GUI_LAUNCH) &&
!launchConfigTypeId.equals(GhidraLaunchUtils.HEADLESS_LAUNCH);
}
}
catch (CoreException e) {
// launchConfigExists can remain false, we'll just overwrite the config
}
if (projectName.isEmpty()) {
message = "Project name must be specified";
}
else if ((BAD_START + BAD).chars().anyMatch(ch -> projectName.charAt(0) == ch)) {
message = "Project name cannot start with an invalid character:\n " + BAD_START + BAD;
}
else if (BAD.chars().anyMatch(ch -> projectName.indexOf(ch) != -1)) {
message = "Project name cannot contain invalid characters:\n " + BAD;
}
else if (projectDir == null) {
message = "Project root directory must be specified";
}
else if (projectDir.exists()) {
message = "Project already exists at: " + projectDir.getAbsolutePath();
}
else if (ResourcesPlugin.getWorkspace().getRoot().getProject(projectName).exists()) {
message = "\"" + projectName + "\" project already exists in workspace";
}
else if (shouldCreateRunConfig() && launchConfigExists) {
message = "Run configuration \"" + projectName +
"\" project already exists (check run configuration filters)";
}
else if (createRunConfigCheckboxButton.getSelection() &&
!runConfigMemoryText.getText().isEmpty() &&
!Pattern.matches("^\\d+[KkMmGg]$", runConfigMemoryText.getText())) {
message = "Invalid run configuration memory value. Value must match JVM -Xmx flag" +
" syntax (4G, 1500m, etc).";
}
setErrorMessage(message);
setPageComplete(message == null);
if (message == null) {
GhidraProjectCreatorPreferences.setGhidraLastProjectRootPath(
projectRootDirText.getText());
}
}
}

View file

@ -0,0 +1,243 @@
/* ###
* 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 org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.utils.PackageFragmentRootSelectionDialog;
/**
* A wizard page that lets the user create a new Ghidra script.
*/
public class CreateGhidraScriptWizardPage extends WizardPage {
private IPackageFragmentRoot selecttion;
private Text scriptFolderText;
private Button scriptFolderButton;
private Text scriptNameText;
private Button javaRadioButton;
private Button pythonRadioButton;
private Text scriptAuthorText;
private Text scriptCategoryText;
private Text scriptDescriptionText;
/**
* Creates a new Ghidra script creation wizard page.
*/
public CreateGhidraScriptWizardPage() {
super("CreateGhidraScriptWizardPage");
setTitle("Create Ghidra Script");
setDescription("Create a new Ghidra script.");
}
/**
* Creates a new Ghidra script creation wizard page.
*
* @param selectedPackageFragmentRoot The current selection in the project explorer.
* Could be null if nothing is selected.
*/
public CreateGhidraScriptWizardPage(IPackageFragmentRoot selectedPackageFragmentRoot) {
this();
this.selecttion = selectedPackageFragmentRoot;
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(3, false));
// Source folder
Label sourceFolderLabel = new Label(container, SWT.NULL);
sourceFolderLabel.setText("Script folder:");
scriptFolderText = new Text(container, SWT.BORDER | SWT.SINGLE);
scriptFolderText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
scriptFolderText.setEditable(false);
scriptFolderText.setText(selecttion != null ? selecttion.getPath().toString() : "");
scriptFolderText.addModifyListener(evt -> validate());
scriptFolderButton = new Button(container, SWT.BUTTON1);
scriptFolderButton.setText("...");
scriptFolderButton.addListener(SWT.Selection, evt -> {
PackageFragmentRootSelectionDialog selectionDialog =
new PackageFragmentRootSelectionDialog(getShell(), "Script Folder Selection",
"Choose a script folder:", "Select script folder");
if (selectionDialog.open() == Window.OK) {
IFolder scriptFolder = ResourcesPlugin.getWorkspace().getRoot().getFolder(
selectionDialog.getPackageFragmentRoot().getPath());
if (scriptFolder != null) {
scriptFolderText.setText(scriptFolder.getFullPath().toString());
}
}
});
// Script name
Label scriptNameLabel = new Label(container, SWT.NULL);
scriptNameLabel.setText("Script name:");
scriptNameText = new Text(container, SWT.BORDER | SWT.SINGLE);
scriptNameText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
scriptNameText.addModifyListener(evt -> validate());
new Label(container, SWT.NONE).setText(""); // empty grid cell
// Script type
Label scriptTypeLabel = new Label(container, SWT.NULL);
scriptTypeLabel.setText("Script type:");
Group scriptTypeGroup = new Group(container, SWT.SHADOW_ETCHED_OUT);
scriptTypeGroup.setLayout(new RowLayout(SWT.HORIZONTAL));
javaRadioButton = new Button(scriptTypeGroup, SWT.RADIO);
javaRadioButton.setSelection(true);
javaRadioButton.setText("Java");
pythonRadioButton = new Button(scriptTypeGroup, SWT.RADIO);
pythonRadioButton.setSelection(false);
pythonRadioButton.setText("Python");
new Label(container, SWT.NONE).setText(""); // empty grid cell
// Script author
Label scriptAuthorLabel = new Label(container, SWT.NULL);
scriptAuthorLabel.setText("Script author:");
scriptAuthorText = new Text(container, SWT.BORDER | SWT.SINGLE);
scriptAuthorText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
scriptAuthorText.addModifyListener(evt -> validate());
new Label(container, SWT.NONE).setText(""); // empty grid cell
// Script category
Label scriptCategoryLabel = new Label(container, SWT.NULL);
scriptCategoryLabel.setText("Script category:");
scriptCategoryText = new Text(container, SWT.BORDER | SWT.SINGLE);
scriptCategoryText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
scriptCategoryText.addModifyListener(evt -> validate());
new Label(container, SWT.NONE).setText(""); // empty grid cell
// Script description
Label scriptDescriptionLabel = new Label(container, SWT.NULL);
scriptDescriptionLabel.setText("Script description:");
scriptDescriptionText =
new Text(container, SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | SWT.BORDER);
scriptDescriptionText.setLayoutData(new GridData(GridData.FILL_BOTH));
scriptDescriptionText.addModifyListener(evt -> validate());
new Label(container, SWT.NONE).setText(""); // empty grid cell
validate();
setControl(container);
}
/**
* Gets the script's target source folder. This is where the script will get created.
*
* @return The script's target source folder. Could be null if unspecified, however, the page
* will not be valid until the source folder is valid, so it should never be null when called
* by other classes.
*/
public IFolder getScriptFolder() {
if (!scriptFolderText.getText().isEmpty()) {
try {
IPath path = new Path(scriptFolderText.getText());
return ResourcesPlugin.getWorkspace().getRoot().getFolder(path);
}
catch (IllegalArgumentException e) {
// Fall through to return null
}
}
return null;
}
/**
* Gets the name of the script to create (including extension).
*
* @return The name of the script to create (including extension).
* Could be empty if unspecified, however, the page will not be valid until the script
* name is valid, so it should never be empty when called by other classes.
*/
public String getScriptName() {
return scriptNameText.getText() + (javaRadioButton.getSelection() ? ".java" : ".py");
}
/**
* Gets the script author.
*
* @return The script author. This is an optional field, so it could be empty.
*/
public String getScriptAuthor() {
return scriptAuthorText.getText();
}
/**
* Gets the script category.
*
* @return The script category. This is an optional field, so it could be empty.
*/
public String getScriptCategory() {
return scriptCategoryText.getText();
}
/**
* Gets the script description as an array of lines.
*
* @return The script description as an array of lines. This is an optional field,
* so it could be empty.
*/
public String[] getScriptDescription() {
return scriptDescriptionText.getText().split("\\n");
}
/**
* 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() {
final String BAD = GhidraProjectUtils.ILLEGAL_FILENAME_CHARS;
final String BAD_START = GhidraProjectUtils.ILLEGAL_FILENAME_START_CHARS;
String message = null;
IFolder scriptFolder = getScriptFolder();
String scriptName = scriptNameText.getText();
if (scriptFolder == null) {
message = "Script folder must be specified";
}
else if (!scriptFolder.exists()) {
message = "Script folder does not exist";
}
else if (scriptName.isEmpty()) {
message = "Script name must be specified";
}
else if ((BAD_START + BAD).chars().anyMatch(ch -> scriptName.charAt(0) == ch)) {
message = "Script name cannot start with an invalid character:\n " + BAD_START + BAD;
}
else if (BAD.chars().anyMatch(ch -> scriptName.indexOf(ch) != -1)) {
message = "Script name cannot contain invalid characters:\n " + BAD;
}
else if (scriptFolder.getFile(getScriptName()).exists()) {
message = "Script already exists";
}
setErrorMessage(message);
setPageComplete(message == null);
}
}

View file

@ -0,0 +1,256 @@
/* ###
* 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 java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import javax.naming.OperationNotSupportedException;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.dialogs.PreferencesUtil;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils;
/**
* A wizard page that lets the user enable python for their project.
*/
public class EnablePythonWizardPage extends WizardPage {
private ChooseGhidraInstallationWizardPage ghidraInstallationPage;
private Button enablePythonCheckboxButton;
private Combo jythonCombo;
private Button addJythonButton;
/**
* Creates a new Python enablement wizard page.
*
* @param ghidraInstallationPage Ghidra installation wizard page.
*/
public EnablePythonWizardPage(ChooseGhidraInstallationWizardPage ghidraInstallationPage) {
super("EnablePythonWizardPage");
setTitle("Python Support");
setDescription("Enable Python support for your project (requires PyDev plugin).");
this.ghidraInstallationPage = ghidraInstallationPage;
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(3, false));
// Enable Python checkbox.
enablePythonCheckboxButton = new Button(container, SWT.CHECK);
enablePythonCheckboxButton.setText("Enable Python");
enablePythonCheckboxButton.setToolTipText("Enables Python support using the PyDev " +
"Eclipse plugin. Requires PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" or later.");
enablePythonCheckboxButton.setSelection(PyDevUtils.isSupportedPyDevInstalled());
enablePythonCheckboxButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent evt) {
validate();
}
@Override
public void widgetDefaultSelected(SelectionEvent evt) {
validate();
}
});
new Label(container, SWT.NONE).setText(""); // filler
new Label(container, SWT.NONE).setText(""); // filler
// Jython interpreter combo box
Label jythonLabel = new Label(container, SWT.NULL);
jythonLabel.setText("Jython interpreter:");
jythonCombo = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
jythonCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
jythonCombo.setToolTipText("The wizard requires a Jython interpreter to be " +
"selected. Click the + button to add or manage Jython interpreters.");
populateJythonCombo();
jythonCombo.addModifyListener(evt -> validate());
// Jython interpreter add button
addJythonButton = new Button(container, SWT.BUTTON1);
addJythonButton.setText("+");
addJythonButton.setToolTipText("Adds/manages Jython interpreters.");
addJythonButton.addListener(SWT.Selection, evt -> {
try {
if (PyDevUtils.getJython27InterpreterNames().isEmpty()) {
File ghidraDir = ghidraInstallationPage.getGhidraInstallDir();
File jythonFile = findJythonInterpreter(ghidraDir);
File jythonLib = findJythonLibrary(ghidraDir);
if (jythonFile != null) {
if (EclipseMessageUtils.showQuestionDialog("Jython Found",
"A Jython interpreter was found bundled with Ghidra. " +
"Would you like to use it as your interpreter?")) {
PyDevUtils.addJythonInterpreter("jython_" + ghidraDir.getName(),
jythonFile, jythonLib);
populateJythonCombo();
validate();
return;
}
}
}
}
catch (OperationNotSupportedException e) {
// Fall through to show PyDev's Jython preference page
}
PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(null,
PyDevUtils.getJythonPreferencePageId(), null, null);
dialog.open();
populateJythonCombo();
validate();
});
validate();
setControl(container);
}
/**
* Checks whether or not Python should be enabled.
*
* @return True if python should be enabled; otherwise, false.
*/
public boolean shouldEnablePython() {
return enablePythonCheckboxButton.getSelection();
}
/**
* Gets the name of the Jython interpreter to use.
*
* @return The name of the Jython interpreter to use. Could be null of Python isn't
* enabled.
*/
public String getJythonInterpreterName() {
if (enablePythonCheckboxButton.getSelection()) {
return jythonCombo.getText();
}
return null;
}
/**
* 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;
boolean pyDevInstalled = PyDevUtils.isSupportedPyDevInstalled();
boolean pyDevEnabled = enablePythonCheckboxButton.getSelection();
boolean comboEnabled = pyDevInstalled && pyDevEnabled;
if (pyDevEnabled) {
if (!pyDevInstalled) {
message = "PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" or later is not installed.";
}
else {
try {
List<String> interpreters = PyDevUtils.getJython27InterpreterNames();
if (interpreters.isEmpty()) {
message = "No Jython interpreters found. Click the + button to add one.";
}
}
catch (OperationNotSupportedException e) {
message = "PyDev version is not supported.";
comboEnabled = false;
}
}
}
jythonCombo.setEnabled(comboEnabled);
addJythonButton.setEnabled(comboEnabled);
setErrorMessage(message);
setPageComplete(message == null);
}
/**
* Populates the Jython combo box with discovered Jython names.
*/
private void populateJythonCombo() {
jythonCombo.removeAll();
try {
for (String jythonName : PyDevUtils.getJython27InterpreterNames()) {
jythonCombo.add(jythonName);
}
}
catch (OperationNotSupportedException e) {
// Nothing to do. Combo should and will be empty.
}
if (jythonCombo.getItemCount() > 0) {
jythonCombo.select(0);
}
}
/**
* Find's a Jython interpreter file in the given Ghidra installation directory.
*
* @param ghidraInstallDir The Ghidra installation directory to search.
* @return A Jython interpreter file from the given Ghidra installation directory, or
* null if one could not be found.
*/
private File findJythonInterpreter(File ghidraInstallDir) {
if (ghidraInstallDir == null || !ghidraInstallDir.isDirectory()) {
return null;
}
try {
return Files.find(ghidraInstallDir.toPath(), 10, (path, attrs) -> {
String name = path.getFileName().toString();
return attrs.isRegularFile() && name.startsWith("jython") && name.endsWith(".jar");
}).map(p -> p.toFile()).findFirst().orElse(null);
}
catch (IOException e) {
return null;
}
}
/**
* Find's a Jython library directory in the given Ghidra installation directory.
*
* @param ghidraInstallDir The Ghidra installation directory to search.
* @return A Jython library directory from the given Ghidra installation directory, or
* null if one could not be found.
*/
private File findJythonLibrary(File ghidraInstallDir) {
if (ghidraInstallDir == null || !ghidraInstallDir.isDirectory()) {
return null;
}
try {
return Files.find(ghidraInstallDir.toPath(), 10, (path, attrs) -> {
String name = path.getFileName().toString();
String parentName = path.getParent().getFileName().toString();
return attrs.isDirectory() && name.equals("Lib") && parentName.startsWith("jython");
}).map(p -> p.toFile()).findFirst().orElse(null);
}
catch (IOException e) {
return null;
}
}
}

View file

@ -0,0 +1,252 @@
/* ###
* 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.ghidrascripteditor;
import java.awt.Dimension;
import java.io.File;
import java.util.*;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.ui.*;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
import org.eclipse.ui.ide.IDE;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.wizards.CreateGhidraScriptProjectWizard;
public class OpenFileRunnable implements Runnable {
private String filePath;
public OpenFileRunnable(String filePath) {
this.filePath = filePath;
}
@Override
public void run() {
List<IFile> projectFiles = findMatchingFiles(filePath);
IFile[] filesToOpen = maybePromptUserForFilesToOpen(projectFiles);
openFiles(filesToOpen);
}
private void openFiles(IFile[] userFileChoices) {
if (userFileChoices == null) {
return; // user cancelled
}
for (IFile file : userFileChoices) {
openFile(file);
}
}
private void openFile(IFile file) {
IWorkbenchPage page = EclipseMessageUtils.getWorkbenchPage();
try {
IDE.openEditor(page, file);
}
catch (PartInitException e) {
EclipseMessageUtils.showErrorDialog("Unable to Open Script",
"Couldn't open editor for " + filePath);
}
page.getWorkbenchWindow().getShell().forceActive();
}
private IFile[] maybePromptUserForFilesToOpen(List<IFile> projectFiles) {
if (projectFiles.size() == 0) {
return null;
}
if (projectFiles.size() == 1) {
return new IFile[] { projectFiles.get(0) };
}
// look for any project ending in 'scripts' and assume that is the preferred project
for (IFile iFile : projectFiles) {
IProject project = iFile.getProject();
String projectName = project.getName();
if (projectName.toLowerCase().endsWith("scripts")) {
return new IFile[] { projectFiles.get(0) };
}
}
IWorkbenchPage page = EclipseMessageUtils.getWorkbenchPage();
ElementListSelectionDialog dialog = new ElementListSelectionDialog(
page.getWorkbenchWindow().getShell(), new LabelProvider());
dialog.setTitle("Choose a File");
List<DisplayableIFile> displayableFiles = formatStrings(projectFiles);
dialog.setMultipleSelection(true);
dialog.setElements(displayableFiles.toArray(new DisplayableIFile[displayableFiles.size()]));
dialog.setMessage("Select a file to open");
Dimension size = calculatePreferredSizeInCharacters(displayableFiles);
dialog.setSize(size.width, size.height);
dialog.open();
Object[] results = dialog.getResult();
IFile[] resultFiles = new IFile[results.length];
for (int i = 0; i < results.length; i++) {
resultFiles[i] = ((DisplayableIFile) results[i]).getFile();
}
return resultFiles;
}
private List<IFile> findMatchingFiles(String path) {
Collection<IJavaProject> javaProjects = GhidraProjectUtils.getGhidraProjects();
List<IFile> projectFiles = findMatchingFilesInProjects(path, javaProjects);
if (projectFiles.isEmpty()) {
try {
for (IJavaProject javaProject : javaProjects) {
javaProject.getProject().refreshLocal(IResource.DEPTH_INFINITE,
new NullProgressMonitor());
}
}
catch (CoreException e1) {
EclipseMessageUtils.showErrorDialog("Unable to Open Script",
"Unexpected Exception refreshing project");
return new ArrayList<IFile>();
}
}
projectFiles = findMatchingFilesInProjects(path, javaProjects);
if (projectFiles.isEmpty()) {
boolean createProject = EclipseMessageUtils.showConfirmDialog("Unable to Open Script",
"File does not exist in any Eclipse project in your workspace.\n\n" +
"Would you like to create a new Ghidra Scripting project?");
if (createProject) {
INewWizard wizard = new CreateGhidraScriptProjectWizard();
wizard.init(PlatformUI.getWorkbench(), new StructuredSelection());
WizardDialog dialog = new WizardDialog(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), wizard);
dialog.setBlockOnOpen(true);
if (dialog.open() == Window.OK) {
return findMatchingFilesInProjects(path,
GhidraProjectUtils.getGhidraProjects());
}
}
return new ArrayList<IFile>();
}
return projectFiles;
}
private Dimension calculatePreferredSizeInCharacters(List<DisplayableIFile> files) {
int width = 0;
int height = 10;
for (DisplayableIFile file : files) {
String displayString = file.getDisplayString();
width = Math.max(width, displayString.length());
}
width = Math.min(width + 7, 100);
height = Math.min(height, files.size() + 3);
return new Dimension(width, height);
}
private List<IFile> findMatchingFilesInProjects(String pathString,
Collection<IJavaProject> javaProjects) {
List<IFile> files = new ArrayList<IFile>();
for (IJavaProject javaProject : javaProjects) {
IProject project = javaProject.getProject();
if (!project.isOpen()) {
continue;
}
try {
IPath path = findPathFromFolder(pathString, project);
if (path != null) {
IFile file = project.getFile(path);
files.add(file);
}
}
catch (CoreException e) {
EclipseMessageUtils.error("Unexpected exception accessing project members", e);
}
}
return files;
}
private IPath findPathFromFolder(String pathString, IResource resource) throws CoreException {
if (!(resource instanceof IContainer)) {
return null;
}
IContainer container = (IContainer) resource;
IResource[] members = container.members();
for (IResource member : members) {
IPath location = member.getLocation();
// compare as files in order to bypass path separator issues
File fileForPath = new File(pathString);
File fileForLocation = location.toFile();
if (fileForLocation.equals(fileForPath)) {
return member.getProjectRelativePath();
}
IPath pathFromFolder = findPathFromFolder(pathString, member);
if (pathFromFolder != null) {
return pathFromFolder;
}
}
return null;
}
private List<DisplayableIFile> formatStrings(List<IFile> projectFiles) {
List<DisplayableIFile> list = new ArrayList<DisplayableIFile>();
for (IFile file : projectFiles) {
list.add(new DisplayableIFile(file));
}
return list;
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class DisplayableIFile {
private final IFile file;
private final String displayString;
private DisplayableIFile(IFile file) {
this.file = file;
String format = "";
String[] strings = file.toString().split("/");
for (int i = 1; i < strings.length - 1; i++) {
format += strings[i] + "/";
}
displayString =
format.substring(0, format.length() - 1) + " - " + strings[strings.length - 1];
}
IFile getFile() {
return file;
}
String getDisplayString() {
return displayString;
}
@Override
public String toString() {
return getDisplayString();
}
}
}

View file

@ -0,0 +1,112 @@
/* ###
* 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.ghidrascripteditor;
import java.io.IOException;
import java.net.ServerSocket;
import ghidradev.*;
import ghidradev.ghidrascripteditor.preferences.GhidraScriptEditorPreferences;
/**
* High level driver for initializing the Ghidra Script Editor subcomponent. Should get called
* by the startup extension. This subcomponent is responsible for receiving a script from Ghidra's
* script manager to Eclipse over a socket.
*
* @see GhidraDevStartup
*/
public class ScriptEditorInitializer {
private static ServerSocket serverSocket;
/**
* Initializes the Ghidra Script Editor subcomponent. Nothing in the package should be
* used until this initialization happens. Should be called during Eclipse startup.
*
* @param firstTimeConsent True if the user has just consented to opening ports; otherwise,
* false.
* @see GhidraDevStartup
*/
public static void init(boolean firstTimeConsent) {
if (firstTimeConsent) {
GhidraScriptEditorPreferences.setScriptEditorEnabled(true);
}
listen(GhidraScriptEditorPreferences.getScriptEditorPort());
}
/**
* Listens for socket connections on the given port. If there is a problem listening,
* a popup is displayed for the user.
*
* @param port The port to listen on. If the port is -1, this method doesn't do anything.
*/
private static void listen(int port) {
if (!GhidraScriptEditorPreferences.isScriptEditorEnabled()) {
EclipseMessageUtils.info(
Activator.PLUGIN_ID + " Script Editor port listening is disabled in preferences.");
return;
}
if (port == -1) {
EclipseMessageUtils.info(Activator.PLUGIN_ID +
" Script Editor port listening is disabled, port not set in preferences.");
return;
}
try {
serverSocket = new ServerSocket(port);
EclipseMessageUtils.info(
Activator.PLUGIN_ID + " Script Editor is listening on port " + port);
Activator.getDefault().registerCloseable(serverSocket);
}
catch (IOException e) {
EclipseMessageUtils.showErrorDialog(Activator.PLUGIN_ID + " Script Editor",
"Failed to listen for connections on port " + port +
". The Script Editor features will be disabled until a valid port is selected in preferences.");
return;
}
new Thread(new SocketSetupRunnable(serverSocket)).start();
}
/**
* Called when the script editor preferences change.
*
* @param enabledWasChanged True if the enablement was changed to a new value.
* @param portWasChanged True if the port preferences was changed to a new value.
*/
public static void notifyPreferencesChanged(boolean enabledWasChanged, boolean portWasChanged) {
if (!enabledWasChanged && !portWasChanged) {
return;
}
// Close the old server socket. Its job is done.
try {
if (serverSocket != null) {
serverSocket.close();
Activator.getDefault().unregisterCloseable(serverSocket);
serverSocket = null;
}
}
catch (IOException e) {
// Oh well, we tried. This port probably won't work next time they pick it.
}
// Listen on the new port
listen(GhidraScriptEditorPreferences.getScriptEditorPort());
}
}

View file

@ -0,0 +1,73 @@
/* ###
* 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.ghidrascripteditor;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import org.eclipse.swt.widgets.Display;
/**
* Code intended to run in a new thread that accepts client connections on the given
* server socket, and opens the requested file in an editor.
*/
public class SocketSetupRunnable implements Runnable {
private ServerSocket serverSocket = null;
/**
* Creates a new runnable.
*
* @param serverSocket The server socket that will be accepting client connections.
*/
public SocketSetupRunnable(ServerSocket serverSocket) {
this.serverSocket = serverSocket;
}
@Override
public void run() {
while (!serverSocket.isClosed()) {
try (Socket socket = serverSocket.accept();
BufferedReader input =
new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter output = new PrintWriter(socket.getOutputStream())) {
String line;
while ((line = input.readLine()) != null) {
String command = line.substring(0, line.indexOf('_'));
if (command.equals("open")) {
openInEditor(line.substring(line.indexOf('_') + 1));
}
}
}
catch (IOException e) {
// Socket was closed
}
}
}
/**
* Opens the given file in an editor.
*
* @param path The path to the file to open.
*/
private void openInEditor(String path) {
File fileToOpen = new File(path);
if (fileToOpen.exists() && fileToOpen.isFile()) {
Display.getDefault().asyncExec(new OpenFileRunnable(path));
}
}
}

View file

@ -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.
*/
package ghidradev.ghidrascripteditor.preferences;
import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
import org.eclipse.jface.preference.IPreferenceStore;
import ghidradev.Activator;
/**
* Class used to initialize default preference values.
*/
public class GhidraScriptEditorPreferenceInitializer extends AbstractPreferenceInitializer {
@Override
public void initializeDefaultPreferences() {
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
store.setDefault(GhidraScriptEditorPreferences.GHIDRA_SCRIPT_EDITOR_ENABLED, false);
store.setDefault(GhidraScriptEditorPreferences.GHIDRA_SCRIPT_EDITOR_PORT_NUMBER, "12321");
}
}

View file

@ -0,0 +1,146 @@
/* ###
* 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.ghidrascripteditor.preferences;
import org.eclipse.jface.preference.*;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import ghidradev.Activator;
import ghidradev.ghidrascripteditor.ScriptEditorInitializer;
/**
* This class represents a preference page that
* is contributed to the Preferences dialog. By
* subclassing <samp>FieldEditorPreferencePage</samp>, we
* can use the field support built into JFace that allows
* us to create a page that is small and knows how to
* save, restore and apply itself.
* <p>
* This page is used to modify preferences only. They
* are stored in the preference store that belongs to
* the main plug-in class. That way, preferences can
* be accessed directly via the preference store.
*/
public class GhidraScriptEditorPreferencePage extends FieldEditorPreferencePage implements
IWorkbenchPreferencePage {
private BooleanFieldEditor enabledField;
private StringFieldEditor portField;
private String previousEnabledString = null;
private String currentEnabledString = null;
private String previousPortString = null;
private String currentPortString = null;
public GhidraScriptEditorPreferencePage() {
super(GRID);
}
@Override
public void init(IWorkbench workbench) {
setPreferenceStore(Activator.getDefault().getPreferenceStore());
}
@Override
public void createFieldEditors() {
enabledField =
new BooleanFieldEditor(GhidraScriptEditorPreferences.GHIDRA_SCRIPT_EDITOR_ENABLED,
"Enabled", getFieldEditorParent());
portField =
new StringFieldEditor(GhidraScriptEditorPreferences.GHIDRA_SCRIPT_EDITOR_PORT_NUMBER,
"Port:", getFieldEditorParent());
addField(enabledField);
addField(portField);
}
@Override
public void checkState() {
super.checkState();
if (!isValid()) {
return;
}
String portValue = portField.getStringValue();
if (!portValue.isEmpty()) {
try {
int portNumber = Integer.parseInt(portValue);
if (portNumber < 1024 || portNumber > 0xFFFF) {
setErrorMessage("Port must be between 1024 and 65535.");
setValid(false);
return;
}
}
catch (NumberFormatException e) {
setErrorMessage("Port must be an integer.");
setValid(false);
return;
}
}
}
@Override
public void propertyChange(PropertyChangeEvent event) {
super.propertyChange(event);
if (event.getProperty().equals(FieldEditor.VALUE)) {
checkState();
}
if (event.getSource() == enabledField) {
if (previousEnabledString == null) {
previousEnabledString = event.getOldValue().toString();
}
currentEnabledString = event.getNewValue().toString();
}
else if (event.getSource() == portField) {
if (previousPortString == null) {
previousPortString = event.getOldValue().toString();
}
currentPortString = event.getNewValue().toString();
}
}
@Override
public boolean performOk() {
super.performOk();
boolean enabledWasChanged = false;
boolean portWasChanged = false;
if (currentEnabledString != null && previousEnabledString != null) {
if (!currentEnabledString.equals(previousEnabledString)) {
enabledWasChanged = true;
}
}
if (currentPortString != null && previousPortString != null) {
if (!currentPortString.equals(previousPortString)) {
portWasChanged = true;
}
}
ScriptEditorInitializer.notifyPreferencesChanged(enabledWasChanged, portWasChanged);
previousEnabledString = null;
previousPortString = null;
return true;
}
@Override
public boolean performCancel() {
super.performCancel();
previousEnabledString = null;
previousPortString = null;
return true;
}
}

View file

@ -0,0 +1,71 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidrascripteditor.preferences;
import org.eclipse.jface.preference.IPreferenceStore;
import ghidradev.Activator;
/**
* Ghidra script editor preference definitions and related utility methods.
*/
public class GhidraScriptEditorPreferences {
/**
* Whether or not the script editor feature is enabled.
*/
static final String GHIDRA_SCRIPT_EDITOR_ENABLED = "ghidradev.scriptEditorEnabled";
/**
* Port used for script editor.
*/
static final String GHIDRA_SCRIPT_EDITOR_PORT_NUMBER = "ghidradev.scriptEditorPortNumber";
/**
* Gets whether or not the script editor feature is enabled.
*
* @return True if the script editor feature is enabled; otherwise, false.
*/
public static boolean isScriptEditorEnabled() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
return prefs.getBoolean(GHIDRA_SCRIPT_EDITOR_ENABLED);
}
/**
* Sets whether or not the script editor feature is enabled.
*
* @param enabled True to enable the script editor feature; false to disable it.
*/
public static void setScriptEditorEnabled(boolean enabled) {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
prefs.setValue(GHIDRA_SCRIPT_EDITOR_ENABLED, enabled);
}
/**
* Gets the port used for script editor.
*
* @return The port used for script editor. Will return -1 if the port is not set.
*/
public static int getScriptEditorPort() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
try {
return Integer.parseInt(prefs.getString(GHIDRA_SCRIPT_EDITOR_PORT_NUMBER));
}
catch (NumberFormatException e) {
return -1;
}
}
}

View file

@ -0,0 +1,249 @@
/* ###
* 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.ghidrasymbollookup;
import java.util.*;
import java.util.regex.Pattern;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.browser.ITypeReference;
import org.eclipse.cdt.core.browser.IndexTypeInfo;
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
import org.eclipse.cdt.core.index.*;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.internal.ui.browser.opentype.ElementSelectionDialog;
import org.eclipse.cdt.internal.ui.browser.opentype.OpenTypeMessages;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.ide.IDE;
import ghidradev.EclipseMessageUtils;
public class OpenDeclarations {
private IProject project;
private HashMap<String, IMarker> symbolMap;
public OpenDeclarations(IProject project) {
this.project = project;
this.symbolMap = new HashMap<String, IMarker>();
}
public void setProject(IProject newProject) {
project = newProject;
}
public boolean open(String filename, int lineNumber) {
openSingleFileAtLineNumber(filename, lineNumber);
return true;
}
public boolean open(String symbolName) {
IIndex index = null;
if (symbolMap.containsKey(symbolName)) {
EclipseMessageUtils.info("Re-using editor for symbol: " + symbolName);
openFileFromMap(symbolName);
return true;
}
EclipseMessageUtils.info("Searching index for symbol: " + symbolName);
List<IIndexName> indexNames = new ArrayList<>();
ICProject cProject = CoreModel.getDefault().getCModel().getCProject(project.getName());
IIndexManager manager = CCorePlugin.getIndexManager();
try {
index = manager.getIndex(cProject);
// we may be called before Eclipse has run the CDT plugin's init
waitForIndexInitialization(index);
index.acquireReadLock();
IIndexBinding[] bindings = index.findBindings(Pattern.compile(symbolName), false,
IndexFilter.ALL, new NullProgressMonitor());
EclipseMessageUtils.info("Found \"" + bindings.length + "\" bindings for symbol");
for (IIndexBinding binding : bindings) {
IIndexName[] names = index.findNames(binding, IIndex.FIND_DEFINITIONS);
for (IIndexName name : names) {
indexNames.add(name);
}
}
if (indexNames.size() == 0) {
EclipseMessageUtils.info("Found no definitions, looking for declarations...");
for (IIndexBinding binding : bindings) {
IIndexName[] names = index.findNames(binding, IIndex.FIND_DECLARATIONS);
for (IIndexName name : names) {
indexNames.add(name);
}
}
}
if (indexNames.size() == 1) {
EclipseMessageUtils.info("Found single match - opening editor");
openSingleFile(indexNames.get(0).getFileLocation(), symbolName);
return true;
}
else if (indexNames.size() > 1) {
EclipseMessageUtils.info("Found multiple matches - showing dialog");
openMultipleFileDialog(symbolName);
return true;
}
return false;
}
catch (Exception e) {
EclipseMessageUtils.error("Unexpected exception searching C index: " + e.getMessage(),
e);
return false;
}
finally {
if (index != null) {
index.releaseReadLock();
}
}
}
private void waitForIndexInitialization(IIndex index) throws CoreException {
int waitCount = 0;
while (waitCount < 2) {
waitCount++;
try {
index.acquireReadLock();
IIndexFile[] allFiles = index.getAllFiles();
if (allFiles.length == 0) {
EclipseMessageUtils.info("C Index is not yet initialized--waiting...");
index.releaseReadLock();
Thread.sleep(1000);
}
}
catch (InterruptedException e) {
// don't care; try again
}
finally {
index.releaseReadLock();
}
}
}
private void openSingleFile(IASTFileLocation location, String functionName) {
String pathToFix = location.getFileName();
String projectName = project.getName();
int index = pathToFix.indexOf(projectName);
if (index == -1) {
EclipseMessageUtils.error("Error opening the file containing " + pathToFix);
return;
}
String relativePath = pathToFix.substring(index);
final IPath path = new Path(relativePath).removeFirstSegments(1); // strip off project name
final int offset = location.getNodeOffset();
final int length = location.getNodeLength();
final String fName = functionName;
Display.getDefault().asyncExec(() -> {
try {
IFile file = project.getFile(path);
IMarker marker = file.createMarker(IMarker.TEXT);
marker.setAttribute(IMarker.CHAR_START, offset);
marker.setAttribute(IMarker.CHAR_END, offset + length);
IDE.openEditor(EclipseMessageUtils.getWorkbenchPage(), marker);
symbolMap.put(fName, marker);
EclipseMessageUtils.getWorkbenchPage().getWorkbenchWindow().getShell().forceActive();
}
catch (CoreException e) {
EclipseMessageUtils.error("Error opening the file containing " + fName, e);
}
});
}
private void openMultipleFileDialog(String functionName) {
final ElementSelectionDialog dialog = new ElementSelectionDialog(
EclipseMessageUtils.getWorkbenchPage().getWorkbenchWindow().getShell());
configureDialog(dialog, functionName);
final String fName = functionName;
Display.getDefault().asyncExec(() -> {
EclipseMessageUtils.getWorkbenchPage().getWorkbenchWindow().getShell().forceActive();
dialog.open();
Object[] results = dialog.getResult();
if (results == null) {
return; // user cancelled the dialog
}
for (Object result : results) {
if (result instanceof IndexTypeInfo) {
ITypeReference reference = ((IndexTypeInfo) result).getResolvedReference();
IPath path = reference.getPath();
path = path.removeFirstSegments(1);
IFile file = project.getFile(path);
try {
IMarker marker = file.createMarker(IMarker.TEXT);
marker.setAttribute(IMarker.CHAR_START, reference.getOffset());
marker.setAttribute(IMarker.CHAR_END,
reference.getOffset() + reference.getLength());
IDE.openEditor(EclipseMessageUtils.getWorkbenchPage(), marker);
symbolMap.put(fName, marker);
}
catch (CoreException e) {
EclipseMessageUtils.error("Error opening file chosen from selection dialog",
e);
}
}
}
});
}
private void openSingleFileAtLineNumber(final String relativeFilename, final int lineNumber) {
final IPath path = new Path(relativeFilename).removeFirstSegments(1); // strip off project
Display.getDefault().asyncExec(() -> {
try {
IFile file = project.getFile(path);
IMarker marker = file.createMarker(IMarker.TEXT);
marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
IDE.openEditor(EclipseMessageUtils.getWorkbenchPage(), marker);
EclipseMessageUtils.getWorkbenchPage().getWorkbenchWindow().getShell().forceActive();
}
catch (CoreException e) {
EclipseMessageUtils.error("Error opening the file containing at line " + lineNumber,
e);
}
});
}
private void configureDialog(ElementSelectionDialog dialog, String functionName) {
dialog.setTitle(OpenTypeMessages.OpenTypeDialog_title);
dialog.setMessage(OpenTypeMessages.OpenTypeDialog_message);
dialog.setDialogSettings(getClass().getName());
dialog.setIgnoreCase(true);
if (functionName.length() > 0 && functionName.length() < 80) {
dialog.setFilter(functionName, true);
}
}
private void openFileFromMap(String functionName) {
final IMarker marker = symbolMap.get(functionName);
Display.getDefault().asyncExec(() -> {
try {
IDE.openEditor(EclipseMessageUtils.getWorkbenchPage(), marker);
EclipseMessageUtils.getWorkbenchPage().getWorkbenchWindow().getShell().forceActive();
}
catch (CoreException e) {
EclipseMessageUtils.error("Error opening file from map", e);
}
});
}
}

View file

@ -0,0 +1,152 @@
/* ###
* 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.ghidrasymbollookup;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.swt.widgets.Display;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidrasymbollookup.preferences.GhidraSymbolLookupPreferences;
import ghidradev.ghidrasymbollookup.utils.CdtUtils;
public class SocketSetupRunnable implements Runnable {
private ServerSocket serverSocket = null;
private OpenDeclarations openDeclsDialog;
private IProject project;
private boolean isInitialized;
public SocketSetupRunnable(ServerSocket serverSocket) {
this.serverSocket = serverSocket;
}
@Override
public void run() {
while (!serverSocket.isClosed()) {
try (Socket socket = serverSocket.accept();
BufferedReader input =
new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter output = new PrintWriter(socket.getOutputStream())) {
String result = "";
// Setup the project to look in and handle any problems like project being closed
if (!isInitialized) {
result = init();
}
// If the project is closed while running the plugin and you try to look in it again
if (isInitialized && !project.isOpen()) {
try {
input.readLine();
}
catch (IOException e) {
EclipseMessageUtils.error(
"Unexpected exception receiving symbol name: " + e.getMessage());
}
isInitialized = false;
result = init();
}
if (isInitialized) {
String symbolName = null;
try {
symbolName = input.readLine();
}
catch (IOException e) {
EclipseMessageUtils.error(
"Unexpected exception looking for symbol: " + e.getMessage());
e.printStackTrace();
}
if (isInitialized) {
lookup(symbolName, output);
}
else {
output.write("Failed to initialize CDT project");
output.flush();
}
}
else {
output.write(result);
}
}
catch (IOException e) {
// Socket was closed
}
}
}
private String init() {
String projectName = GhidraSymbolLookupPreferences.getSymbolLookupProjectName();
final String errorMessageContainer[] = { "" };
Display.getDefault().syncExec(() -> {
while (!isInitialized) {
if (projectName == null) {
errorMessageContainer[0] =
"Project name not defined in the Ghidra Symbol Lookup preference page.";
EclipseMessageUtils.showWarnDialog("Ghidra Symbol Lookup",
errorMessageContainer[0]);
break;
}
project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
if (!project.exists()) {
errorMessageContainer[0] =
"The project \"" + projectName + "\" does not exist " +
"in your workspace. Please edit the \"Project Name\" field in " +
"the Ghidra Symbol Lookup preference page.";
EclipseMessageUtils.showWarnDialog("Project Does Not Exist",
errorMessageContainer[0]);
break;
}
if (!project.isOpen()) {
errorMessageContainer[0] = "Please open the project \"" + project.getName() +
"\" or choose a different one in the plugin preference page.";
EclipseMessageUtils.showWarnDialog("Project Not Open",
errorMessageContainer[0]);
break;
}
if (!CdtUtils.isCdtProject(project)) {
errorMessageContainer[0] =
"The project \"" + project.getName() + "\" is not a C or C++ project." +
"\nPlease edit the \"Project Name\" field in the Ghidra Symbol " +
"Lookup preference page.";
EclipseMessageUtils.showWarnDialog("Not a C/C++ Project",
errorMessageContainer[0]);
break;
}
openDeclsDialog = new OpenDeclarations(project);
isInitialized = true;
}
});
return errorMessageContainer[0];
}
private void lookup(String symbolName, PrintWriter output) {
EclipseMessageUtils.info("Looking for symbol name: " + symbolName);
boolean result = openDeclsDialog.open(symbolName);
if (result) {
output.write("Found symbol " + symbolName + "\n");
}
else {
output.write("Couldn't find " + symbolName + "\n");
}
output.flush();
}
}

View file

@ -0,0 +1,112 @@
/* ###
* 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.ghidrasymbollookup;
import java.io.IOException;
import java.net.ServerSocket;
import ghidradev.*;
import ghidradev.ghidrasymbollookup.preferences.GhidraSymbolLookupPreferences;
/**
* High level driver for initializing the Ghidra Symbol Lookup subcomponent. Should get called
* by the startup extension. This subcomponent is responsible for receiving symbol lookup requests
* from Ghidra to Eclipse over a socket.
*
* @see GhidraDevStartup
*/
public class SymbolLookupInitializer {
private static ServerSocket serverSocket;
/**
* Initializes the Ghidra Symbol Lookup subcomponent. Nothing in the package should be
* used until this initialization happens. Should be called during Eclipse startup.
*
* @param firstTimeConsent True if the user has just consented to opening ports; otherwise,
* false.
* @see GhidraDevStartup
*/
public static void init(boolean firstTimeConsent) {
if (firstTimeConsent) {
GhidraSymbolLookupPreferences.setSymbolLookupEnabled(true);
}
listen(GhidraSymbolLookupPreferences.getSymbolLookupPort());
}
/**
* Listens for socket connections on the given port. If there is a problem listening,
* a popup is displayed for the user.
*
* @param port The port to listen on. If the port is -1, this method doesn't do anything.
*/
private static void listen(int port) {
if (!GhidraSymbolLookupPreferences.isSymbolLookupEnabled()) {
EclipseMessageUtils.info(
Activator.PLUGIN_ID + " Symbol Lookup port listening is disabled in preferences.");
return;
}
if (port == -1) {
EclipseMessageUtils.info(Activator.PLUGIN_ID +
" Symbol Lookup port listening is disabled, port not set in preferences.");
return;
}
try {
serverSocket = new ServerSocket(port);
EclipseMessageUtils.info(
Activator.PLUGIN_ID + " Symbol Lookup is listening on port " + port);
Activator.getDefault().registerCloseable(serverSocket);
}
catch (IOException e) {
EclipseMessageUtils.showErrorDialog(Activator.PLUGIN_ID + " Symbol Lookup",
"Failed to listen for connections on port " + port +
". The Symbol Lookup features will be disabled until a valid port is selected in preferences.");
return;
}
new Thread(new SocketSetupRunnable(serverSocket)).start();
}
/**
* Called when the symbol lookup preferences change.
*
* @param enabledWasChanged True if the enablement was changed to a new value.
* @param portWasChanged True if the port preferences was changed to a new value.
*/
public static void preferencesChanged(boolean enabledWasChanged, boolean portWasChanged) {
if (!enabledWasChanged && !portWasChanged) {
return;
}
// Close the old server socket. Its job is done.
try {
if (serverSocket != null) {
serverSocket.close();
Activator.getDefault().unregisterCloseable(serverSocket);
serverSocket = null;
}
}
catch (IOException e) {
// Oh well, we tried. This port probably won't work next time they pick it.
}
// Listen on the new port
listen(GhidraSymbolLookupPreferences.getSymbolLookupPort());
}
}

View file

@ -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.
*/
package ghidradev.ghidrasymbollookup.preferences;
import org.eclipse.jface.preference.StringButtonFieldEditor;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
import ghidradev.ghidrasymbollookup.utils.CdtUtils;
/**
* A field editor that lets the user select an open CDT project.
*/
public class CdtProjectFieldEditor extends StringButtonFieldEditor {
/**
* Creates a new CDT project field editor.
*
* @param name the name of the preference this field editor works on
* @param labelText the label text of the field editor
* @param parent the parent of the field editor's control
*/
public CdtProjectFieldEditor(String name, String labelText, Composite parent) {
super(name, labelText, parent);
}
@Override
protected String changePressed() {
ElementListSelectionDialog dialog =
new ElementListSelectionDialog(getShell(), new LabelProvider());
dialog.setTitle("CDT project selection");
dialog.setMessage("Select an open CDT project:");
dialog.setElements(CdtUtils.getCDTProjects().stream().map(p -> p.getName()).toArray());
dialog.open();
Object[] result = dialog.getResult();
if (result != null && result.length > 0 && result[0] instanceof String) {
return (String) result[0];
}
return null;
}
}

View file

@ -0,0 +1,35 @@
/* ###
* 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.ghidrasymbollookup.preferences;
import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
import org.eclipse.jface.preference.IPreferenceStore;
import ghidradev.Activator;
/**
* Class used to initialize default preference values.
*/
public class GhidraSymbolLookupPreferenceInitializer extends AbstractPreferenceInitializer {
@Override
public void initializeDefaultPreferences() {
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
store.setDefault(GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_ENABLED, false);
store.setDefault(GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_PROJECT_NAME, "");
store.setDefault(GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_PORT_NUMBER, "12322");
}
}

View file

@ -0,0 +1,160 @@
/* ###
* 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.ghidrasymbollookup.preferences;
import org.eclipse.core.resources.*;
import org.eclipse.jface.preference.*;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import ghidradev.Activator;
import ghidradev.ghidrasymbollookup.SymbolLookupInitializer;
import ghidradev.ghidrasymbollookup.utils.CdtUtils;
/**
* This class represents a preference page that is contributed to the Preferences dialog. By
* subclassing {@link FieldEditorPreferencePage}, we can use the field support built into JFace
* that allows us to create a page that is small and knows how to save, restore and apply itself.
* <p>
* This page is used to modify preferences only. They are stored in the preference store that
* belongs to the main plug-in class. That way, preferences can be accessed directly via the
* preference store.
*/
public class GhidraSymbolLookupPreferencePage extends FieldEditorPreferencePage
implements IWorkbenchPreferencePage {
private BooleanFieldEditor enabledField;
private CdtProjectFieldEditor projectField;
private StringFieldEditor portField;
private String previousEnabledString = null;
private String currentEnabledString = null;
private String previousPortString = null;
private String currentPortString = null;
public GhidraSymbolLookupPreferencePage() {
super(GRID);
}
@Override
public void init(IWorkbench workbench) {
setPreferenceStore(Activator.getDefault().getPreferenceStore());
}
@Override
public void createFieldEditors() {
enabledField =
new BooleanFieldEditor(GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_ENABLED,
"Enabled", getFieldEditorParent());
projectField = new CdtProjectFieldEditor(
GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_PROJECT_NAME, "Project Name:",
getFieldEditorParent());
portField =
new StringFieldEditor(GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_PORT_NUMBER,
"Port:", getFieldEditorParent());
addField(enabledField);
addField(projectField);
addField(portField);
}
@Override
public void checkState() {
super.checkState();
if (!isValid()) {
return;
}
String projectValue = projectField.getStringValue();
String portValue = portField.getStringValue();
setValid(true);
if (!projectValue.isEmpty()) {
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRoot workspaceRoot = workspace.getRoot();
IProject project = workspaceRoot.getProject(projectValue);
if (!project.exists() || !project.isOpen() || !CdtUtils.isCdtProject(project)) {
setErrorMessage("Project is not an open CDT project.");
setValid(false);
return;
}
}
if (!portValue.isEmpty()) {
try {
int portNumber = Integer.parseInt(portValue);
if (portNumber < 1024 || portNumber > 0xFFFF) {
setErrorMessage("Port must be between 1024 and 65535.");
setValid(false);
return;
}
}
catch (NumberFormatException e) {
setErrorMessage("Port must be an integer.");
setValid(false);
return;
}
}
}
@Override
public void propertyChange(PropertyChangeEvent event) {
super.propertyChange(event);
if (event.getProperty().equals(FieldEditor.VALUE)) {
checkState();
}
if (event.getSource() == enabledField) {
if (previousEnabledString == null) {
previousEnabledString = event.getOldValue().toString();
}
currentEnabledString = event.getNewValue().toString();
}
else if (event.getSource() == portField) {
if (previousPortString == null) {
previousPortString = event.getOldValue().toString();
}
currentPortString = event.getNewValue().toString();
}
}
@Override
public boolean performOk() {
super.performOk();
boolean enabledWasChanged = false;
boolean portWasChanged = false;
if (currentEnabledString != null && previousEnabledString != null) {
if (!currentEnabledString.equals(previousEnabledString)) {
enabledWasChanged = true;
}
}
if (currentPortString != null && previousPortString != null) {
if (!currentPortString.equals(previousPortString)) {
portWasChanged = true;
}
}
SymbolLookupInitializer.preferencesChanged(enabledWasChanged, portWasChanged);
previousEnabledString = null;
previousPortString = null;
return true;
}
@Override
public boolean performCancel() {
super.performCancel();
previousEnabledString = null;
previousPortString = null;
return true;
}
}

View file

@ -0,0 +1,91 @@
/* ###
* 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.ghidrasymbollookup.preferences;
import org.eclipse.jface.preference.IPreferenceStore;
import ghidradev.Activator;
/**
* Ghidra symbol lookup preference definitions and related utility methods.
*/
public class GhidraSymbolLookupPreferences {
/**
* Whether or not the symbol lookup feature is enabled.
*/
static final String GHIDRA_SYMBOL_LOOKUP_ENABLED = "ghidradev.symbolLookupEnabled";
/**
* Name of CDT project that will be used for symbol lookup.
*/
static final String GHIDRA_SYMBOL_LOOKUP_PROJECT_NAME = "ghidradev.symbolLookupProjectName";
/**
* Port used for symbol lookup.
*/
static final String GHIDRA_SYMBOL_LOOKUP_PORT_NUMBER = "ghidradev.symbolLookupPortNumber";
/**
* Gets whether or not the symbol lookup feature is enabled.
*
* @return True if the symbol lookup feature is enabled; otherwise, false.
*/
public static boolean isSymbolLookupEnabled() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
return prefs.getBoolean(GHIDRA_SYMBOL_LOOKUP_ENABLED);
}
/**
* Sets whether or not the symbol lookup feature is enabled.
*
* @param enabled True to enable the symbol lookup feature; false to disable it.
*/
public static void setSymbolLookupEnabled(boolean enabled) {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
prefs.setValue(GHIDRA_SYMBOL_LOOKUP_ENABLED, enabled);
}
/**
* Gets the name of the CDT project that used for symbol lookup.
*
* @return The name of the CDT project used for symbol lookup, or null
* if one hasn't been set.
*/
public static String getSymbolLookupProjectName() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
String name = prefs.getString(GHIDRA_SYMBOL_LOOKUP_PROJECT_NAME);
if (name.isEmpty()) {
return null;
}
return name;
}
/**
* Gets the port used for symbol lookup.
*
* @return The port used for symbol lookup. Will return -1 if the port is not set.
*/
public static int getSymbolLookupPort() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
try {
return Integer.parseInt(prefs.getString(GHIDRA_SYMBOL_LOOKUP_PORT_NUMBER));
}
catch (NumberFormatException e) {
return -1;
}
}
}

View file

@ -0,0 +1,72 @@
/* ###
* 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.ghidrasymbollookup.utils;
import java.util.*;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import ghidradev.EclipseMessageUtils;
/**
* Utility methods for interacting with CDT.
*/
public class CdtUtils {
/**
* CDT C nature.
*/
public static final String C_NATURE = "org.eclipse.cdt.core.cnature";
/**
* CDT C++ nature.
*/
public static final String CC_NATURE = "org.eclipse.cdt.core.ccnature";
/**
* Gets all of the open CDT projects in the workspace.
*
* @return A collection of the open CDT projects in the workspace.
*/
public static Collection<IProject> getCDTProjects() {
List<IProject> cdtProjects = new ArrayList<>();
for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
if (project.isOpen() && isCdtProject(project)) {
cdtProjects.add(project);
}
}
return cdtProjects;
}
/**
* Checks to see if the given project is a CDT project.
*
* @param project The project to check.
* @return True if the given project is a CDT project; otherwise, false.
*/
public static boolean isCdtProject(IProject project) {
try {
return project != null && (project.hasNature(C_NATURE) || project.hasNature(CC_NATURE));
}
catch (CoreException e) {
EclipseMessageUtils.error("CDT project check failed", e);
return false;
}
}
}