From 443232947966f095ea0e7741937058dac1be9700 Mon Sep 17 00:00:00 2001
From: dragonmacher <48328597+dragonmacher@users.noreply.github.com>
Date: Sat, 25 Jan 2025 11:04:47 -0500
Subject: [PATCH 1/3] GP-5313 - Fixed the Escape key not working inside of cell
editors when it is also bound to a global action
---
.../KeyBindingOverrideKeyEventDispatcher.java | 23 ++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingOverrideKeyEventDispatcher.java b/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingOverrideKeyEventDispatcher.java
index 6c3c55d2e8..3c4a9f0741 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingOverrideKeyEventDispatcher.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingOverrideKeyEventDispatcher.java
@@ -281,7 +281,7 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
destination = focusOwner;
}
- if (!(destination instanceof JTextComponent)) {
+ if (!(destination instanceof JTextComponent textComponent)) {
return false; // we only handle text components
}
@@ -299,7 +299,9 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
// widgets register actions for Escape and then check for that action.
int code = event.getKeyCode();
if (code == KeyEvent.VK_ESCAPE) {
- return false;
+ // Cell editors will process the Escape key, so let them have it. Otherwise, allow the
+ // system to process the Escape key as, described above.
+ return isCellEditing(textComponent);
}
// We've made the executive decision to allow all keys to go through to the text component
@@ -310,7 +312,22 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
}
// the key is modified; let it through if the component has a mapping for the key
- return hasRegisteredKeyBinding((JTextComponent) destination, event);
+ return hasRegisteredKeyBinding(textComponent, event);
+ }
+
+ private boolean isCellEditing(JTextComponent c) {
+ Container parent = c.getParent();
+ while (parent != null) {
+ if (parent instanceof JTree tree) {
+ return tree.isEditing();
+ }
+ else if (parent instanceof JTable table) {
+ return table.isEditing();
+ }
+
+ parent = parent.getParent();
+ }
+ return false;
}
/**
From 0f47f411f02190eab5f696e6ba2ec1bc4f923a5e Mon Sep 17 00:00:00 2001
From: James <49045138+ghidracadabra@users.noreply.github.com>
Date: Mon, 27 Jan 2025 12:43:53 -0500
Subject: [PATCH 2/3] GP-5300 changes related to viewing source files
---
.../SourceFilesTable.html | 53 +++-
.../SourceFilesTableModel.java | 9 +-
.../SourceFilesTablePlugin.java | 247 +++++++++++++++++-
.../app/util/viewer/format/FormatManager.java | 20 +-
4 files changed, 319 insertions(+), 10 deletions(-)
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/SourceFilesTablePlugin/SourceFilesTable.html b/Ghidra/Features/Base/src/main/help/help/topics/SourceFilesTablePlugin/SourceFilesTable.html
index 375a464a2a..8181c6f2b6 100644
--- a/Ghidra/Features/Base/src/main/help/help/topics/SourceFilesTablePlugin/SourceFilesTable.html
+++ b/Ghidra/Features/Base/src/main/help/help/topics/SourceFilesTablePlugin/SourceFilesTable.html
@@ -26,8 +26,8 @@
- - A path, which must be an absolute, normalized path using forward slashes (think file
- URI).
+ - A path, which must be an absolute, normalized path using forward slashes.
+ E.g., "/usr/src/main/file.c", "/C:/Users/Ghidra/sourceFile.cc".
- A SourceFileIdType, which can be NONE, UNKNOWN, TIMESTAMP_64, MD5, SHA1, SHA256,
or SHA512.
@@ -81,8 +81,9 @@
Note that adding source files,
- removing source files, or changing the source map requires exclusive access to a program.
- Reading the source file list or source map does not require exclusive access.
+ removing source files, or changing the source map requires an exclusive checkout if
+ the program is in a shared Ghidra repository. Reading the source file list or source map does
+ not require an exclusive checkout.
@@ -111,7 +112,8 @@
path.
- Directory Transforms, which replace a parent directory of a source file's path
- with another directory.
+ with another directory. For example, the directory transform "/src/ -> "/usr/test/files/"
+ would transform the path "/src/dir1/file.c" to "/usr/test/files/dir1/file.c".
@@ -176,15 +178,54 @@
This action removes the selected transform from the list of transforms.
Edit Transform
-
This action allows you to change the destination of a transform (but not the source).
+
+
+ Listing Actions
+
+
+ View Source
+
+ This Listing action is enabled if there is source map information
+ for an address. It will open the corresponding source file at the
+ appropriate line in a source code viewer (currently Eclipse and VS Code
+ are supported). Options for configuring the viewer are described
+ below. If there are
+ multiple source map entries defined for an address, the user will be
+ prompted to select which one to send to the viewer. Performing
+ this action on a particular line of the Source Map Listing field will
+ open the corresponding file in the viewer even if there are multiple
+ entries defined at the current address.
+
+
+
+
+
+ Plugin Options
+
+
+ Use Existing As Default
+
+ If enabled, the SourcePathTransformer will just return a SourceFile's path if
+ no transform applies to the file.
+
+ Viewer for Source Files
+ Selects the viewer to use for source files. The supported viewers are Eclipse
+ and Visual Studio Code. Your viewer of choice must be configured via the
+ appropriate option in the Front-End tool.
+
+
Related Topics:
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/sourcefilestable/SourceFilesTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/sourcefilestable/SourceFilesTableModel.java
index 7c83bb7c20..3d50aa90a9 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/sourcefilestable/SourceFilesTableModel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/sourcefilestable/SourceFilesTableModel.java
@@ -22,7 +22,8 @@ import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.database.sourcemap.*;
import ghidra.program.model.listing.Program;
-import ghidra.program.model.sourcemap.*;
+import ghidra.program.model.sourcemap.SourceFileManager;
+import ghidra.program.model.sourcemap.SourcePathTransformer;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@@ -150,6 +151,12 @@ public class SourceFilesTableModel extends ThreadedTableModelStub isEnabled(c))
+ .onAction(c -> viewSourceFile(c))
+ .buildAndInstall(tool);
+ }
+
+ private boolean isEnabled(ListingActionContext context) {
+ Address address = context.getAddress();
+ SourceFileManager manager = getCurrentProgram().getSourceFileManager();
+ return manager.getSourceMapEntries(address).size() > 0;
+ }
+
+ private void viewSourceFile(ListingActionContext context) {
+ Address address = context.getAddress();
+ SourceFileManager manager = getCurrentProgram().getSourceFileManager();
+ List entries = manager.getSourceMapEntries(address);
+ if (entries.isEmpty()) {
+ return; // sanity check
+ }
+ // if there's only one entry associated with the address, just view it
+ if (entries.size() == 1) {
+ openInViewer(entries.get(0));
+ return;
+ }
+ // if there are multiple entries, we need to decide which one to view
+ // if the user right-clicked in the SourceMapField in the Listing, open
+ // the associated entry
+ if (context.getLocation() instanceof SourceMapFieldLocation sourceLoc) {
+ openInViewer(sourceLoc.getSourceMapEntry());
+ return;
+ }
+ // otherwise pop up a window and ask the user to select an entry
+ GValuesMap valuesMap = new GValuesMap();
+ Map stringsToEntries = new HashMap<>();
+ for (SourceMapEntry entry : entries) {
+ stringsToEntries.put(entry.toString(), entry);
+ }
+ valuesMap.defineChoice("Entry", entries.get(0).toString(),
+ stringsToEntries.keySet().toArray(new String[0]));
+ ValuesMapDialog dialog =
+ new ValuesMapDialog("Select Entry to View", null, valuesMap);
+ DockingWindowManager.showDialog(dialog);
+ if (dialog.isCancelled()) {
+ return;
+ }
+ GValuesMap results = dialog.getValues();
+ if (results == null) {
+ return;
+ }
+ String selected = results.getChoice("Entry");
+ if (selected == null) {
+ return;
+ }
+ SourceMapEntry entryToShow = stringsToEntries.get(selected);
+ openInViewer(entryToShow);
+ }
+
+ private void openInViewer(SourceMapEntry entry) {
+ if (entry == null) {
+ return;
+ }
+ SourcePathTransformer transformer =
+ UserDataPathTransformer.getPathTransformer(currentProgram);
+ String transformedPath =
+ transformer.getTransformedPath(entry.getSourceFile(), useExistingAsDefault);
+
+ if (transformedPath == null) {
+ Msg.showWarn(this, null, "No Path Transform",
+ "No path transformation applies to " + entry.getSourceFile().toString());
+ return;
+ }
+
+ File localSourceFile = new File(transformedPath);
+ if (!localSourceFile.exists()) {
+ Msg.showWarn(transformer, null, "File Not Found",
+ localSourceFile.getAbsolutePath() + " does not exist");
+ return;
+ }
+
+ switch (selectedViewer) {
+ case ECLIPSE:
+ openFileInEclipse(localSourceFile.getAbsolutePath(), entry.getLineNumber());
+ break;
+ case VS_CODE:
+ openFileInVsCode(localSourceFile.getAbsolutePath(), entry.getLineNumber());
+ break;
+ default:
+ throw new AssertionError("Unsupported Viewer: " + selectedViewer);
+ }
+ }
+
+ private void openFileInEclipse(String path, int lineNumber) {
+ EclipseIntegrationService eclipseService = tool.getService(EclipseIntegrationService.class);
+ if (eclipseService == null) {
+ Msg.showError(this, null, "Eclipse Service Error",
+ "Eclipse service not configured for tool");
+ return;
+ }
+
+ try {
+ eclipseExecutable = eclipseService.getEclipseExecutableFile();
+ }
+ catch (FileNotFoundException e) {
+ Msg.showError(this, null, "Missing Eclipse Executable", e.getMessage());
+ return;
+ }
+ MonitoredRunnable r = m -> {
+ try {
+ List args = new ArrayList<>();
+ args.add(eclipseExecutable.getAbsolutePath());
+ args.add(path + ":" + lineNumber);
+ new ProcessBuilder(args).redirectErrorStream(true).start();
+ }
+ catch (Exception e) {
+ eclipseService.handleEclipseError(
+ "Unexpected exception occurred while launching Eclipse.", false,
+ null);
+ return;
+ }
+ };
+
+ new TaskBuilder("Opening File in Eclipse", r)
+ .setHasProgress(false)
+ .setCanCancel(true)
+ .launchModal();
+ return;
+
+ }
+
+ private void openFileInVsCode(String path, int lineNumber) {
+ VSCodeIntegrationService vscodeService = tool.getService(VSCodeIntegrationService.class);
+ if (vscodeService == null) {
+ Msg.showError(this, null, "VSCode Service Error",
+ "VSCode service not configured for tool");
+ return;
+ }
+
+ try {
+ vscodeExecutable = vscodeService.getVSCodeExecutableFile();
+ }
+ catch (FileNotFoundException e) {
+ Msg.showError(this, null, "Missing VSCode executable", e.getMessage());
+ return;
+ }
+
+ MonitoredRunnable r = m -> {
+ try {
+ List args = new ArrayList<>();
+ args.add(vscodeExecutable.getAbsolutePath());
+ args.add("--goto");
+ args.add(path + ":" + lineNumber);
+ new ProcessBuilder(args).redirectErrorStream(true).start();
+ }
+ catch (Exception e) {
+ vscodeService.handleVSCodeError(
+ "Unexpected exception occurred while launching Visual Studio Code.", false,
+ null);
+ return;
+ }
+ };
+
+ new TaskBuilder("Opening File in VSCode", r)
+ .setHasProgress(false)
+ .setCanCancel(true)
+ .launchModal();
+ return;
+
+ }
+
+
+ private void initOptions(ToolOptions options) {
+ options.registerOption(USE_EXISTING_AS_DEFAULT_OPTION_NAME, true,
+ new HelpLocation(getName(), "Use_Existing_As_Default"),
+ "Use a source file's existing path if no transform applies");
+ useExistingAsDefault = options.getBoolean(USE_EXISTING_AS_DEFAULT_OPTION_NAME, true);
+
+ options.registerOption(SELECTED_VIEWER_OPTION_NAME,
+ OptionType.STRING_TYPE, VS_CODE,
+ new HelpLocation(getName(), "Viewer_for_Source_Files"),
+ "Viewer for Source Files",
+ () -> new StringWithChoicesEditor(VIEWERS));
+ selectedViewer = options.getString(SELECTED_VIEWER_OPTION_NAME, VS_CODE);
+ options.addOptionsChangeListener(this);
+ }
+
+
+
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FormatManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FormatManager.java
index 4e3e47e354..90242c486e 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FormatManager.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FormatManager.java
@@ -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.
@@ -591,6 +591,22 @@ public class FormatManager implements OptionsChangeListener {
rowElem.addContent(colElem);
root.addContent(rowElem);
+
+ rowElem = new Element("ROW");
+
+ colElem = new Element("FIELD");
+ colElem.setAttribute("WIDTH", "200");
+ colElem.setAttribute("ENABLED", "true");
+ rowElem.addContent(colElem);
+
+ colElem = new Element("FIELD");
+ colElem.setAttribute("NAME", "Source Map");
+ colElem.setAttribute("WIDTH", "440");
+ colElem.setAttribute("ENABLED", "true");
+ rowElem.addContent(colElem);
+
+ root.addContent(rowElem);
+
rowElem = new Element("ROW");
colElem = new Element("FIELD");
From d7722f878b241227bd5c61a450400a735501cbe9 Mon Sep 17 00:00:00 2001
From: dev747368 <48332326+dev747368@users.noreply.github.com>
Date: Mon, 27 Jan 2025 16:39:28 +0000
Subject: [PATCH 3/3] GP-5312 fix NPE when logging message about unsupported
elf compression (Closes #7403)
---
.../ghidra/app/util/bin/format/elf/ElfSectionHeader.java | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSectionHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSectionHeader.java
index 6b2837ad13..c58226bdbe 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSectionHeader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfSectionHeader.java
@@ -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.
@@ -146,7 +146,7 @@ public class ElfSectionHeader implements StructConverter, MemoryLoadable {
ElfCompressedSectionHeader.read(getRawSectionReader(), header);
if (!isSupportedCompressionType(result.getCh_type())) {
throw new IOException("Unknown ELF section compression type 0x%x for section %s"
- .formatted(compressedHeader.getCh_type(), getNameAsString()));
+ .formatted(result.getCh_type(), getNameAsString()));
}
return result;
}
@@ -465,7 +465,7 @@ public class ElfSectionHeader implements StructConverter, MemoryLoadable {
return new ByteProviderWrapper(reader.getByteProvider(), sh_offset, sh_size);
}
- private BinaryReader getRawSectionReader() throws IOException {
+ private BinaryReader getRawSectionReader() {
return new BinaryReader(getRawSectionByteProvider(), header.isLittleEndian());
}