Candidate release of source code.

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

View file

@ -0,0 +1 @@
.project

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?pde version="3.8"?><target name="Running Platform" sequenceNumber="60">
<locations>
<location path="${eclipse_home}" type="Profile"/>
<location path="${workspace_loc:Eclipse GhidraDevPlugin}/build/data/buildDependencies/cdt" type="Directory"/>
<location path="${workspace_loc:Eclipse GhidraDevPlugin}/build/data/buildDependencies/pydev" type="Directory"/>
</locations>
<environment>
<os>macosx</os>
<ws>cocoa</ws>
<arch>x86_64</arch>
<nl>en_US</nl>
</environment>
</target>

View file

@ -0,0 +1,374 @@
<!DOCTYPE html>
<html>
<head>
<title>GhidraDev README</title>
<style name="text/css">
li { font-family:times new roman; font-size:14pt; font-family:times new roman; font-size:14pt; margin-bottom: 12px; }
h1 { color:#000080; font-family:times new roman; font-size:36pt; font-style:italic; font-weight:bold; text-align:center; }
h2 { padding-top:30px; color:#984c4c; font-family:times new roman; font-size:18pt; font-weight:bold; }
p { font-family:times new roman; font-size:14pt; }
td { font-family:times new roman; font-size:14pt; padding-left:10px; padding-right:10px; }
th { font-family:times new roman; font-size:14pt; font-weight:bold; padding-left:10px; padding-right:10px; }
code { color:black; font-family:courier new font-size: 14pt; }
span.code { font-family:courier new font-size: 14pt; color:#000000; }
</style>
</head>
<body>
<h1>GhidraDev README</h1>
<p>GhidraDev provides support for developing and debugging Ghidra scripts and modules in Eclipse.
</p>
<p>The information provided in this document is effective as of GhidraDev 2.0.0 and is subject to
change with future releases.</p>
<ul>
<li><a href="#ChangeHistory">Change History</a></li>
<li><a href="#MinimumRequirements">Minimum Requirements</a></li>
<li><a href="#OptionalRequirements">Optional Requirements</a></li>
<li><a href="#Install">Installing</a></li>
<ul>
<li><a href="#ManualInstall">Manual Installation in Eclipse</a></li>
<li><a href="#AutoInstall">Automatic Installation through Ghidra</a></li>
</ul>
<li><a href="#Uninstall">Uninstalling</a></li>
<li><a href="#Upgrade">Upgrading</a></li>
<li><a href="#Features">GhidraDev Features</a></li>
<ul>
<li><a href="#NewGhidraScript">New Ghidra Script</a></li>
<li><a href="#NewGhidraScriptProject">New Ghidra Script Project</a></li>
<li><a href="#NewGhidraModuleProject">New Ghidra Module Project</a></li>
<li><a href="#ExportGhidraModuleExtension">Export Ghidra Module Extension</a></li>
<li><a href="#Preferences">Preferences</a></li>
<li><a href="#LinkGhidra">Link Ghidra</a></li>
</ul>
<li><a href="#Launching">Launching and Debugging Ghidra</a></li>
<li><a href="#PyDevSupport">PyDev Support</a></li>
<ul>
<li><a href="#PyDevInstall">Installing PyDev</a></li>
<li><a href="#PyDevConfigure">Configuring PyDev</a></li>
</ul>
<li><a href="#FAQ">Frequently Asked Questions</a></li>
<li><a href="#AdditionalResources">Additional Resources</a></li>
</ul>
<h2><a name="ChangeHistory"></a>Change History</h2>
<p><u><b>2.0.0</b>:</u></p>
<ul>
<li>
Improved Ghidra module project starting templates for Analyzer and Plugin and added new
templates for Loader, Exporter, and FileSystem.
</li>
<li>
When creating a new Ghidra project, there is now an option to automatically create a Ghidra run
configuration for the project with a customizable amount of maximum Java heap space.
</li>
<li>
When creating a new Ghidra project, the project root directory now defaults to the workspace
directory if a project root directory has never been set.
</li>
<li>
When creating a new Ghidra project, the add button in the Python Support wizard page now
automatically adds the Jython interpreter found in the Ghidra installation directory to PyDev if
PyDev does have any Jython interpreters configured.
</li>
<li>
A Ghidra project's dependencies that are also projects are now passed along to a launched
Ghidra so Ghidra can discover those projects as potential modules.
</li>
<li>
The GhidraDev popup menu is now visible from within the Project Explorer (it was previously only
visible in the Package Explorer).
</li>
<li>
A new page has been added to the Export Ghidra Module Extension wizard that allows the user to
point to a specific Gradle installation.
</li>
</ul>
<p><u><b>1.0.2</b>:</u> Fixed exception that occurred when performing a "Link Ghidra" on projects
that specify other projects on their build paths.</p>
<p><u><b>1.0.1</b>:</u> Initial Release.</p>
<h2><a name="MinimumRequirements"></a>Minimum Requirements</h2>
<ul>
<li>Eclipse 2018-12 4.10 or later</li>
<li>Ghidra 9.0 or later</li>
</ul>
<p>(<a href="#top">Back to Top</a>)</p>
<h2><a name="OptionalRequirements"></a>Optional Requirements</h2>
<ul>
<li>PyDev 6.3.1 or later (<a href="#PyDevSupport">more info</a>)</li>
<li>CDT 8.6.0 or later</li>
</ul>
<p>(<a href="#top">Back to Top</a>)</p>
<h2><a name="Install"></a>Installing GhidraDev</h2>
<p>GhidraDev can be installed either manually into Eclipse or automatically by Ghidra, depending on
your uses cases. The following two sections outline both procedures.</p>
<h3><a name="ManualInstall"></a>Manual Installation in Eclipse</h3>
<p>GhidraDev can be installed into an existing installation of Eclipse the same way most Eclipse
plugins are installed. From Eclipse:</p>
<ol>
<li>Click <b>Help &#8594; Install New Software...</b></li>
<li>Click <b>Add...</b></li>
<li>Click <b>Archive...</b></li>
<li>
Select GhidraDev zip file from <i>&lt;GhidraInstallDir&gt;</i>/Extensions/Eclipse/GhidraDev/
</li>
<li>Click <b>OK</b> (name field can be blank)</li>
<li>Check <b>Ghidra</b> category (or <b>GhidraDev</b> entry)</li>
<li>Click <b>Next</b></li>
<li>Click <b>Next</b></li>
<li>Accept the terms of the license agreement</li>
<li>Click <b>Finish</b></li>
<li>Click <b>Install anyway</b></li>
<li>Click <b>Restart Now</b></li>
</ol>
<h3><a name="AutoInstall"></a>Automatic Installation through Ghidra</h3>
<p>Ghidra has the ability to launch an externally linked Eclipse when certain actions are performed,
such as choosing to edit a Ghidra script by clicking the Eclipse icon in the Ghidra Script Manager.
Ghidra requires knowledge of where Eclipse is installed before it can launch it, and will prompt the
user to enter this information if it has not been defined. Before Ghidra attempts to launch
Eclipse, it will attempt to install GhidraDev into Eclipse's <i>dropins</i> directory if GhidraDev
is not already installed.</p>
<p>(<a href="#top">Back to Top</a>)</p>
<h2><a name="Uninstall"></a>Uninstalling</h2>
<p>GhidraDev is uninstalled differently depending on how it was installed. If GhidraDev was
<a href="#ManualInstall">manually installed in Eclipse</a>, it can be uninstalled as follows from
Eclipse:</p>
<ol>
<li>Click <b>Help &#8594; About Eclipse</b></li>
<ul>
<li><i>For macOS:</i> <b>Eclipse &#8594; About Eclipse</b></li>
</ul>
<li>Click <b>Installation Details</b></li>
<li>Select GhidraDev</li>
<li>Click <b>Uninstall...</b></li>
<li>Select GhidraDev</li>
<li>Click <b>Finish</b></li>
<li>Click <b>Restart Now</b></li>
</ol>
<p>If GhidraDev was <a href="#AutoInstall">automatically installed through Ghidra</a>, it can be
uninstalled by simply removing the GhidraDev file from Eclipse's <i>dropins</i> directory and
restarting Eclipse. The <i>dropins</i> directory can be found at the top level of Eclipse's
installation directory.</p>
<p>(<a href="#top">Back to Top</a>)</p>
<h2><a name="Upgrade"></a>Upgrading</h2>
<p>GhidraDev is upgraded differently depending on how it was installed. If GhidraDev was
<a href="#ManualInstall">manually installed in Eclipse</a>, it can be upgraded the same was it was
<a href="#ManualInstall">installed</a>.</p>
<p>If GhidraDev was <a href="#AutoInstall">automatically installed through Ghidra</a>, it can be
upgraded by simply removing the GhidraDev file from Eclipse's <i>dropins</i> directory before
following one of the two techniques described in the <a href="#Install">Installing GhidraDev</a>
section.</p>
<p>(<a href="#top">Back to Top</a>)</p>
<h2><a name="Features"></a>GhidraDev Features</h2>
<p>GhidraDev provides a variety of features for creating and interacting with Ghidra-related
projects in Eclipse. GhidraDev supports creating both Ghidra script and Ghidra module projects.
Ghidra scripts are typically designed as a single Java source file that is compiled by Ghidra at
runtime and run through Ghidra's Script Manager or passed to the Headless Analyzer on the command
line for execution. Ghidra modules are intended to represent larger, more complex features such as
Analyzers or Plugins. When Ghidra modules are ready for production, they can be exported and
installed into Ghidra as an "extension".</p>
<p>When GhidraDev is installed, a <i>GhidraDev</i> menu appears in Eclipse's
title bar that provides direct access to these features:</p>
<ul>
<li>New</li>
<ul>
<li>
<a name="NewGhidraScript"></a><b>Ghidra Script:</b> Opens a wizard that creates a new Ghidra
script with the provided metadata in the specified location. Ghidra scripts can be created
in both Ghidra script and Ghidra module projects.
</li>
<li>
<a name="NewGhidraScriptProject"></a><b>Ghidra Script Project:</b> Opens a wizard that creates
a new Ghidra scripting project that is linked against a specified Ghidra installation. The
project can be set up to develop scripts in both the user's home <i>ghidra_scripts</i>
directory, as well as any scripts found in the Ghidra installation.
</li>
<li>
<a name="NewGhidraModuleProject"></a><b>Ghidra Module Project:</b> Opens a wizard that creates
a new Ghidra module project that is linked against a specified Ghidra installation. The
project can be initialized with optional template source files that provide a good starting
point for implementing advanced Ghidra features such as Analyzers, Plugins, Loaders, etc.
</li>
</ul>
<li>Export</li>
<ul>
<li>
<a name="ExportGhidraModuleExtension"></a><b>Ghidra Module Extension:</b> Opens a wizard that
exports a Ghidra module project as a Ghidra extension to the project's <i>dist</i> folder.
The exported extension archive file can be distributed to other users and imported via
Ghidra's front-end GUI. The export process requires Gradle, which is configured in the
wizard.
</li>
</ul>
<li><a name="Preferences"></a>Preferences</li>
<ul>
<li>
<b>Ghidra Installations:</b> Add or remove Ghidra installations. Certain features such as
creating Ghidra script/module projects require linking against a valid installation of Ghidra.
</li>
<li>
<b>Script Editor:</b> The port used by Ghidra to open a script in Eclipse. Must match the
corresponding port in Ghidra's <i>Eclipse Integration</i> tool options. Disable this
preference to prevent GhidraDev from listening on a port for this feature.
</li>
<li>
<b>Symbol Lookup:</b> The project name and port used by Ghidra to perform symbol lookup in
Eclipse. Must match the corresponding port in Ghidra's <i>Eclipse Integration</i> tool
options. Disable this preference to prevent GhidraDev from listening on a port for this
feature. Symbol lookup requires the Eclipse CDT plugin to be installed
(see <a href="#OptionalRequirements">optional requirements</a> for supported versions).
</li>
</ul>
<li>
<a name="LinkGhidra"></a><b>Link Ghidra:</b> Links a Ghidra installation to an existing Java
project, which enables Ghidra script/module development for the project. If a Ghidra
installation is already linked to the project when this operation is performed, the project will
be relinked to the specified Ghidra installation, which can be used to build the project for
a different version of Ghidra, discover new Ghidra extensions that were later added to a Ghidra
installation, or repair a corrupted project.
</li>
</ul>
<p>Most GhidraDev features can also be accessed by right-clicking on appropriate project elements in
Eclipse's Project/Package Explorer. For example, the <a href="#LinkGhidra">Link Ghidra</a> feature
can be accessed by right-clicking on an existing Java project, and then clicking <b>Ghidra &#8594;
Link Ghidra...</b>
</p>
<p>(<a href="#top">Back to Top</a>)</p>
<h2><a name="Launching"></a>Launching and Debugging Ghidra</h2>
<p>GhidraDev introduces two new run configurations to Eclipse which are capable of launching the
installation of Ghidra that an Eclipse Ghidra project is linked to:</p>
<ul>
<li>
<b>Ghidra:</b> Launches the Ghidra GUI.
</li>
<li>
<b>Ghidra Headless:</b> Launches Ghidra in headless mode. By default, this run configuration
will not have any program arguments associated with it, which are required to tell headless
Ghidra what project to open, what scripts to run, etc. Newly created <i>Ghidra Headless</i>
run configurations will have to be modified with the desired headless program arguments. For
more information on headless command line arguments, see
<i>&lt;GhidraInstallDir&gt;</i>/support/analyzeHeadlessREADME.html.
</li>
</ul>
<p>There are two ways to create Ghidra run configurations:</p>
<ol>
<li>Click <b>Run &#8594; Run Configurations...</b></li>
<li>Right-click on <i>Ghidra</i> (or <i>Ghidra Headless</i>), and click <b>New</b></li>
<li>In the <i>Main</i> tab, click <b>Browse...</b> and select the Ghidra project to launch</li>
<li>Optionally rename the new run configuration by editing the <i>Name</i> field at the top
</ol>
<p>Alternatively, you can right-click on any Ghidra project in the Eclipse package explorer, and
then click <b>Run As &#8594; Ghidra</b>.</p>
<p>To debug Ghidra, click <b>Debug As &#8594; Ghidra</b>. GhidraDev will automatically switch
Eclipse to the debug perspective.</p>
<p><b>NOTE:</b> Ghidra can only be launched/debugged from an existing Eclipse Ghidra project.
Launching Ghidra from Eclipse independent of a project is not supported.</p>
<p>(<a href="#top">Back to Top</a>)</p>
<h2><a name="PyDevSupport"></a>PyDev Support</h2>
<p>GhidraDev is able to integrate with PyDev to conveniently configure Python support into Ghidra
script and module projects.</p>
<h3><a name="PyDevInstall"></a>Installing PyDev</h3>
<p>From Eclipse:</p>
<ol>
<li>
Download PyDev (see <a href="#OptionalRequirements">optional requirements</a> for supported
versions)
</li>
<li>Unzip PyDev</li>
<li>Click <b>Help &#8594; Install New Software...</b></li>
<li>Click <b>Add...</b></li>
<li>Click <b>Local...</b></li>
<li>Select unzipped PyDev directory</li>
<li>Click <b>OK</b> (name field can be blank)</li>
<li>Uncheck <b>Group items by category</b> (if applicable)</li>
<li>Check <b>PyDev for Eclipse</b></li>
<li>Click <b>Next</b></li>
<li>Click <b>Next</b></li>
<li>Accept the terms of the license agreement</li>
<li>Click <b>Finish</b></li>
<li>Click <b>Restart Now</b></li>
</ol>
<h3><a name="PyDevConfigure"></a>Configuring PyDev</h3>
<p>GhidraDev can add Python support to a Ghidra project when:
<ul>
<li>Creating a new Ghidra module project</li>
<li>Creating a new Ghidra script project</li>
<li>Linking a Ghidra installation to an existing Java project</li>
</ul>
<p>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 <b>+</b> icon.
When the <b>+</b> 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.</p>
<p>(<a href="#top">Back to Top</a>)</p>
<h2><a name="FAQ"></a>Frequently Asked Questions</h2>
<ul>
<li>
<b><i>I've created a Ghidra script project. Where should I create my new scripts?</i></b>
<ul>
<li>
<p>The best place to create your scripts in is your home <i>~/ghidra_scripts</i> directory
because Ghidra will automatically find them there without any additional configuration. By
default, your Ghidra script project will have a folder named <b>Home scripts</b> which is
linked to your home <i>~/ghidra_scripts</i> directory. Either right-click on this folder in
Eclipse and do <b>GhidraDev &#8594; New &#8594; GhidraScript...</b> or from the menu bar do
<b>GhidraDev &#8594; New &#8594; GhidraScript...</b> and populate the <i>Script folder</i>
box with your project's <b>Home scripts</b> folder.</p>
</li>
</ul>
</li>
<li>
<b><i>How do I launch Ghidra in headless mode from Eclipse?</i></b>
<ul>
<li>
<p>GhidraDev provides custom run configurations to launch Ghidra installations both in GUI
mode and headlessly. See the <a href="#Launching"> Launching</a> section for information on
how to launch Ghidra from Eclipse.</p>
</li>
</ul>
</li>
<li>
<b><i>Why doesn't my Ghidra module project know about the Ghidra extension I installed into my
Ghidra installation?</i></b>
<ul>
<li>
<p>You most likely installed the Ghidra extension after the Ghidra installation was linked
to your Ghida module project, which automatically happens when the project is created.
Simply <a href="#LinkGhidra">relink</a> your Ghidra installation to the project, and your
project will pick up any newly discovered Ghidra extensions.</p>
</li>
</ul>
</li>
</ul>
<p>(<a href="#top">Back to Top</a>)</p>
<h2><a name="AdditionalResources"></a>Additional Resources</h2>
<p>For more information on the GhidraDev plugin and developing for Ghidra in an Eclipse environment,
please see:
<ul>
<li>
<b>Ghidra Scripting slide deck:</b>
<i>&lt;GhidraInstallDir&gt;</i>/docs/GhidraClass/Intermediate/Scripting.html</li>
</li>
</ul>
<p>(<a href="#top">Back to Top</a>)</p>
<!-- Some padding -->
<br>
<br>
<br>
</body>
</html>

View file

@ -0,0 +1,33 @@
Automatic-Module-Name: ghidra.ghidradev
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: GhidraDev
Bundle-SymbolicName: ghidra.ghidradev;singleton:=true
Bundle-Version: 2.0.0.qualifier
Bundle-Activator: ghidradev.Activator
Require-Bundle: org.eclipse.ant.core;bundle-version="3.5.200",
org.eclipse.buildship.core;bundle-version="3.0.0",
org.eclipse.core.expressions;bundle-version="3.6.200",
org.eclipse.core.externaltools;bundle-version="1.1.200",
org.eclipse.core.runtime;bundle-version="3.15.100",
org.eclipse.debug.ui;bundle-version="3.13.200",
org.eclipse.jdt.core;bundle-version="3.16.0",
org.eclipse.jdt.core.manipulation;bundle-version="1.11.0",
org.eclipse.jdt.debug.ui;bundle-version="3.10.100",
org.eclipse.jdt.launching;bundle-version="3.12.0",
org.eclipse.jdt.ui;bundle-version="3.16.0",
org.eclipse.ltk.core.refactoring;bundle-version="3.9.200",
org.eclipse.ui;bundle-version="3.111.0",
org.eclipse.ui.ide;bundle-version="3.14.200",
com.python.pydev.debug;bundle-version="6.3.1";resolution:=optional,
org.python.pydev;bundle-version="6.3.1";resolution:=optional,
org.python.pydev.core;bundle-version="6.3.1";resolution:=optional,
org.python.pydev.ast;bundle-version="6.3.1";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-1.8
Bundle-Vendor: Ghidra
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .,
build/data/Utility.jar,
build/data/LaunchSupport.jar

View file

@ -0,0 +1,92 @@
apply plugin: 'eclipse'
eclipse {
project {
name = 'Eclipse GhidraDevPlugin'
buildCommand 'org.eclipse.pde.ManifestBuilder'
buildCommand 'org.eclipse.pde.SchemaBuilder'
natures 'org.eclipse.pde.PluginNature'
classpath.file {
def requiredPlugins = 'org.eclipse.pde.core.requiredPlugins'
beforeMerged { classpath ->
classpath.entries.removeAll { entry ->
entry.path == requiredPlugins
}
}
whenMerged { classpath ->
withXml {
def node = it.asNode()
node.appendNode('classpathentry', [kind: 'con', path: requiredPlugins])
}
}
}
}
}
// We want GhidraDev to run with Eclipses launched with Java 8
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile project(':Utility')
compile project(':LaunchSupport')
}
// We are currently building this with Eclipse, so prevent gradle from trying.
// See build_README.txt for instructions on how to build from Eclipse.
compileJava.enabled = false
jar.enabled = false
File libraryJarDestDir = file("build/data")
File pyDevSourceZipFile = file("${BIN_REPO}/GhidraBuild/EclipsePlugins/GhidraDev/buildDependencies/PyDev 6.3.1.zip")
File cdtSourceZipFile = file("${BIN_REPO}/GhidraBuild/EclipsePlugins/GhidraDev/buildDependencies/cdt-8.6.0.zip")
File pyDevDestDir = file("build/data/buildDependencies/pydev")
File cdtDestDir = file("build/data/buildDependencies/cdt")
task utilityJar(type:Copy) {
from (project(':Utility').jar)
destinationDir libraryJarDestDir
}
task launchSupportJar(type:Copy) {
from (project(':LaunchSupport').jar)
destinationDir libraryJarDestDir
}
task pyDevUnpack(type:Copy) {
description "Unpack PyDev plugin archive for development use"
group "Development Preparation"
// Without this, the copyTask will unzip the file to check for "up to date"
onlyIf {
!pyDevDestDir.exists()
}
from zipTree(pyDevSourceZipFile)
exclude "**/.project", "**/.pydevproject"
destinationDir pyDevDestDir
}
task cdtUnpack(type:Copy) {
description "Unpack CDT plugin archive for development use"
group "Development Preparation"
// Without this, the copyTask will unzip the file to check for "up to date"
onlyIf {
!cdtDestDir.exists()
}
from zipTree(cdtSourceZipFile)
destinationDir cdtDestDir
}
// PrepDev dependencies
prepDev.dependsOn utilityJar
prepDev.dependsOn launchSupportJar
prepDev.dependsOn pyDevUnpack
prepDev.dependsOn cdtUnpack

View file

@ -0,0 +1,8 @@
source.. = src/main/java/
output.. = bin
bin.includes = META-INF/,\
plugin.xml,\
icons/,\
.,\
build/data/Utility.jar,\
build/data/LaunchSupport.jar

View file

@ -0,0 +1,40 @@
GhidraDev is currently built from Eclipse and checked into the bin repo. 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, hence the build.gradle file.
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) Uncomment the line in settings.gradle that includes the GhidraDev project.
2) Run "gradle eclipse" to generate the GhidraDev Eclipse projects.
3) From Eclipse, File --> Import --> General --> Existing Projects into Workspace
4) From the ghidra repo, import "Eclipse GhidraDevFeature" and "Eclipse GhidraDevPlugin".
Changing version number (GhidraDev is versioned independently of Ghidra):
1) Open plugin.xml in the GhidraDevPlugin project.
2) In the "Overview" tab, update the "Version" field to x.y.z.qualifier and save.
3) Open feature.xml in the GhidraDevFeature project.
4) In the "Overview" tab, update the "Version" field to x.y.z.qualifier and save.
5) Open category.xml in the GhidraDevFeature project.
6) Highlight ghidra.ghidradev (x.y.z.qualifier), and click "Remove".
7) Highlight ghidra.ghidradev, and click "Add Feature".
8) Select ghidra.ghidradev (x.y.z.qualifer), click "OK", and save.
9) Update GhidraDev_README.html "Change History" section if necessary.
Building from Eclipse:
1) Do a Gradle prepDev to ensure GhidraDev's dependencies are up-to-date.
2) File --> Export --> Plug-in Development --> Deployable features
3) Check ghidra.ghidradev (x.y.z.qualifier)
4) Select "Archive file" and choose a directory to save it to. It must end up in
ghidra.bin/GhidraBuild/Eclipse/GhidraDev/. Name it GhidraDev-x.y.z.zip.
5) In the "Options" tab make sure things look like this:
- Export source: UNCHECKED
- Package as individual JAR archives: CHECKED
- Generate p2 repository: CHECKED
- Categorize repository: CHECKED + Browse to category.xml file in the GhidraDevFeature project.
- Qualifier replacement: CHECKED + clear field so default is used
- Save as Ant script: UNCHECKED
- Allow for binary cycles in target platform: CHECKED
- Use class files compiled in the workspace: UNCHECKED
6) Finish

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

View file

@ -0,0 +1,473 @@
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension
point="org.eclipse.ui.startup">
<startup
class="ghidradev.GhidraDevStartup">
</startup>
</extension>
<extension
point="org.eclipse.ui.newWizards">
<category
id="GhidraCategory"
name="Ghidra">
</category>
<wizard
category="GhidraCategory"
class="ghidradev.ghidraprojectcreator.wizards.CreateGhidraScriptWizard"
icon="icons/script_code_red.png"
id="ghidradev.ghidraprojectcreator.wizards.CreateGhidraScriptWizard"
name="Ghidra Script"
project="false">
</wizard>
<wizard
category="GhidraCategory"
class="ghidradev.ghidraprojectcreator.wizards.CreateGhidraScriptProjectWizard"
icon="icons/script_add.png"
id="ghidradev.ghidraprojectcreator.wizards.CreateGhidraScriptProjectWizard"
name="Ghidra Script Project"
project="true">
</wizard>
<wizard
category="GhidraCategory"
class="ghidradev.ghidraprojectcreator.wizards.CreateGhidraModuleProjectWizard"
icon="icons/brick_add.png"
id="ghidradev.ghidraprojectcreator.wizards.CreateGhidraModuleProjectWizard"
name="Ghidra Module Project"
project="true">
</wizard>
</extension>
<extension
point="org.eclipse.ui.exportWizards">
<category
id="GhidraCategory"
name="Ghidra">
</category>
<wizard
category="GhidraCategory"
class="ghidradev.ghidraprojectcreator.wizards.ExportGhidraModuleWizard"
icon="icons/brick_go.png"
id="ghidradev.ghidraprojectcreator.wizards.ExportGhidraModuleWizard"
name="Ghidra Module Extension">
</wizard>
</extension>
<extension
point="org.eclipse.ui.commands">
<command
defaultHandler="ghidradev.ghidraprojectcreator.commands.LinkGhidraCommand"
description="Link Ghidra"
id="ghidradev.ghidraprojectcreator.commands.LinkGhidraCommand"
name="Link Ghidra">
</command>
<command
defaultHandler="ghidradev.ghidraprojectcreator.commands.AboutCommand"
description="About GhidraDev"
id="ghidradev.ghidraprojectcreator.commands.AboutCommand"
name="About GhidraDev">
</command>
</extension>
<extension
point="org.eclipse.ui.preferencePages">
<page
class="ghidradev.GhidraRootPreferencePage"
id="ghidradev.GhidraRootPreferencePage"
name="GhidraDev">
</page>
<page
category="ghidradev.GhidraRootPreferencePage"
class="ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferencePage"
id="ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferencePage"
name="Ghidra Installations">
</page>
<page
category="ghidradev.GhidraRootPreferencePage"
class="ghidradev.ghidrascripteditor.preferences.GhidraScriptEditorPreferencePage"
id="ghidradev.ghidrascripteditor.preferences.GhidraScriptEditorPreferencePage"
name="Script Editor">
</page>
<page
category="ghidradev.GhidraRootPreferencePage"
class="ghidradev.ghidrasymbollookup.preferences.GhidraSymbolLookupPreferencePage"
id="ghidradev.ghidrasymbollookup.preferences.GhidraSymbolLookupPreferencePage"
name="Symbol Lookup">
</page>
</extension>
<extension
point="org.eclipse.core.runtime.preferences">
<initializer
class="ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferenceInitializer">
</initializer>
<initializer
class="ghidradev.ghidrascripteditor.preferences.GhidraScriptEditorPreferenceInitializer">
</initializer>
<initializer
class="ghidradev.ghidrasymbollookup.preferences.GhidraSymbolLookupPreferenceInitializer">
</initializer>
</extension>
<extension
point="org.eclipse.ui.menus">
<menuContribution
allPopups="false"
locationURI="menu:org.eclipse.ui.main.menu">
<menu
label="GhidraDev">
<menu
label="New">
<command
commandId="org.eclipse.ui.newWizard"
label="Ghidra Script"
style="push">
<parameter
name="newWizardId"
value="ghidradev.ghidraprojectcreator.wizards.CreateGhidraScriptWizard">
</parameter>
</command>
<separator
name="GhidraNewMenuSeparator1"
visible="true">
</separator>
<command
commandId="org.eclipse.ui.newWizard"
label="Ghidra Script Project"
style="push">
<parameter
name="newWizardId"
value="ghidradev.ghidraprojectcreator.wizards.CreateGhidraScriptProjectWizard">
</parameter>
</command>
<command
commandId="org.eclipse.ui.newWizard"
label="Ghidra Module Project"
style="push">
<parameter
name="newWizardId"
value="ghidradev.ghidraprojectcreator.wizards.CreateGhidraModuleProjectWizard">
</parameter>
</command>
</menu>
<menu
label="Export">
<command
commandId="org.eclipse.ui.file.export"
label="Ghidra Module Extension"
style="push">
<parameter
name="exportWizardId"
value="ghidradev.ghidraprojectcreator.wizards.ExportGhidraModuleWizard">
</parameter>
</command>
</menu>
<menu
label="Preferences">
<command
commandId="org.eclipse.ui.window.preferences"
label="Ghidra Installations..."
style="push">
<parameter
name="preferencePageId"
value="ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferencePage">
</parameter>
</command>
<command
commandId="org.eclipse.ui.window.preferences"
label="Script Editor..."
style="push">
<parameter
name="preferencePageId"
value="ghidradev.ghidrascripteditor.preferences.GhidraScriptEditorPreferencePage">
</parameter>
</command>
<command
commandId="org.eclipse.ui.window.preferences"
label="Symbol Lookup..."
style="push">
<parameter
name="preferencePageId"
value="ghidradev.ghidrasymbollookup.preferences.GhidraSymbolLookupPreferencePage">
</parameter>
</command>
</menu>
<separator
name="GhidraMenuSeparator1"
visible="true">
</separator>
<command
commandId="ghidradev.ghidraprojectcreator.commands.LinkGhidraCommand"
icon="icons/folder_link.png"
label="Link Ghidra..."
style="push">
</command>
<separator
name="GhidraMenuSeparator2"
visible="true">
</separator>
<command
commandId="ghidradev.ghidraprojectcreator.commands.AboutCommand"
icon="icons/GhidraIcon16.png"
label="About GhidraDev..."
style="push">
</command>
</menu>
</menuContribution>
<menuContribution
allPopups="false"
locationURI="popup:org.eclipse.jdt.ui.PackageExplorer?after=additions">
<menu
icon="icons/GhidraIcon16.png"
label="GhidraDev">
<visibleWhen
checkEnabled="false">
<test
property="ghidradev.ghidraprojectcreator.testers.isJavaProject"
value="true">
</test>
</visibleWhen>
<menu
label="New">
<command
commandId="org.eclipse.ui.newWizard"
label="New Ghidra Script..."
style="push">
<parameter
name="newWizardId"
value="ghidradev.ghidraprojectcreator.wizards.CreateGhidraScriptWizard">
</parameter>
<visibleWhen
checkEnabled="false">
<test
property="ghidradev.ghidraprojectcreator.testers.isPackageFragmentRoot"
value="true">
</test>
</visibleWhen>
</command>
</menu>
<menu
label="Export">
<command
commandId="org.eclipse.ui.file.export"
label="Ghidra Module Extension"
style="push">
<parameter
name="exportWizardId"
value="ghidradev.ghidraprojectcreator.wizards.ExportGhidraModuleWizard">
</parameter>
<visibleWhen
checkEnabled="false">
<test
property="ghidradev.ghidraprojectcreator.testers.isGhidraModuleProject"
value="true">
</test>
</visibleWhen>
</command>
</menu>
<command
commandId="ghidradev.ghidraprojectcreator.commands.LinkGhidraCommand"
icon="icons/folder_link.png"
label="Link Ghidra..."
style="push">
</command>
</menu>
</menuContribution>
<menuContribution
allPopups="false"
locationURI="popup:org.eclipse.ui.navigator.ProjectExplorer#PopupMenu?after=additions">
<menu
icon="icons/GhidraIcon16.png"
label="GhidraDev">
<visibleWhen
checkEnabled="false">
<test
property="ghidradev.ghidraprojectcreator.testers.isJavaProject"
value="true">
</test>
</visibleWhen>
<menu
label="New">
<command
commandId="org.eclipse.ui.newWizard"
label="New Ghidra Script..."
style="push">
<parameter
name="newWizardId"
value="ghidradev.ghidraprojectcreator.wizards.CreateGhidraScriptWizard">
</parameter>
<visibleWhen
checkEnabled="false">
<test
property="ghidradev.ghidraprojectcreator.testers.isPackageFragmentRoot"
value="true">
</test>
</visibleWhen>
</command>
</menu>
<menu
label="Export">
<command
commandId="org.eclipse.ui.file.export"
label="Ghidra Module Extension"
style="push">
<parameter
name="exportWizardId"
value="ghidradev.ghidraprojectcreator.wizards.ExportGhidraModuleWizard">
</parameter>
<visibleWhen
checkEnabled="false">
<test
property="ghidradev.ghidraprojectcreator.testers.isGhidraModuleProject"
value="true">
</test>
</visibleWhen>
</command>
</menu>
<command
commandId="ghidradev.ghidraprojectcreator.commands.LinkGhidraCommand"
icon="icons/folder_link.png"
label="Link Ghidra..."
style="push">
</command>
</menu>
</menuContribution>
</extension>
<extension
point="org.eclipse.debug.core.launchConfigurationTypes">
<launchConfigurationType
delegate="ghidradev.ghidraprojectcreator.launchers.GhidraLaunchDelegate"
id="GhidraGuiLaunchConfigurationType"
name="Ghidra">
</launchConfigurationType>
<launchConfigurationType
delegate="ghidradev.ghidraprojectcreator.launchers.GhidraLaunchDelegate"
id="GhidraHeadlessLaunchConfigurationType"
name="Ghidra Headless">
</launchConfigurationType>
</extension>
<extension
point="org.eclipse.debug.ui.launchConfigurationTypeImages">
<launchConfigurationTypeImage
configTypeID="GhidraGuiLaunchConfigurationType"
icon="icons/GhidraIcon16.png"
id="GhidraGuiLaunchConfigurationTypeImage">
</launchConfigurationTypeImage>
<launchConfigurationTypeImage
configTypeID="GhidraHeadlessLaunchConfigurationType"
icon="icons/GhidraIcon16.png"
id="GhidraHeadlessLaunchConfigurationTypeImage">
</launchConfigurationTypeImage>
</extension>
<extension
point="org.eclipse.debug.core.launchDelegates">
<launchDelegate
delegate="ghidradev.ghidraprojectcreator.launchers.GhidraLaunchDelegate"
id="GhidraGuiLaunchDelegate"
modes="run, debug"
name="Ghidra"
type="GhidraGuiLaunchConfigurationType">
</launchDelegate>
<launchDelegate
delegate="ghidradev.ghidraprojectcreator.launchers.GhidraLaunchDelegate"
id="GhidraHeadlessLaunchDelegate"
modes="run, debug"
name="Ghidra Headless"
type="GhidraHeadlessLaunchConfigurationType">
</launchDelegate>
</extension>
<extension
point="org.eclipse.debug.ui.launchConfigurationTabGroups">
<launchConfigurationTabGroup
class="ghidradev.ghidraprojectcreator.launchers.GhidraLaunchTabGroup"
description="Run and debug Ghidra modules and scripts"
id="GhidraGuiLaunchConfigurationTabGroup"
type="GhidraGuiLaunchConfigurationType">
</launchConfigurationTabGroup>
<launchConfigurationTabGroup
class="ghidradev.ghidraprojectcreator.launchers.GhidraLaunchTabGroup"
description="Run and debug Ghidra modules and scripts in headless mode"
id="GhidraHeadlessLaunchConfigurationTabGroup"
type="GhidraHeadlessLaunchConfigurationType">
</launchConfigurationTabGroup>
</extension>
<extension
point="org.eclipse.debug.ui.launchShortcuts">
<shortcut
class="ghidradev.ghidraprojectcreator.launchers.GhidraGuiLaunchShortcut"
icon="icons/GhidraIcon16.png"
id="GhidraGuiLaunchShortcut"
label="Ghidra"
modes="run, debug">
<contextualLaunch>
<enablement>
<with
variable="selection">
<count
value="1">
</count>
<iterate
ifEmpty="false">
<test
property="ghidradev.ghidraprojectcreator.testers.isGhidraProject"
value="true">
</test>
</iterate>
</with>
</enablement>
</contextualLaunch>
</shortcut>
<shortcut
class="ghidradev.ghidraprojectcreator.launchers.GhidraHeadlessLaunchShortcut"
icon="icons/GhidraIcon16.png"
id="GhidraHeadlessLaunchShortcut"
label="Ghidra Headless"
modes="run, debug">
<contextualLaunch>
<enablement>
<with
variable="selection">
<count
value="1">
</count>
<iterate
ifEmpty="false">
<test
property="ghidradev.ghidraprojectcreator.testers.isGhidraProject"
value="true">
</test>
</iterate>
</with>
</enablement>
</contextualLaunch>
</shortcut>
</extension>
<extension
point="org.eclipse.core.expressions.propertyTesters">
<propertyTester
class="ghidradev.ghidraprojectcreator.testers.GhidraProjectPropertyTester"
id="GhidraProjectPropertyTester"
namespace="ghidradev.ghidraprojectcreator.testers"
properties="isGhidraProject"
type="java.lang.Object">
</propertyTester>
<propertyTester
class="ghidradev.ghidraprojectcreator.testers.JavaProjectPropertyTester"
id="JavaProjectPropertyTester"
namespace="ghidradev.ghidraprojectcreator.testers"
properties="isJavaProject"
type="java.lang.Object">
</propertyTester>
<propertyTester
class="ghidradev.ghidraprojectcreator.testers.PackageFragmentRootPropertyTester"
id="PackageFragmentRootPropertyTester"
namespace="ghidradev.ghidraprojectcreator.testers"
properties="isPackageFragmentRoot"
type="java.lang.Object">
</propertyTester>
<propertyTester
class="ghidradev.ghidraprojectcreator.testers.GhidraModuleProjectPropertyTester"
id="GhidraModuleProjectPropertyTester"
namespace="ghidradev.ghidraprojectcreator.testers"
properties="isGhidraModuleProject"
type="java.lang.Object">
</propertyTester>
</extension>
</plugin>

View file

@ -0,0 +1,130 @@
/* ###
* IP: GHIDRA
* NOTE: eclipse plugin code
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev;
import java.io.*;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
/**
* The activator class controls the plug-in life cycle.
*
* NOTE: Plugins declaring extensions or extension points must be implemented as a singleton.
*/
public class Activator extends AbstractUIPlugin {
private Set<Closeable> closeSet = new HashSet<>();
/**
* The plug-in ID.
*/
public static final String PLUGIN_ID = "GhidraDev";
/**
* A system property that Ghidra sets when launching Eclipse.
*/
private static final String GHIDRA_INSTALL_DIR_PROPERTY = "ghidra.install.dir";
/**
* The shared instance.
*/
private static Activator plugin;
/**
* Returns the shared instance.
*
* @return the shared instance. Could be null if the plugin is not started.
*/
public static Activator getDefault() {
return plugin;
}
/**
* Registers an object to be closed when the plugin shuts down.
*
* @param c The object to register for close on shutdown.
*/
public synchronized void registerCloseable(Closeable c) {
closeSet.add(c);
}
/**
* Unregisters an object to be closed when the plugin shuts down.
*
* @param c The object to unregister for close on shutdown.
*/
public synchronized void unregisterCloseable(Closeable c) {
closeSet.remove(c);
}
/**
* Gets the installation directory of the Ghidra that launched Eclipse.
*
* @return The installation directory of the Ghidra that launched Eclipse. Could be null if
* Ghidra did not launch Eclipse, or Ghidra did not pass along it's installation directory to
* Eclipse properly.
*/
public File getGhidraInstallDir() {
String ghidraInstallDirProperty = System.getProperty(GHIDRA_INSTALL_DIR_PROPERTY);
if (ghidraInstallDirProperty != null && !ghidraInstallDirProperty.isEmpty()) {
return new File(ghidraInstallDirProperty);
}
return null;
}
/**
* Checks to see if Eclipse was launched by Ghidra.
*
* @return True if Eclipse was launched by Ghidra; otherwise, false.
*/
public boolean isLaunchedByGhidra() {
return System.getProperty(GHIDRA_INSTALL_DIR_PROPERTY) != null;
}
@Override
public void start(BundleContext context) throws Exception {
super.start(context);
plugin = this;
EclipseMessageUtils.info("Starting " + PLUGIN_ID + " plugin");
}
@Override
public void stop(BundleContext context) throws Exception {
plugin = null;
super.stop(context);
EclipseMessageUtils.info("Stopping " + PLUGIN_ID + " plugin");
// Close registered items
synchronized (this) {
for (Closeable c : closeSet) {
if (c != null) {
EclipseMessageUtils.info("Closing " + c);
try {
c.close();
}
catch (IOException e) {
EclipseMessageUtils.info("Failed to close " + c);
}
}
}
closeSet.clear();
}
}
}

View file

@ -0,0 +1,242 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.*;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.statushandlers.StatusManager;
public class EclipseMessageUtils {
private static IWorkbenchPage workbenchPage = getWorkbenchPage();
private static Shell shell = workbenchPage.getWorkbenchWindow().getShell();
/**
* Prevent instantiation.
*/
private EclipseMessageUtils() {
// Nothing to do
}
/**
* Shows an information dialog with a custom title and message.
*
* @param title The title of the information dialog.
* @param message The message of the information dialog.
*/
public static void showInfoDialog(final String title, final String message) {
Display.getDefault().syncExec(() -> {
shell.forceActive();
MessageDialog.openInformation(shell, title, message);
});
}
/**
* Shows a confirmation dialog with a custom title and message.
*
* @param title The title of the confirmation dialog.
* @param message The message of the confirmation dialog.
* @return True of ok was pressed; false if canceled was pressed.
*/
public static boolean showConfirmDialog(final String title, final String message) {
AtomicBoolean okPressed = new AtomicBoolean();
Display.getDefault().syncExec(() -> {
shell.forceActive();
okPressed.set(MessageDialog.openConfirm(shell, title, message));
});
return okPressed.get();
}
/**
* Shows a question dialog with a custom title and message.
*
* @param title The title of the question dialog.
* @param message The message of the question dialog.
* @return True of yes was pressed; false if no was pressed.
*/
public static boolean showQuestionDialog(final String title, final String message) {
AtomicBoolean yesPressed = new AtomicBoolean();
Display.getDefault().syncExec(() -> {
shell.forceActive();
yesPressed.set(MessageDialog.openQuestion(shell, title, message));
});
return yesPressed.get();
}
/**
* Shows an error dialog with a custom title and message.
*
* @param title The title of the error dialog.
* @param message The message of the error dialog.
*/
public static void showErrorDialog(final String title, final String message) {
Display.getDefault().syncExec(() -> {
shell.forceActive();
MessageDialog.openError(shell, title, message);
});
}
/**
* Shows an error dialog with a default title and custom message.
*
* @param message The message of the error dialog.
*/
public static void showErrorDialog(final String message) {
showErrorDialog(Activator.PLUGIN_ID + " error", message);
}
/**
* Shows a warning dialog with a custom title and custom message.
*
* @param title The title of the warning dialog.
* @param message The message of the error dialog.
*/
public static void showWarnDialog(final String title, final String message) {
Display.getDefault().syncExec(() -> {
shell.forceActive();
MessageDialog.openWarning(shell, title, message);
});
}
/**
* Shows an error dialog that a wizard can use to display information about an exception that
* occurred while wizard'ing.
*
* @param wizardShell The wizard's shell.
* @param e The exception that occurred.
* @return The displayed message.
*/
public static String showWizardErrorDialog(Shell wizardShell, InvocationTargetException e) {
String message = null;
Throwable cause = e.getCause();
if (cause != null) {
message = cause.getClass().getSimpleName();
if (cause.getMessage() != null && !cause.getMessage().isEmpty()) {
message += ": " + cause.getMessage();
}
}
else {
message = e.getClass().getSimpleName();
}
MessageDialog.openError(wizardShell, "Error", message);
return message;
}
/**
* Logs an info message.
*
* @param message The message to display.
*/
public static void info(String message) {
StatusManager.getManager().handle(new Status(IStatus.INFO, Activator.PLUGIN_ID, message));
}
/**
* Logs an error message.
*
* @param message The message to display.
*/
public static void error(String message) {
StatusManager.getManager().handle(new Status(IStatus.ERROR, Activator.PLUGIN_ID, message));
}
/**
* Logs an error message with the responsible error included.
*
* @param message The message to display.
* @param t The responsible throwable.
*/
public static void error(String message, Throwable t) {
StatusManager.getManager().handle(
new Status(IStatus.ERROR, Activator.PLUGIN_ID, message, t));
}
public static IWorkbenchPage getWorkbenchPage() {
IWorkbench workbench = PlatformUI.getWorkbench();
IWorkbenchWindow workbenchWindow = getWorkbenchWindow(workbench);
if (workbenchWindow == null) {
error("Couldn't get workbench window");
return null;
}
IWorkbenchPage wbp = getPage(workbenchWindow);
if (wbp == null) {
error("Couldn't get workbench page");
return null;
}
return wbp;
}
private static IWorkbenchWindow getWorkbenchWindow(IWorkbench workbench) {
IWorkbenchWindow workbenchWindow = workbench.getActiveWorkbenchWindow();
if (workbenchWindow != null) {
return workbenchWindow;
}
IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
for (IWorkbenchWindow window : windows) {
if (window != null) {
return window;
}
}
return null;
}
private static IWorkbenchPage getPage(IWorkbenchWindow workbenchWindow) {
IWorkbenchPage wbp = workbenchWindow.getActivePage();
if (wbp != null) {
return wbp;
}
IWorkbenchPage[] pages = workbenchWindow.getPages();
for (IWorkbenchPage page : pages) {
if (page != null) {
return page;
}
}
return null;
}
/**
* Displays the given file in an editor using the Java perspective.
* If something goes wrong, this method has no effect.
*
* @param file The file to display.
* @param workbench The workbench.
*/
public static void displayInEditor(IFile file, IWorkbench workbench) {
new UIJob("Display in editor") {
@Override
public IStatus runInUIThread(IProgressMonitor m) {
try {
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
IDE.openEditor(window.getActivePage(), file);
workbench.showPerspective("org.eclipse.jdt.ui.JavaPerspective", window);
return Status.OK_STATUS;
}
catch (NullPointerException | WorkbenchException e) {
return Status.CANCEL_STATUS;
}
}
}.schedule();
}
}

View file

@ -0,0 +1,79 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.*;
import org.eclipse.ui.intro.IIntroManager;
import org.eclipse.ui.intro.IIntroPart;
import ghidradev.ghidrascripteditor.ScriptEditorInitializer;
import ghidradev.ghidrasymbollookup.SymbolLookupInitializer;
/**
* When Eclipse starts, initializes the plugin's subcomponents.
*/
public class GhidraDevStartup implements IStartup {
@Override
public void earlyStartup() {
Job job = new Job(Activator.PLUGIN_ID + " startup") {
@Override
protected IStatus run(IProgressMonitor monitor) {
monitor.beginTask("Initializing " + Activator.PLUGIN_ID, 2);
// If we were launched from Ghidra, close the Eclipse welcome screen if present,
// and make it so it never shows up again.
if (Activator.getDefault().isLaunchedByGhidra()) {
IIntroManager introManager = PlatformUI.getWorkbench().getIntroManager();
IIntroPart intro = introManager.getIntro();
if (intro != null) {
Display.getDefault().syncExec(() -> introManager.closeIntro(intro));
}
PlatformUI.getPreferenceStore().setValue(
IWorkbenchPreferenceConstants.SHOW_INTRO, false);
}
// Ask the user (only once) for consent before listening on any ports
boolean firstTimeConsent = false;
if (!GhidraRootPreferences.requestedConsentToOpenPorts()) {
firstTimeConsent = EclipseMessageUtils.showQuestionDialog(
Activator.PLUGIN_ID + "User Consent",
Activator.PLUGIN_ID + " opens ports to enable communication with Ghidra " +
"for various features such as initiating script editing and symbol " +
"lookup from Ghidra.\n\nDo you consent to the ports being opened?\n\n" +
"If you do not consent now, you can enable these features at any " +
"time in the " + Activator.PLUGIN_ID + " preferences.");
GhidraRootPreferences.setOpenPortConsentRequest(true);
}
// Initialize the script editor
ScriptEditorInitializer.init(firstTimeConsent);
monitor.worked(1);
// Initialize symbol lookup
SymbolLookupInitializer.init(firstTimeConsent);
monitor.worked(1);
monitor.done();
return Status.OK_STATUS;
}
};
job.schedule();
}
}

View file

@ -0,0 +1,31 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev;
import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
import org.eclipse.jface.preference.IPreferenceStore;
/**
* Class used to initialize default preference values.
*/
public class GhidraRootPreferenceInitializer extends AbstractPreferenceInitializer {
@Override
public void initializeDefaultPreferences() {
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
store.setDefault(GhidraRootPreferences.GHIDRA_REQUESTED_OPEN_PORT_CONSENT, false);
}
}

View file

@ -0,0 +1,38 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
public class GhidraRootPreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
public GhidraRootPreferencePage() {
noDefaultAndApplyButton();
}
public void init(IWorkbench workbench) {
setDescription("Please see subcategories for Ghidra preferences.");
}
@Override
protected Control createContents(Composite parent) {
return null;
}
}

View file

@ -0,0 +1,49 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev;
import org.eclipse.jface.preference.IPreferenceStore;
/**
* General preference definitions and related utility methods.
*/
public class GhidraRootPreferences {
/**
* Whether or not we have requested consent to open network ports.
*/
static final String GHIDRA_REQUESTED_OPEN_PORT_CONSENT = "ghidradev.requestedOpenPortConsent";
/**
* Gets whether or not consent was requested to open network ports.
*
* @return True if consent was requested to open network ports; otherwise, false.
*/
public static boolean requestedConsentToOpenPorts() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
return prefs.getBoolean(GHIDRA_REQUESTED_OPEN_PORT_CONSENT);
}
/**
* Sets whether or not the user has given consent to open network ports.
*
* @param requested True if consent was requested to open network ports; otherwise, false.
*/
public static void setOpenPortConsentRequest(boolean requested) {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
prefs.setValue(GHIDRA_REQUESTED_OPEN_PORT_CONSENT, requested);
}
}

View file

@ -0,0 +1,38 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.commands;
import org.eclipse.core.commands.*;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.Version;
import ghidradev.EclipseMessageUtils;
/**
* Pops up a dialog with information about the plugin.
*/
public class AboutCommand extends AbstractHandler {
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
Version version = FrameworkUtil.getBundle(getClass()).getVersion();
StringBuilder message = new StringBuilder();
message.append("GhidraDev " + version.toString() + "\n\n");
message.append("Ghidra Development support for Eclipse");
EclipseMessageUtils.showInfoDialog("About", message.toString());
return null;
}
}

View file

@ -0,0 +1,40 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.commands;
import org.eclipse.core.commands.*;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.handlers.HandlerUtil;
import ghidradev.ghidraprojectcreator.wizards.LinkGhidraWizard;
/**
* Launches the {@link LinkGhidraWizard}.
*/
public class LinkGhidraCommand extends AbstractHandler {
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindow(event);
ISelection selection = window.getSelectionService().getSelection();
WizardDialog dialog =
new WizardDialog(window.getShell(), new LinkGhidraWizard(selection));
dialog.open();
return null;
}
}

View file

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

View file

@ -0,0 +1,33 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.launchers;
import ghidradev.ghidraprojectcreator.utils.GhidraLaunchUtils;
/**
* The Ghidra GUI launch shortcut actions.
*
* @see AbstractGhidraLaunchShortcut
*/
public class GhidraGuiLaunchShortcut extends AbstractGhidraLaunchShortcut {
/**
* Creates a new Ghidra GUI launch shortcut.
*/
public GhidraGuiLaunchShortcut() {
super(GhidraLaunchUtils.GUI_LAUNCH, "");
}
}

View file

@ -0,0 +1,33 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.launchers;
import ghidradev.ghidraprojectcreator.utils.GhidraLaunchUtils;
/**
* The Ghidra headless launch shortcut actions.
*
* @see AbstractGhidraLaunchShortcut
*/
public class GhidraHeadlessLaunchShortcut extends AbstractGhidraLaunchShortcut {
/**
* Creates a new Ghidra headless launch shortcut.
*/
public GhidraHeadlessLaunchShortcut() {
super(GhidraLaunchUtils.HEADLESS_LAUNCH, " (Headless)");
}
}

View file

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

View file

@ -0,0 +1,192 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.launchers;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.ui.*;
import org.eclipse.jdt.debug.ui.launchConfigurations.JavaClasspathTab;
import org.eclipse.jdt.debug.ui.launchConfigurations.JavaMainTab;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraLaunchUtils;
/**
* The Ghidra launcher tab group with default values needed for running/debugging
* Ghidra. Some Java tabs are hidden here because we don't want the user changing their properties.
* These properties are set in {@link GhidraLaunchDelegate}, which occurs right before
* the launch.
*/
public class GhidraLaunchTabGroup extends AbstractLaunchConfigurationTabGroup {
@Override
public void createTabs(ILaunchConfigurationDialog dialog, String mode) {
// Create the tabs
List<ILaunchConfigurationTab> tabs = new ArrayList<>();
tabs.add(getJavaMainTab());
tabs.add(getUserDefinedArgumentsTab());
tabs.add(new JavaClasspathTab());
tabs.add(getCommonTab());
// Set the tabs
setTabs(tabs.toArray(new ILaunchConfigurationTab[tabs.size()]));
}
/**
* Gets the {@link JavaMainTab} to use, with Ghidra's main method pre-configured in.
*
* @return The {@link JavaMainTab} to use, with Ghidra's main method pre-configured in.
*/
private JavaMainTab getJavaMainTab() {
return new JavaMainTab() {
@Override
public void initializeFrom(ILaunchConfiguration config) {
try {
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
GhidraLaunchUtils.setMainTypeName(wc);
super.initializeFrom(wc.doSave());
}
catch (CoreException e) {
EclipseMessageUtils.error("Failed to initialize the java main tab.", e);
}
}
};
}
/**
* Gets the user-defined arguments to use. These will be appended to Ghidra's required
* launch arguments, which are hidden from the tab group.
*
* @return The user-defined arguments to use.
*/
private AbstractLaunchConfigurationTab getUserDefinedArgumentsTab() {
return new AbstractLaunchConfigurationTab() {
private Text programArgsText;
private Text vmArgsText;
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NONE);
container.setLayout(new GridLayout(1, true));
GridData gd = new GridData(GridData.FILL_BOTH);
container.setLayoutData(gd);
// Program arguments
Group group = new Group(container, SWT.NONE);
group.setLayout(new GridLayout());
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setText("Program arguments:");
programArgsText = new Text(group, SWT.MULTI | SWT.WRAP | SWT.BORDER | SWT.V_SCROLL);
gd = new GridData(GridData.FILL_BOTH);
gd.heightHint = 40;
gd.widthHint = 100;
programArgsText.setLayoutData(gd);
programArgsText.addModifyListener(evt -> scheduleUpdateJob());
// VM arguments
group = new Group(container, SWT.NONE);
group.setLayout(new GridLayout());
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setText("VM arguments (appended to arguments defined in launch.properties):");
vmArgsText = new Text(group, SWT.MULTI | SWT.WRAP | SWT.BORDER | SWT.V_SCROLL);
gd = new GridData(GridData.FILL_BOTH);
gd.heightHint = 40;
gd.widthHint = 100;
vmArgsText.setLayoutData(gd);
vmArgsText.addModifyListener(evt -> scheduleUpdateJob());
setControl(container);
}
@Override
public void setDefaults(ILaunchConfigurationWorkingCopy config) {
try {
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
wc.setAttribute(GhidraLaunchUtils.ATTR_PROGAM_ARGUMENTS, "");
wc.setAttribute(GhidraLaunchUtils.ATTR_VM_ARGUMENTS, "");
wc.doSave();
}
catch (CoreException e) {
EclipseMessageUtils.error("Failed to set argument defaults.", e);
}
}
@Override
public void initializeFrom(ILaunchConfiguration config) {
try {
programArgsText.setText(
config.getAttribute(GhidraLaunchUtils.ATTR_PROGAM_ARGUMENTS, ""));
vmArgsText.setText(
config.getAttribute(GhidraLaunchUtils.ATTR_VM_ARGUMENTS, ""));
}
catch (CoreException e) {
EclipseMessageUtils.error("Failed to initialize the arguments.", e);
}
}
@Override
public void performApply(ILaunchConfigurationWorkingCopy config) {
try {
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
wc.setAttribute(GhidraLaunchUtils.ATTR_PROGAM_ARGUMENTS,
programArgsText.getText());
wc.setAttribute(GhidraLaunchUtils.ATTR_VM_ARGUMENTS, vmArgsText.getText());
wc.doSave();
}
catch (CoreException e) {
EclipseMessageUtils.error("Failed to apply the arguments.", e);
}
}
@Override
public String getName() {
return "Arguments";
}
};
}
/**
* Gets the {@link CommonTab} to use, with the new launch configuration added to the favorites.
*
* @return The {@link CommonTab} to use, with the new launch configuration added to the
* favorites.
*/
private CommonTab getCommonTab() {
return new CommonTab() {
@Override
public void initializeFrom(ILaunchConfiguration config) {
try {
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
GhidraLaunchUtils.setFavorites(wc);
super.initializeFrom(wc.doSave());
}
catch (CoreException e) {
EclipseMessageUtils.error("Failed to initialize the common tab.", e);
}
}
};
}
}

View file

@ -0,0 +1,56 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.preferences;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
import org.eclipse.jface.preference.IPreferenceStore;
import ghidradev.Activator;
/**
* Class used to initialize default preference values.
*/
public class GhidraProjectCreatorPreferenceInitializer extends AbstractPreferenceInitializer {
@Override
public void initializeDefaultPreferences() {
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
store.setDefault(GhidraProjectCreatorPreferences.GHIDRA_INSTALL_PATHS, "");
store.setDefault(GhidraProjectCreatorPreferences.GHIDRA_DEFAULT_INSTALL_PATH, "");
store.setDefault(GhidraProjectCreatorPreferences.GHIDRA_LAST_PROJECT_ROOT_PATH,
ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString());
store.setDefault(GhidraProjectCreatorPreferences.GHIDRA_LAST_GRADLE_DISTRIBUTION, "");
// If Ghidra launched Eclipse, automatically add in that Ghidra's location (if it doesn't
// already exist) as a convenience to the user.
File ghidraInstallDir = Activator.getDefault().getGhidraInstallDir();
if (ghidraInstallDir != null) {
Set<File> dirs = new HashSet<>(GhidraProjectCreatorPreferences.getGhidraInstallDirs());
if (!dirs.contains(ghidraInstallDir)) {
if (dirs.isEmpty()) {
GhidraProjectCreatorPreferences.setDefaultGhidraInstallDir(ghidraInstallDir);
}
dirs.add(ghidraInstallDir);
GhidraProjectCreatorPreferences.setGhidraInstallDirs(dirs);
}
}
}
}

View file

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

View file

@ -0,0 +1,161 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.preferences;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
import org.eclipse.buildship.core.GradleDistribution;
import org.eclipse.jface.preference.IPreferenceStore;
import ghidradev.Activator;
/**
* Ghidra project creator preference definitions and related utility methods.
*/
public class GhidraProjectCreatorPreferences {
/**
* Paths to the Ghidra installation directories.
*/
static final String GHIDRA_INSTALL_PATHS = "ghidradev.ghidraInstallPaths";
/**
* Path to the default Ghidra installation directory.
*/
static final String GHIDRA_DEFAULT_INSTALL_PATH = "ghidradev.ghidraDefaultInstallPath";
/**
* Path to the last used Ghidra project root directory.
*/
static final String GHIDRA_LAST_PROJECT_ROOT_PATH = "ghidradev.ghidraLastProjectRootPath";
/**
* The last used Gradle distribution.
*/
static final String GHIDRA_LAST_GRADLE_DISTRIBUTION = "ghidradev.ghidraLastGradleDistribution";
/**
* Gets the set of Ghidra installation directories that's defined in the preferences.
*
* @return The set of Ghidra installation directories that's defined in the preferences.
*/
public static Set<File> getGhidraInstallDirs() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
String ghidraInstallDirPaths = prefs.getString(GHIDRA_INSTALL_PATHS);
if (ghidraInstallDirPaths.isEmpty()) {
return Collections.emptySet();
}
return Arrays.stream(ghidraInstallDirPaths.split(File.pathSeparator)).map(
p -> new File(p)).collect(Collectors.toSet());
}
/**
* Sets the set of Ghidra installation directories that's defined in the preferences.
*
* @param dirs The set of Ghidra installation directories that's defined in the preferences.
*/
public static void setGhidraInstallDirs(Set<File> dirs) {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
String paths = dirs.stream().map(dir -> dir.getAbsolutePath()).collect(
Collectors.joining(File.pathSeparator));
prefs.setValue(GHIDRA_INSTALL_PATHS, paths);
}
/**
* Gets the default Ghidra installation directory that's defined in the preferences.
*
* @return The default Ghidra installation directory that's defined in the preferences.
* Could be null if a default is not defined.
*/
public static File getGhidraDefaultInstallDir() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
String ghidraDefaultInstallDirPath = prefs.getString(GHIDRA_DEFAULT_INSTALL_PATH);
if (ghidraDefaultInstallDirPath.isEmpty()) {
return null;
}
return new File(ghidraDefaultInstallDirPath);
}
/**
* Sets the default Ghidra installation directory that's defined in the preferences.
*
* @param dir The default Ghidra installation directory that's defined in the preferences.
* Could be null if there is no default.
*/
public static void setDefaultGhidraInstallDir(File dir) {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
prefs.setValue(GHIDRA_DEFAULT_INSTALL_PATH, dir != null ? dir.getAbsolutePath() : "");
}
/**
* Gets the last used Ghidra project root path that's defined in the preferences.
*
* @return The last used Ghidra project root path that's defined in the preferences.
* Could be the empty string.
*/
public static String getGhidraLastProjectRootPath() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
return prefs.getString(GHIDRA_LAST_PROJECT_ROOT_PATH);
}
/**
* Sets the last used Ghidra project root path that's defined in the preferences.
*
* @param path The last used Ghidra project root path that's defined in the preferences.
*/
public static void setGhidraLastProjectRootPath(String path) {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
prefs.setValue(GHIDRA_LAST_PROJECT_ROOT_PATH, path);
}
/**
* Gets the last used Ghidra Gradle distribution that's defined in the preferences.
*
* @return The last used Ghidra Gradle distribution that's defined in the preferences.
* Could be null if there is no last used distribution.
*/
public static GradleDistribution getGhidraLastGradleDistribution() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
String pref = prefs.getString(GHIDRA_LAST_GRADLE_DISTRIBUTION);
if (pref != null && !pref.isEmpty()) {
try {
return GradleDistribution.fromString(pref);
}
catch (Exception e) {
// Failed to parse the string for some reason. Fall through to null.
}
}
return null;
}
/**
* Sets the last used Ghidra Gradle distribution that's defined in the preferences.
*
* @param gradleDistribution The last used Ghidra Gradle distribution that's defined in the
* preferences. Could be null if the preference should be set to the default.
*/
public static void setGhidraLastGradleDistribution(GradleDistribution gradleDistribution) {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
if (gradleDistribution != null) {
prefs.setValue(GHIDRA_LAST_GRADLE_DISTRIBUTION, gradleDistribution.toString());
}
else {
prefs.setToDefault(GHIDRA_LAST_GRADLE_DISTRIBUTION);
}
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,41 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.testers;
import java.util.List;
import org.eclipse.core.expressions.PropertyTester;
import org.eclipse.jdt.core.IPackageFragmentRoot;
/**
* A {@link PropertyTester} used to determine if a given Eclipse resource is a Java package
* fragment root (which is basically a Java source folder on the build path).
*/
public class PackageFragmentRootPropertyTester extends PropertyTester {
@Override
public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
if (receiver instanceof List) {
List<?> list = (List<?>) receiver;
if (list.size() == 1) {
receiver = list.iterator().next();
}
}
return receiver instanceof IPackageFragmentRoot;
}
}

View file

@ -0,0 +1,200 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.utils;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.*;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.internal.ui.launchConfigurations.LaunchConfigurationManager;
import org.eclipse.debug.internal.ui.launchConfigurations.LaunchHistory;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import ghidra.GhidraLauncher;
/**
* Utility methods for working with Ghidra launchers in Eclipse.
*/
public class GhidraLaunchUtils {
/**
* Launch configuration ID for a Ghidra GUI launch. Must match corresponding value in
* plugin.xml.
*/
public static final String GUI_LAUNCH = "GhidraGuiLaunchConfigurationType";
/**
* Launch configuration ID for a Ghidra Headless launch. Must match corresponding value in
* plugin.xml.
*/
public static final String HEADLESS_LAUNCH = "GhidraHeadlessLaunchConfigurationType";
/**
* Program arguments that will get passed to the launched Ghidra. These will be appended
* to the required program arguments that are required to launch Ghidra, which are hidden
* from the user.
*/
public static final String ATTR_PROGAM_ARGUMENTS = "ghidradev.ghidraProgramArguments";
/**
* VM arguments that will get passed to the launched Ghidra. These will be appended
* to the required VM arguments that are required to launch Ghidra, which are hidden
* from the user.
*/
public static final String ATTR_VM_ARGUMENTS = "ghidradev.ghidraVmArguments";
/**
* Creates a new launch configuration for the given Java project.
*
* @param javaProject The Java project to create a launch configuration for.
* @param launchConfigTypeId The type of launch configuration.
* @param launchConfigName The name of the launch configuration.
* @param runConfigMemory The run configuration's desired memory. Could be null.
* @return A launch configuration working copy.
* @throws CoreException If there was an Eclipse-related problem with creating the launch
* configuration.
*/
public static ILaunchConfigurationWorkingCopy createLaunchConfig(IJavaProject javaProject,
String launchConfigTypeId, String launchConfigName, String runConfigMemory)
throws CoreException {
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
ILaunchConfigurationType launchType =
launchManager.getLaunchConfigurationType(launchConfigTypeId);
ILaunchConfigurationWorkingCopy wc = launchType.newInstance(null, launchConfigName);
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
javaProject.getProject().getName());
setMainTypeName(wc);
setMemory(wc, runConfigMemory);
setFavorites(wc);
return wc;
}
/**
* Gets the launch configuration with the given name.
*
* @param name The name of the launch configuration to get.
* @return The launch configuration with the given name, or null if it doesn't exist.
* @throws CoreException If there was an Eclipse-related problem with getting the launch
* configuration.
*/
public static ILaunchConfiguration getLaunchConfig(String name) throws CoreException {
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
for (ILaunchConfiguration lc : launchManager.getLaunchConfigurations()) {
if (lc.getName().equals(name)) {
return lc;
}
}
return null;
}
/**
* Gets the launch configuration with the given name and the given type ID.
*
* @param name The name of the launch configuration to get.
* @param id The launch configuration type id of the launch configuration to get.
* @return The launch configuration with the given name and type, or null if it doesn't exist.
* @throws CoreException If there was an Eclipse-related problem with getting the launch
* configuration.
*/
public static ILaunchConfiguration getLaunchConfig(String name, String id)
throws CoreException {
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
ILaunchConfigurationType type = launchManager.getLaunchConfigurationType(id);
if (type != null) {
for (ILaunchConfiguration lc : launchManager.getLaunchConfigurations(type)) {
if (lc.getName().equals(name)) {
return lc;
}
}
}
return null;
}
/**
* Sets the main type name attribute in the provided working copy. For Ghidra projects, this
* should be {@link GhidraLauncher}.
*
* @param wc The launch configuration working copy to modify.
* @return The modified working copy.
*/
public static ILaunchConfigurationWorkingCopy setMainTypeName(
ILaunchConfigurationWorkingCopy wc) {
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
GhidraLauncher.class.getName());
return wc;
}
/**
* Appends the maximum Java heap size (-Xmx) to the VM arguments in the provided working copy.
*
* @param memory The desired maximum Java heap size. Could be null if the default is to be
* used.
* @param wc The launch configuration working copy to modify.
* @return The modified working copy.
* @throws CoreException if there was an Eclipse-related issue appending the VM argument.
*
* @see #ATTR_VM_ARGUMENTS
*/
public static ILaunchConfigurationWorkingCopy setMemory(ILaunchConfigurationWorkingCopy wc,
String memory) throws CoreException {
if (memory != null) {
String vmArgs = wc.getAttribute(ATTR_VM_ARGUMENTS, "");
if (!vmArgs.isEmpty()) {
vmArgs += " ";
}
wc.setAttribute(ATTR_VM_ARGUMENTS, vmArgs + "-Xmx" + memory);
}
return wc;
}
/**
* Sets the favorites attribute in the provided working copy to include the launcher in both
* the run and debug launch groups.
*
* @param wc The launch configuration working copy to modify.
* @return The modified working copy.
* @throws CoreException If there was an Eclipse-related problem with setting the favorites
* attribute.
*/
public static ILaunchConfigurationWorkingCopy setFavorites(ILaunchConfigurationWorkingCopy wc)
throws CoreException {
List<String> list =
wc.getAttribute(IDebugUIConstants.ATTR_FAVORITE_GROUPS, new ArrayList<>());
list.add(IDebugUIConstants.ID_DEBUG_LAUNCH_GROUP);
list.add(IDebugUIConstants.ID_RUN_LAUNCH_GROUP);
wc.setAttribute(IDebugUIConstants.ATTR_FAVORITE_GROUPS, list);
return wc;
}
/**
* Adds the given launch configuration to the GUI's favorites list. This is useful to do if
* you create a launch configuration and want it to appear in the favorites list before ever
* launching it.
*
* @param launchConfig The launch configuration to add.
*/
public static void addToFavorites(ILaunchConfiguration launchConfig) {
LaunchConfigurationManager mgr = DebugUIPlugin.getDefault().getLaunchConfigurationManager();
LaunchHistory runHistory = mgr.getLaunchHistory(IDebugUIConstants.ID_RUN_LAUNCH_GROUP);
LaunchHistory debugHistory = mgr.getLaunchHistory(IDebugUIConstants.ID_DEBUG_LAUNCH_GROUP);
runHistory.addFavorite(launchConfig);
debugHistory.addFavorite(launchConfig);
}
}

View file

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

View file

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

View file

@ -0,0 +1,173 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.utils;
import java.io.*;
import java.text.ParseException;
import java.util.*;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.*;
import ghidra.GhidraApplicationLayout;
import ghidra.framework.GModule;
/**
* Utility methods for working with Ghidra scripts in Eclipse.
*/
public class GhidraScriptUtils {
public static File userScriptsDir =
new File(System.getProperty("user.home") + "/ghidra_scripts");
/**
* Creates a new Ghidra script project with the given name.
*
* @param projectName The name of the project to create.
* @param projectDir The directory the project will be created in.
* @param createRunConfig Whether or not to create a new run configuration for the project.
* @param runConfigMemory The run configuration's desired memory. Could be null.
* @param linkUserScripts Whether or not to link in the user scripts directory.
* @param linkSystemScripts Whether or not to link in the system scripts directories.
* @param ghidraLayout The Ghidra layout to link the project to.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param monitor The progress monitor to use during project creation.
* @return The created project.
* @throws IOException If there was a file-related problem with creating the project.
* @throws ParseException If there was a parse-related problem with creating the project.
* @throws CoreException If there was an Eclipse-related problem with creating the project.
*/
public static IJavaProject createGhidraScriptProject(String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory, boolean linkUserScripts,
boolean linkSystemScripts, GhidraApplicationLayout ghidraLayout,
String jythonInterpreterName, IProgressMonitor monitor)
throws IOException, ParseException, CoreException {
List<IClasspathEntry> classpathEntries = new ArrayList<>();
// Create empty Ghidra project
IJavaProject javaProject = GhidraProjectUtils.createEmptyGhidraProject(projectName,
projectDir, createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName,
monitor);
// Link each module's ghidra_scripts directory to the project
if (linkSystemScripts) {
for (GModule module : ghidraLayout.getModules().values()) {
File moduleDir = module.getModuleRoot().getFile(false);
File moduleScriptsDir = new File(moduleDir, "ghidra_scripts");
if (!moduleScriptsDir.exists()) {
continue;
}
IPath moduleScriptsDirPath = new Path(moduleScriptsDir.getAbsolutePath());
String moduleName = moduleDir.getName();
String scriptsDirName = "Ghidra " + moduleName + " scripts";
IFolder link = javaProject.getProject().getFolder(scriptsDirName);
link.createLink(moduleScriptsDirPath, IResource.NONE, monitor);
classpathEntries.add(JavaCore.newSourceEntry(link.getFullPath()));
}
}
// Link in the user's personal ghidra_scripts directory
if (linkUserScripts) {
IFolder link = javaProject.getProject().getFolder("Home scripts");
link.createLink(new Path(userScriptsDir.getAbsolutePath()), IResource.NONE, monitor);
classpathEntries.add(JavaCore.newSourceEntry(link.getFullPath()));
}
// Update the project's classpath
GhidraProjectUtils.addToClasspath(javaProject, classpathEntries, monitor);
return javaProject;
}
/**
* Create a Ghidra script with the given name in the in the user's ghidra_scripts, and link it in
* to the provided project.
*
* @param scriptFolder The folder to create the script in.
* @param scriptName The name of the script to create.
* @param scriptAuthor The script's author.
* @param scriptCategory The script's category.
* @param scriptDescription The script's description lines.
* @param monitor The progress monitor to use during script creation.
* @return The script file (which could be opened in an editor by default).
* @throws IOException If there was a file-related problem with creating the script.
* @throws CoreException If there was an Eclipse-related problem with creating the script.
*/
public static IFile createGhidraScript(IFolder scriptFolder, String scriptName,
String scriptAuthor, String scriptCategory, String[] scriptDescription,
IProgressMonitor monitor) throws CoreException, IOException {
// Create the scripts folder directory, if necessary
if (!scriptFolder.exists()) {
GhidraProjectUtils.createFolder(scriptFolder, monitor);
}
IFile scriptFile = scriptFolder.getFile(scriptName);
// Does the script exist already? If so, it's a problem.
if (scriptFile.exists()) {
throw new IOException("File already exists: " + scriptFile);
}
// Create the script file, and fill in a useful entry point
try (PrintWriter writer =
new PrintWriter(new FileWriter(scriptFile.getLocation().toFile()))) {
if (scriptName.endsWith(".java")) {
Arrays.stream(scriptDescription).forEach(line -> writer.println("//" + line));
writer.println("//@author " + scriptAuthor);
writer.println("//@category " + scriptCategory);
writer.println("//@keybinding");
writer.println("//@menupath");
writer.println("//@toolbar");
writer.println();
writer.println("import ghidra.app.script.GhidraScript;");
writer.println();
writer.println("public class " + scriptName.substring(0, scriptName.length() - 5) +
" extends GhidraScript {");
writer.println();
writer.println("\t@Override");
writer.println("\tprotected void run() throws Exception {");
writer.println("\t\t//TODO: Add script code here");
writer.println("\t}");
writer.println("}");
}
else if (scriptName.endsWith(".py")) {
Arrays.stream(scriptDescription).forEach(line -> writer.println("#" + line));
writer.println("#@author " + scriptAuthor);
writer.println("#@category " + scriptCategory);
writer.println("#@keybinding");
writer.println("#@menupath");
writer.println("#@toolbar");
writer.println();
writer.println("#TODO: Add script code here");
}
}
catch (IOException e) {
throw new IOException("Failed to create: " + scriptFile);
}
// Refresh project to it sees the new script
scriptFile.getProject().refreshLocal(IResource.DEPTH_INFINITE, monitor);
return scriptFile;
}
}

View file

@ -0,0 +1,94 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.utils;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.ui.JavaElementLabelProvider;
import org.eclipse.jdt.ui.StandardJavaElementContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
import org.eclipse.ui.dialogs.ISelectionStatusValidator;
import ghidradev.Activator;
/**
* A dialog that lets you choose a Java package fragment root, which is basically a top-level
* source folder.
*/
public class PackageFragmentRootSelectionDialog extends ElementTreeSelectionDialog {
/**
* Creates a new package fragment root selection dialog.
*
* @param shell The parent shell for the dialog.
* @param title The title of the dialog.
* @param message The message of the dialog.
* @param errorMessage the error message to display if an invalid selection is made.
*/
public PackageFragmentRootSelectionDialog(Shell shell, String title, String message,
String errorMessage) {
super(shell, new JavaElementLabelProvider(JavaElementLabelProvider.SHOW_DEFAULT),
new StandardJavaElementContentProvider());
setTitle(title);
setMessage(message);
setAllowMultiple(false);
setInput(JavaCore.create(ResourcesPlugin.getWorkspace().getRoot()));
setValidator(new ISelectionStatusValidator() {
@Override
public IStatus validate(Object[] sel) {
if (sel.length == 1 && sel[0] instanceof IPackageFragmentRoot) {
return new Status(IStatus.OK, Activator.PLUGIN_ID, IStatus.OK, "", null);
}
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, IStatus.ERROR, errorMessage,
null);
}
});
addFilter(new ViewerFilter() {
@Override
public boolean select(Viewer viewer, Object parentObject, Object element) {
if (element instanceof IPackageFragmentRoot) {
IPackageFragmentRoot packageFragmentRoot = (IPackageFragmentRoot) element;
return !packageFragmentRoot.isArchive() && !packageFragmentRoot.isExternal();
}
return element instanceof IJavaModel || element instanceof IJavaProject;
}
});
}
/**
* Gets the selected package fragment root.
*
* @return The selected package fragment root. Could be null if there is not a valid
* selection.
*/
public IPackageFragmentRoot getPackageFragmentRoot() {
Object[] result = getResult();
if (result.length == 1 && result[0] instanceof IPackageFragmentRoot) {
return (IPackageFragmentRoot) result[0];
}
return null;
}
}

View file

@ -0,0 +1,162 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.utils;
import java.io.File;
import java.util.List;
import javax.naming.OperationNotSupportedException;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
/**
* Utility methods for interacting with PyDev.
*/
public class PyDevUtils {
public final static String MIN_SUPPORTED_VERSION = "6.3.1";
/**
* Checks to see if a supported version of PyDev is installed.
*
* @return True if a supported version of PyDev is installed; otherwise, false.
*/
public static boolean isSupportedPyDevInstalled() {
try {
if (PyDevUtilsInternal.isPyDevInstalled()) {
// Make sure the installed version of PyDev is new enough to support the following
// operation.
getJython27InterpreterNames();
return true;
}
}
catch (OperationNotSupportedException | NoClassDefFoundError e) {
// Fall through to return false
}
return false;
}
/**
* Gets a list of discovered Jython 2.7 interpreter names.
*
* @return a list of discovered Jython 2.7 interpreter names.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static List<String> getJython27InterpreterNames() throws OperationNotSupportedException {
try {
return PyDevUtilsInternal.getJython27InterpreterNames();
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Adds the given Jython interpreter to PyDev.
*
* @param interpreterName The name of the interpreter to add.
* @param interpreterFile The interpreter file to add.
* @param interpreterLibDir The interpreter library directory to add.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static void addJythonInterpreter(String interpreterName, File interpreterFile,
File interpreterLibDir)
throws OperationNotSupportedException {
try {
PyDevUtilsInternal.addJythonInterpreter(interpreterName, interpreterFile,
interpreterLibDir);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Sets up Python for the given Java project.
*
* @param javaProject The Java project to enable Python for.
* @param classpathEntries The classpath entries to add to the Python path.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* If this is null, Python support will be removed from the project.
* @param monitor The progress monitor used during link.
* @throws CoreException if there was an Eclipse-related problem with enabling Python for the
* project.
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static void setupPythonForProject(IJavaProject javaProject,
List<IClasspathEntry> classpathEntries, String jythonInterpreterName,
IProgressMonitor monitor) throws CoreException, OperationNotSupportedException {
try {
PyDevUtilsInternal.setupPythonForProject(javaProject, classpathEntries,
jythonInterpreterName, monitor);
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Starts the PyDev remote debugger.
*
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
* operation.
*/
public static void startPyDevRemoteDebugger() throws OperationNotSupportedException {
try {
PyDevUtilsInternal.startPyDevRemoteDebugger();
}
catch (NoClassDefFoundError | NoSuchMethodError e) {
throw new OperationNotSupportedException(e.getMessage());
}
}
/**
* Gets the PyDev Jython preference page ID.
*
* @return the PyDev Jython preference page ID.
*/
public static String getJythonPreferencePageId() {
return "org.python.pydev.ui.pythonpathconf.interpreterPreferencesPageJython";
}
/**
* Gets The PyDev source directory.
*
* @return The PyDev source directory, or null if it was not found.
*/
public static File getPyDevSrcDir() {
String eclipsePath = Platform.getInstallLocation().getURL().getFile();
File pluginsDir = new File(eclipsePath, "plugins");
File[] pluginSubDirs = pluginsDir.listFiles(File::isDirectory);
if (pluginSubDirs != null) {
for (File dir : pluginSubDirs) {
if (dir.getName().startsWith("org.python.pydev")) {
File pysrcDir = new File(dir, "pysrc");
if (pysrcDir.isDirectory()) {
return pysrcDir;
}
}
}
}
return null;
}
}

View file

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

View file

@ -0,0 +1,151 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.wizards;
import static ghidradev.EclipseMessageUtils.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils.ModuleTemplateType;
import ghidradev.ghidraprojectcreator.wizards.pages.*;
/**
* Wizard to create a new Ghidra module project.
*/
public class CreateGhidraModuleProjectWizard extends Wizard implements INewWizard {
private IWorkbench workbench;
private CreateGhidraProjectWizardPage projectPage;
private ConfigureGhidraModuleProjectWizardPage projectConfigPage;
private ChooseGhidraInstallationWizardPage ghidraInstallationPage;
private EnablePythonWizardPage pythonPage;
/**
* Creates a new Ghidra module project wizard.
*/
public CreateGhidraModuleProjectWizard() {
setNeedsProgressMonitor(true);
}
@Override
public void init(IWorkbench wb, IStructuredSelection selection) {
workbench = wb;
projectPage = new CreateGhidraProjectWizardPage();
projectConfigPage = new ConfigureGhidraModuleProjectWizardPage();
ghidraInstallationPage = new ChooseGhidraInstallationWizardPage();
pythonPage = new EnablePythonWizardPage(ghidraInstallationPage);
}
@Override
public void addPages() {
addPage(projectPage);
addPage(projectConfigPage);
addPage(ghidraInstallationPage);
addPage(pythonPage);
}
@Override
public boolean performFinish() {
File ghidraInstallDir = ghidraInstallationPage.getGhidraInstallDir();
String projectName = projectPage.getProjectName();
boolean createRunConfig = projectPage.shouldCreateRunConfig();
String runConfigMemory = projectPage.getRunConfigMemory();
File projectDir = projectPage.getProjectDir();
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
Set<ModuleTemplateType> moduleTemplateTypes = projectConfigPage.getModuleTemplateTypes();
try {
getContainer().run(true, false,
monitor -> create(ghidraInstallDir, projectName, projectDir, createRunConfig,
runConfigMemory, moduleTemplateTypes, jythonInterpreterName, monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
catch (InvocationTargetException e) {
error(showWizardErrorDialog(getShell(), e), e);
return false;
}
return true;
}
/**
* Creates a Ghidra module project.
*
* @param ghidraInstallDir The Ghidra installation directory to use.
* @param projectName The name of the project to create.
* @param projectDir The project's directory.
* @param createRunConfig Whether or not to create a new run configuration for the project.
* @param runConfigMemory The run configuration's desired memory. Could be null.
* @param moduleTemplateTypes The desired module template types.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param monitor The monitor to use during project creation.
* @throws InvocationTargetException if an error occurred during project creation.
*/
private void create(File ghidraInstallDir, String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory,
Set<ModuleTemplateType> moduleTemplateTypes, String jythonInterpreterName,
IProgressMonitor monitor) throws InvocationTargetException {
try {
info("Creating " + projectName + " at " + projectDir);
monitor.beginTask("Creating " + projectName, 3);
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraInstallDir);
monitor.worked(1);
IJavaProject javaProject =
GhidraModuleUtils.createGhidraModuleProject(projectName, projectDir,
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
monitor.worked(1);
IFile sourceFile = GhidraModuleUtils.configureModuleSource(javaProject,
projectDir, ghidraLayout, moduleTemplateTypes, monitor);
monitor.worked(1);
if (sourceFile != null) {
EclipseMessageUtils.displayInEditor(sourceFile, workbench);
}
info("Finished creating " + projectName);
}
catch (IOException | ParseException | CoreException e) {
throw new InvocationTargetException(e);
}
finally {
monitor.done();
}
}
}

View file

@ -0,0 +1,137 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.wizards;
import static ghidradev.EclipseMessageUtils.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout;
import ghidradev.ghidraprojectcreator.utils.GhidraScriptUtils;
import ghidradev.ghidraprojectcreator.wizards.pages.*;
/**
* Wizard to create a new Ghidra scripting project.
*/
public class CreateGhidraScriptProjectWizard extends Wizard implements INewWizard {
private CreateGhidraProjectWizardPage projectPage;
private ConfigureGhidraScriptProjectWizardPage projectConfigPage;
private ChooseGhidraInstallationWizardPage ghidraInstallationPage;
private EnablePythonWizardPage pythonPage;
/**
* Creates a new Ghidra scripting project wizard.
*/
public CreateGhidraScriptProjectWizard() {
setNeedsProgressMonitor(true);
}
@Override
public void init(IWorkbench wb, IStructuredSelection selection) {
projectPage = new CreateGhidraProjectWizardPage("GhidraScripts");
projectConfigPage = new ConfigureGhidraScriptProjectWizardPage();
ghidraInstallationPage = new ChooseGhidraInstallationWizardPage();
pythonPage = new EnablePythonWizardPage(ghidraInstallationPage);
}
@Override
public void addPages() {
addPage(projectPage);
addPage(projectConfigPage);
addPage(ghidraInstallationPage);
addPage(pythonPage);
}
@Override
public boolean performFinish() {
File ghidraInstallDir = ghidraInstallationPage.getGhidraInstallDir();
String projectName = projectPage.getProjectName();
File projectDir = projectPage.getProjectDir();
boolean createRunConfig = projectPage.shouldCreateRunConfig();
String runConfigMemory = projectPage.getRunConfigMemory();
boolean linkUserScripts = projectConfigPage.shouldLinkUsersScripts();
boolean linkSystemScripts = projectConfigPage.shouldLinkSystemScripts();
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
try {
getContainer().run(true, false,
monitor -> create(ghidraInstallDir, projectName, projectDir, createRunConfig,
runConfigMemory, linkUserScripts, linkSystemScripts, jythonInterpreterName,
monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
catch (InvocationTargetException e) {
error(showWizardErrorDialog(getShell(), e), e);
return false;
}
return true;
}
/**
* Creates a Ghidra script project.
*
* @param ghidraInstallDir The Ghidra installation directory to use.
* @param projectName The name of the project to create.
* @param projectDir The project's directory.
* @param createRunConfig Whether or not to create a new run configuration for the project.
* @param runConfigMemory The run configuration's desired memory. Could be null.
* @param linkUserScripts Whether or not to link in the user scripts directory.
* @param linkSystemScripts Whether or not to link in the system scripts directories.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param monitor The monitor to use during project creation.
* @throws InvocationTargetException if an error occurred during project creation.
*/
private void create(File ghidraInstallDir, String projectName, File projectDir,
boolean createRunConfig, String runConfigMemory, boolean linkUserScripts,
boolean linkSystemScripts, String jythonInterpreterName, IProgressMonitor monitor)
throws InvocationTargetException {
try {
info("Creating " + projectName + " at " + projectDir);
monitor.beginTask("Creating " + projectName, 2);
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraInstallDir);
monitor.worked(1);
GhidraScriptUtils.createGhidraScriptProject(projectName, projectDir, createRunConfig,
runConfigMemory, linkUserScripts, linkSystemScripts, ghidraLayout,
jythonInterpreterName, monitor);
monitor.worked(1);
info("Finished creating " + projectName);
}
catch (IOException | ParseException | CoreException e) {
throw new InvocationTargetException(e);
}
finally {
monitor.done();
}
}
}

View file

@ -0,0 +1,128 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.wizards;
import static ghidradev.EclipseMessageUtils.*;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraScriptUtils;
import ghidradev.ghidraprojectcreator.wizards.pages.CreateGhidraScriptWizardPage;
/**
* Wizard to create a new Ghidra script file in an existing project.
*/
public class CreateGhidraScriptWizard extends Wizard implements INewWizard {
private IWorkbench workbench;
private CreateGhidraScriptWizardPage scriptPage;
/**
* Creates a new Ghidra script wizard.
*/
public CreateGhidraScriptWizard() {
setNeedsProgressMonitor(true);
}
@Override
public void init(IWorkbench wb, IStructuredSelection selection) {
workbench = wb;
IPackageFragmentRoot selectedPackageFragmentRoot = null;
Object firstElement = selection.getFirstElement();
if (firstElement instanceof IPackageFragmentRoot) {
selectedPackageFragmentRoot = (IPackageFragmentRoot) firstElement;
}
scriptPage = new CreateGhidraScriptWizardPage(selectedPackageFragmentRoot);
}
@Override
public void addPages() {
addPage(scriptPage);
}
@Override
public boolean performFinish() {
IFolder scriptFolder = scriptPage.getScriptFolder();
String scriptName = scriptPage.getScriptName();
String scriptAuthor = scriptPage.getScriptAuthor();
String scriptCategory = scriptPage.getScriptCategory();
String[] scriptDescription = scriptPage.getScriptDescription();
try {
getContainer().run(true, false, monitor -> create(scriptFolder, scriptName,
scriptAuthor, scriptCategory, scriptDescription, monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
catch (InvocationTargetException e) {
error(showWizardErrorDialog(getShell(), e), e);
return false;
}
return true;
}
/**
* Creates a Ghidra script.
*
* @param scriptFolder The folder to create the script in.
* @param scriptName The name of the script to create.
* @param scriptAuthor The script's author.
* @param scriptCategory The script's category.
* @param scriptDescription The script's description lines.
* @param monitor The monitor to use during project/script creation.
* @throws InvocationTargetException if an error occurred during project/script creation.
*/
private void create(IFolder scriptFolder, String scriptName, String scriptAuthor,
String scriptCategory, String[] scriptDescription, IProgressMonitor monitor)
throws InvocationTargetException {
try {
info("Creating " + scriptName + " in " + scriptFolder.toString());
monitor.beginTask("Creating " + scriptName + " in " + scriptFolder.toString(), 1);
IFile scriptFile = GhidraScriptUtils.createGhidraScript(scriptFolder, scriptName,
scriptAuthor, scriptCategory, scriptDescription, monitor);
monitor.worked(1);
if (scriptFile != null) {
EclipseMessageUtils.displayInEditor(scriptFile, workbench);
}
info("Finished creating " + scriptName);
}
catch (IOException | CoreException e) {
throw new InvocationTargetException(e);
}
finally {
monitor.done();
}
}
}

View file

@ -0,0 +1,166 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.wizards;
import static ghidradev.EclipseMessageUtils.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.*;
import org.eclipse.buildship.core.GradleDistribution;
import org.eclipse.buildship.core.internal.CorePlugin;
import org.eclipse.buildship.core.internal.launch.GradleLaunchConfigurationManager;
import org.eclipse.buildship.core.internal.launch.GradleRunConfigurationAttributes;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout;
import ghidra.launch.JavaConfig;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.wizards.pages.ChooseGhidraModuleProjectWizardPage;
import ghidradev.ghidraprojectcreator.wizards.pages.ConfigureGradleWizardPage;
/**
* Wizard for exporting a Ghidra module project to a releasable extension zip bundle.
*/
@SuppressWarnings("restriction")
public class ExportGhidraModuleWizard extends Wizard implements INewWizard {
private ChooseGhidraModuleProjectWizardPage projectPage;
private ConfigureGradleWizardPage gradlePage;
/**
* Creates a new Ghidra module export wizard.
*/
public ExportGhidraModuleWizard() {
setNeedsProgressMonitor(true);
}
@Override
public void init(IWorkbench wb, IStructuredSelection selection) {
projectPage = new ChooseGhidraModuleProjectWizardPage(
GhidraProjectUtils.getSelectedProject(selection));
gradlePage = new ConfigureGradleWizardPage(projectPage);
}
@Override
public void addPages() {
addPage(projectPage);
addPage(gradlePage);
}
@Override
public boolean performFinish() {
IJavaProject javaProject = projectPage.getGhidraModuleProject();
GradleDistribution gradleDist = gradlePage.getGradleDistribution();
try {
getContainer().run(true, false, monitor -> export(javaProject, gradleDist, monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
catch (InvocationTargetException e) {
error(showWizardErrorDialog(getShell(), e), e);
return false;
}
return true;
}
/**
* Exports the given Ghidra module project to an extension zip file.
*
* @param javaProject The Ghidra module project to export.
* @param gradleDistribution The Gradle distribution to use to export.
* @param monitor The monitor to use during export.
* @throws InvocationTargetException if an error occurred during export.
*/
private void export(IJavaProject javaProject, GradleDistribution gradleDistribution,
IProgressMonitor monitor)
throws InvocationTargetException {
try {
IProject project = javaProject.getProject();
info("Exporting " + project.getName());
monitor.beginTask("Exporting " + project.getName(), 2);
// Get path to Ghidra installation directory
String ghidraInstallDirPath = project.getFolder(
GhidraProjectUtils.GHIDRA_FOLDER_NAME).getRawLocation().toOSString();
// Get project's java. Gradle should use the same version.
// TODO: It's more correct to get this from the project's classpath, since Ghidra's
// saved Java home can change from launch to launch.
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(new File(ghidraInstallDirPath));
File javaHomeDir = new JavaConfig(
ghidraLayout.getApplicationInstallationDir().getFile(false)).getSavedJavaHome();
if(javaHomeDir == null) {
throw new IOException("Failed to get the Java home associated with the project. " +
"Perform a \"Link Ghidra\" operation on the project and try again.");
}
// Setup the Gradle build attributes
List<String> tasks = new ArrayList<>();
String workingDir = project.getRawLocation().toOSString();
String gradleDist = gradleDistribution.toString();
String gradleUserHome = "";
String javaHome = javaHomeDir.getAbsolutePath();
List<String> jvmArgs = new ArrayList<>();
List<String> gradleArgs =
Arrays.asList(new String[] { "-PGHIDRA_INSTALL_DIR=" + ghidraInstallDirPath });
boolean showExecutionView = false;
boolean showConsoleView = true;
boolean overrideWorkspaceSettings = true;
boolean isOffline = true;
boolean isBuildScansEnabled = false;
GradleRunConfigurationAttributes gradleAttributes =
new GradleRunConfigurationAttributes(tasks, workingDir, gradleDist, gradleUserHome,
javaHome, jvmArgs, gradleArgs, showExecutionView, showConsoleView,
overrideWorkspaceSettings, isOffline, isBuildScansEnabled);
// Launch Gradle
GradleLaunchConfigurationManager lm = CorePlugin.gradleLaunchConfigurationManager();
ILaunchConfiguration lc = lm.getOrCreateRunConfiguration(gradleAttributes);
lc.launch(ILaunchManager.RUN_MODE, monitor, true, true);
lc.delete();
monitor.worked(1);
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
monitor.worked(1);
info("Finished exporting " + project.getName());
}
catch (IOException | ParseException | CoreException e) {
throw new InvocationTargetException(e);
}
finally {
monitor.done();
}
}
}

View file

@ -0,0 +1,119 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.wizards;
import static ghidradev.EclipseMessageUtils.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.wizard.Wizard;
import ghidra.GhidraApplicationLayout;
import ghidra.launch.JavaConfig;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.wizards.pages.*;
/**
* Wizard for linking a Java project's classpath and external links to a Ghidra installation directory.
*/
public class LinkGhidraWizard extends Wizard {
private ChooseGhidraInstallationWizardPage ghidraInstallationPage;
private ChooseJavaProjectWizardPage projectPage;
private EnablePythonWizardPage pythonPage;
public LinkGhidraWizard(ISelection selection) {
setNeedsProgressMonitor(true);
this.ghidraInstallationPage = new ChooseGhidraInstallationWizardPage();
this.projectPage =
new ChooseJavaProjectWizardPage((GhidraProjectUtils.getSelectedProject(selection)));
this.pythonPage = new EnablePythonWizardPage(ghidraInstallationPage);
}
@Override
public void addPages() {
addPage(ghidraInstallationPage);
addPage(projectPage);
addPage(pythonPage);
}
@Override
public boolean performFinish() {
File ghidraInstallDir = ghidraInstallationPage.getGhidraInstallDir();
IJavaProject javaProject = projectPage.getJavaProject();
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
try {
getContainer().run(true, false,
monitor -> link(ghidraInstallDir, javaProject, jythonInterpreterName, monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
catch (InvocationTargetException e) {
error(showWizardErrorDialog(getShell(), e), e);
return false;
}
return true;
}
/**
* Links a Java project's classpath and external links to a Ghidra installation directory.
*
* @param ghidraInstallDir The Ghidra installation directory to use.
* @param javaProject The Java project to link.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param monitor The monitor to use during project link.
* @throws InvocationTargetException if an error occurred during link.
*/
private void link(File ghidraInstallDir, IJavaProject javaProject, String jythonInterpreterName,
IProgressMonitor monitor) throws InvocationTargetException {
IProject project = javaProject.getProject();
try {
info("Linking " + project.getName());
monitor.beginTask("Linking " + project.getName(), 2);
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraInstallDir);
JavaConfig javaConfig =
new JavaConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
GhidraProjectUtils.linkGhidraToProject(javaProject, ghidraLayout, javaConfig,
jythonInterpreterName, monitor);
monitor.worked(1);
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
monitor.worked(1);
info("Finished linking " + project.getName());
}
catch (IOException | ParseException | CoreException e) {
throw new InvocationTargetException(e);
}
finally {
monitor.done();
}
}
}

View file

@ -0,0 +1,143 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.wizards.pages;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.dialogs.PreferencesUtil;
import ghidra.launch.JavaConfig;
import ghidra.launch.JavaFinder.JavaFilter;
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferencePage;
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferences;
/**
* A wizard page that lets the user choose a Ghidra installation.
*/
public class ChooseGhidraInstallationWizardPage extends WizardPage {
private Combo ghidraInstallDirCombo;
private Button addGhidraInstallDirButton;
/**
* Creates a new Ghidra installation wizard page.
*/
public ChooseGhidraInstallationWizardPage() {
super("ChooseGhidraInstallationWizardPage");
setTitle("Choose a Ghidra Installation");
setDescription("Choose the Ghidra installation to use.");
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(3, false));
Label ghidraInstallDirLabel = new Label(container, SWT.NULL);
ghidraInstallDirLabel.setText("Ghidra installation:");
ghidraInstallDirCombo = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
ghidraInstallDirCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
populateGhidraInstallationCombo();
ghidraInstallDirCombo.addModifyListener(evt -> validate());
ghidraInstallDirCombo.setToolTipText("The wizard requires a Ghidra installation to be " +
"selected. Click the + button to add or manage Ghidra installations.");
addGhidraInstallDirButton = new Button(container, SWT.BUTTON1);
addGhidraInstallDirButton.setText("+");
addGhidraInstallDirButton.setToolTipText("Adds/manages Ghidra installations.");
addGhidraInstallDirButton.addListener(SWT.Selection, evt -> {
PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(null,
GhidraProjectCreatorPreferencePage.class.getName(), null, null);
dialog.open();
populateGhidraInstallationCombo();
validate();
});
validate();
setControl(container);
}
/**
* Gets the Ghidra installation directory.
*
* @return The Ghidra installation directory.
* Could be null if unspecified, however, the page will not be valid until the Ghidra
* installation directory is valid, so it should never be null when called by other
* classes.
*/
public File getGhidraInstallDir() {
return new File(ghidraInstallDirCombo.getText());
}
/**
* Validates the fields on the page and updates the page's status.
* Should be called every time a field on the page changes.
*/
private void validate() {
String message = null;
if (GhidraProjectCreatorPreferences.getGhidraInstallDirs().isEmpty()) {
message = "No Ghidra installations found. Click the + button to add one.";
}
else if (ghidraInstallDirCombo.getText().isEmpty()) {
message = "Ghidra installation must be specified.";
}
else {
try {
File ghidraInstallDir = new File(ghidraInstallDirCombo.getText());
GhidraProjectCreatorPreferencePage.validateGhidraInstallation(ghidraInstallDir);
try {
JavaConfig javaConfig = new JavaConfig(ghidraInstallDir);
if (!javaConfig.isSupportedJavaHomeDir(javaConfig.getSavedJavaHome(),
JavaFilter.JDK_ONLY)) {
message = "A supported JDK is not associated with this Ghidra " +
"installation. Please run this Ghidra and try again.";
}
}
catch (ParseException | IOException e) {
message = "Failed to determine Ghidra's JDK version. " + e.getMessage();
}
}
catch (IOException e) {
message = e.getMessage();
}
}
setErrorMessage(message);
setPageComplete(message == null);
}
/**
* Populates the Ghidra installations combo box with the values found in preferences.
*/
private void populateGhidraInstallationCombo() {
ghidraInstallDirCombo.removeAll();
for (File dir : GhidraProjectCreatorPreferences.getGhidraInstallDirs()) {
ghidraInstallDirCombo.add(dir.getAbsolutePath());
if (dir.equals(GhidraProjectCreatorPreferences.getGhidraDefaultInstallDir())) {
ghidraInstallDirCombo.setText(dir.getAbsolutePath());
}
}
}
}

View file

@ -0,0 +1,101 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.wizards.pages;
import org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
/**
* A wizard page that lets the user choose an open Ghidra module project.
*/
public class ChooseGhidraModuleProjectWizardPage extends WizardPage {
private IProject selectedProject;
private Combo projectCombo;
/**
* Creates a new Ghidra module project chooser wizard page.
*
* @param selectedProject The currently selected project in the project explorer.
*/
public ChooseGhidraModuleProjectWizardPage(IProject selectedProject) {
super("ChooseGhidraModuleProjectWizardPage");
setTitle("Choose Ghidra Module Project");
setDescription("Choose an open Ghidra module project.");
this.selectedProject = selectedProject;
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(2, false));
Label projectNameLabel = new Label(container, SWT.NULL);
projectNameLabel.setText("Ghida module project:");
projectCombo = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
projectCombo.setLayoutData(gd);
projectCombo.addModifyListener(evt -> validate());
for (IJavaProject javaProject : GhidraProjectUtils.getGhidraProjects()) {
if (GhidraProjectUtils.isGhidraModuleProject(javaProject.getProject())) {
IProject project = javaProject.getProject();
projectCombo.add(project.getName());
if (project.equals(selectedProject)) {
projectCombo.setText(project.getName());
}
}
}
validate();
setControl(container);
}
/**
* Gets the Java project.
*
* @return The chosen Ghidra module project. Only valid when the page is complete.
* Could be null if unspecified, however, the page will not be valid until the project
* is valid, so it should never be null when called by other classes.
*/
public IJavaProject getGhidraModuleProject() {
return GhidraProjectUtils.getGhidraProject(projectCombo.getText());
}
/**
* Validates the fields on the page and updates the page's status.
* Should be called every time a field on the page changes.
*/
private void validate() {
String message = null;
String projectName = projectCombo.getText();
if (projectName.isEmpty()) {
message = "Project name must be specified";
}
setErrorMessage(message);
setPageComplete(message == null);
}
}

View file

@ -0,0 +1,105 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.wizards.pages;
import org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
/**
* A wizard page that lets the user choose an open Java project.
*/
public class ChooseJavaProjectWizardPage extends WizardPage {
private IProject selectedProject;
private Combo projectCombo;
/**
* Creates a new Java project chooser wizard page.
*
* @param selectedProject The currently selected project in the project explorer.
*/
public ChooseJavaProjectWizardPage(IProject selectedProject) {
super("ChooseJavaProjectWizardPage");
setTitle("Choose Java Project");
setDescription("Choose an existing Java project.");
this.selectedProject = selectedProject;
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(2, false));
Label projectNameLabel = new Label(container, SWT.NULL);
projectNameLabel.setText("Java project:");
projectCombo = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
projectCombo.setLayoutData(gd);
projectCombo.addModifyListener(evt -> validate());
for (IJavaProject javaProject : GhidraProjectUtils.getJavaProjects()) {
IProject project = javaProject.getProject();
projectCombo.add(project.getName());
if (project.equals(selectedProject)) {
projectCombo.setText(project.getName());
}
}
validate();
setControl(container);
}
/**
* Gets the Java project.
*
* @return The chosen Java project. Only valid when the page is complete.
* Could be null if unspecified, however, the page will not be valid until the project
* is valid, so it should never be null when called by other classes.
*/
public IJavaProject getJavaProject() {
for (IJavaProject javaProject : GhidraProjectUtils.getJavaProjects()) {
IProject project = javaProject.getProject();
if (project.getName().equals(projectCombo.getText())) {
return javaProject;
}
}
return null;
}
/**
* Validates the fields on the page and updates the page's status.
* Should be called every time a field on the page changes.
*/
private void validate() {
String message = null;
String projectName = projectCombo.getText();
if (projectName.isEmpty()) {
message = "Existing Java project must be specified";
}
setErrorMessage(message);
setPageComplete(message == null);
}
}

View file

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

View file

@ -0,0 +1,81 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.wizards.pages;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import ghidradev.ghidraprojectcreator.utils.GhidraScriptUtils;
public class ConfigureGhidraScriptProjectWizardPage extends WizardPage {
private Button userScriptsCheckboxButton;
private Button systemScriptsCheckboxButton;
/**
* Creates a new Ghidra script project configuration wizard page.
*/
public ConfigureGhidraScriptProjectWizardPage() {
super("ConfigureGhidraScriptProjectWizardPage");
setTitle("Configure Ghidra Script Project");
setDescription("Configure a new Ghidra script project.");
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(1, false));
userScriptsCheckboxButton = new Button(container, SWT.CHECK);
userScriptsCheckboxButton.setText(
"Link user home script directory (" + GhidraScriptUtils.userScriptsDir + ")");
userScriptsCheckboxButton.setToolTipText("Automatically links Ghidra's default user home " +
"script directory to the new project. This is recommended.");
userScriptsCheckboxButton.setSelection(true);
systemScriptsCheckboxButton = new Button(container, SWT.CHECK);
systemScriptsCheckboxButton.setText("Link Ghidra installation script directories");
systemScriptsCheckboxButton.setToolTipText("Automatically links any script directories " +
"found in the Ghidra installation to the new project, which serve as good examples.");
systemScriptsCheckboxButton.setSelection(true);
setErrorMessage(null);
setPageComplete(true);
setControl(container);
}
/**
* Checks whether or not the user scripts directory should be linked into the project.
*
* @return True if the user scripts directory should be linked into the project; otherwise, false.
*/
public boolean shouldLinkUsersScripts() {
return userScriptsCheckboxButton.getSelection();
}
/**
* Checks whether or not the system scripts directory should be linked into the project.
*
* @return True if the system scripts directory should be linked into the project; otherwise, false.
*/
public boolean shouldLinkSystemScripts() {
return systemScriptsCheckboxButton.getSelection();
}
}

View file

@ -0,0 +1,219 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.wizards.pages;
import java.io.File;
import java.io.IOException;
import org.eclipse.buildship.core.*;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import ghidra.GhidraApplicationLayout;
import ghidra.framework.ApplicationProperties;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferences;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
public class ConfigureGradleWizardPage extends WizardPage {
private Button gradleWrapperChoiceButton;
private Button gradleLocalChoiceButton;
private Text gradleLocalDirText;
private Button gradlLocalDirButton;
private ChooseGhidraModuleProjectWizardPage projectPage;
private String gradleVersion;
/**
* Creates a new Gradle configuration wizard page.
*
* @param projectPage A {@link ChooseGhidraModuleProjectWizardPage} to get the project from.
*/
public ConfigureGradleWizardPage(ChooseGhidraModuleProjectWizardPage projectPage) {
super("ConfigureGradleWizardPage");
setTitle("Configure Gradle");
setDescription("Configure Gradle.");
this.projectPage = projectPage;
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(4, false));
SelectionListener selectionListener = new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent evt) {
validate();
}
@Override
public void widgetDefaultSelected(SelectionEvent evt) {
validate();
}
};
// Local Gradle
String gradleLocalTooltip = "Use a local installation of Gradle. For best results, " +
"ensure that the version of this local Gradle matches the version specified on this wizard page's description.";
gradleLocalChoiceButton = new Button(container, SWT.RADIO);
gradleLocalChoiceButton.addSelectionListener(selectionListener);
gradleLocalChoiceButton.setToolTipText(gradleLocalTooltip);
Label gradleLocalDirLabel = new Label(container, SWT.NULL);
gradleLocalDirLabel.setText("Local installation directory:");
gradleLocalDirText = new Text(container, SWT.BORDER | SWT.SINGLE);
gradleLocalDirText.setToolTipText(gradleLocalTooltip);
gradleLocalDirText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
gradleLocalDirText.addModifyListener(evt -> validate());
gradlLocalDirButton = new Button(container, SWT.BUTTON1);
gradlLocalDirButton.setText("...");
gradlLocalDirButton.addListener(SWT.Selection, evt -> {
DirectoryDialog dialog = new DirectoryDialog(container.getShell());
String path = dialog.open();
if (path != null) {
gradleLocalDirText.setText(path);
}
validate();
});
// Gradle Wrapper
String gradleWrapperTooltip = "Use the Gradle Wrapper, which will automatically download " +
"the correct version of Gradle to use from the Internet.";
gradleWrapperChoiceButton = new Button(container, SWT.RADIO);
gradleWrapperChoiceButton.addSelectionListener(selectionListener);
gradleWrapperChoiceButton.setToolTipText(gradleWrapperTooltip);
Label gradleWrapperDirLabel = new Label(container, SWT.NULL);
gradleWrapperDirLabel.setText("Gradle Wrapper");
Label internetLabel = new Label(container, SWT.NONE);
internetLabel.setText("INTERNET CONNECTION REQUIRED");
internetLabel.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_RED));
internetLabel.setToolTipText(gradleWrapperTooltip);
new Label(container, SWT.NONE).setText(""); // empty grid cell
// Set default value from preferences
GradleDistribution lastGradleDistribution =
GhidraProjectCreatorPreferences.getGhidraLastGradleDistribution();
if (lastGradleDistribution instanceof LocalGradleDistribution) {
gradleLocalChoiceButton.setSelection(true);
LocalGradleDistribution localGradleDistribution =
(LocalGradleDistribution) lastGradleDistribution;
if (localGradleDistribution.getLocation() != null) {
gradleLocalDirText.setText(localGradleDistribution.getLocation().getAbsolutePath());
}
}
else if (lastGradleDistribution instanceof WrapperGradleDistribution ||
lastGradleDistribution instanceof FixedVersionGradleDistribution) {
gradleWrapperChoiceButton.setSelection(true);
}
else {
gradleLocalChoiceButton.setSelection(true);
}
validate();
setControl(container);
}
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
// Update the page's description to reference the version of Gradle that should be used.
if (visible) {
IProject project = projectPage.getGhidraModuleProject().getProject();
IFolder ghidraFolder = project.getFolder(GhidraProjectUtils.GHIDRA_FOLDER_NAME);
File ghidraDir = ghidraFolder.getRawLocation().toFile();
try {
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraDir);
ApplicationProperties props = ghidraLayout.getApplicationProperties();
gradleVersion =
props.getProperty(ApplicationProperties.APPLICATION_GRADLE_VERSION_PROPERTY);
if (gradleVersion != null && !gradleVersion.isEmpty()) {
setDescription("Configure Gradle. Version " + gradleVersion + " is expected.");
}
}
catch (IOException e) {
EclipseMessageUtils.error("Unable to determine required Gradle version.");
}
}
}
/**
* Gets the Gradle distribution to use.
*
* @return The gradle distribution to use.
*/
public GradleDistribution getGradleDistribution() {
if (gradleLocalChoiceButton.getSelection()) {
return GradleDistribution.forLocalInstallation(new File(gradleLocalDirText.getText()));
}
else if (gradleVersion != null) {
return GradleDistribution.forVersion(gradleVersion);
}
else {
// This case should only happen if someone deleted the Gradle version from
// application.properties. In that case, we'll just try the standard wrapper and hope
// for the best.
return GradleDistribution.fromBuild();
}
}
/**
* Validates the fields on the page and updates the page's status.
* Should be called every time a field on the page changes.
*/
private void validate() {
String message = null;
if (gradleLocalChoiceButton.getSelection()) {
String path = gradleLocalDirText.getText().trim();
File dir = new File(path);
if (path.isEmpty()) {
message = "Path to local Gradle installation must be specified.";
}
else if (!dir.exists()) {
message = "Path to local Gradle installation does not exist.";
}
else if (!dir.isDirectory()) {
message = "Path to local Gradle installation is not a directory.";
}
else if (!new File(dir, "bin/gradle").exists()) {
message =
"Path to local Gradle installation appears invalid. Missing gradle binary.";
}
}
if (message == null) {
GhidraProjectCreatorPreferences.setGhidraLastGradleDistribution(
getGradleDistribution());
}
else {
GhidraProjectCreatorPreferences.setGhidraLastGradleDistribution(null);
}
setErrorMessage(message);
setPageComplete(message == null);
}
}

View file

@ -0,0 +1,257 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.wizards.pages;
import java.io.File;
import java.util.regex.Pattern;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferences;
import ghidradev.ghidraprojectcreator.utils.GhidraLaunchUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
/**
* A wizard page that lets the user create a new Ghidra project.
*/
public class CreateGhidraProjectWizardPage extends WizardPage {
private String suggestedProjectName;
private Text projectNameText;
private Text projectRootDirText;
private Button projectDirButton;
private Button createRunConfigCheckboxButton;
private Text runConfigMemoryText;
/**
* Creates a Ghidra new project wizard page with the given suggested project name.
*
* @param suggestedProjectName The suggested project name.
*/
public CreateGhidraProjectWizardPage(String suggestedProjectName) {
super("CreateGhidraProjectWizardPage");
setTitle("Create Ghidra Project");
setDescription("Create a new Ghidra project.");
this.suggestedProjectName = suggestedProjectName;
}
/**
* Creates a Ghidra new project wizard page.
*/
public CreateGhidraProjectWizardPage() {
this("");
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(3, false));
// Project name
Label projectNameLabel = new Label(container, SWT.NULL);
projectNameLabel.setText("Project name:");
projectNameText = new Text(container, SWT.BORDER | SWT.SINGLE);
projectNameText.setText(suggestedProjectName);
projectNameText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
projectNameText.addModifyListener(evt -> validate());
new Label(container, SWT.NONE).setText(""); // empty grid cell
// Project directory
Label projectDirLabel = new Label(container, SWT.NULL);
String projectDirToolTip = "The directory where this project will be created.";
projectDirLabel.setText("Project root directory:");
projectDirLabel.setToolTipText(projectDirToolTip);
projectRootDirText = new Text(container, SWT.BORDER | SWT.SINGLE);
projectRootDirText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
projectRootDirText.setText(GhidraProjectCreatorPreferences.getGhidraLastProjectRootPath());
projectRootDirText.addModifyListener(evt -> validate());
projectRootDirText.setToolTipText(projectDirToolTip);
projectDirButton = new Button(container, SWT.BUTTON1);
projectDirButton.setText("...");
projectDirButton.setToolTipText("Browse to select project root directory");
projectDirButton.addListener(SWT.Selection, evt -> {
DirectoryDialog dialog = new DirectoryDialog(container.getShell());
String path = dialog.open();
if (path != null) {
projectRootDirText.setText(path);
}
});
// Create run configuration checkbox
createRunConfigCheckboxButton = new Button(container, SWT.CHECK);
createRunConfigCheckboxButton.setText("Create run configuration");
createRunConfigCheckboxButton.setToolTipText("Automatically create a Ghidra run " +
"configuration that can be used to launch and debug this project in Ghidra. Run " +
"configurations can be created later and modified in the \"Run --> Run " +
"Configurations\" menu.");
createRunConfigCheckboxButton.setSelection(true);
createRunConfigCheckboxButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent evt) {
validate();
}
@Override
public void widgetDefaultSelected(SelectionEvent evt) {
validate();
}
});
new Label(container, SWT.NONE).setText(""); // empty grid cell
new Label(container, SWT.NONE).setText(""); // empty grid cell
// Run configuration memory
Label runConfigMemoryLabel = new Label(container, SWT.NULL);
String runConfigMemoryToolTip = "The maximum Java heap size (-Xmx) Ghidra will use when " +
"launched with the created run configuration (ex: 4G, 1500m, etc). If left blank, " +
"Java's default heap size will be used, which is determined by your system's memory " +
"capacity.";
runConfigMemoryLabel.setText("Run configuration memory:");
runConfigMemoryLabel.setToolTipText(runConfigMemoryToolTip);
runConfigMemoryText = new Text(container, SWT.BORDER | SWT.SINGLE);
runConfigMemoryText.addModifyListener(evt -> validate());
runConfigMemoryText.setToolTipText(runConfigMemoryToolTip);
new Label(container, SWT.NONE).setText(""); // empty grid cell
validate();
setControl(container);
}
/**
* Gets the name of the project.
*
* @return The name of the project.
*/
public String getProjectName() {
return projectNameText.getText();
}
/**
* Gets the project directory. This is the directory where the .project file should live.
*
* @return The project directory. This is the directory where the .project file should live.
* Could be null if unspecified, however, the page will not be valid until the project
* directory is valid, so it should never be null when called by other classes.
*/
public File getProjectDir() {
if (projectNameText.getText().isEmpty()) {
return null;
}
if (projectRootDirText.getText().isEmpty()) {
return null;
}
return new File(projectRootDirText.getText(), getProjectName());
}
/**
* Checks to see whether or not a run configuration for the new project should be automatically
* created.
*
* @return True if a run configuration for the new project should be automatically created;
* otherwise, false.
*/
public boolean shouldCreateRunConfig() {
return createRunConfigCheckboxButton.getSelection();
}
/**
* Gets the run configuration's desired memory.
*
* @return The run configuration's desired memory. Could be null if unspecified.
*/
public String getRunConfigMemory() {
if (!createRunConfigCheckboxButton.getSelection() ||
runConfigMemoryText.getText().isEmpty()) {
return null;
}
return runConfigMemoryText.getText();
}
/**
* Validates the fields on the page and updates the page's status.
* Should be called every time a field on the page changes.
*/
private void validate() {
final String BAD = GhidraProjectUtils.ILLEGAL_FILENAME_CHARS;
final String BAD_START = GhidraProjectUtils.ILLEGAL_FILENAME_START_CHARS;
runConfigMemoryText.setEnabled(createRunConfigCheckboxButton.getSelection());
runConfigMemoryText.setMessage(runConfigMemoryText.isEnabled() ? "default" : "");
String message = null;
String projectName = getProjectName();
File projectDir = getProjectDir();
boolean launchConfigExists = false;
try {
ILaunchConfiguration launchConfig = GhidraLaunchUtils.getLaunchConfig(projectName);
if (launchConfig != null) {
String launchConfigTypeId = launchConfig.getType().getIdentifier();
launchConfigExists = !launchConfigTypeId.equals(GhidraLaunchUtils.GUI_LAUNCH) &&
!launchConfigTypeId.equals(GhidraLaunchUtils.HEADLESS_LAUNCH);
}
}
catch (CoreException e) {
// launchConfigExists can remain false, we'll just overwrite the config
}
if (projectName.isEmpty()) {
message = "Project name must be specified";
}
else if ((BAD_START + BAD).chars().anyMatch(ch -> projectName.charAt(0) == ch)) {
message = "Project name cannot start with an invalid character:\n " + BAD_START + BAD;
}
else if (BAD.chars().anyMatch(ch -> projectName.indexOf(ch) != -1)) {
message = "Project name cannot contain invalid characters:\n " + BAD;
}
else if (projectDir == null) {
message = "Project root directory must be specified";
}
else if (projectDir.exists()) {
message = "Project already exists at: " + projectDir.getAbsolutePath();
}
else if (ResourcesPlugin.getWorkspace().getRoot().getProject(projectName).exists()) {
message = "\"" + projectName + "\" project already exists in workspace";
}
else if (shouldCreateRunConfig() && launchConfigExists) {
message = "Run configuration \"" + projectName +
"\" project already exists (check run configuration filters)";
}
else if (createRunConfigCheckboxButton.getSelection() &&
!runConfigMemoryText.getText().isEmpty() &&
!Pattern.matches("^\\d+[KkMmGg]$", runConfigMemoryText.getText())) {
message = "Invalid run configuration memory value. Value must match JVM -Xmx flag" +
" syntax (4G, 1500m, etc).";
}
setErrorMessage(message);
setPageComplete(message == null);
if (message == null) {
GhidraProjectCreatorPreferences.setGhidraLastProjectRootPath(
projectRootDirText.getText());
}
}
}

View file

@ -0,0 +1,243 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.wizards.pages;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.utils.PackageFragmentRootSelectionDialog;
/**
* A wizard page that lets the user create a new Ghidra script.
*/
public class CreateGhidraScriptWizardPage extends WizardPage {
private IPackageFragmentRoot selecttion;
private Text scriptFolderText;
private Button scriptFolderButton;
private Text scriptNameText;
private Button javaRadioButton;
private Button pythonRadioButton;
private Text scriptAuthorText;
private Text scriptCategoryText;
private Text scriptDescriptionText;
/**
* Creates a new Ghidra script creation wizard page.
*/
public CreateGhidraScriptWizardPage() {
super("CreateGhidraScriptWizardPage");
setTitle("Create Ghidra Script");
setDescription("Create a new Ghidra script.");
}
/**
* Creates a new Ghidra script creation wizard page.
*
* @param selectedPackageFragmentRoot The current selection in the project explorer.
* Could be null if nothing is selected.
*/
public CreateGhidraScriptWizardPage(IPackageFragmentRoot selectedPackageFragmentRoot) {
this();
this.selecttion = selectedPackageFragmentRoot;
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(3, false));
// Source folder
Label sourceFolderLabel = new Label(container, SWT.NULL);
sourceFolderLabel.setText("Script folder:");
scriptFolderText = new Text(container, SWT.BORDER | SWT.SINGLE);
scriptFolderText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
scriptFolderText.setEditable(false);
scriptFolderText.setText(selecttion != null ? selecttion.getPath().toString() : "");
scriptFolderText.addModifyListener(evt -> validate());
scriptFolderButton = new Button(container, SWT.BUTTON1);
scriptFolderButton.setText("...");
scriptFolderButton.addListener(SWT.Selection, evt -> {
PackageFragmentRootSelectionDialog selectionDialog =
new PackageFragmentRootSelectionDialog(getShell(), "Script Folder Selection",
"Choose a script folder:", "Select script folder");
if (selectionDialog.open() == Window.OK) {
IFolder scriptFolder = ResourcesPlugin.getWorkspace().getRoot().getFolder(
selectionDialog.getPackageFragmentRoot().getPath());
if (scriptFolder != null) {
scriptFolderText.setText(scriptFolder.getFullPath().toString());
}
}
});
// Script name
Label scriptNameLabel = new Label(container, SWT.NULL);
scriptNameLabel.setText("Script name:");
scriptNameText = new Text(container, SWT.BORDER | SWT.SINGLE);
scriptNameText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
scriptNameText.addModifyListener(evt -> validate());
new Label(container, SWT.NONE).setText(""); // empty grid cell
// Script type
Label scriptTypeLabel = new Label(container, SWT.NULL);
scriptTypeLabel.setText("Script type:");
Group scriptTypeGroup = new Group(container, SWT.SHADOW_ETCHED_OUT);
scriptTypeGroup.setLayout(new RowLayout(SWT.HORIZONTAL));
javaRadioButton = new Button(scriptTypeGroup, SWT.RADIO);
javaRadioButton.setSelection(true);
javaRadioButton.setText("Java");
pythonRadioButton = new Button(scriptTypeGroup, SWT.RADIO);
pythonRadioButton.setSelection(false);
pythonRadioButton.setText("Python");
new Label(container, SWT.NONE).setText(""); // empty grid cell
// Script author
Label scriptAuthorLabel = new Label(container, SWT.NULL);
scriptAuthorLabel.setText("Script author:");
scriptAuthorText = new Text(container, SWT.BORDER | SWT.SINGLE);
scriptAuthorText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
scriptAuthorText.addModifyListener(evt -> validate());
new Label(container, SWT.NONE).setText(""); // empty grid cell
// Script category
Label scriptCategoryLabel = new Label(container, SWT.NULL);
scriptCategoryLabel.setText("Script category:");
scriptCategoryText = new Text(container, SWT.BORDER | SWT.SINGLE);
scriptCategoryText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
scriptCategoryText.addModifyListener(evt -> validate());
new Label(container, SWT.NONE).setText(""); // empty grid cell
// Script description
Label scriptDescriptionLabel = new Label(container, SWT.NULL);
scriptDescriptionLabel.setText("Script description:");
scriptDescriptionText =
new Text(container, SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | SWT.BORDER);
scriptDescriptionText.setLayoutData(new GridData(GridData.FILL_BOTH));
scriptDescriptionText.addModifyListener(evt -> validate());
new Label(container, SWT.NONE).setText(""); // empty grid cell
validate();
setControl(container);
}
/**
* Gets the script's target source folder. This is where the script will get created.
*
* @return The script's target source folder. Could be null if unspecified, however, the page
* will not be valid until the source folder is valid, so it should never be null when called
* by other classes.
*/
public IFolder getScriptFolder() {
if (!scriptFolderText.getText().isEmpty()) {
try {
IPath path = new Path(scriptFolderText.getText());
return ResourcesPlugin.getWorkspace().getRoot().getFolder(path);
}
catch (IllegalArgumentException e) {
// Fall through to return null
}
}
return null;
}
/**
* Gets the name of the script to create (including extension).
*
* @return The name of the script to create (including extension).
* Could be empty if unspecified, however, the page will not be valid until the script
* name is valid, so it should never be empty when called by other classes.
*/
public String getScriptName() {
return scriptNameText.getText() + (javaRadioButton.getSelection() ? ".java" : ".py");
}
/**
* Gets the script author.
*
* @return The script author. This is an optional field, so it could be empty.
*/
public String getScriptAuthor() {
return scriptAuthorText.getText();
}
/**
* Gets the script category.
*
* @return The script category. This is an optional field, so it could be empty.
*/
public String getScriptCategory() {
return scriptCategoryText.getText();
}
/**
* Gets the script description as an array of lines.
*
* @return The script description as an array of lines. This is an optional field,
* so it could be empty.
*/
public String[] getScriptDescription() {
return scriptDescriptionText.getText().split("\\n");
}
/**
* Validates the fields on the page and updates the page's status.
* Should be called every time a field on the page changes.
*/
private void validate() {
final String BAD = GhidraProjectUtils.ILLEGAL_FILENAME_CHARS;
final String BAD_START = GhidraProjectUtils.ILLEGAL_FILENAME_START_CHARS;
String message = null;
IFolder scriptFolder = getScriptFolder();
String scriptName = scriptNameText.getText();
if (scriptFolder == null) {
message = "Script folder must be specified";
}
else if (!scriptFolder.exists()) {
message = "Script folder does not exist";
}
else if (scriptName.isEmpty()) {
message = "Script name must be specified";
}
else if ((BAD_START + BAD).chars().anyMatch(ch -> scriptName.charAt(0) == ch)) {
message = "Script name cannot start with an invalid character:\n " + BAD_START + BAD;
}
else if (BAD.chars().anyMatch(ch -> scriptName.indexOf(ch) != -1)) {
message = "Script name cannot contain invalid characters:\n " + BAD;
}
else if (scriptFolder.getFile(getScriptName()).exists()) {
message = "Script already exists";
}
setErrorMessage(message);
setPageComplete(message == null);
}
}

View file

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

View file

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

View file

@ -0,0 +1,112 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidrascripteditor;
import java.io.IOException;
import java.net.ServerSocket;
import ghidradev.*;
import ghidradev.ghidrascripteditor.preferences.GhidraScriptEditorPreferences;
/**
* High level driver for initializing the Ghidra Script Editor subcomponent. Should get called
* by the startup extension. This subcomponent is responsible for receiving a script from Ghidra's
* script manager to Eclipse over a socket.
*
* @see GhidraDevStartup
*/
public class ScriptEditorInitializer {
private static ServerSocket serverSocket;
/**
* Initializes the Ghidra Script Editor subcomponent. Nothing in the package should be
* used until this initialization happens. Should be called during Eclipse startup.
*
* @param firstTimeConsent True if the user has just consented to opening ports; otherwise,
* false.
* @see GhidraDevStartup
*/
public static void init(boolean firstTimeConsent) {
if (firstTimeConsent) {
GhidraScriptEditorPreferences.setScriptEditorEnabled(true);
}
listen(GhidraScriptEditorPreferences.getScriptEditorPort());
}
/**
* Listens for socket connections on the given port. If there is a problem listening,
* a popup is displayed for the user.
*
* @param port The port to listen on. If the port is -1, this method doesn't do anything.
*/
private static void listen(int port) {
if (!GhidraScriptEditorPreferences.isScriptEditorEnabled()) {
EclipseMessageUtils.info(
Activator.PLUGIN_ID + " Script Editor port listening is disabled in preferences.");
return;
}
if (port == -1) {
EclipseMessageUtils.info(Activator.PLUGIN_ID +
" Script Editor port listening is disabled, port not set in preferences.");
return;
}
try {
serverSocket = new ServerSocket(port);
EclipseMessageUtils.info(
Activator.PLUGIN_ID + " Script Editor is listening on port " + port);
Activator.getDefault().registerCloseable(serverSocket);
}
catch (IOException e) {
EclipseMessageUtils.showErrorDialog(Activator.PLUGIN_ID + " Script Editor",
"Failed to listen for connections on port " + port +
". The Script Editor features will be disabled until a valid port is selected in preferences.");
return;
}
new Thread(new SocketSetupRunnable(serverSocket)).start();
}
/**
* Called when the script editor preferences change.
*
* @param enabledWasChanged True if the enablement was changed to a new value.
* @param portWasChanged True if the port preferences was changed to a new value.
*/
public static void notifyPreferencesChanged(boolean enabledWasChanged, boolean portWasChanged) {
if (!enabledWasChanged && !portWasChanged) {
return;
}
// Close the old server socket. Its job is done.
try {
if (serverSocket != null) {
serverSocket.close();
Activator.getDefault().unregisterCloseable(serverSocket);
serverSocket = null;
}
}
catch (IOException e) {
// Oh well, we tried. This port probably won't work next time they pick it.
}
// Listen on the new port
listen(GhidraScriptEditorPreferences.getScriptEditorPort());
}
}

View file

@ -0,0 +1,73 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidrascripteditor;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import org.eclipse.swt.widgets.Display;
/**
* Code intended to run in a new thread that accepts client connections on the given
* server socket, and opens the requested file in an editor.
*/
public class SocketSetupRunnable implements Runnable {
private ServerSocket serverSocket = null;
/**
* Creates a new runnable.
*
* @param serverSocket The server socket that will be accepting client connections.
*/
public SocketSetupRunnable(ServerSocket serverSocket) {
this.serverSocket = serverSocket;
}
@Override
public void run() {
while (!serverSocket.isClosed()) {
try (Socket socket = serverSocket.accept();
BufferedReader input =
new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter output = new PrintWriter(socket.getOutputStream())) {
String line;
while ((line = input.readLine()) != null) {
String command = line.substring(0, line.indexOf('_'));
if (command.equals("open")) {
openInEditor(line.substring(line.indexOf('_') + 1));
}
}
}
catch (IOException e) {
// Socket was closed
}
}
}
/**
* Opens the given file in an editor.
*
* @param path The path to the file to open.
*/
private void openInEditor(String path) {
File fileToOpen = new File(path);
if (fileToOpen.exists() && fileToOpen.isFile()) {
Display.getDefault().asyncExec(new OpenFileRunnable(path));
}
}
}

View file

@ -0,0 +1,34 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidrascripteditor.preferences;
import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
import org.eclipse.jface.preference.IPreferenceStore;
import ghidradev.Activator;
/**
* Class used to initialize default preference values.
*/
public class GhidraScriptEditorPreferenceInitializer extends AbstractPreferenceInitializer {
@Override
public void initializeDefaultPreferences() {
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
store.setDefault(GhidraScriptEditorPreferences.GHIDRA_SCRIPT_EDITOR_ENABLED, false);
store.setDefault(GhidraScriptEditorPreferences.GHIDRA_SCRIPT_EDITOR_PORT_NUMBER, "12321");
}
}

View file

@ -0,0 +1,146 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidrascripteditor.preferences;
import org.eclipse.jface.preference.*;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import ghidradev.Activator;
import ghidradev.ghidrascripteditor.ScriptEditorInitializer;
/**
* This class represents a preference page that
* is contributed to the Preferences dialog. By
* subclassing <samp>FieldEditorPreferencePage</samp>, we
* can use the field support built into JFace that allows
* us to create a page that is small and knows how to
* save, restore and apply itself.
* <p>
* This page is used to modify preferences only. They
* are stored in the preference store that belongs to
* the main plug-in class. That way, preferences can
* be accessed directly via the preference store.
*/
public class GhidraScriptEditorPreferencePage extends FieldEditorPreferencePage implements
IWorkbenchPreferencePage {
private BooleanFieldEditor enabledField;
private StringFieldEditor portField;
private String previousEnabledString = null;
private String currentEnabledString = null;
private String previousPortString = null;
private String currentPortString = null;
public GhidraScriptEditorPreferencePage() {
super(GRID);
}
@Override
public void init(IWorkbench workbench) {
setPreferenceStore(Activator.getDefault().getPreferenceStore());
}
@Override
public void createFieldEditors() {
enabledField =
new BooleanFieldEditor(GhidraScriptEditorPreferences.GHIDRA_SCRIPT_EDITOR_ENABLED,
"Enabled", getFieldEditorParent());
portField =
new StringFieldEditor(GhidraScriptEditorPreferences.GHIDRA_SCRIPT_EDITOR_PORT_NUMBER,
"Port:", getFieldEditorParent());
addField(enabledField);
addField(portField);
}
@Override
public void checkState() {
super.checkState();
if (!isValid()) {
return;
}
String portValue = portField.getStringValue();
if (!portValue.isEmpty()) {
try {
int portNumber = Integer.parseInt(portValue);
if (portNumber < 1024 || portNumber > 0xFFFF) {
setErrorMessage("Port must be between 1024 and 65535.");
setValid(false);
return;
}
}
catch (NumberFormatException e) {
setErrorMessage("Port must be an integer.");
setValid(false);
return;
}
}
}
@Override
public void propertyChange(PropertyChangeEvent event) {
super.propertyChange(event);
if (event.getProperty().equals(FieldEditor.VALUE)) {
checkState();
}
if (event.getSource() == enabledField) {
if (previousEnabledString == null) {
previousEnabledString = event.getOldValue().toString();
}
currentEnabledString = event.getNewValue().toString();
}
else if (event.getSource() == portField) {
if (previousPortString == null) {
previousPortString = event.getOldValue().toString();
}
currentPortString = event.getNewValue().toString();
}
}
@Override
public boolean performOk() {
super.performOk();
boolean enabledWasChanged = false;
boolean portWasChanged = false;
if (currentEnabledString != null && previousEnabledString != null) {
if (!currentEnabledString.equals(previousEnabledString)) {
enabledWasChanged = true;
}
}
if (currentPortString != null && previousPortString != null) {
if (!currentPortString.equals(previousPortString)) {
portWasChanged = true;
}
}
ScriptEditorInitializer.notifyPreferencesChanged(enabledWasChanged, portWasChanged);
previousEnabledString = null;
previousPortString = null;
return true;
}
@Override
public boolean performCancel() {
super.performCancel();
previousEnabledString = null;
previousPortString = null;
return true;
}
}

View file

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

View file

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

View file

@ -0,0 +1,152 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidrasymbollookup;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.swt.widgets.Display;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidrasymbollookup.preferences.GhidraSymbolLookupPreferences;
import ghidradev.ghidrasymbollookup.utils.CdtUtils;
public class SocketSetupRunnable implements Runnable {
private ServerSocket serverSocket = null;
private OpenDeclarations openDeclsDialog;
private IProject project;
private boolean isInitialized;
public SocketSetupRunnable(ServerSocket serverSocket) {
this.serverSocket = serverSocket;
}
@Override
public void run() {
while (!serverSocket.isClosed()) {
try (Socket socket = serverSocket.accept();
BufferedReader input =
new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter output = new PrintWriter(socket.getOutputStream())) {
String result = "";
// Setup the project to look in and handle any problems like project being closed
if (!isInitialized) {
result = init();
}
// If the project is closed while running the plugin and you try to look in it again
if (isInitialized && !project.isOpen()) {
try {
input.readLine();
}
catch (IOException e) {
EclipseMessageUtils.error(
"Unexpected exception receiving symbol name: " + e.getMessage());
}
isInitialized = false;
result = init();
}
if (isInitialized) {
String symbolName = null;
try {
symbolName = input.readLine();
}
catch (IOException e) {
EclipseMessageUtils.error(
"Unexpected exception looking for symbol: " + e.getMessage());
e.printStackTrace();
}
if (isInitialized) {
lookup(symbolName, output);
}
else {
output.write("Failed to initialize CDT project");
output.flush();
}
}
else {
output.write(result);
}
}
catch (IOException e) {
// Socket was closed
}
}
}
private String init() {
String projectName = GhidraSymbolLookupPreferences.getSymbolLookupProjectName();
final String errorMessageContainer[] = { "" };
Display.getDefault().syncExec(() -> {
while (!isInitialized) {
if (projectName == null) {
errorMessageContainer[0] =
"Project name not defined in the Ghidra Symbol Lookup preference page.";
EclipseMessageUtils.showWarnDialog("Ghidra Symbol Lookup",
errorMessageContainer[0]);
break;
}
project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
if (!project.exists()) {
errorMessageContainer[0] =
"The project \"" + projectName + "\" does not exist " +
"in your workspace. Please edit the \"Project Name\" field in " +
"the Ghidra Symbol Lookup preference page.";
EclipseMessageUtils.showWarnDialog("Project Does Not Exist",
errorMessageContainer[0]);
break;
}
if (!project.isOpen()) {
errorMessageContainer[0] = "Please open the project \"" + project.getName() +
"\" or choose a different one in the plugin preference page.";
EclipseMessageUtils.showWarnDialog("Project Not Open",
errorMessageContainer[0]);
break;
}
if (!CdtUtils.isCdtProject(project)) {
errorMessageContainer[0] =
"The project \"" + project.getName() + "\" is not a C or C++ project." +
"\nPlease edit the \"Project Name\" field in the Ghidra Symbol " +
"Lookup preference page.";
EclipseMessageUtils.showWarnDialog("Not a C/C++ Project",
errorMessageContainer[0]);
break;
}
openDeclsDialog = new OpenDeclarations(project);
isInitialized = true;
}
});
return errorMessageContainer[0];
}
private void lookup(String symbolName, PrintWriter output) {
EclipseMessageUtils.info("Looking for symbol name: " + symbolName);
boolean result = openDeclsDialog.open(symbolName);
if (result) {
output.write("Found symbol " + symbolName + "\n");
}
else {
output.write("Couldn't find " + symbolName + "\n");
}
output.flush();
}
}

View file

@ -0,0 +1,112 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidrasymbollookup;
import java.io.IOException;
import java.net.ServerSocket;
import ghidradev.*;
import ghidradev.ghidrasymbollookup.preferences.GhidraSymbolLookupPreferences;
/**
* High level driver for initializing the Ghidra Symbol Lookup subcomponent. Should get called
* by the startup extension. This subcomponent is responsible for receiving symbol lookup requests
* from Ghidra to Eclipse over a socket.
*
* @see GhidraDevStartup
*/
public class SymbolLookupInitializer {
private static ServerSocket serverSocket;
/**
* Initializes the Ghidra Symbol Lookup subcomponent. Nothing in the package should be
* used until this initialization happens. Should be called during Eclipse startup.
*
* @param firstTimeConsent True if the user has just consented to opening ports; otherwise,
* false.
* @see GhidraDevStartup
*/
public static void init(boolean firstTimeConsent) {
if (firstTimeConsent) {
GhidraSymbolLookupPreferences.setSymbolLookupEnabled(true);
}
listen(GhidraSymbolLookupPreferences.getSymbolLookupPort());
}
/**
* Listens for socket connections on the given port. If there is a problem listening,
* a popup is displayed for the user.
*
* @param port The port to listen on. If the port is -1, this method doesn't do anything.
*/
private static void listen(int port) {
if (!GhidraSymbolLookupPreferences.isSymbolLookupEnabled()) {
EclipseMessageUtils.info(
Activator.PLUGIN_ID + " Symbol Lookup port listening is disabled in preferences.");
return;
}
if (port == -1) {
EclipseMessageUtils.info(Activator.PLUGIN_ID +
" Symbol Lookup port listening is disabled, port not set in preferences.");
return;
}
try {
serverSocket = new ServerSocket(port);
EclipseMessageUtils.info(
Activator.PLUGIN_ID + " Symbol Lookup is listening on port " + port);
Activator.getDefault().registerCloseable(serverSocket);
}
catch (IOException e) {
EclipseMessageUtils.showErrorDialog(Activator.PLUGIN_ID + " Symbol Lookup",
"Failed to listen for connections on port " + port +
". The Symbol Lookup features will be disabled until a valid port is selected in preferences.");
return;
}
new Thread(new SocketSetupRunnable(serverSocket)).start();
}
/**
* Called when the symbol lookup preferences change.
*
* @param enabledWasChanged True if the enablement was changed to a new value.
* @param portWasChanged True if the port preferences was changed to a new value.
*/
public static void preferencesChanged(boolean enabledWasChanged, boolean portWasChanged) {
if (!enabledWasChanged && !portWasChanged) {
return;
}
// Close the old server socket. Its job is done.
try {
if (serverSocket != null) {
serverSocket.close();
Activator.getDefault().unregisterCloseable(serverSocket);
serverSocket = null;
}
}
catch (IOException e) {
// Oh well, we tried. This port probably won't work next time they pick it.
}
// Listen on the new port
listen(GhidraSymbolLookupPreferences.getSymbolLookupPort());
}
}

View file

@ -0,0 +1,55 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidrasymbollookup.preferences;
import org.eclipse.jface.preference.StringButtonFieldEditor;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
import ghidradev.ghidrasymbollookup.utils.CdtUtils;
/**
* A field editor that lets the user select an open CDT project.
*/
public class CdtProjectFieldEditor extends StringButtonFieldEditor {
/**
* Creates a new CDT project field editor.
*
* @param name the name of the preference this field editor works on
* @param labelText the label text of the field editor
* @param parent the parent of the field editor's control
*/
public CdtProjectFieldEditor(String name, String labelText, Composite parent) {
super(name, labelText, parent);
}
@Override
protected String changePressed() {
ElementListSelectionDialog dialog =
new ElementListSelectionDialog(getShell(), new LabelProvider());
dialog.setTitle("CDT project selection");
dialog.setMessage("Select an open CDT project:");
dialog.setElements(CdtUtils.getCDTProjects().stream().map(p -> p.getName()).toArray());
dialog.open();
Object[] result = dialog.getResult();
if (result != null && result.length > 0 && result[0] instanceof String) {
return (String) result[0];
}
return null;
}
}

View file

@ -0,0 +1,35 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidrasymbollookup.preferences;
import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
import org.eclipse.jface.preference.IPreferenceStore;
import ghidradev.Activator;
/**
* Class used to initialize default preference values.
*/
public class GhidraSymbolLookupPreferenceInitializer extends AbstractPreferenceInitializer {
@Override
public void initializeDefaultPreferences() {
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
store.setDefault(GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_ENABLED, false);
store.setDefault(GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_PROJECT_NAME, "");
store.setDefault(GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_PORT_NUMBER, "12322");
}
}

View file

@ -0,0 +1,160 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidrasymbollookup.preferences;
import org.eclipse.core.resources.*;
import org.eclipse.jface.preference.*;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import ghidradev.Activator;
import ghidradev.ghidrasymbollookup.SymbolLookupInitializer;
import ghidradev.ghidrasymbollookup.utils.CdtUtils;
/**
* This class represents a preference page that is contributed to the Preferences dialog. By
* subclassing {@link FieldEditorPreferencePage}, we can use the field support built into JFace
* that allows us to create a page that is small and knows how to save, restore and apply itself.
* <p>
* This page is used to modify preferences only. They are stored in the preference store that
* belongs to the main plug-in class. That way, preferences can be accessed directly via the
* preference store.
*/
public class GhidraSymbolLookupPreferencePage extends FieldEditorPreferencePage
implements IWorkbenchPreferencePage {
private BooleanFieldEditor enabledField;
private CdtProjectFieldEditor projectField;
private StringFieldEditor portField;
private String previousEnabledString = null;
private String currentEnabledString = null;
private String previousPortString = null;
private String currentPortString = null;
public GhidraSymbolLookupPreferencePage() {
super(GRID);
}
@Override
public void init(IWorkbench workbench) {
setPreferenceStore(Activator.getDefault().getPreferenceStore());
}
@Override
public void createFieldEditors() {
enabledField =
new BooleanFieldEditor(GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_ENABLED,
"Enabled", getFieldEditorParent());
projectField = new CdtProjectFieldEditor(
GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_PROJECT_NAME, "Project Name:",
getFieldEditorParent());
portField =
new StringFieldEditor(GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_PORT_NUMBER,
"Port:", getFieldEditorParent());
addField(enabledField);
addField(projectField);
addField(portField);
}
@Override
public void checkState() {
super.checkState();
if (!isValid()) {
return;
}
String projectValue = projectField.getStringValue();
String portValue = portField.getStringValue();
setValid(true);
if (!projectValue.isEmpty()) {
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRoot workspaceRoot = workspace.getRoot();
IProject project = workspaceRoot.getProject(projectValue);
if (!project.exists() || !project.isOpen() || !CdtUtils.isCdtProject(project)) {
setErrorMessage("Project is not an open CDT project.");
setValid(false);
return;
}
}
if (!portValue.isEmpty()) {
try {
int portNumber = Integer.parseInt(portValue);
if (portNumber < 1024 || portNumber > 0xFFFF) {
setErrorMessage("Port must be between 1024 and 65535.");
setValid(false);
return;
}
}
catch (NumberFormatException e) {
setErrorMessage("Port must be an integer.");
setValid(false);
return;
}
}
}
@Override
public void propertyChange(PropertyChangeEvent event) {
super.propertyChange(event);
if (event.getProperty().equals(FieldEditor.VALUE)) {
checkState();
}
if (event.getSource() == enabledField) {
if (previousEnabledString == null) {
previousEnabledString = event.getOldValue().toString();
}
currentEnabledString = event.getNewValue().toString();
}
else if (event.getSource() == portField) {
if (previousPortString == null) {
previousPortString = event.getOldValue().toString();
}
currentPortString = event.getNewValue().toString();
}
}
@Override
public boolean performOk() {
super.performOk();
boolean enabledWasChanged = false;
boolean portWasChanged = false;
if (currentEnabledString != null && previousEnabledString != null) {
if (!currentEnabledString.equals(previousEnabledString)) {
enabledWasChanged = true;
}
}
if (currentPortString != null && previousPortString != null) {
if (!currentPortString.equals(previousPortString)) {
portWasChanged = true;
}
}
SymbolLookupInitializer.preferencesChanged(enabledWasChanged, portWasChanged);
previousEnabledString = null;
previousPortString = null;
return true;
}
@Override
public boolean performCancel() {
super.performCancel();
previousEnabledString = null;
previousPortString = null;
return true;
}
}

View file

@ -0,0 +1,91 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidrasymbollookup.preferences;
import org.eclipse.jface.preference.IPreferenceStore;
import ghidradev.Activator;
/**
* Ghidra symbol lookup preference definitions and related utility methods.
*/
public class GhidraSymbolLookupPreferences {
/**
* Whether or not the symbol lookup feature is enabled.
*/
static final String GHIDRA_SYMBOL_LOOKUP_ENABLED = "ghidradev.symbolLookupEnabled";
/**
* Name of CDT project that will be used for symbol lookup.
*/
static final String GHIDRA_SYMBOL_LOOKUP_PROJECT_NAME = "ghidradev.symbolLookupProjectName";
/**
* Port used for symbol lookup.
*/
static final String GHIDRA_SYMBOL_LOOKUP_PORT_NUMBER = "ghidradev.symbolLookupPortNumber";
/**
* Gets whether or not the symbol lookup feature is enabled.
*
* @return True if the symbol lookup feature is enabled; otherwise, false.
*/
public static boolean isSymbolLookupEnabled() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
return prefs.getBoolean(GHIDRA_SYMBOL_LOOKUP_ENABLED);
}
/**
* Sets whether or not the symbol lookup feature is enabled.
*
* @param enabled True to enable the symbol lookup feature; false to disable it.
*/
public static void setSymbolLookupEnabled(boolean enabled) {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
prefs.setValue(GHIDRA_SYMBOL_LOOKUP_ENABLED, enabled);
}
/**
* Gets the name of the CDT project that used for symbol lookup.
*
* @return The name of the CDT project used for symbol lookup, or null
* if one hasn't been set.
*/
public static String getSymbolLookupProjectName() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
String name = prefs.getString(GHIDRA_SYMBOL_LOOKUP_PROJECT_NAME);
if (name.isEmpty()) {
return null;
}
return name;
}
/**
* Gets the port used for symbol lookup.
*
* @return The port used for symbol lookup. Will return -1 if the port is not set.
*/
public static int getSymbolLookupPort() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
try {
return Integer.parseInt(prefs.getString(GHIDRA_SYMBOL_LOOKUP_PORT_NUMBER));
}
catch (NumberFormatException e) {
return -1;
}
}
}

View file

@ -0,0 +1,72 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidrasymbollookup.utils;
import java.util.*;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import ghidradev.EclipseMessageUtils;
/**
* Utility methods for interacting with CDT.
*/
public class CdtUtils {
/**
* CDT C nature.
*/
public static final String C_NATURE = "org.eclipse.cdt.core.cnature";
/**
* CDT C++ nature.
*/
public static final String CC_NATURE = "org.eclipse.cdt.core.ccnature";
/**
* Gets all of the open CDT projects in the workspace.
*
* @return A collection of the open CDT projects in the workspace.
*/
public static Collection<IProject> getCDTProjects() {
List<IProject> cdtProjects = new ArrayList<>();
for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
if (project.isOpen() && isCdtProject(project)) {
cdtProjects.add(project);
}
}
return cdtProjects;
}
/**
* Checks to see if the given project is a CDT project.
*
* @param project The project to check.
* @return True if the given project is a CDT project; otherwise, false.
*/
public static boolean isCdtProject(IProject project) {
try {
return project != null && (project.hasNature(C_NATURE) || project.hasNature(CC_NATURE));
}
catch (CoreException e) {
EclipseMessageUtils.error("CDT project check failed", e);
return false;
}
}
}