mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-23 13:16:48 +08:00
Merge remote-tracking branch 'origin/GP-5148_ryanmkurtz_vscode--SQUASHED'
This commit is contained in:
@@ -607,6 +607,7 @@ src/main/help/help/topics/Trees/images/FilterOptions.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Trees/images/TableColumnFilter.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Trees/images/TableColumnFilterAfterFilterApplied.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Trees/images/TableColumnFilterDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/VSCodeIntegration/VSCodeIntegration.htm||GHIDRA||||END|
|
||||
src/main/help/help/topics/ValidateProgram/ValidateProgram.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/ValidateProgram/images/ValidateProgram.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/ValidateProgram/images/ValidateProgramDone.png||GHIDRA||||END|
|
||||
@@ -934,6 +935,7 @@ src/main/resources/images/view_left_right.png||Nuvola Icons - LGPL 2.1|||Nuvola
|
||||
src/main/resources/images/view_top_bottom.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
|
||||
src/main/resources/images/viewmag+.png||Crystal Clear Icons - LGPL 2.1||||END|
|
||||
src/main/resources/images/viewmag.png||GHIDRA||||END|
|
||||
src/main/resources/images/vscode.png||MIT||||END|
|
||||
src/main/resources/images/window.png||GHIDRA||||END|
|
||||
src/main/resources/images/wizard.png||Nuvola Icons - LGPL 2.1|||nuvola|END|
|
||||
src/main/resources/images/x-office-document-template.png||Tango Icons - Public Domain|||tango icon set|END|
|
||||
|
||||
@@ -267,6 +267,7 @@ icon.plugin.scriptmanager.run = icon.run
|
||||
icon.plugin.scriptmanager.run.again = play_again.png
|
||||
icon.plugin.scriptmanager.edit = accessories-text-editor.png
|
||||
icon.plugin.scriptmanager.edit.eclipse = eclipse.png
|
||||
icon.plugin.scriptmanager.edit.vscode = vscode.png
|
||||
icon.plugin.scriptmanager.keybinding = key.png
|
||||
icon.plugin.scriptmanager.delete = icon.delete
|
||||
icon.plugin.scriptmanager.rename = icon.rename
|
||||
|
||||
+11
@@ -210,6 +210,17 @@
|
||||
Ghidra scripts in Eclipse, see Extensions/Eclipse/GhidraDev/GhidraDev_README.html.</I></P>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3 align="left"><A name="EditVSCode"></A>Edit Script with Visual Studio Code <IMG src="images/vscode.png" border="0"></H3>
|
||||
<BLOCKQUOTE>
|
||||
<P align="left">Edits the selected script in Visual Studio Code.
|
||||
<BLOCKQUOTE>
|
||||
<P align="left"><I><IMG src="help/shared/note.png" border="0">Before a script can be edited in
|
||||
Visual Studio Code, a Visual Studio Code executable path must be defined in the Tool's
|
||||
<A href="help/topics/VSCodeIntegration/VSCodeIntegration.htm"> Visual Studio Code Integration</A>
|
||||
options if Visual Studio Code is installed in a non-default location.</I></P>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3 align="left"><A name="Key_Binding"></A>Assign Key Binding <IMG src="images/key.png"
|
||||
border="0"></H3>
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Visual Studio Code Integration</TITLE>
|
||||
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
<H1><A name="VSCodeIntegration"></A>Visual Studio Code Integration</H1>
|
||||
|
||||
<P>Ghidra is capable of integrating with an existing Visual Studio Code installation to aid
|
||||
in the development of Ghidra scripts and modules.<p>
|
||||
|
||||
<H2><A name="VSCodeIntegrationOptions"></A>Visual Studio Code Integration Tool Options</H2>
|
||||
<P>The following Front-End tool options (Edit -> Tool Options) may need to be configured for
|
||||
Ghidra to successfully launch Visual Studio Code on your platform.</P>
|
||||
<BR>
|
||||
|
||||
<CENTER>
|
||||
<TABLE border="1" width="80%">
|
||||
<TBODY>
|
||||
<TR valign="middle">
|
||||
<TD colspan="2" bgcolor="#c0c0c0" valign="top" align="left">
|
||||
<P align="center"><B>Tool Options</B></P>
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD bgcolor="#DDDDDD" align="left"><B>Option</B></TD>
|
||||
<TD bgcolor="#DDDDDD" align="left"><B>Description</B></TD>
|
||||
</TR>
|
||||
|
||||
<TR valign="middle">
|
||||
<TD valign="top" width="200" align="left">Visual Studio Code Executable Path</TD>
|
||||
<TD valign="top" align="left">Path to a Visual Studio Code executable file. It defaults
|
||||
to the most commonly used location on your specific platform.</TD>
|
||||
</TR>
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
</CENTER>
|
||||
|
||||
<H2><A name="VSCodeModuleProject"></A>Create Visual Studio Code Module Project</H2>
|
||||
<P>This action creates a new Visual Studio Code project folder which can be used as a convenient
|
||||
starting point to develop a new Ghidra module. The new project will be linked against the
|
||||
version of Ghidra that was used to create the project. Once the project is created, the
|
||||
associated Ghidra version cannot change. This behavior may become more flexible in the
|
||||
future.</P>
|
||||
|
||||
<P>Visual Studio Code launchers are provided which will allow you to debug your module's code.
|
||||
Also, a Gradle task named <i>ghidra/distributeExtension</i> is provided that will allow you to
|
||||
build a distributable Ghidra extension.
|
||||
</P>
|
||||
|
||||
</BODY>
|
||||
</HTML>
|
||||
+3
-3
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -35,7 +35,7 @@ import ghidra.util.HelpLocation;
|
||||
packageName = CorePluginPackage.NAME,
|
||||
category = PluginCategoryNames.COMMON,
|
||||
shortDescription = "Eclipse Integration Options",
|
||||
description = "Options Eclipse Integration"
|
||||
description = "Options for Eclipse Integration"
|
||||
)
|
||||
//@formatter:on
|
||||
public class EclipseIntegrationOptionsPlugin extends Plugin implements ApplicationLevelOnlyPlugin {
|
||||
|
||||
+5
-2
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -226,6 +226,9 @@ class GhidraScriptActionManager {
|
||||
createScriptAction("EditEclipse", "Edit with Eclipse", "Edit Script with Eclipse",
|
||||
new GIcon("icon.plugin.scriptmanager.edit.eclipse"), null, provider::editScriptEclipse);
|
||||
|
||||
createScriptAction("EditVSCode", "Edit with VSCode", "Edit Script with Visual Studio Code",
|
||||
new GIcon("icon.plugin.scriptmanager.edit.vscode"), null, provider::editScriptVSCode);
|
||||
|
||||
keyBindingAction =
|
||||
createScriptAction("Key Binding", "Assign Key Binding", "Assign Key Binding",
|
||||
new GIcon("icon.plugin.scriptmanager.keybinding"), null,
|
||||
|
||||
+14
@@ -927,6 +927,20 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||
plugin.tryToEditFileInEclipse(script);
|
||||
}
|
||||
|
||||
void editScriptVSCode() {
|
||||
ResourceFile script = getSelectedScript();
|
||||
if (script == null) {
|
||||
plugin.getTool().setStatusInfo("Script is null.");
|
||||
return;
|
||||
}
|
||||
if (!script.exists()) {
|
||||
plugin.getTool().setStatusInfo("Script " + script.getName() + " does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.tryToEditFileInVSCode(script);
|
||||
}
|
||||
|
||||
GhidraScriptEditorComponentProvider editScriptInGhidra(ResourceFile script) {
|
||||
GhidraScriptEditorComponentProvider editor = editorMap.get(script);
|
||||
if (editor == null) {
|
||||
|
||||
+9
-2
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -155,6 +155,13 @@ public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScript
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryToEditFileInVSCode(ResourceFile file) {
|
||||
VSCodeIntegrationService service = tool.getService(VSCodeIntegrationService.class);
|
||||
service.launchVSCode(file.getFile(false));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void programClosed(Program program) {
|
||||
provider.programClosed(program);
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/* ###
|
||||
* 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 ghidra.app.plugin.core.vscode;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
import ghidra.framework.main.ApplicationLevelOnlyPlugin;
|
||||
import ghidra.framework.options.OptionType;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.util.HelpLocation;
|
||||
|
||||
/**
|
||||
* {@link Plugin} responsible for registering Visual Studio Code-related options
|
||||
*/
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
status = PluginStatus.RELEASED,
|
||||
packageName = CorePluginPackage.NAME,
|
||||
category = PluginCategoryNames.COMMON,
|
||||
shortDescription = "Visual Studio Code Integration Options",
|
||||
description = "Options for Visual Studio Code Integration"
|
||||
)
|
||||
//@formatter:on
|
||||
public class VSCodeIntegrationOptionsPlugin extends Plugin implements ApplicationLevelOnlyPlugin {
|
||||
|
||||
public static final String PLUGIN_OPTIONS_NAME = "Visual Studio Code Integration";
|
||||
|
||||
public static final String VSCODE_EXE_PATH_OPTION = "Visual Studio Code Executable Path";
|
||||
private static final String VSCODE_EXE_PATH_DESC = "Path to Visual Studio Code executable";
|
||||
private static final File VSCODE_EXE_PATH_DEFAULT = getDefaultVSCodeExecutable();
|
||||
|
||||
public VSCodeIntegrationOptionsPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
super.init();
|
||||
ToolOptions options = tool.getOptions(PLUGIN_OPTIONS_NAME);
|
||||
options.registerOption(VSCODE_EXE_PATH_OPTION, OptionType.FILE_TYPE,
|
||||
VSCODE_EXE_PATH_DEFAULT, null, VSCODE_EXE_PATH_DESC);
|
||||
options.setOptionsHelpLocation(
|
||||
new HelpLocation("VSCodeIntegration", "VSCodeIntegrationOptions"));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the default Visual Studio Code executable location for the current platform}
|
||||
*/
|
||||
private static File getDefaultVSCodeExecutable() {
|
||||
return switch (OperatingSystem.CURRENT_OPERATING_SYSTEM) {
|
||||
case WINDOWS -> new File(System.getenv("LOCALAPPDATA"),
|
||||
"Programs/Microsoft VS Code/bin/code.cmd");
|
||||
case MAC_OS_X -> new File(
|
||||
"/Applications/Visual Studio Code.app/Contents/MacOS/Electron");
|
||||
case LINUX -> new File("/usr/bin/code");
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+259
-57
@@ -13,11 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// Creates a new VSCode project for Ghidra script and module development.
|
||||
// @category Development
|
||||
package ghidra.app.plugin.core.vscode;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
@@ -25,22 +23,81 @@ import org.apache.commons.io.*;
|
||||
|
||||
import com.google.gson.*;
|
||||
|
||||
import docking.DockingWindowManager;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.options.OptionsService;
|
||||
import docking.tool.ToolConstants;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.values.ValuesMapDialog;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.*;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.ProgramPlugin;
|
||||
import ghidra.app.services.VSCodeIntegrationService;
|
||||
import ghidra.features.base.values.GhidraValuesMap;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.ApplicationProperties;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.task.TaskLauncher;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
public class VSCodeProjectScript extends GhidraScript {
|
||||
/**
|
||||
* {@link Plugin} responsible integrating Ghidra with Visual Studio Code
|
||||
*/
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
status = PluginStatus.RELEASED,
|
||||
packageName = CorePluginPackage.NAME,
|
||||
category = PluginCategoryNames.COMMON,
|
||||
shortDescription = "Visual Studio Code Integration",
|
||||
description = "Allows Ghidra to integrate with Visual Studio Code.",
|
||||
servicesRequired = { OptionsService.class },
|
||||
servicesProvided = { VSCodeIntegrationService.class }
|
||||
)
|
||||
//@formatter:on
|
||||
public class VSCodeIntegrationPlugin extends ProgramPlugin implements VSCodeIntegrationService {
|
||||
|
||||
private ToolOptions options;
|
||||
private Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
||||
|
||||
/**
|
||||
* Create a new {@link VSCodeIntegrationPlugin}
|
||||
*
|
||||
* @param tool The associated {@link PluginTool tool}
|
||||
*/
|
||||
public VSCodeIntegrationPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() throws Exception {
|
||||
public void init() {
|
||||
super.init();
|
||||
|
||||
options = AppInfo.getFrontEndTool().getOptions(
|
||||
VSCodeIntegrationOptionsPlugin.PLUGIN_OPTIONS_NAME);
|
||||
|
||||
new ActionBuilder("CreateVSCodeModuleProject", name)
|
||||
.menuPath(ToolConstants.MENU_TOOLS, "Create VSCode Module Project...")
|
||||
.menuIcon(new GIcon("icon.plugin.scriptmanager.edit.vscode"))
|
||||
.description("Creates a new Visual Studio Code module project.")
|
||||
.helpLocation(new HelpLocation("VSCodeIntegration", "VSCodeModuleProject"))
|
||||
.onAction(context -> showNewProjectDialog())
|
||||
.buildAndInstall(tool);
|
||||
}
|
||||
|
||||
/**
|
||||
* Present the user with a dialog that allows them to select a name and location for their new
|
||||
* Visual Studio Code Module Project
|
||||
*/
|
||||
private void showNewProjectDialog() {
|
||||
if (!SystemUtilities.isInReleaseMode()) {
|
||||
printerr("This script may only run from a built Ghidra release.");
|
||||
Msg.showInfo(this, tool.getToolFrame(), name,
|
||||
"This action may only run from a built Ghidra release.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -50,27 +107,132 @@ public class VSCodeProjectScript extends GhidraScript {
|
||||
GhidraValuesMap values = new GhidraValuesMap();
|
||||
values.defineString(PROJECT_NAME_PROMPT);
|
||||
values.defineDirectory(PROJECT_ROOT_PROMPT, new File(System.getProperty("user.home")));
|
||||
values = askValues("Setup New VSCode Project", null, values);
|
||||
ValuesMapDialog dialog =
|
||||
new ValuesMapDialog("Create New Visual Studio Code Module Project", null, values);
|
||||
DockingWindowManager.showDialog(dialog);
|
||||
if (dialog.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
values = (GhidraValuesMap) dialog.getValues();
|
||||
|
||||
String projectName = values.getString(PROJECT_NAME_PROMPT);
|
||||
File projectRootDir = values.getFile(PROJECT_ROOT_PROMPT);
|
||||
File projectDir = new File(projectRootDir, projectName);
|
||||
if (projectDir.exists()) {
|
||||
printerr("Directory '%s' already exists...exiting".formatted(projectDir));
|
||||
Msg.showError(this, tool.getToolFrame(), name,
|
||||
"Directory '%s' already exists...exiting".formatted(projectDir));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
createVSCodeModuleProject(projectDir);
|
||||
Msg.showInfo(this, tool.getToolFrame(), name,
|
||||
"Successfully created Visual Studio Code module project directory at: " +
|
||||
projectDir);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, tool.getToolFrame(), name,
|
||||
"Failed to create Visual Studio Code module project directory at: " + projectDir,
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ToolOptions getVSCodeIntegrationOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getVSCodeExecutableFile() throws FileNotFoundException {
|
||||
File vscodeExecutableFile =
|
||||
options.getFile(VSCodeIntegrationOptionsPlugin.VSCODE_EXE_PATH_OPTION, null);
|
||||
if (vscodeExecutableFile == null || !vscodeExecutableFile.isFile()) {
|
||||
throw new FileNotFoundException(
|
||||
"Visual Studio Code installation executable file does not exist.");
|
||||
}
|
||||
return vscodeExecutableFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchVSCode(File file) {
|
||||
TaskLauncher.launch(new VSCodeLauncherTask(this, file));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleVSCodeError(String error, boolean askAboutOptions, Throwable t) {
|
||||
if (askAboutOptions && !SystemUtilities.isInHeadlessMode()) {
|
||||
SystemUtilities.runSwingNow(() -> {
|
||||
int choice =
|
||||
OptionDialog.showYesNoDialog(null, "Failed to launch Visual Studio Code",
|
||||
error + "\nWould you like to verify your \"" +
|
||||
VSCodeIntegrationOptionsPlugin.PLUGIN_OPTIONS_NAME +
|
||||
"\" options now?");
|
||||
if (choice == OptionDialog.YES_OPTION) {
|
||||
AppInfo.getFrontEndTool()
|
||||
.getService(OptionsService.class)
|
||||
.showOptionsDialog(
|
||||
VSCodeIntegrationOptionsPlugin.PLUGIN_OPTIONS_NAME, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
Msg.showError(VSCodeIntegrationPlugin.class, null,
|
||||
"Failed to launch Visual Studio Code", error, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createVSCodeModuleProject(File projectDir) throws IOException {
|
||||
|
||||
File installDir = Application.getInstallationDirectory().getFile(false);
|
||||
Map<String, String> classpathSourceMap = getClasspathSourceMap();
|
||||
writeSettings(installDir, projectDir, classpathSourceMap);
|
||||
writeLaunch(installDir, projectDir, classpathSourceMap);
|
||||
|
||||
JsonObject settings = createSettings(installDir, projectDir, classpathSourceMap,
|
||||
List.of("src/main/java", "ghidra_scripts"), "bin/main");
|
||||
JsonObject launch =
|
||||
createLaunch(installDir, projectDir, classpathSourceMap, "${workspaceFolder}");
|
||||
|
||||
File vscodeDir = new File(projectDir, ".vscode");
|
||||
if (!FileUtilities.mkdirs(vscodeDir)) {
|
||||
throw new IOException("Failed to create: " + vscodeDir);
|
||||
}
|
||||
File settingsFile = new File(vscodeDir, "settings.json");
|
||||
File launchFile = new File(vscodeDir, "launch.json");
|
||||
FileUtils.writeStringToFile(settingsFile, gson.toJson(settings), StandardCharsets.UTF_8);
|
||||
FileUtils.writeStringToFile(launchFile, gson.toJson(launch), StandardCharsets.UTF_8);
|
||||
|
||||
writeSampleScriptJava(projectDir);
|
||||
writeSampleScriptPyGhidra(projectDir);
|
||||
writeSampleModule(installDir, projectDir);
|
||||
}
|
||||
|
||||
println("Successfully created VSCode project directory at: " + projectDir);
|
||||
println(
|
||||
"To debug, please close Ghidra and relaunch from the VSCode Ghidra launch configuration.");
|
||||
@Override
|
||||
public void addToVSCodeWorkspace(File workspaceFile, File projectDir) throws IOException {
|
||||
|
||||
File installDir = Application.getInstallationDirectory().getFile(false);
|
||||
Map<String, String> classpathSourceMap = getClasspathSourceMap();
|
||||
JsonObject settings =
|
||||
createSettings(installDir, projectDir, classpathSourceMap, List.of("."), null);
|
||||
JsonObject launch = createLaunch(installDir, projectDir, classpathSourceMap, null);
|
||||
|
||||
JsonObject workspace;
|
||||
if (workspaceFile.isFile()) {
|
||||
String str = FileUtils.readFileToString(workspaceFile, StandardCharsets.UTF_8);
|
||||
JsonElement element = JsonParser.parseString(str);
|
||||
if (!(element instanceof JsonObject json)) {
|
||||
throw new IOException("'%s' was not a JsonObject".formatted(workspaceFile));
|
||||
}
|
||||
workspace = addToExistingWorkspace(projectDir, json, settings, launch);
|
||||
}
|
||||
else {
|
||||
workspace = addToExistingWorkspace(projectDir, createNewWorkspace(settings, launch),
|
||||
settings, launch);
|
||||
}
|
||||
|
||||
if (!FileUtilities.mkdirs(workspaceFile.getParentFile())) {
|
||||
throw new IOException("Failed to create: " + workspaceFile.getParentFile());
|
||||
}
|
||||
FileUtils.writeStringToFile(workspaceFile, gson.toJson(workspace), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,21 +256,22 @@ public class VSCodeProjectScript extends GhidraScript {
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the .vscode/settings.json file
|
||||
* Creates the VSCode settings json
|
||||
*
|
||||
* @param installDir The Ghidra installation directory
|
||||
* @param projectDir The VSCode project directory
|
||||
* @param classpathSourceMap The classpath/source map (see {@link #getClasspathSourceMap()})
|
||||
* @param sourcePaths A {@link List} of source paths
|
||||
* @param outputPath The output path (null for default)
|
||||
* @return The VSCode settings json
|
||||
* @throws IOException if an IO-related error occurs
|
||||
*/
|
||||
private void writeSettings(File installDir, File projectDir,
|
||||
Map<String, String> classpathSourceMap) throws IOException {
|
||||
File vscodeDir = new File(projectDir, ".vscode");
|
||||
File settingsFile = new File(vscodeDir, "settings.json");
|
||||
private JsonObject createSettings(File installDir, File projectDir,
|
||||
Map<String, String> classpathSourceMap, List<String> sourcePaths, String outputPath)
|
||||
throws IOException {
|
||||
String gradleVersion = Application
|
||||
.getApplicationProperty(ApplicationProperties.APPLICATION_GRADLE_MIN_PROPERTY);
|
||||
String pythonInterpreterPath = System.getProperty("pyghidra.sys.prefix", null);
|
||||
|
||||
|
||||
// Build settings json object
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("java.import.maven.enabled", false);
|
||||
@@ -120,12 +283,13 @@ public class VSCodeProjectScript extends GhidraScript {
|
||||
|
||||
JsonArray sourcePathArray = new JsonArray();
|
||||
json.add("java.project.sourcePaths", sourcePathArray);
|
||||
sourcePathArray.add("src/main/java");
|
||||
sourcePathArray.add("ghidra_scripts");
|
||||
sourcePaths.forEach(sourcePathArray::add);
|
||||
|
||||
if (outputPath != null) {
|
||||
json.addProperty("java.project.outputPath", outputPath);
|
||||
}
|
||||
|
||||
json.addProperty("java.project.outputPath", "bin/main");
|
||||
JsonObject referencedLibrariesObject = new JsonObject();
|
||||
|
||||
json.add("java.project.referencedLibraries", referencedLibrariesObject);
|
||||
JsonArray includeArray = new JsonArray();
|
||||
referencedLibrariesObject.add("include", includeArray);
|
||||
@@ -139,29 +303,22 @@ public class VSCodeProjectScript extends GhidraScript {
|
||||
|
||||
json.addProperty("python.analysis.stubPath",
|
||||
new File(installDir, "docs/ghidra_stubs/typestubs").getAbsolutePath());
|
||||
if (pythonInterpreterPath != null) {
|
||||
json.addProperty("python.defaultInterpreterPath", pythonInterpreterPath);
|
||||
}
|
||||
|
||||
// Write settings json object
|
||||
if (!FileUtilities.mkdirs(settingsFile.getParentFile())) {
|
||||
throw new IOException("Failed to create: " + settingsFile.getParentFile());
|
||||
}
|
||||
FileUtils.writeStringToFile(settingsFile, gson.toJson(json), StandardCharsets.UTF_8);
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the .vscode/launch.json file
|
||||
* Creates the VSCode launch json
|
||||
*
|
||||
* @param installDir The Ghidra installation directory
|
||||
* @param projectDir The VSCode project directory
|
||||
* @param classpathSourceMap The classpath/source map (see {@link #getClasspathSourceMap()})
|
||||
* @param externalModules The Ghidra external modules to pass to Ghidra (could be null)
|
||||
* @return The VSCode launch json
|
||||
* @throws IOException if an IO-related error occurs
|
||||
*/
|
||||
private void writeLaunch(File installDir, File projectDir,
|
||||
Map<String, String> classpathSourceMap) throws IOException {
|
||||
File vscodeDir = new File(projectDir, ".vscode");
|
||||
File launchFile = new File(vscodeDir, "launch.json");
|
||||
private JsonObject createLaunch(File installDir, File projectDir,
|
||||
Map<String, String> classpathSourceMap, String externalModules) throws IOException {
|
||||
|
||||
// Get the path of Utility.jar so we can put it on the classpath
|
||||
String utilityJarPath = classpathSourceMap.keySet()
|
||||
@@ -201,7 +358,9 @@ public class VSCodeProjectScript extends GhidraScript {
|
||||
classPathsArray.add(utilityJarPath);
|
||||
JsonArray vmArgsArray = new JsonArray();
|
||||
ghidraConfigObject.add("vmArgs", vmArgsArray);
|
||||
vmArgsArray.add("-Dghidra.external.modules=${workspaceFolder}");
|
||||
if (externalModules != null) {
|
||||
vmArgsArray.add("-Dghidra.external.modules=" + externalModules);
|
||||
}
|
||||
vmArgs.forEach(vmArgsArray::add);
|
||||
|
||||
// PyGhidra launcher
|
||||
@@ -222,20 +381,63 @@ public class VSCodeProjectScript extends GhidraScript {
|
||||
pyghidraConfigObject.add("env", envObject);
|
||||
envObject.addProperty("PYGHIDRA_DEBUG", "1");
|
||||
|
||||
// PyGhidra Java Attach
|
||||
JsonObject pyghidraAttachObject = new JsonObject();
|
||||
configurationsArray.add(pyghidraAttachObject);
|
||||
pyghidraAttachObject.addProperty("type", "java");
|
||||
pyghidraAttachObject.addProperty("name", "PyGhidra Java Attach");
|
||||
pyghidraAttachObject.addProperty("request", "attach");
|
||||
pyghidraAttachObject.addProperty("hostName", "localhost");
|
||||
pyghidraAttachObject.addProperty("port", 18001);
|
||||
// Ghidra Attach
|
||||
JsonObject ghidraAttachObject = new JsonObject();
|
||||
configurationsArray.add(ghidraAttachObject);
|
||||
ghidraAttachObject.addProperty("type", "java");
|
||||
ghidraAttachObject.addProperty("name", "Ghidra Attach");
|
||||
ghidraAttachObject.addProperty("request", "attach");
|
||||
ghidraAttachObject.addProperty("hostName", "localhost");
|
||||
ghidraAttachObject.addProperty("port", 18001);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
// Write launch json object
|
||||
if (!FileUtilities.mkdirs(launchFile.getParentFile())) {
|
||||
throw new IOException("Failed to create: " + launchFile.getParentFile());
|
||||
/**
|
||||
* Creates a new VSCode workspace with no folders added
|
||||
*
|
||||
* @param settings The VSCode settings JSON
|
||||
* @param launch The VSCode launch JSON
|
||||
* @return The new workspace JSON
|
||||
*/
|
||||
private JsonObject createNewWorkspace(JsonObject settings, JsonObject launch) {
|
||||
JsonObject json = new JsonObject();
|
||||
JsonArray foldersArray = new JsonArray();
|
||||
json.add("folders", foldersArray);
|
||||
JsonObject folderObject = new JsonObject();
|
||||
foldersArray.add(folderObject);
|
||||
json.add("settings", settings);
|
||||
json.add("launch", launch);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given project directory to the given workspace
|
||||
*
|
||||
* @param projectDir The VSCode project directory to add
|
||||
* @param workspace The VSCode workspace to add to
|
||||
* @param settings The VSCode settings JSON
|
||||
* @param launch The VSCode launch JSON
|
||||
* @return The new workspace JSON with the project added
|
||||
*/
|
||||
private JsonObject addToExistingWorkspace(File projectDir, JsonObject workspace, JsonObject settings,
|
||||
JsonObject launch) {
|
||||
File projectParentDir = projectDir.getParentFile();
|
||||
String folderName = projectDir.getName();
|
||||
if (projectParentDir != null) {
|
||||
folderName = projectParentDir.getName() + "/" + folderName;
|
||||
}
|
||||
FileUtils.writeStringToFile(launchFile, gson.toJson(json), StandardCharsets.UTF_8);
|
||||
|
||||
JsonArray foldersArray = workspace.getAsJsonArray("folders");
|
||||
JsonObject folderObject = new JsonObject();
|
||||
folderObject.addProperty("name",
|
||||
projectDir.getParentFile().getName() + "/" + projectDir.getName());
|
||||
folderObject.addProperty("path", projectDir.getAbsolutePath());
|
||||
if (!foldersArray.contains(folderObject)) {
|
||||
foldersArray.add(folderObject);
|
||||
}
|
||||
return workspace;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -265,7 +467,7 @@ public class VSCodeProjectScript extends GhidraScript {
|
||||
}
|
||||
FileUtils.writeStringToFile(scriptFile, sampleScript, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
|
||||
private void writeSampleScriptPyGhidra(File projectDir) throws IOException {
|
||||
File scriptsDir = new File(projectDir, "ghidra_scripts");
|
||||
File scriptFile = new File(scriptsDir, "sample_script.py");
|
||||
@@ -309,11 +511,11 @@ public class VSCodeProjectScript extends GhidraScript {
|
||||
|
||||
// Rename java files and text replace their contents
|
||||
for (File f : newPackageDir.listFiles()) {
|
||||
String name = f.getName();
|
||||
if (!name.startsWith(skeleton)) {
|
||||
String oldName = f.getName();
|
||||
if (!oldName.startsWith(skeleton)) {
|
||||
continue;
|
||||
}
|
||||
String newName = projectName + name.substring(skeleton.length(), name.length());
|
||||
String newName = projectName + oldName.substring(skeleton.length(), oldName.length());
|
||||
File newFile = new File(f.getParentFile(), newName);
|
||||
if (!f.renameTo(newFile)) {
|
||||
throw new IOException("Failed to rename: " + f);
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.vscode;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.app.services.VSCodeIntegrationService;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.task.Task;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A {@link Task} to launch Visual Studio Code
|
||||
*/
|
||||
class VSCodeLauncherTask extends Task {
|
||||
|
||||
private VSCodeIntegrationService vscodeService;
|
||||
private File file;
|
||||
|
||||
/**
|
||||
* Constructs a new Visual Studio Code launcher task
|
||||
*
|
||||
* @param vscodeService The Visual Studio Code integration service
|
||||
* @param file The file to open in Visual Studio Code
|
||||
*/
|
||||
public VSCodeLauncherTask(VSCodeIntegrationService vscodeService, File file) {
|
||||
super("Visual Studio Code Launcher Task", true, true, true);
|
||||
this.vscodeService = vscodeService;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) {
|
||||
|
||||
if (SystemUtilities.isInDevelopmentMode()) {
|
||||
vscodeService.handleVSCodeError(
|
||||
"Launching Visual Studio Code is not supported in development mode.", false, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get required Visual Studio Code components. If VSCode isn't found at the default
|
||||
// location present the user with the options window, and when they close that window, try
|
||||
// again.
|
||||
File vscodeExecutableFile;
|
||||
try {
|
||||
vscodeExecutableFile = vscodeService.getVSCodeExecutableFile();
|
||||
}
|
||||
catch (IOException e1) {
|
||||
vscodeService.handleVSCodeError(e1.getMessage(), true, null);
|
||||
try {
|
||||
vscodeExecutableFile = vscodeService.getVSCodeExecutableFile();
|
||||
}
|
||||
catch (IOException e2) {
|
||||
vscodeService.handleVSCodeError(
|
||||
"Failed to launch Visual Studio Code. The required Visual Studio Code components have not been configured.",
|
||||
false, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the workspace
|
||||
File vscodeSettingsDir = new File(Application.getUserSettingsDirectory(), "vscode");
|
||||
File workspaceFile = new File(vscodeSettingsDir, "ghidra_scripts.code-workspace");
|
||||
try {
|
||||
vscodeService.addToVSCodeWorkspace(workspaceFile, file.getParentFile());
|
||||
}
|
||||
catch (IOException e) {
|
||||
vscodeService.handleVSCodeError("Failed to create Visual Studio Code workspace.", false,
|
||||
e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Launch Visual Studio Code
|
||||
monitor.setIndeterminate(true);
|
||||
monitor.setMessage("Launching Visual Studio Code...");
|
||||
try {
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add(vscodeExecutableFile.getAbsolutePath());
|
||||
args.add("-a");
|
||||
args.add(workspaceFile.getAbsolutePath());
|
||||
args.add(file.getAbsolutePath());
|
||||
new ProcessBuilder(args).redirectErrorStream(true).start();
|
||||
}
|
||||
catch (Exception e) {
|
||||
vscodeService.handleVSCodeError(
|
||||
"Unexpected exception occurred while launching Visual Studio Code.", false, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -30,4 +30,12 @@ public interface GhidraScriptService {
|
||||
* @return True if the file opened in Eclipse; otherwise, false.
|
||||
*/
|
||||
public boolean tryToEditFileInEclipse(ResourceFile file);
|
||||
|
||||
/**
|
||||
* Attempts to edit the provided file in Visual Studio Code.
|
||||
*
|
||||
* @param file The file to edit in Visual Studio Code.
|
||||
* @return True if the file opened in Visual Studio Code; otherwise, false.
|
||||
*/
|
||||
public boolean tryToEditFileInVSCode(ResourceFile 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 ghidra.app.services;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
|
||||
/**
|
||||
* Service that provides Visual Studio Code-related functionality
|
||||
*/
|
||||
public interface VSCodeIntegrationService {
|
||||
|
||||
/**
|
||||
* {@return the Visual Studio Code Integration options}
|
||||
*/
|
||||
public ToolOptions getVSCodeIntegrationOptions();
|
||||
|
||||
/**
|
||||
* {@return the Visual Studio Code executable file}
|
||||
*
|
||||
* @throws FileNotFoundException if the executable file does not exist
|
||||
*/
|
||||
public File getVSCodeExecutableFile() throws FileNotFoundException;
|
||||
|
||||
/**
|
||||
* Launches Visual Studio Code
|
||||
*
|
||||
* @param file The initial file to open in Visual Studio Code
|
||||
*/
|
||||
public void launchVSCode(File file);
|
||||
|
||||
/**
|
||||
* Displays the given Visual Studio Code related error message in an error dialog
|
||||
*
|
||||
* @param error The error message to display in a dialog
|
||||
* @param askAboutOptions True if we should ask the user if they want to be taken to the Visual
|
||||
* Studio Code options; otherwise, false
|
||||
* @param t An optional throwable to tie to the message
|
||||
*/
|
||||
public void handleVSCodeError(String error, boolean askAboutOptions, Throwable t);
|
||||
|
||||
/**
|
||||
* Creates a new Visual Studio Code module project at the given directory
|
||||
*
|
||||
* @param projectDir The new directory to create
|
||||
* @throws IOException if the directory failed to be created
|
||||
*/
|
||||
public void createVSCodeModuleProject(File projectDir) throws IOException;
|
||||
|
||||
/**
|
||||
* Adds the given project directory to the the given Visual Studio Code workspace file
|
||||
* A new workspace will be created if it doesn't already exist
|
||||
*
|
||||
* @param workspaceFile The location of the workspace file
|
||||
* @param projectDir An existing project directory to add to the workspace
|
||||
* @throws IOException if the directory failed to be created
|
||||
*/
|
||||
public void addToVSCodeWorkspace(File workspaceFile, File projectDir) throws IOException;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
Reference in New Issue
Block a user