GP-5138: GhidraDev/PyDev/PyGhidra integration

This commit is contained in:
Ryan Kurtz 2024-12-11 12:43:39 -05:00
parent 7c4d91f568
commit 31ee251a5c
35 changed files with 1300 additions and 315 deletions

View file

@ -898,6 +898,7 @@ src/main/resources/images/pencil16.png||GHIDRA||||END|
src/main/resources/images/pin.png||GHIDRA||||END|
src/main/resources/images/play_again.png||GHIDRA||||END|
src/main/resources/images/preferences-system.png||Tango Icons - Public Domain|||tango|END|
src/main/resources/images/python.png||GHIDRA||||END|
src/main/resources/images/question_zero.png||GHIDRA||||END|
src/main/resources/images/red-cross.png||GHIDRA||||END|
src/main/resources/images/redQuestionMark.png||GHIDRA||||END|

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

View file

@ -160,11 +160,14 @@ class PyGhidraLauncher:
install_dir = install_dir or os.getenv("GHIDRA_INSTALL_DIR")
self._install_dir = self._validate_install_dir(install_dir)
java_home_override = os.getenv("JAVA_HOME_OVERRIDE")
if java_home_override:
self._java_home = java_home_override
# check if we are in the ghidra source tree
support = Path(install_dir) / "support"
if not support.exists():
self._dev_mode = True
self._java_home = os.getenv("JAVA_HOME_OVERRIDE")
self._plugins: List[Tuple[Path, ExtensionDetails]] = []
self.verbose = verbose
@ -469,7 +472,7 @@ class PyGhidraLauncher:
self._pre_launch_init()
self._launch()
except Exception as e:
self._report_fatal_error("An error occured launching Ghidra", str(e), e)
self._report_fatal_error("An error occurred launching Ghidra", str(e), e)
def get_install_path(self, plugin_name: str) -> Path:
"""

View file

@ -2,7 +2,7 @@
<feature
id="ghidra.ghidradev"
label="GhidraDev"
version="4.0.1.qualifier"
version="5.0.0.qualifier"
provider-name="Ghidra">
<description>

View file

@ -167,7 +167,9 @@
<setEntry value="com.google.guava.failureaccess@default:default"/>
<setEntry value="com.google.guava@default:default"/>
<setEntry value="com.ibm.icu@default:default"/>
<setEntry value="com.python.pydev.analysis*6.3.1.201802272029@default:default"/>
<setEntry value="com.python.pydev.analysis*9.3.0.202203051235@default:default"/>
<setEntry value="com.python.pydev.debug*9.3.0.202203051235@default:default"/>
<setEntry value="com.python.pydev.refactoring*9.3.0.202203051235@default:default"/>
<setEntry value="com.sun.el.javax.el@default:default"/>
<setEntry value="com.sun.jna.platform@default:default"/>
<setEntry value="com.sun.jna@default:default"/>
@ -176,7 +178,6 @@
<setEntry value="jakarta.inject.jakarta.inject-api*1.0.5@default:default"/>
<setEntry value="jakarta.inject.jakarta.inject-api*2.0.1@default:default"/>
<setEntry value="jakarta.servlet-api@default:default"/>
<setEntry value="javax.xml@default:default"/>
<setEntry value="jaxen@default:default"/>
<setEntry value="org.apache.aries.spifly.dynamic.bundle@default:default"/>
<setEntry value="org.apache.batik.constants@default:default"/>
@ -202,6 +203,8 @@
<setEntry value="org.apache.xml.resolver@default:default"/>
<setEntry value="org.apache.xml.serializer@default:default"/>
<setEntry value="org.apache.xmlgraphics@default:default"/>
<setEntry value="org.commonmark-gfm-tables@default:default"/>
<setEntry value="org.commonmark@default:default"/>
<setEntry value="org.eclipse.ant.core@default:default"/>
<setEntry value="org.eclipse.buildship.compat@default:default"/>
<setEntry value="org.eclipse.buildship.core@default:default"/>
@ -296,7 +299,7 @@
<setEntry value="org.eclipse.help.base@default:default"/>
<setEntry value="org.eclipse.help.ui@default:default"/>
<setEntry value="org.eclipse.help@default:default"/>
<setEntry value="org.eclipse.jdt.annotation*2.3.0.v20240111-2306@default:default"/>
<setEntry value="org.eclipse.jdt.annotation@default:default"/>
<setEntry value="org.eclipse.jdt.core.compiler.batch@default:default"/>
<setEntry value="org.eclipse.jdt.core.manipulation@default:default"/>
<setEntry value="org.eclipse.jdt.core@default:default"/>
@ -308,10 +311,11 @@
<setEntry value="org.eclipse.jdt.launching@default:default"/>
<setEntry value="org.eclipse.jdt.ui@default:default"/>
<setEntry value="org.eclipse.jem.util@default:default"/>
<setEntry value="org.eclipse.jetty.ee10.servlet@default:default"/>
<setEntry value="org.eclipse.jetty.ee10.webapp@default:default"/>
<setEntry value="org.eclipse.jetty.ee8.security@default:default"/>
<setEntry value="org.eclipse.jetty.ee8.server@default:default"/>
<setEntry value="org.eclipse.jetty.ee8.servlet@default:default"/>
<setEntry value="org.eclipse.jetty.ee8.webapp@default:default"/>
<setEntry value="org.eclipse.jetty.ee@default:default"/>
<setEntry value="org.eclipse.jetty.http@default:default"/>
<setEntry value="org.eclipse.jetty.io@default:default"/>
@ -414,14 +418,19 @@
<setEntry value="org.osgi.util.position@default:default"/>
<setEntry value="org.osgi.util.promise@default:default"/>
<setEntry value="org.osgi.util.xml@default:default"/>
<setEntry value="org.python.pydev*6.3.1.201802272029@default:default"/>
<setEntry value="org.python.pydev.ast*6.3.1.201802272029@default:default"/>
<setEntry value="org.python.pydev.core*6.3.1.201802272029@default:default"/>
<setEntry value="org.python.pydev.jython*6.3.1.201802272029@default:default"/>
<setEntry value="org.python.pydev.parser*6.3.1.201802272029@default:default"/>
<setEntry value="org.python.pydev.shared_core*6.3.1.201802272029@default:default"/>
<setEntry value="org.python.pydev.shared_interactive_console*6.3.1.201802272029@default:default"/>
<setEntry value="org.python.pydev.shared_ui*6.3.1.201802272029@default:default"/>
<setEntry value="org.python.pydev*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.ast*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.core*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.customizations*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.debug*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.django*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.help*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.jython*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.parser*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.refactoring*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.shared_core*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.shared_interactive_console*9.3.0.202203051235@default:default"/>
<setEntry value="org.python.pydev.shared_ui*9.3.0.202203051235@default:default"/>
<setEntry value="org.sat4j.core@default:default"/>
<setEntry value="org.sat4j.pb@default:default"/>
<setEntry value="org.tukaani.xz@default:default"/>
@ -430,7 +439,7 @@
<setAttribute key="selected_workspace_bundles">
<setEntry value="ghidra.ghidradev@default:default"/>
</setAttribute>
<booleanAttribute key="show_selected_only" value="true"/>
<booleanAttribute key="show_selected_only" value="false"/>
<booleanAttribute key="tracing" value="false"/>
<booleanAttribute key="useCustomFeatures" value="false"/>
<booleanAttribute key="useDefaultConfig" value="true"/>

View file

@ -3,7 +3,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: GhidraDev
Bundle-SymbolicName: ghidra.ghidradev;singleton:=true
Bundle-Version: 4.0.1.qualifier
Bundle-Version: 5.0.0.qualifier
Bundle-Activator: ghidradev.Activator
Require-Bundle: org.eclipse.ant.core;bundle-version="3.7.200",
org.eclipse.buildship.core;bundle-version="3.1.8",
@ -21,10 +21,11 @@ Require-Bundle: org.eclipse.ant.core;bundle-version="3.7.200",
org.eclipse.ltk.core.refactoring;bundle-version="3.14.200",
org.eclipse.ui;bundle-version="3.205.0",
org.eclipse.ui.ide;bundle-version="3.22.0",
com.python.pydev.debug;bundle-version="6.3.1";resolution:=optional,
org.python.pydev;bundle-version="[6.3.1,10.0.0)";resolution:=optional,
org.python.pydev.core;bundle-version="[6.3.1,10.0.0)";resolution:=optional,
org.python.pydev.ast;bundle-version="[6.3.1,10.0.0)";resolution:=optional,
com.python.pydev.debug;bundle-version="9.3.0";resolution:=optional,
org.python.pydev;bundle-version="9.3.0";resolution:=optional,
org.python.pydev.core;bundle-version="9.3.0";resolution:=optional,
org.python.pydev.ast;bundle-version="9.3.0";resolution:=optional,
org.python.pydev.debug;bundle-version="9.3.0";resolution:=optional,
org.eclipse.cdt.core;bundle-version="5.9.1";resolution:=optional,
org.eclipse.cdt.ui;bundle-version="5.9.0";resolution:=optional
Bundle-RequiredExecutionEnvironment: JavaSE-21

View file

@ -1,7 +1,7 @@
# GhidraDev Eclipse Plugin
GhidraDev provides support for developing and debugging Ghidra scripts and modules in Eclipse.
The information provided in this document is effective as of GhidraDev 4.0.0 and is subject to
The information provided in this document is effective as of GhidraDev 5.0.0 and is subject to
change with future releases.
## Table of Contents
@ -31,6 +31,9 @@ change with future releases.
12. [Building](#building)
## Change History
__5.0.0:__
* Added support for PyGhidra.
__4.0.1:__
* New Ghidra module projects now contain a default `README.md` file.
* Fixed a bug that prevented an imported module source project from being discovered by Ghidra when
@ -132,7 +135,7 @@ __1.0.1:__
* Ghidra 11.2 or later
## Optional Requirements
* PyDev 6.3.1 - 9.3.0 ([more info](#pydev-support))
* PyDev 9.3.0 or later ([more info](#pydev-support))
* Gradle - required version(s) specified by linked Ghidra release
([more info](#export-ghidra-module-extension))
@ -268,7 +271,10 @@ Ghidra from Eclipse independent of a project is not supported.
## PyDev Support
GhidraDev is able to integrate with PyDev to conveniently configure Python support into Ghidra
script and module projects.
script and module projects. GhidraDev supports both Jython and PyGhidra Python implementations.
__NOTE:__ PyDev discontinued Jython 2 support in version 10.0.0. If you want to use GhidraDev with
Jython, you must use __PyDev 9.3.0__. The latest vesions of PyDev support PyGhidra.
### Installing PyDev
From Eclipse:
@ -293,13 +299,19 @@ GhidraDev can add Python support to a Ghidra project when:
* Creating a new Ghidra script project
* Linking a Ghidra installation to an existing Java project
In order for GhidraDev to add in Python support, PyDev must have a Jython interpreter configured.
GhidraDev will present a list of detected Jython interpreters that it found in PyDev's preferences.
If no Jython interpreters were found, one can be added from GhidraDev by clicking the `+` icon.
When the `+` icon is clicked, GhidraDev will attempt to find the Jython interpreter bundled with the
selected Ghidra installation and automatically configure PyDev to use it. If for some reason
GhidraDev was unable to find a Jython interpreter in the Ghidra installation, one will have to be
added manually in the PyDev preferences.
In order for GhidraDev to add in Python support, PyDev must have a PyGhidra or Jython interpreter
configured. GhidraDev will present a list of detected PyGhidra/Jython interpreters that it found in
PyDev's preferences. If no interpreters were found, one can be added from GhidraDev by clicking
the `+` icons.
When the Jython `+` icon is clicked, GhidraDev will attempt to find the Jython interpreter bundled
with the selected Ghidra installation and automatically configure PyDev to use it. If for some
reason GhidraDev was unable to find a Jython interpreter in the Ghidra installation, one will have
to be added manually in the PyDev preferences.
When the PyGhidra `+` icon is clicked, GhidraDev will attempt to find the PyGhidra interpreter
that was last used to launch PyGhidra. If it cannot find it, you will have to launch PyGhidra
and try again.
## Upgrading
GhidraDev is upgraded differently depending on how it was installed. If GhidraDev was
@ -347,9 +359,6 @@ installation directory.
to your Ghidra module project, which automatically happens when the project is created.
Simply [relink](#link-ghidra) your Ghidra installation to the project, and your project will
pick up any newly discovered Ghidra extensions.
* __Why doesn't GhidraDev support PyDev 10.0 or later?__
* PyDev dropped support for Python 2 in their 10.0 release. Ghidra currently does not support
Python 3.
## Additional Resources
For more information on the GhidraDev plugin and developing for Ghidra in an Eclipse environment,
@ -358,14 +367,14 @@ at `<GhidraInstallDir>/docs/GhidraClass/Intermediate/Scripting.html`.
## Building
GhidraDev is currently built from Eclipse and distributed with Ghidra manually. Ideally we will use
Gradle one day, but we aren't there yet. We do rely on `gradle prepDev` to generate the Eclipse
project and build GhidraDev's dependencies though.
Gradle one day, but we aren't there yet. We do rely on Gradle to generate the Eclipse project and
build GhidraDev's dependencies though.
__NOTE:__ Only "Eclipse for RCP and RAP Developers" has the ability to do the below instructions.
The following instructions assume that you are using this version of Eclipse.
#### Importing GhidraDev Eclipse projects (they are deactivated by default):
1. Run `gradle eclipse -PeclipsePDE`
1. Run `gradle prepGhidraDev eclipse -PeclipsePDE`
2. From Eclipse, `File -> Import -> General -> Existing Projects into Workspace`
3. From the ghidra repo, import `Eclipse GhidraDevFeature` and `Eclipse GhidraDevPlugin`

View file

@ -81,8 +81,8 @@ task pyDevUnpack(type:Copy) {
!pyDevDestDir.exists()
}
File depsFile = file("${DEPS_DIR}/GhidraDev/PyDev 6.3.1.zip")
File binRepoFile = file("${BIN_REPO}/GhidraBuild/EclipsePlugins/GhidraDev/buildDependencies/PyDev 6.3.1.zip")
File depsFile = file("${DEPS_DIR}/GhidraDev/PyDev 9.3.0.zip")
File binRepoFile = file("${BIN_REPO}/GhidraBuild/EclipsePlugins/GhidraDev/buildDependencies/PyDev 9.3.0.zip")
// First check if the file is in the dependencies repo. If not, check in the bin repo.
def pyDevZipTree = depsFile.exists() ? zipTree(depsFile) : zipTree(binRepoFile)
@ -115,6 +115,13 @@ task cdtUnpack(type:Copy) {
destinationDir cdtDestDir
}
task prepGhidraDev {
dependsOn("utilityJar")
dependsOn("launchSupportJar")
dependsOn("pyDevUnpack")
dependsOn("cdtUnpack")
}
// We do not currently build GhidraDev plugin at Ghidra build time so we must
// copy the prebuilt zip file from the BIN_REPO
rootProject.assembleDistribution {
@ -129,9 +136,3 @@ rootProject.assembleMarkdownToHtml {
into "Extensions/Eclipse/GhidraDev/"
}
}
// PrepDev dependencies
rootProject.prepDev.dependsOn utilityJar
rootProject.prepDev.dependsOn launchSupportJar
rootProject.prepDev.dependsOn pyDevUnpack
rootProject.prepDev.dependsOn cdtUnpack

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -378,6 +378,11 @@
id="GhidraHeadlessLaunchConfigurationType"
name="Ghidra Headless">
</launchConfigurationType>
<launchConfigurationType
delegate="ghidradev.ghidraprojectcreator.launchers.PyGhidraLaunchDelegate"
id="PyGhidraGuiLaunchConfigurationType"
name="PyGhidra">
</launchConfigurationType>
</extension>
<extension
point="org.eclipse.debug.ui.launchConfigurationTypeImages">
@ -391,6 +396,11 @@
icon="icons/GhidraIcon16_bw.png"
id="GhidraHeadlessLaunchConfigurationTypeImage">
</launchConfigurationTypeImage>
<launchConfigurationTypeImage
configTypeID="PyGhidraGuiLaunchConfigurationType"
icon="icons/python.png"
id="PyGhidraGuiLaunchConfigurationTypeImage">
</launchConfigurationTypeImage>
</extension>
<extension
point="org.eclipse.debug.core.launchDelegates">
@ -408,6 +418,13 @@
name="Ghidra Headless"
type="GhidraHeadlessLaunchConfigurationType">
</launchDelegate>
<launchDelegate
delegate="ghidradev.ghidraprojectcreator.launchers.PyGhidraLaunchDelegate"
id="PyGhidraGuiLaunchDelegate"
modes="run, debug"
name="PyGhidra GUI"
type="PyGhidraGuiLaunchConfigurationType">
</launchDelegate>
</extension>
<extension
point="org.eclipse.debug.ui.launchConfigurationTabGroups">
@ -423,6 +440,12 @@
id="GhidraHeadlessLaunchConfigurationTabGroup"
type="GhidraHeadlessLaunchConfigurationType">
</launchConfigurationTabGroup>
<launchConfigurationTabGroup
class="org.python.pydev.debug.ui.PythonTabGroup"
description="Run and debug PyGhidra modules and scripts"
id="PyGhidraGuiLaunchConfigurationTabGroup"
type="PyGhidraGuiLaunchConfigurationType">
</launchConfigurationTabGroup>
</extension>
<extension
point="org.eclipse.debug.ui.launchShortcuts">
@ -474,6 +497,30 @@
</enablement>
</contextualLaunch>
</shortcut>
<shortcut
class="ghidradev.ghidraprojectcreator.launchers.PyGhidraGuiLaunchShortcut"
icon="icons/python.png"
id="PyGhidraGuiLaunchShortcut"
label="PyGhidra"
modes="run, debug">
<contextualLaunch>
<enablement>
<with
variable="selection">
<count
value="1">
</count>
<iterate
ifEmpty="false">
<test
property="ghidradev.ghidraprojectcreator.testers.isPyGhidraProject"
value="true">
</test>
</iterate>
</with>
</enablement>
</contextualLaunch>
</shortcut>
</extension>
<extension
point="org.eclipse.core.expressions.propertyTesters">
@ -505,6 +552,13 @@
properties="isGhidraModuleProject"
type="java.lang.Object">
</propertyTester>
<propertyTester
class="ghidradev.ghidraprojectcreator.testers.PyGhidraProjectPropertyTester"
id="PyGhidraProjectPropertyTester"
namespace="ghidradev.ghidraprojectcreator.testers"
properties="isPyGhidraProject"
type="java.lang.Object">
</propertyTester>
</extension>
</plugin>

View file

@ -0,0 +1,107 @@
/* ###
* 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 javax.naming.OperationNotSupportedException;
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.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.*;
/**
* PyGhidra launch shortcut actions. These shortcuts appear when you right click on a
* PyGhidra 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 AbstractPyGhidraLaunchShortcut implements ILaunchShortcut {
private String launchConfigTypeId;
private String launchConfigNameSuffix;
/**
* Creates a new PyGhidra launch shortcut associated with the given launch configuration type ID.
*
* @param launchConfigTypeId The launch configuration type ID of this PyGhidra launch shortcut.
* @param launchConfigNameSuffix A string to append to the name of the launch configuration.
*/
protected AbstractPyGhidraLaunchShortcut(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(project, mode);
}
}
@Override
public void launch(IEditorPart editor, String mode) {
IEditorInput input = editor.getEditorInput();
IResource resource = input.getAdapter(IResource.class);
if (resource != null) {
launch(resource.getProject(), mode);
}
}
/**
* Launches the given Python nature in the given mode with a PyGhidra launcher.
*
* @param project The project to launch.
* @param mode The mode to launch in (run/debug).
*/
private void launch(IProject project, String mode) {
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
ILaunchConfigurationType launchType =
launchManager.getLaunchConfigurationType(launchConfigTypeId);
String launchConfigName = project.getName() + launchConfigNameSuffix;
try {
ILaunchConfiguration lc = GhidraLaunchUtils.getLaunchConfig(launchConfigName);
ILaunchConfigurationWorkingCopy wc = null;
if (lc == null) {
wc = launchType.newInstance(null, launchConfigName);
wc.setAttribute(PyDevUtils.getAttrProject(), project.getName());
}
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 | OperationNotSupportedException e) {
EclipseMessageUtils.showErrorDialog(e.getMessage());
}
}
}

View file

@ -33,7 +33,7 @@ import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IPerspectiveDescriptor;
import org.eclipse.ui.PlatformUI;
import ghidra.launch.JavaConfig;
import ghidra.launch.AppConfig;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.*;
@ -62,10 +62,10 @@ public class GhidraLaunchDelegate extends JavaLaunchDelegate {
}
IFolder ghidraFolder =
javaProject.getProject().getFolder(GhidraProjectUtils.GHIDRA_FOLDER_NAME);
JavaConfig javaConfig;
AppConfig appConfig;
String ghidraInstallPath = ghidraFolder.getLocation().toOSString();
try {
javaConfig = new JavaConfig(new File(ghidraInstallPath));
appConfig = new AppConfig(new File(ghidraInstallPath));
}
catch (ParseException | IOException e) {
EclipseMessageUtils.showErrorDialog(
@ -98,7 +98,7 @@ public class GhidraLaunchDelegate extends JavaLaunchDelegate {
}
// Set VM arguments
String vmArgs = javaConfig.getLaunchProperties().getVmArgs();
String vmArgs = appConfig.getLaunchProperties().getVmArgs();
vmArgs += " " + configuration.getAttribute(GhidraLaunchUtils.ATTR_VM_ARGUMENTS, "").trim();
vmArgs += " -Dghidra.external.modules=\"%s%s%s\"".formatted(
javaProject.getProject().getLocation(), File.pathSeparator,
@ -171,7 +171,7 @@ public class GhidraLaunchDelegate extends JavaLaunchDelegate {
}
// Start PyDev debugger
if (PyDevUtils.isSupportedPyDevInstalled()) {
if (PyDevUtils.isSupportedJythonPyDevInstalled()) {
try {
PyDevUtils.startPyDevRemoteDebugger();
}

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 PyGhidra GUI launch shortcut actions.
*
* @see AbstractGhidraLaunchShortcut
*/
public class PyGhidraGuiLaunchShortcut extends AbstractPyGhidraLaunchShortcut {
/**
* Creates a new PyGhidra GUI launch shortcut.
*/
public PyGhidraGuiLaunchShortcut() {
super(GhidraLaunchUtils.PYGHIDRA_GUI_LAUNCH, " GUI");
}
}

View file

@ -0,0 +1,142 @@
/* ###
* 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 java.util.HashMap;
import java.util.Map;
import javax.naming.OperationNotSupportedException;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.*;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IPerspectiveDescriptor;
import org.eclipse.ui.PlatformUI;
import org.python.pydev.debug.ui.launching.RegularLaunchConfigurationDelegate;
import ghidra.launch.AppConfig;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils;
/**
* The PyGhidra Launch delegate handles the final launch of PyGhidra.
* We can do any extra custom launch behavior here.
*/
public class PyGhidraLaunchDelegate extends RegularLaunchConfigurationDelegate {
@Override
public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch,
IProgressMonitor monitor) throws CoreException {
try {
ILaunchConfigurationWorkingCopy wc = configuration.getWorkingCopy();
// Get project
String projectName = wc.getAttribute(PyDevUtils.getAttrProject(), "");
IJavaProject javaProject = GhidraProjectUtils.getGhidraProject(projectName);
if (javaProject == null) {
EclipseMessageUtils.showErrorDialog("Failed to launch project \"" + projectName +
"\".\nDoes not appear to be a Ghidra project.");
return;
}
IProject project = javaProject.getProject();
// Get needed application.properties values
String javaComplianceLevel = null;
String ghidraVmErrorMsg = "";
try {
IFolder ghidraFolder = project.getFolder(GhidraProjectUtils.GHIDRA_FOLDER_NAME);
String ghidraInstallPath = ghidraFolder.getLocation().toOSString();
AppConfig appConfig = new AppConfig(new File(ghidraInstallPath));
javaComplianceLevel = appConfig.getCompilerComplianceLevel();
}
catch (ParseException | IOException e) {
ghidraVmErrorMsg = e.getMessage();
}
if (javaComplianceLevel == null) {
EclipseMessageUtils
.showErrorDialog("Failed to get JVM compliance level from project \"" +
projectName + "\".\n" + ghidraVmErrorMsg);
return;
}
// Set program location
wc.setAttribute(PyDevUtils.getAttrLocation(),
"${workspace_loc:%s/Ghidra/Ghidra/Features/PyGhidra/pypkg/src/pyghidra}"
.formatted(project.getName()));
// Set program arguments
wc.setAttribute(PyDevUtils.getAttrProgramArguments(), "-v -g");
// Set Python interpreter
String interpreterName = PyDevUtils.getInterpreterName(project);
wc.setAttribute(PyDevUtils.getAttrInterpreter(), interpreterName);
wc.setAttribute(PyDevUtils.getAttrInterpreterDefault(), interpreterName);
// Set environment variables
Map<String, String> env = new HashMap<>();
//env.put("GHIDRA_INSTALL_DIR", "${project_loc:/%s/Ghidra}".formatted(project.getName()));
env.put("GHIDRA_INSTALL_DIR",
"${resource_loc:/%s/Ghidra}".formatted(project.getName()));
env.put("JAVA_HOME_OVERRIDE", "${ee_home:JavaSE-%s}".formatted(javaComplianceLevel));
if (mode.equals("debug")) {
env.put("PYGHIDRA_DEBUG", "1");
handleDebugMode();
}
wc.setAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES, env);
super.launch(wc.doSave(), mode, launch, monitor);
}
catch (OperationNotSupportedException e) {
EclipseMessageUtils.showErrorDialog("PyDev error",
"Failed to launch. PyDev version is not supported.");
}
}
/**
* 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
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,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.testers;
import javax.naming.OperationNotSupportedException;
import org.eclipse.core.expressions.PropertyTester;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils;
/**
* A {@link PropertyTester} used to determine if a given Eclipse resource is part
* of a PyGhidra project.
*/
public class PyGhidraProjectPropertyTester extends PropertyTester {
@Override
public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
try {
return PyDevUtils.isPyGhidraProject(GhidraProjectUtils.getEnclosingProject(receiver));
}
catch (OperationNotSupportedException e) {
return false;
}
}
}

View file

@ -49,6 +49,12 @@ public class GhidraLaunchUtils {
*/
public static final String HEADLESS_LAUNCH = "GhidraHeadlessLaunchConfigurationType";
/**
* Launch configuration ID for a PyGhidra GUI launch. Must match corresponding value in
* plugin.xml.
*/
public static final String PYGHIDRA_GUI_LAUNCH = "PyGhidraGuiLaunchConfigurationType";
/**
* 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

View file

@ -30,6 +30,7 @@ import org.eclipse.ltk.core.refactoring.*;
import ghidra.GhidraApplicationLayout;
import ghidra.util.exception.CancelledException;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import utilities.util.FileUtilities;
/**
@ -89,7 +90,7 @@ public class GhidraModuleUtils {
*/
public static IJavaProject createGhidraModuleProject(String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory, GhidraApplicationLayout ghidraLayout,
String jythonInterpreterName, IProgressMonitor monitor)
ProjectPythonInterpreter jythonInterpreterName, IProgressMonitor monitor)
throws IOException, ParseException, CoreException {
// Create empty Ghidra project
@ -227,8 +228,7 @@ public class GhidraModuleUtils {
* @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 pythonInterpreter The Python interpreter to use.
* @param monitor The progress monitor to use during project creation.
* @return The imported project.
* @throws IOException If there was a file-related problem with creating the project.
@ -237,13 +237,13 @@ public class GhidraModuleUtils {
*/
public static IJavaProject importGhidraModuleSource(String projectName, File moduleSourceDir,
boolean createRunConfig, String runConfigMemory, GhidraApplicationLayout ghidraLayout,
String jythonInterpreterName, IProgressMonitor monitor)
ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
throws IOException, ParseException, CoreException {
// Create empty Ghidra project
IJavaProject javaProject =
GhidraProjectUtils.createEmptyGhidraProject(projectName, moduleSourceDir,
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
createRunConfig, runConfigMemory, ghidraLayout, pythonInterpreter, monitor);
IProject project = javaProject.getProject();
// Set default output location

View file

@ -36,9 +36,10 @@ import org.eclipse.ui.part.FileEditorInput;
import generic.jar.ResourceFile;
import ghidra.GhidraApplicationLayout;
import ghidra.framework.GModule;
import ghidra.launch.JavaConfig;
import ghidra.launch.AppConfig;
import ghidradev.Activator;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import utility.module.ModuleUtilities;
/**
@ -262,8 +263,7 @@ public class GhidraProjectUtils {
* @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 pythonInterpreter The Python interpreter to use.
* @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.
@ -272,12 +272,12 @@ public class GhidraProjectUtils {
*/
public static IJavaProject createEmptyGhidraProject(String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory, GhidraApplicationLayout ghidraLayout,
String jythonInterpreterName, IProgressMonitor monitor)
ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
throws IOException, ParseException, CoreException {
// Get Ghidra's Java configuration
JavaConfig javaConfig =
new JavaConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
AppConfig appConfig =
new AppConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
// Make new Java project
IWorkspace workspace = ResourcesPlugin.getWorkspace();
@ -299,7 +299,7 @@ public class GhidraProjectUtils {
javaProject.setRawClasspath(new IClasspathEntry[0], monitor);
// Configure Java compiler for the project
configureJavaCompiler(javaProject, javaConfig);
configureJavaCompiler(javaProject, appConfig);
// Setup default bin folder
IFolder binFolder = project.getFolder("bin/default");
@ -310,7 +310,7 @@ public class GhidraProjectUtils {
monitor);
// Link in Ghidra to the project
linkGhidraToProject(javaProject, ghidraLayout, javaConfig, jythonInterpreterName, monitor);
linkGhidraToProject(javaProject, ghidraLayout, appConfig, pythonInterpreter, monitor);
// Create run configuration (if necessary)
if (createRunConfig) {
@ -338,23 +338,22 @@ public class GhidraProjectUtils {
*
* @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 appConfig Ghidra's application configuration.
* @param pythonInterpreter The Python interpreter to use.
* @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)
GhidraApplicationLayout ghidraLayout, AppConfig appConfig,
ProjectPythonInterpreter pythonInterpreter, 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);
IVMInstall vm = getGhidraVm(appConfig);
IPath vmPath =
new Path(JavaRuntime.JRE_CONTAINER).append(vm.getVMInstallType().getId()).append(
vm.getName());
@ -457,17 +456,15 @@ public class GhidraProjectUtils {
GhidraModuleUtils.writeAntProperties(javaProject.getProject(), ghidraLayout);
// Setup Python for the project
if (PyDevUtils.isSupportedPyDevInstalled()) {
try {
PyDevUtils.setupPythonForProject(javaProject, libraryClasspathEntries,
jythonInterpreterName, monitor);
pythonInterpreter, monitor);
}
catch (OperationNotSupportedException e) {
EclipseMessageUtils.showErrorDialog("PyDev error",
"Failed to setup Python for the project. PyDev version is not supported.");
}
}
}
/**
* Checks to see if the given path is contained within the given Ghidra installation path.
@ -559,14 +556,14 @@ public class GhidraProjectUtils {
/**
* Gets the required VM used to build and run the Ghidra defined by the given layout.
*
* @param javaConfig Ghidra's Java configuration.
* @param appConfig Ghidra's application 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 {
private static IVMInstall getGhidraVm(AppConfig appConfig) throws IOException, CoreException {
File requiredJavaHomeDir = javaConfig.getSavedJavaHome(); // safe to assume it's valid
File requiredJavaHomeDir = appConfig.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.
@ -617,19 +614,19 @@ public class GhidraProjectUtils {
* Configures the default Java compiler behavior for the given java project.
*
* @param jp The Java project to configure.
* @param javaConfig Ghidra's Java configuration.
* @param appConfig Ghidra's application configuration.
*/
private static void configureJavaCompiler(IJavaProject jp, JavaConfig javaConfig) {
private static void configureJavaCompiler(IJavaProject jp, AppConfig appConfig) {
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_SOURCE, appConfig.getCompilerComplianceLevel());
jp.setOption(JavaCore.COMPILER_COMPLIANCE, appConfig.getCompilerComplianceLevel());
jp.setOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM,
javaConfig.getCompilerComplianceLevel());
appConfig.getCompilerComplianceLevel());
// Code style
jp.setOption(JavaCore.COMPILER_PB_STATIC_ACCESS_RECEIVER, WARNING);

View file

@ -26,6 +26,7 @@ import org.eclipse.jdt.core.*;
import ghidra.GhidraApplicationLayout;
import ghidra.framework.GModule;
import ghidradev.Activator;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
/**
* Utility methods for working with Ghidra scripts in Eclipse.
@ -45,8 +46,7 @@ public class GhidraScriptUtils {
* @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 pythonInterpreter The Python interpreter to use.
* @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.
@ -56,15 +56,14 @@ public class GhidraScriptUtils {
public static IJavaProject createGhidraScriptProject(String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory, boolean linkUserScripts,
boolean linkSystemScripts, GhidraApplicationLayout ghidraLayout,
String jythonInterpreterName, IProgressMonitor monitor)
ProjectPythonInterpreter pythonInterpreter, 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);
projectDir, createRunConfig, runConfigMemory, ghidraLayout, pythonInterpreter, monitor);
// Link each module's ghidra_scripts directory to the project
if (linkSystemScripts) {

View file

@ -26,6 +26,7 @@ import java.util.stream.Stream;
import javax.naming.OperationNotSupportedException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
@ -38,23 +39,58 @@ import ghidradev.Activator;
*/
public class PyDevUtils {
public final static String MIN_SUPPORTED_VERSION = "6.3.1";
public final static String MAX_SUPPORTED_VERSION = "9.3.0";
public final static String MIN_SUPPORTED_VERSION = "9.3.0";
public final static String MAX_JYTHON_SUPPORTED_VERSION = "9.3.0";
/**
* Checks to see if a supported version of PyDev is installed.
*
* @return True if a supported version of PyDev is installed; otherwise, false.
* The various types of supported Python interpreters
*/
public static boolean isSupportedPyDevInstalled() {
public static enum ProjectPythonInterpreterType {
NONE,
PYGHIDRA,
JYTHON
}
/**
* The projects Python interpreter to use
*
* @param name The name of the interpreter
* @param type The {@link ProjectPythonInterpreterType type} of the interpreter
*/
public static record ProjectPythonInterpreter(String name, ProjectPythonInterpreterType type) {}
/**
* {@return true if a supported version of PyDev is installed for use with PyGhidra; otherwise,
* false}
*/
public static boolean isSupportedPyGhidraPyDevInstalled() {
Version min = Version.valueOf(MIN_SUPPORTED_VERSION);
Version max = Version.valueOf(MAX_SUPPORTED_VERSION);
try {
Version version = PyDevUtilsInternal.getPyDevVersion();
if (version != null) {
return version.compareTo(min) >= 0;
}
}
catch (NoClassDefFoundError e) {
// Fall through to return false
}
return false;
}
/**
* {@return true if a supported version of PyDev is installed for use with Jython; otherwise,
* false}
*/
public static boolean isSupportedJythonPyDevInstalled() {
Version min = Version.valueOf(MIN_SUPPORTED_VERSION);
Version max = Version.valueOf(MAX_JYTHON_SUPPORTED_VERSION);
try {
Version version = PyDevUtilsInternal.getPyDevVersion();
if (version != null) {
// Make sure the installed version of PyDev is new enough to support the following
// operation.
getJython27InterpreterNames();
getJythonInterpreterNames();
return version.compareTo(min) >= 0 && version.compareTo(max) <= 0;
}
}
@ -66,15 +102,53 @@ public class PyDevUtils {
}
/**
* Gets a list of discovered Jython 2.7 interpreter names.
*
* @return a list of discovered Jython 2.7 interpreter names.
* Gets a list of discovered PyGhidra interpreter names.
* @param requiredFileMatch if not {@code null}, only interpreter names that correspond to the
* given interpreter file will be returned.
* @return a list of discovered PyGhidra interpreter names.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static List<String> getJython27InterpreterNames() throws OperationNotSupportedException {
public static List<String> getPyGhidraInterpreterNames(File requiredFileMatch)
throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getJython27InterpreterNames();
return PyDevUtilsInternal.getPyGhidraInterpreterNames(requiredFileMatch);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets a list of discovered Jython interpreter names.
*
* @return a list of discovered Jython interpreter names.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static List<String> getJythonInterpreterNames() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getJythonInterpreterNames();
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Adds the given PyGhidra interpreter to PyDev.
*
* @param interpreterName The name of the interpreter to add.
* @param interpreterFile The interpreter file to add.
* @param pypredefDir The pypredef directory to use (could be null if not supported)
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static void addPyGhidraInterpreter(String interpreterName, File interpreterFile,
File pypredefDir) throws OperationNotSupportedException {
try {
PyDevUtilsInternal.addPyGhidraInterpreter(interpreterName, interpreterFile,
pypredefDir);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
@ -107,8 +181,7 @@ public class PyDevUtils {
*
* @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 pythonInterpreter The Python interpreter to use.
* @param monitor The progress monitor used during link.
* @throws CoreException if there was an Eclipse-related problem with enabling Python for the
* project.
@ -116,11 +189,11 @@ public class PyDevUtils {
* operation.
*/
public static void setupPythonForProject(IJavaProject javaProject,
List<IClasspathEntry> classpathEntries, String jythonInterpreterName,
List<IClasspathEntry> classpathEntries, ProjectPythonInterpreter pythonInterpreter,
IProgressMonitor monitor) throws CoreException, OperationNotSupportedException {
try {
PyDevUtilsInternal.setupPythonForProject(javaProject, classpathEntries,
jythonInterpreterName, monitor);
pythonInterpreter, monitor);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
@ -151,6 +224,15 @@ public class PyDevUtils {
return "org.python.pydev.ui.pythonpathconf.interpreterPreferencesPageJython";
}
/**
* Gets the PyDev Python preference page ID.
*
* @return the PyDev Python preference page ID.
*/
public static String getPythonPreferencePageId() {
return "org.python.pydev.ui.pythonpathconf.interpreterPreferencesPagePython";
}
/**
* Gets The PyDev source directory.
*
@ -184,4 +266,138 @@ public class PyDevUtils {
return null;
}
/**
* Checks to see if the given project is a Python project.
*
* @param project The project to check.
* @return True if the given project is a Python project; otherwise, false.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static boolean isPythonProject(IProject project) throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.isPythonProject(project);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Checks to see if the given project is a PyGhidra project.
*
* @param project The project to check.
* @return True if the given project is a PyGhidra project; otherwise, false.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static boolean isPyGhidraProject(IProject project)
throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.isPyGhidraProject(project);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the interpreter name of the given Python project.
*
* @param project The project to get the interpreter name from.
* @return The interpreter name of the given Python project, or null it it's not a Python
* project or doesn't have an interpreter.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getInterpreterName(IProject project)
throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getInterpreterName(project);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev "project" attribute.
*
* @return The PyDev "project" attribute.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getAttrProject() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getAttrProject();
}
catch (NoClassDefFoundError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev "location" attribute.
*
* @return The PyDev "location" attribute.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getAttrLocation() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getAttrLocation();
}
catch (NoClassDefFoundError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev "program arguments" attribute.
*
* @return The PyDev "program arguments" attribute.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getAttrProgramArguments() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getAttrProgramArguments();
}
catch (NoClassDefFoundError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev "interpreter" attribute.
*
* @return The PyDev "interpreter" attribute.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getAttrInterpreter() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getAttrInterpreter();
}
catch (NoClassDefFoundError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev "interpreter default" attribute.
*
* @return The PyDev "interpreter default" attribute.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static String getAttrInterpreterDefault() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getAttrInterpreterDefault();
}
catch (NoClassDefFoundError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
}

View file

@ -19,6 +19,7 @@ import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
@ -26,11 +27,13 @@ import org.osgi.framework.*;
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.debug.core.Constants;
import org.python.pydev.plugin.nature.PythonNature;
import com.python.pydev.debug.remote.client_api.PydevRemoteDebuggerServer;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
/**
* Utility methods for interacting with PyDev.
@ -62,20 +65,50 @@ class PyDevUtilsInternal {
}
/**
* Gets a list of discovered Jython 2.7 interpreter names.
* Gets a list of discovered PyGhidra interpreter names.
*
* @return a list of discovered Jython 2.7 interpreter names.
* @param requiredFileMatch if not {@code null}, only interpreter names that correspond to the
* given interpreter file will be returned.
* @return a list of discovered PyGhidra 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()
public static List<String> getPyGhidraInterpreterNames(File requiredFileMatch)
throws NoClassDefFoundError, NoSuchMethodError {
List<String> interpreters = new ArrayList<>();
IInterpreterManager iMan = InterpreterManagersAPI.getPythonInterpreterManager(true);
for (IInterpreterInfo info : iMan.getInterpreterInfos()) {
ISystemModulesManager modulesManager = info.getModulesManager();
if (info.getInterpreterType() == IPythonNature.INTERPRETER_TYPE_PYTHON &&
!modulesManager.getAllModulesStartingWith("pyghidra.__main__").isEmpty()) {
if (requiredFileMatch == null ||
requiredFileMatch.getAbsolutePath().equals(info.getExecutableOrJar())) {
interpreters.add(info.getName());
}
}
}
return interpreters;
}
/**
* Gets a list of discovered Jython interpreter names.
*
* @return a list of discovered Jython 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> getJythonInterpreterNames()
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")) {
if (info.getInterpreterType() == IPythonNature.INTERPRETER_TYPE_JYTHON &&
info.getVersion().equals("2.7")) {
interpreters.add(info.getName());
}
}
@ -83,6 +116,38 @@ class PyDevUtilsInternal {
return interpreters;
}
/**
* Adds the given PyGhidra interpreter to PyDev.
*
* @param interpreterName The name of the interpreter to add.
* @param interpreterFile The interpreter to add.
* @param pypredefDir The pypredef directory to use (could be null if not supported)
* @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 addPyGhidraInterpreter(String interpreterName, File interpreterFile,
File pypredefDir) throws NoClassDefFoundError, NoSuchMethodError {
IProgressMonitor monitor = new NullProgressMonitor();
IInterpreterManager iMan = InterpreterManagersAPI.getPythonInterpreterManager(true);
IInterpreterInfo[] interpreterInfos = iMan.getInterpreterInfos();
for (IInterpreterInfo iInfo : interpreterInfos) {
if (iInfo.getName().equals(interpreterName) &&
iInfo.getExecutableOrJar().equals(interpreterFile.getAbsolutePath())) {
return;
}
}
IInterpreterInfo iInfo =
iMan.createInterpreterInfo(interpreterFile.getAbsolutePath(), monitor, false);
iInfo.setName(interpreterName);
if (iInfo instanceof InterpreterInfo ii && pypredefDir != null) {
ii.addPredefinedCompletionsPath(pypredefDir.getAbsolutePath());
}
IInterpreterInfo[] newInterpreterInfos =
Arrays.copyOf(interpreterInfos, interpreterInfos.length + 1);
newInterpreterInfos[interpreterInfos.length] = iInfo;
iMan.setInfos(newInterpreterInfos, null, monitor);
}
/**
* Adds the given Jython interpreter to PyDev.
*
@ -119,26 +184,38 @@ class PyDevUtilsInternal {
*
* @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 pythonInterpreter The Python interpreter to use.
* @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,
List<IClasspathEntry> classpathEntries, ProjectPythonInterpreter pythonInterpreter,
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);
String version;
String libs;
switch (pythonInterpreter.type()) {
case PYGHIDRA:
version = IPythonNature.PYTHON_VERSION_INTERPRETER;
libs = null;
break;
case JYTHON:
version = IPythonNature.JYTHON_VERSION_INTERPRETER;
libs = classpathEntries.stream()
.map(e -> e.getPath().toOSString())
.collect(Collectors.joining("|"));
break;
default:
return;
}
PythonNature.addNature(javaProject.getProject(), monitor, version, null, libs,
pythonInterpreter.name(), null);
}
/**
@ -151,6 +228,109 @@ class PyDevUtilsInternal {
PydevRemoteDebuggerServer.startServer();
}
/**
* Checks to see if the given project is a Python project.
*
* @param project The project to check.
* @return True if the given project is a Python project; otherwise, false.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
*/
public static boolean isPythonProject(IProject project) throws NoClassDefFoundError {
try {
return project.hasNature(PythonNature.PYTHON_NATURE_ID);
}
catch (CoreException e) {
return false;
}
}
/**
* Checks to see if the given project is a PyGhidra project.
*
* @param project The project to check.
* @return True if the given project is a PyGhidra project; otherwise, false.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
*/
public static boolean isPyGhidraProject(IProject project) throws NoClassDefFoundError {
return isPythonProject(project) && GhidraProjectUtils.isGhidraProject(project);
}
/**
* Gets the interpreter name of the given Python project.
*
* @param project The project to get the interpreter name from.
* @return The interpreter name of the given Python project, or null it it's not a Python
* project or doesn't have an interpreter.
* @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 String getInterpreterName(IProject project)
throws NoClassDefFoundError, NoSuchMethodError {
PythonNature nature = PythonNature.getPythonNature(project);
if (nature != null) {
try {
IInterpreterInfo info = nature.getProjectInterpreter();
if (info != null) {
return info.getName();
}
}
catch (PythonNatureWithoutProjectException | MisconfigurationException e) {
// Fall through
}
}
return null;
}
/**
* Gets the PyDev "project" attribute.
*
* @return The PyDev "project" attribute.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
*/
public static String getAttrProject() throws NoClassDefFoundError {
return Constants.ATTR_PROJECT;
}
/**
* Gets the PyDev "location" attribute.
*
* @return The PyDev "location" attribute.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
*/
public static String getAttrLocation() throws NoClassDefFoundError {
return Constants.ATTR_LOCATION;
}
/**
* Gets the PyDev "program arguments" attribute.
*
* @return The PyDev "program arguments" attribute.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
*/
public static String getAttrProgramArguments() throws NoClassDefFoundError {
return Constants.ATTR_PROGRAM_ARGUMENTS;
}
/**
* Gets the PyDev "interpreter" attribute.
*
* @return The PyDev "interpreter" attribute.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
*/
public static String getAttrInterpreter() throws NoClassDefFoundError {
return Constants.ATTR_INTERPRETER;
}
/**
* Gets the PyDev "interpreter default" attribute.
*
* @return The PyDev "interpreter default" attribute.
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
*/
public static String getAttrInterpreterDefault() throws NoClassDefFoundError {
return Constants.ATTR_INTERPRETER_DEFAULT;
}
private PyDevUtilsInternal() throws NoClassDefFoundError {
// Prevent instantiation
}

View file

@ -36,6 +36,7 @@ import ghidra.GhidraApplicationLayout;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils.ModuleTemplateType;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import ghidradev.ghidraprojectcreator.wizards.pages.*;
import utilities.util.FileUtilities;
@ -87,12 +88,12 @@ public class CreateGhidraModuleProjectWizard extends Wizard implements INewWizar
boolean createRunConfig = projectPage.shouldCreateRunConfig();
String runConfigMemory = projectPage.getRunConfigMemory();
File projectDir = projectPage.getProjectDir();
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
ProjectPythonInterpreter pythonInterpreter = pythonPage.getProjectPythonInterpreter();
Set<ModuleTemplateType> moduleTemplateTypes = projectConfigPage.getModuleTemplateTypes();
try {
getContainer().run(true, false,
monitor -> create(ghidraInstallDir, projectName, projectDir, createRunConfig,
runConfigMemory, moduleTemplateTypes, jythonInterpreterName, monitor));
runConfigMemory, moduleTemplateTypes, pythonInterpreter, monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
@ -115,14 +116,13 @@ public class CreateGhidraModuleProjectWizard extends Wizard implements INewWizar
* @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 pythonInterpreter The Python interpreter to use.
* @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,
Set<ModuleTemplateType> moduleTemplateTypes, ProjectPythonInterpreter pythonInterpreter,
IProgressMonitor monitor) throws InvocationTargetException {
try {
info("Creating " + projectName + " at " + projectDir);
@ -133,7 +133,7 @@ public class CreateGhidraModuleProjectWizard extends Wizard implements INewWizar
IJavaProject javaProject =
GhidraModuleUtils.createGhidraModuleProject(projectName, projectDir,
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
createRunConfig, runConfigMemory, ghidraLayout, pythonInterpreter, monitor);
monitor.worked(1);
IFile sourceFile = GhidraModuleUtils.configureModuleSource(javaProject,

View file

@ -32,6 +32,7 @@ import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraScriptUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import ghidradev.ghidraprojectcreator.wizards.pages.*;
import utilities.util.FileUtilities;
@ -81,11 +82,11 @@ public class CreateGhidraScriptProjectWizard extends Wizard implements INewWizar
String runConfigMemory = projectPage.getRunConfigMemory();
boolean linkUserScripts = projectConfigPage.shouldLinkUsersScripts();
boolean linkSystemScripts = projectConfigPage.shouldLinkSystemScripts();
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
ProjectPythonInterpreter pythonInterpreter = pythonPage.getProjectPythonInterpreter();
try {
getContainer().run(true, false,
monitor -> create(ghidraInstallDir, projectName, projectDir, createRunConfig,
runConfigMemory, linkUserScripts, linkSystemScripts, jythonInterpreterName,
runConfigMemory, linkUserScripts, linkSystemScripts, pythonInterpreter,
monitor));
}
catch (InterruptedException e) {
@ -110,15 +111,14 @@ public class CreateGhidraScriptProjectWizard extends Wizard implements INewWizar
* @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 pythonInterpreter The Python interpreter to use.
* @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 {
boolean linkSystemScripts, ProjectPythonInterpreter pythonInterpreter,
IProgressMonitor monitor) throws InvocationTargetException {
try {
info("Creating " + projectName + " at " + projectDir);
monitor.beginTask("Creating " + projectName, 2);
@ -128,7 +128,7 @@ public class CreateGhidraScriptProjectWizard extends Wizard implements INewWizar
GhidraScriptUtils.createGhidraScriptProject(projectName, projectDir, createRunConfig,
runConfigMemory, linkUserScripts, linkSystemScripts, ghidraLayout,
jythonInterpreterName, monitor);
pythonInterpreter, monitor);
monitor.worked(1);
info("Finished creating " + projectName);

View file

@ -40,7 +40,7 @@ import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout;
import ghidra.launch.JavaConfig;
import ghidra.launch.AppConfig;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.wizards.pages.ChooseGhidraModuleProjectWizardPage;
@ -123,7 +123,7 @@ public class ExportGhidraModuleWizard extends Wizard implements INewWizard {
// 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(
File javaHomeDir = new AppConfig(
ghidraLayout.getApplicationInstallationDir().getFile(false)).getSavedJavaHome();
if(javaHomeDir == null) {
throw new IOException("Failed to get the Java home associated with the project. " +

View file

@ -32,6 +32,7 @@ import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import ghidradev.ghidraprojectcreator.wizards.pages.*;
import utilities.util.FileUtilities;
@ -76,11 +77,11 @@ public class ImportGhidraModuleSourceWizard extends Wizard implements IImportWiz
String projectName = projectPage.getProjectName();
boolean createRunConfig = projectPage.shouldCreateRunConfig();
String runConfigMemory = projectPage.getRunConfigMemory();
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
ProjectPythonInterpreter pythonInterpreter = pythonPage.getProjectPythonInterpreter();
try {
getContainer().run(true, false,
monitor -> importModuleSource(ghidraInstallDir, projectName, moduleSourceDir,
createRunConfig, runConfigMemory, jythonInterpreterName, monitor));
createRunConfig, runConfigMemory, pythonInterpreter, monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
@ -102,14 +103,14 @@ public class ImportGhidraModuleSourceWizard extends Wizard implements IImportWiz
* @param moduleSourceDir The module source directory to import.
* @param createRunConfig Whether or not to create a new run configuration for the project.
* @param runConfigMemory The run configuration's desired memory. Could be null.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param pythonInterpreter The Python interpreter to use.
* @param monitor The monitor to use during project creation.
* @throws InvocationTargetException if an error occurred during project creation.
*/
private void importModuleSource(File ghidraInstallDir, String projectName, File moduleSourceDir,
boolean createRunConfig, String runConfigMemory, String jythonInterpreterName,
IProgressMonitor monitor) throws InvocationTargetException {
boolean createRunConfig, String runConfigMemory,
ProjectPythonInterpreter pythonInterpreter, IProgressMonitor monitor)
throws InvocationTargetException {
try {
info("Importing " + projectName + " at " + moduleSourceDir);
monitor.beginTask("Importing " + projectName, 2);
@ -118,7 +119,7 @@ public class ImportGhidraModuleSourceWizard extends Wizard implements IImportWiz
monitor.worked(1);
GhidraModuleUtils.importGhidraModuleSource(projectName, moduleSourceDir,
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
createRunConfig, runConfigMemory, ghidraLayout, pythonInterpreter, monitor);
monitor.worked(1);
info("Finished importing " + projectName);

View file

@ -31,8 +31,9 @@ import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.wizard.Wizard;
import ghidra.GhidraApplicationLayout;
import ghidra.launch.JavaConfig;
import ghidra.launch.AppConfig;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import ghidradev.ghidraprojectcreator.wizards.pages.*;
/**
@ -63,10 +64,10 @@ public class LinkGhidraWizard extends Wizard {
public boolean performFinish() {
File ghidraInstallDir = ghidraInstallationPage.getGhidraInstallDir();
IJavaProject javaProject = projectPage.getJavaProject();
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
ProjectPythonInterpreter pythonInterpreter = pythonPage.getProjectPythonInterpreter();
try {
getContainer().run(true, false,
monitor -> link(ghidraInstallDir, javaProject, jythonInterpreterName, monitor));
monitor -> link(ghidraInstallDir, javaProject, pythonInterpreter, monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
@ -85,23 +86,23 @@ public class LinkGhidraWizard extends Wizard {
*
* @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 pythonInterpreter The Python interpreter to use.
* @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 {
private void link(File ghidraInstallDir, IJavaProject javaProject,
ProjectPythonInterpreter pythonInterpreter, 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);
AppConfig appConfig =
new AppConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
GhidraProjectUtils.linkGhidraToProject(javaProject, ghidraLayout, appConfig,
pythonInterpreter, monitor);
monitor.worked(1);
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);

View file

@ -27,7 +27,7 @@ import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.dialogs.PreferencesUtil;
import ghidra.launch.JavaConfig;
import ghidra.launch.AppConfig;
import ghidra.launch.JavaFinder.JavaFilter;
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferencePage;
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferences;
@ -108,8 +108,8 @@ public class ChooseGhidraInstallationWizardPage extends WizardPage {
File ghidraInstallDir = new File(ghidraInstallDirCombo.getText());
GhidraProjectCreatorPreferencePage.validateGhidraInstallation(ghidraInstallDir);
try {
JavaConfig javaConfig = new JavaConfig(ghidraInstallDir);
if (!javaConfig.isSupportedJavaHomeDir(javaConfig.getSavedJavaHome(),
AppConfig appConfig = new AppConfig(ghidraInstallDir);
if (!appConfig.isSupportedJavaHomeDir(appConfig.getSavedJavaHome(),
JavaFilter.JDK_ONLY)) {
message = "A supported JDK is not associated with this Ghidra " +
"installation. Please run this Ghidra and try again.";

View file

@ -15,8 +15,7 @@
*/
package ghidradev.ghidraprojectcreator.wizards.pages;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.nio.file.Files;
import java.util.List;
@ -27,13 +26,15 @@ 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.layout.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.dialogs.PreferencesUtil;
import ghidra.launch.AppConfig;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreter;
import ghidradev.ghidraprojectcreator.utils.PyDevUtils.ProjectPythonInterpreterType;
/**
* A wizard page that lets the user enable python for their project.
@ -41,7 +42,11 @@ import ghidradev.ghidraprojectcreator.utils.PyDevUtils;
public class EnablePythonWizardPage extends WizardPage {
private ChooseGhidraInstallationWizardPage ghidraInstallationPage;
private Button enablePythonCheckboxButton;
private Button pyghidraButton;
private Button jythonButton;
private Button noneButton;
private Combo pyghidraCombo;
private Button addPyGhidraButton;
private Combo jythonCombo;
private Button addJythonButton;
@ -61,46 +66,107 @@ public class EnablePythonWizardPage extends WizardPage {
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(3, false));
container.setLayout(new GridLayout(1, 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 +
" - " + PyDevUtils.MAX_SUPPORTED_VERSION);
enablePythonCheckboxButton.setSelection(PyDevUtils.isSupportedPyDevInstalled());
enablePythonCheckboxButton.addSelectionListener(new SelectionListener() {
// Project type selection
SelectionListener projectTypeSelectionListener = new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent evt) {
validate();
validate(null);
}
@Override
public void widgetDefaultSelected(SelectionEvent evt) {
validate();
validate(null);
}
};
Group projectTypeGroup = new Group(container, SWT.SHADOW_ETCHED_OUT);
projectTypeGroup.setLayout(new RowLayout(SWT.HORIZONTAL));
projectTypeGroup.setText("Project Type");
pyghidraButton = new Button(projectTypeGroup, SWT.RADIO);
pyghidraButton.setSelection(PyDevUtils.isSupportedPyGhidraPyDevInstalled());
pyghidraButton.setText("PyGhidra");
pyghidraButton.setToolTipText("Enables PyGhidra support using the PyDev " +
"Eclipse plugin. Requires PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" or later.");
pyghidraButton.addSelectionListener(projectTypeSelectionListener);
jythonButton = new Button(projectTypeGroup, SWT.RADIO);
jythonButton.setSelection(false);
jythonButton.setText("Jython");
jythonButton.setToolTipText("Enables Jython support using the PyDev " +
"Eclipse plugin. Requires PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" - " + PyDevUtils.MAX_JYTHON_SUPPORTED_VERSION);
jythonButton.addSelectionListener(projectTypeSelectionListener);
noneButton = new Button(projectTypeGroup, SWT.RADIO);
noneButton.setSelection(!PyDevUtils.isSupportedPyGhidraPyDevInstalled());
noneButton.setText("None");
noneButton.setToolTipText("Disables Python support for the project.");
noneButton.addSelectionListener(projectTypeSelectionListener);
Composite interpreterContainer = new Composite(container, SWT.NULL);
interpreterContainer.setLayout(new GridLayout(3, false));
interpreterContainer.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
// PyGhidra interpreter combo box
Label pyGhidraLabel = new Label(interpreterContainer, SWT.NULL);
pyGhidraLabel.setText("PyGhidra interpreter:");
pyghidraCombo = new Combo(interpreterContainer, SWT.DROP_DOWN | SWT.READ_ONLY);
pyghidraCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
pyghidraCombo.setToolTipText("The wizard requires a Python interpreter to be " +
"selected. Click the + button to add or manage Python interpreters.");
File savedPyGhidraInterpreter = populatePyGhidraCombo(null);
pyghidraCombo.addModifyListener(evt -> validate(null));
// PyGhidra interpreter add button
addPyGhidraButton = new Button(interpreterContainer, SWT.BUTTON1);
addPyGhidraButton.setText("+");
addPyGhidraButton.setToolTipText("Adds/manages PyGhidra interpreters.");
addPyGhidraButton.addListener(SWT.Selection, evt -> {
try {
File ghidraDir = ghidraInstallationPage.getGhidraInstallDir();
File pyghidraInterpreter = findPyGhidraInterpreter();
File pypredefDir = new File(ghidraDir, "docs/ghidra_stubs/pypredef");
if (!pypredefDir.isDirectory()) {
pypredefDir = null;
}
if (EclipseMessageUtils.showQuestionDialog("Python Found",
"PyGhidra was previously launched with: \"" + pyghidraInterpreter +
"\". Would you like to use it as your interpreter?")) {
PyDevUtils.addPyGhidraInterpreter("pyghidra_" + ghidraDir.getName(),
pyghidraInterpreter, pypredefDir);
populatePyGhidraCombo(pyghidraInterpreter);
validate(pyghidraInterpreter);
return;
}
}
catch (Exception e) {
// Fall through to show PyDev's Python preference page
}
PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(null,
PyDevUtils.getPythonPreferencePageId(), null, null);
dialog.open();
populatePyGhidraCombo(savedPyGhidraInterpreter);
validate(null);
});
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);
Label jythonLabel = new Label(interpreterContainer, SWT.NULL);
jythonLabel.setText("Jython interpreter:");
jythonCombo = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
jythonCombo = new Combo(interpreterContainer, 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());
jythonCombo.addModifyListener(evt -> validate(null));
// Jython interpreter add button
addJythonButton = new Button(container, SWT.BUTTON1);
addJythonButton = new Button(interpreterContainer, SWT.BUTTON1);
addJythonButton.setText("+");
addJythonButton.setToolTipText("Adds/manages Jython interpreters.");
addJythonButton.addListener(SWT.Selection, evt -> {
try {
if (PyDevUtils.getJython27InterpreterNames().isEmpty()) {
if (PyDevUtils.getJythonInterpreterNames().isEmpty()) {
File ghidraDir = ghidraInstallationPage.getGhidraInstallDir();
File jythonFile = findJythonInterpreter(ghidraDir);
File jythonLib = findJythonLibrary(ghidraDir);
@ -111,7 +177,7 @@ public class EnablePythonWizardPage extends WizardPage {
PyDevUtils.addJythonInterpreter("jython_" + ghidraDir.getName(),
jythonFile, jythonLib);
populateJythonCombo();
validate();
validate(null);
return;
}
}
@ -124,80 +190,142 @@ public class EnablePythonWizardPage extends WizardPage {
PyDevUtils.getJythonPreferencePageId(), null, null);
dialog.open();
populateJythonCombo();
validate();
validate(null);
});
validate();
validate(savedPyGhidraInterpreter);
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();
@Override
public void setVisible(boolean visible) {
if (visible) {
validate(populatePyGhidraCombo(null));
}
super.setVisible(visible);
}
/**
* 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.
* {@return the project Python interpreter to use}
*/
public String getJythonInterpreterName() {
if (enablePythonCheckboxButton.getSelection()) {
return jythonCombo.getText();
public ProjectPythonInterpreter getProjectPythonInterpreter() {
if (pyghidraButton.getSelection()) {
return new ProjectPythonInterpreter(pyghidraCombo.getText(),
ProjectPythonInterpreterType.PYGHIDRA);
}
return null;
if (jythonButton.getSelection()) {
return new ProjectPythonInterpreter(jythonCombo.getText(),
ProjectPythonInterpreterType.JYTHON);
}
return new ProjectPythonInterpreter(null, ProjectPythonInterpreterType.NONE);
}
/**
* Validates the fields on the page and updates the page's status.
* Should be called every time a field on the page changes.
*
* @param pyghidraInterpreter The Python interpreter used to launch PyGhidra (could be null if
* unknown).
*/
private void validate() {
private void validate(File pyghidraInterpreter) {
String message = null;
boolean pyDevInstalled = PyDevUtils.isSupportedPyDevInstalled();
boolean pyDevEnabled = enablePythonCheckboxButton.getSelection();
boolean comboEnabled = pyDevInstalled && pyDevEnabled;
boolean pyghidraSupported = PyDevUtils.isSupportedPyGhidraPyDevInstalled();
boolean jythonSupported = PyDevUtils.isSupportedJythonPyDevInstalled();
if (pyDevEnabled) {
if (!pyDevInstalled) {
if (pyghidraButton.getSelection()) {
if (!pyghidraSupported) {
message = "PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" - " + PyDevUtils.MAX_SUPPORTED_VERSION + " is not installed.";
" or later is not installed.";
}
else {
try {
List<String> interpreters = PyDevUtils.getJython27InterpreterNames();
if (pyghidraInterpreter == null) {
pyghidraInterpreter = findPyGhidraInterpreter();
}
if (pyghidraInterpreter == null) {
message =
"Please first launch PyGhidra to associate the Ghidra installation with a supported version of Python.";
pyghidraSupported = false;
}
else {
List<String> interpreters =
PyDevUtils.getPyGhidraInterpreterNames(pyghidraInterpreter);
if (interpreters.isEmpty()) {
message = "No Jython interpreters found. Click the + button to add one.";
message ="No PyGhidra interpreters found. Click the + button or set project type to \"None\".";
}
}
}
catch (OperationNotSupportedException e) {
message = "PyDev version is not supported.";
comboEnabled = false;
message = "PyDev version is not supported for Jython.";
pyghidraSupported = false;
}
catch (Exception e) {
message =
"Failed to lookup Python interpreter associated with the Ghidra installation.";
pyghidraSupported = false;
}
}
}
else if (jythonButton.getSelection()) {
if (!jythonSupported) {
message = "PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
" - " + PyDevUtils.MAX_JYTHON_SUPPORTED_VERSION + " is not installed.";
}
else {
try {
List<String> interpreters = PyDevUtils.getJythonInterpreterNames();
if (interpreters.isEmpty()) {
message =
"No Jython interpreters found. Click the + button or set project type to \"None\".";
}
}
catch (OperationNotSupportedException e) {
message = "PyDev version is not supported for Jython.";
jythonSupported = false;
}
}
}
jythonCombo.setEnabled(comboEnabled);
addJythonButton.setEnabled(comboEnabled);
pyghidraCombo.setEnabled(pyghidraButton.getSelection() && pyghidraSupported);
addPyGhidraButton.setEnabled(pyghidraButton.getSelection() && pyghidraSupported);
jythonCombo.setEnabled(jythonButton.getSelection() && jythonSupported);
addJythonButton.setEnabled(jythonButton.getSelection() && jythonSupported);
setErrorMessage(message);
setPageComplete(message == null);
}
/**
* Populates the Jython combo box with discovered Jython names.
* Populates the PyGhidra combo box with discovered PyGhidra interpreter names.
*
* @param pyghidraInterpreter The Python interpreter used to launch PyGhidra (could be null if
* unknown).
* @return The Python interpreter used to launch PyGhidra (could be null if unknown).
*/
private File populatePyGhidraCombo(File pyghidraInterpreter) {
pyghidraCombo.removeAll();
try {
if (pyghidraInterpreter == null) {
pyghidraInterpreter = findPyGhidraInterpreter();
}
PyDevUtils.getPyGhidraInterpreterNames(pyghidraInterpreter).forEach(pyghidraCombo::add);
}
catch (Exception e) {
// Nothing to do. Combo should and will be empty.
}
if (pyghidraCombo.getItemCount() > 0) {
pyghidraCombo.select(0);
}
return pyghidraInterpreter;
}
/**
* Populates the Jython combo box with discovered Jython interpreter names.
*/
private void populateJythonCombo() {
jythonCombo.removeAll();
try {
for (String jythonName : PyDevUtils.getJython27InterpreterNames()) {
jythonCombo.add(jythonName);
}
PyDevUtils.getJythonInterpreterNames().forEach(jythonCombo::add);
}
catch (OperationNotSupportedException e) {
// Nothing to do. Combo should and will be empty.
@ -207,6 +335,32 @@ public class EnablePythonWizardPage extends WizardPage {
}
}
/**
* Find's the Python interpreter file that was used to launch PyGhidra.
*
* @return The Python interpreter file that was used to launch PyGhidra, or null if one could
* not be found.
* @throws Exception if there was a problem finding the Python interpreter.
*/
private File findPyGhidraInterpreter() throws Exception {
File ghidraDir = ghidraInstallationPage.getGhidraInstallDir();
AppConfig appConfig = new AppConfig(ghidraDir);
List<String> cmd = appConfig.getSavedPythonCommand();
if (cmd == null) {
return null;
}
cmd.add("-c");
cmd.add("import sys; print(sys.executable)");
Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start();
BufferedReader reader =
new BufferedReader(new InputStreamReader(p.getInputStream()));
String pythonExecutable = reader.readLine();
if (p.waitFor() == 0) {
return new File(pythonExecutable);
}
return null;
}
/**
* Find's a Jython interpreter file in the given Ghidra installation directory.
*

View file

@ -13,6 +13,7 @@ GhidraDevPlugin/icons/GhidraIcon16_bw.png||GHIDRA||||END|
GhidraDevPlugin/icons/brick_add.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
GhidraDevPlugin/icons/brick_go.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
GhidraDevPlugin/icons/folder_link.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
GhidraDevPlugin/icons/python.png||GHIDRA||||END|
GhidraDevPlugin/icons/script_add.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
GhidraDevPlugin/icons/script_code_red.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
GhidraDevPlugin/plugin.xml||GHIDRA||||END|

View file

@ -78,20 +78,20 @@ public class LaunchSupport {
try {
File installDir = new File(installDirPath).getCanonicalFile(); // change relative path to absolute
JavaConfig javaConfig = new JavaConfig(installDir);
AppConfig appConfig = new AppConfig(installDir);
JavaFinder javaFinder = JavaFinder.create();
// Pass control to a mode-specific handler
switch (mode.toLowerCase()) {
case "-java_home":
exitCode = handleJavaHome(javaConfig, javaFinder, JavaFilter.ANY, ask, save);
exitCode = handleJavaHome(appConfig, javaFinder, JavaFilter.ANY, ask, save);
break;
case "-jdk_home":
exitCode =
handleJavaHome(javaConfig, javaFinder, JavaFilter.JDK_ONLY, ask, save);
handleJavaHome(appConfig, javaFinder, JavaFilter.JDK_ONLY, ask, save);
break;
case "-vmargs":
exitCode = handleVmArgs(javaConfig);
exitCode = handleVmArgs(appConfig);
break;
default:
System.err.println("LaunchSupport received illegal argument: " + mode);
@ -109,7 +109,7 @@ public class LaunchSupport {
* Handles figuring out a Java home directory to use for the launch. If it is successfully
* determined, an exit code that indicates success is returned.
*
* @param javaConfig The Java configuration that defines what we support.
* @param appConfig The appConfig configuration that defines what we support.
* @param javaFinder The Java finder.
* @param javaFilter A filter used to restrict what kind of Java installations we search for.
* @param ask True to interact with the user to they can specify a Java home directory.
@ -120,12 +120,12 @@ public class LaunchSupport {
* successfully determined.
* @throws IOException if there was a disk-related problem.
*/
private static int handleJavaHome(JavaConfig javaConfig, JavaFinder javaFinder,
private static int handleJavaHome(AppConfig appConfig, JavaFinder javaFinder,
JavaFilter javaFilter, boolean ask, boolean save) throws IOException {
if (ask) {
return askJavaHome(javaConfig, javaFinder, javaFilter);
return askJavaHome(appConfig, javaFinder, javaFilter);
}
return findJavaHome(javaConfig, javaFinder, javaFilter, save);
return findJavaHome(appConfig, javaFinder, javaFilter, save);
}
/**
@ -133,7 +133,7 @@ public class LaunchSupport {
* found, its path is printed to STDOUT and an exit code that indicates success is
* returned. Otherwise, nothing is printed to STDOUT and an error exit code is returned.
*
* @param javaConfig The Java configuration that defines what we support.
* @param appConfig The application configuration that defines what we support.
* @param javaFinder The Java finder.
* @param javaFilter A filter used to restrict what kind of Java installations we search for.
* @param save True if the determined Java home directory should get saved to a file.
@ -141,19 +141,19 @@ public class LaunchSupport {
* successfully determined.
* @throws IOException if there was a problem saving the java home to disk.
*/
private static int findJavaHome(JavaConfig javaConfig, JavaFinder javaFinder,
private static int findJavaHome(AppConfig appConfig, JavaFinder javaFinder,
JavaFilter javaFilter, boolean save) throws IOException {
File javaHomeDir;
LaunchProperties launchProperties = javaConfig.getLaunchProperties();
LaunchProperties launchProperties = appConfig.getLaunchProperties();
// PRIORITY 1: JAVA_HOME_OVERRIDE property
// If a valid java home override is specified in the launch properties, use that.
// Someone presumably wants to force that specific version.
javaHomeDir = launchProperties.getJavaHomeOverride();
if (javaConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) {
if (appConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) {
if (save) {
javaConfig.saveJavaHome(javaHomeDir);
appConfig.saveJavaHome(javaHomeDir);
}
System.out.println(javaHomeDir);
return EXIT_SUCCESS;
@ -162,10 +162,10 @@ public class LaunchSupport {
// PRIORITY 2: Java on PATH
// This program (LaunchSupport) was started with the Java on the PATH. Try to use this one
// next because it is most likely the one that is being upgraded on the user's system.
javaHomeDir = javaFinder.findSupportedJavaHomeFromCurrentJavaHome(javaConfig, javaFilter);
javaHomeDir = javaFinder.findSupportedJavaHomeFromCurrentJavaHome(appConfig, javaFilter);
if (javaHomeDir != null) {
if (save) {
javaConfig.saveJavaHome(javaHomeDir);
appConfig.saveJavaHome(javaHomeDir);
}
System.out.println(javaHomeDir);
return EXIT_SUCCESS;
@ -173,19 +173,19 @@ public class LaunchSupport {
// PRIORITY 3: Last used Java
// Check to see if a prior launch resulted in that Java being saved. If so, try to use that.
javaHomeDir = javaConfig.getSavedJavaHome();
if (javaConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) {
javaHomeDir = appConfig.getSavedJavaHome();
if (appConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) {
System.out.println(javaHomeDir);
return EXIT_SUCCESS;
}
// PRIORITY 4: Find all supported Java installations, and use the newest.
List<File> javaHomeDirs =
javaFinder.findSupportedJavaHomeFromInstallations(javaConfig, javaFilter);
javaFinder.findSupportedJavaHomeFromInstallations(appConfig, javaFilter);
if (!javaHomeDirs.isEmpty()) {
javaHomeDir = javaHomeDirs.iterator().next();
if (save) {
javaConfig.saveJavaHome(javaHomeDir);
appConfig.saveJavaHome(javaHomeDir);
}
System.out.println(javaHomeDir);
return EXIT_SUCCESS;
@ -199,7 +199,7 @@ public class LaunchSupport {
* If a valid Java home directory was successfully determined, it is saved to the user's
* Java home save file, and an exit code that indicates success is returned.
*
* @param javaConfig The Java configuration that defines what we support.
* @param appConfig The application configuration that defines what we support.
* @param javaFinder The Java finder.
* @param javaFilter A filter used to restrict what kind of Java installations we search for.
* * @return A suggested exit code based on whether or not a valid Java home directory was
@ -207,13 +207,13 @@ public class LaunchSupport {
* @throws IOException if there was a problem interacting with the user, or saving the java
* home location to disk.
*/
private static int askJavaHome(JavaConfig javaConfig, JavaFinder javaFinder,
private static int askJavaHome(AppConfig appConfig, JavaFinder javaFinder,
JavaFilter javaFilter) throws IOException {
String javaName = javaFilter.equals(JavaFilter.JDK_ONLY) ? "JDK" : "Java";
String javaRange;
int min = javaConfig.getMinSupportedJava();
int max = javaConfig.getMaxSupportedJava();
int min = appConfig.getMinSupportedJava();
int max = appConfig.getMaxSupportedJava();
if (min == max) {
javaRange = min + "";
}
@ -226,7 +226,7 @@ public class LaunchSupport {
System.out.println("******************************************************************");
System.out.println(
javaName + " " + javaRange + " (" + javaConfig.getSupportedArchitecture() +
javaName + " " + javaRange + " (" + appConfig.getSupportedArchitecture() +
"-bit) could not be found and must be manually chosen!");
System.out.println("******************************************************************");
@ -254,13 +254,13 @@ public class LaunchSupport {
continue;
}
try {
JavaVersion javaVersion = javaConfig.getJavaVersion(javaHomeDir, javaFilter);
if (javaConfig.isJavaVersionSupported(javaVersion)) {
JavaVersion javaVersion = appConfig.getJavaVersion(javaHomeDir, javaFilter);
if (appConfig.isJavaVersionSupported(javaVersion)) {
break;
}
System.out.println(
"Java version " + javaVersion + " is outside of supported range: [" +
javaRange + " " + javaConfig.getSupportedArchitecture() + "-bit]");
javaRange + " " + appConfig.getSupportedArchitecture() + "-bit]");
}
catch (FileNotFoundException e) {
System.out.println(
@ -271,7 +271,7 @@ public class LaunchSupport {
}
}
File javaHomeSaveFile = javaConfig.saveJavaHome(javaHomeDir);
File javaHomeSaveFile = appConfig.saveJavaHome(javaHomeDir);
System.out.println("Saved changes to " + javaHomeSaveFile);
return EXIT_SUCCESS;
}
@ -281,18 +281,18 @@ public class LaunchSupport {
* to STDOUT as a new-line delimited string that can be parsed and added to the command line,
* and an exit code that indicates success is returned.
* @param javaConfig The Java configuration that defines what we support.
* @param appConfig The appConfig configuration that defines what we support.
* @return A suggested exit code based on whether or not the VM arguments were successfully
* gotten.
*/
private static int handleVmArgs(JavaConfig javaConfig) {
if (javaConfig.getLaunchProperties() == null) {
private static int handleVmArgs(AppConfig appConfig) {
if (appConfig.getLaunchProperties() == null) {
System.out.println("Launch properties file was not specified!");
return EXIT_FAILURE;
}
// Force newline style to make cross-platform parsing consistent
javaConfig.getLaunchProperties().getVmArgList().forEach(e -> System.out.print(e + "\r\n"));
appConfig.getLaunchProperties().getVmArgList().forEach(e -> System.out.print(e + "\r\n"));
return EXIT_SUCCESS;
}
}

View file

@ -17,21 +17,23 @@ package ghidra.launch;
import java.io.*;
import java.text.ParseException;
import java.util.Properties;
import java.util.*;
import ghidra.launch.JavaFinder.JavaFilter;
/**
* Class to determine and represent a required Java configuration, including minimum and maximum
* supported versions, compiler compliance level, etc.
* Class to determine and represent a required application configuration, including minimum and
* maximum supported Java versions, compiler compliance level, etc.
*/
public class JavaConfig {
public class AppConfig {
private static final String LAUNCH_PROPERTIES_NAME = "launch.properties";
private static final String JAVA_HOME_SAVE_NAME = "java_home.save";
private static final String PYTHON_COMMAND_SAVE_NAME = "python_command.save";
private LaunchProperties launchProperties;
private File javaHomeSaveFile;
private File pythonCommandSaveFile;
private String applicationName; // example: Ghidra
private String applicationVersion; // example: 9.0.1
@ -42,43 +44,43 @@ public class JavaConfig {
private String compilerComplianceLevel;
/**
* Creates a new Java configuration for the given installation.
* Creates a new application configuration for the given installation.
*
* @param installDir The installation directory.
* @throws FileNotFoundException if a required file was not found.
* @throws IOException if there was a problem reading a required file.
* @throws ParseException if there was a problem parsing a required file.
*/
public JavaConfig(File installDir) throws FileNotFoundException, IOException, ParseException {
public AppConfig(File installDir) throws FileNotFoundException, IOException, ParseException {
initApplicationProperties(installDir);
initLaunchProperties(installDir);
initJavaHomeSaveFile(installDir);
javaHomeSaveFile = getSaveFile(installDir, JAVA_HOME_SAVE_NAME);
pythonCommandSaveFile = getSaveFile(installDir, PYTHON_COMMAND_SAVE_NAME);
}
/**
* Gets the launch properties associated with this Java configuration. Certain aspects of the
* Java configuration are stored in the launch properties.
* Gets the launch properties associated with this application configuration.
*
* @return The launch properties associated with this Java configuration. Could be null if
* this Java configuration does not use launch properties.
* @return The launch properties associated with this application configuration. Could be null
* if this application configuration does not use launch properties.
*/
public LaunchProperties getLaunchProperties() {
return launchProperties;
}
/**
* Gets the Java configuration's minimum supported major Java version.
* Gets the application configuration's minimum supported major Java version.
*
* @return The Java configuration's minimum supported major Java version.
* @return The application configuration's minimum supported major Java version.
*/
public int getMinSupportedJava() {
return minSupportedJava;
}
/**
* Gets the Java configuration's maximum supported major Java version.
* Gets the application configuration's maximum supported major Java version.
*
* @return The Java configuration's maximum supported major Java version. If there is no
* @return The application configuration's maximum supported major Java version. If there is no
* restriction, the value will be 0.
*/
public int getMaxSupportedJava() {
@ -86,20 +88,20 @@ public class JavaConfig {
}
/**
* Gets the Java configuration's supported Java architecture. All supported Java
* Gets the application configuration's supported Java architecture. All supported Java
* configurations must have an architecture of <code>64</code>.
*
* @return The Java configuration's supported Java architecture (64).
* @return The application configuration's supported Java architecture (64).
*/
public int getSupportedArchitecture() {
return 64;
}
/**
* Gets the Java configuration's compiler compliance level that was used to build the
* associated installation.
* Gets the application configuration's Java compiler compliance level that was used to build
* the associated installation.
*
* @return The Java configuration's compiler compliance level.
* @return The application configuration's compiler compliance level.
*/
public String getCompilerComplianceLevel() {
return compilerComplianceLevel;
@ -115,10 +117,13 @@ public class JavaConfig {
public File getSavedJavaHome() throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(javaHomeSaveFile))) {
String line = reader.readLine().trim();
if (line != null && !line.isEmpty()) {
if (line != null) {
line = line.trim();
if (!line.isEmpty()) {
return new File(line);
}
}
}
catch (FileNotFoundException e) {
// Fall through to return null
}
@ -148,12 +153,12 @@ public class JavaConfig {
}
/**
* Tests to see if the given directory is a supported Java home directory for this Java
* Tests to see if the given directory is a supported Java home directory for this application
* configuration.
*
* @param dir The directory to test.
* @param javaFilter A filter used to restrict what kind of Java installations we support.
* @return True if the given directory is a supported Java home directory for this Java
* @return True if the given directory is a supported Java home directory for this application
* configuration.
*/
public boolean isSupportedJavaHomeDir(File dir, JavaFilter javaFilter) {
@ -166,10 +171,10 @@ public class JavaConfig {
}
/**
* Tests to see if the given Java version is supported by this Java launch configuration.
* Tests to see if the given Java version is supported by this application configuration.
*
* @param javaVersion The java version to check.
* @return True if the given Java version is supported by this Java launch configuration.
* @return True if the given Java version is supported by this application configuration.
*/
public boolean isJavaVersionSupported(JavaVersion javaVersion) {
if (javaVersion.getArchitecture() != getSupportedArchitecture()) {
@ -233,6 +238,30 @@ public class JavaConfig {
return runAndGetJavaVersion(javaExecutable);
}
/**
* Gets the Python command from the user's Python command save file.
*
* @return The Python command from the user's Python command save file, or null if the file
* does not exist or is empty.
* @throws IOException if there was a problem reading the Python command save file.
*/
public List<String> getSavedPythonCommand() throws IOException {
List<String> command = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new FileReader(pythonCommandSaveFile))) {
String line = null;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (!line.isEmpty()) {
command.add(line);
}
}
return command;
}
catch (FileNotFoundException e) {
return null;
}
}
/**
* Gets the version of the given Java executable from the output of running "java -version".
*
@ -354,13 +383,13 @@ public class JavaConfig {
}
/**
* Initializes the Java home save file.
* Gets the given "save file".
*
* @param installDir The Ghidra installation directory. This is the directory that has the
* "Ghidra" subdirectory in it.
* @throws FileNotFoundException if the user's home directory was not found.
*/
private void initJavaHomeSaveFile(File installDir) throws FileNotFoundException {
private File getSaveFile(File installDir, String saveFileName) throws FileNotFoundException {
boolean isDev = new File(installDir, "build.gradle").isFile();
String appName = applicationName.replaceAll("\\s", "").toLowerCase();
@ -385,16 +414,14 @@ public class JavaConfig {
// Handle legacy application layout
if (applicationLayoutVersion.equals("1")) {
userSettingsDir = new File(userHomeDir, "." + appName + "/." + userSettingsDirName);
javaHomeSaveFile = new File(userSettingsDir, JAVA_HOME_SAVE_NAME);
return;
return new File(userSettingsDir, saveFileName);
}
// Look for XDG environment variable
String xdgConfigHomeDirStr = System.getenv("XDG_CONFIG_HOME");
if (xdgConfigHomeDirStr != null && !xdgConfigHomeDirStr.isEmpty()) {
userSettingsDir = new File(xdgConfigHomeDirStr, appName + "/" + userSettingsDirName);
javaHomeSaveFile = new File(userSettingsDir, JAVA_HOME_SAVE_NAME);
return;
return new File(userSettingsDir, saveFileName);
}
// Look in current user settings directory
@ -420,7 +447,7 @@ public class JavaConfig {
"Failed to find the user settings directory: Unsupported operating system.");
}
javaHomeSaveFile = new File(userSettingsDir, JAVA_HOME_SAVE_NAME);
return new File(userSettingsDir, saveFileName);
}
/**

View file

@ -79,11 +79,11 @@ public abstract class JavaFinder {
* Returns a list of supported Java home directories from discovered Java installations.
* The list is sorted from newest Java version to oldest.
*
* @param javaConfig The Java configuration that defines what we support.
* @param appConfig The appConfig configuration that defines what we support.
* @param javaFilter A filter used to restrict what kind of Java installations we search for.
* @return A sorted list of supported Java home directories from discovered Java installations.
*/
public List<File> findSupportedJavaHomeFromInstallations(JavaConfig javaConfig,
public List<File> findSupportedJavaHomeFromInstallations(AppConfig appConfig,
JavaFilter javaFilter) {
Set<File> potentialJavaHomeSet = new TreeSet<>();
for (File javaRootInstallDir : getJavaRootInstallDirs()) {
@ -107,8 +107,8 @@ public abstract class JavaFinder {
for (File potentialJavaHomeDir : potentialJavaHomeSet) {
try {
JavaVersion javaVersion =
javaConfig.getJavaVersion(potentialJavaHomeDir, javaFilter);
if (javaConfig.isJavaVersionSupported(javaVersion)) {
appConfig.getJavaVersion(potentialJavaHomeDir, javaFilter);
if (appConfig.isJavaVersionSupported(javaVersion)) {
javaHomeToVersionMap.put(potentialJavaHomeDir, javaVersion);
}
}
@ -130,12 +130,12 @@ public abstract class JavaFinder {
* Returns the Java home directory corresponding to the current "java.home" system
* property (if it supported).
*
* @param javaConfig The Java configuration that defines what we support.
* @param appConfig The appConfig configuration that defines what we support.
* @param javaFilter A filter used to restrict what kind of Java installations we search for.
* @return The Java home directory corresponding to the current "java.home" system property.
* Could be null if the current "java.home" is not supported.
*/
public File findSupportedJavaHomeFromCurrentJavaHome(JavaConfig javaConfig,
public File findSupportedJavaHomeFromCurrentJavaHome(AppConfig appConfig,
JavaFilter javaFilter) {
Set<File> potentialJavaHomeSet = new HashSet<>();
String javaHomeProperty = System.getProperty("java.home");
@ -149,8 +149,8 @@ public abstract class JavaFinder {
}
for (File potentialJavaHomeDir : potentialJavaHomeSet) {
try {
if (javaConfig.isJavaVersionSupported(
javaConfig.getJavaVersion(potentialJavaHomeDir, javaFilter))) {
if (appConfig.isJavaVersionSupported(
appConfig.getJavaVersion(potentialJavaHomeDir, javaFilter))) {
return potentialJavaHomeDir;
}
}

View file

@ -432,6 +432,9 @@ task assembleDistribution (type: Copy) {
from ("${ROOT_PROJECT_DIR}/build/typestubs/src") {
into 'docs/ghidra_stubs/typestubs'
}
from ("${ROOT_PROJECT_DIR}/build/typestubs/pypredef") {
into 'docs/ghidra_stubs/pypredef'
}
from (createGhidraStubsWheel) {
into 'docs/ghidra_stubs'
}

View file

@ -95,9 +95,9 @@ ext.deps = [
destination: file("${DEPS_DIR}/BSim")
],
[
name: "PyDev 6.3.1.zip",
url: "https://sourceforge.net/projects/pydev/files/pydev/PyDev%206.3.1/PyDev%206.3.1.zip",
sha256: "4d81fe9d8afe7665b8ea20844d3f5107f446742927c59973eade4f29809b0699",
name: "PyDev 9.3.0.zip",
url: "https://sourceforge.net/projects/pydev/files/pydev/PyDev%209.3.0/PyDev%209.3.0.zip",
sha256: "45398edf2adb56078a80bc88a919941578f0c0b363efbdd011bfd158a99b112e",
destination: file("${DEPS_DIR}/GhidraDev")
],
[