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 @@
    -
  1. A path, which must be an absolute, normalized path using forward slashes (think file - URI).
  2. +
  3. A path, which must be an absolute, normalized path using forward slashes. + E.g., "/usr/src/main/file.c", "/C:/Users/Ghidra/sourceFile.cc".
  4. A SourceFileIdType, which can be NONE, UNKNOWN, TIMESTAMP_64, MD5, SHA1, SHA256, or SHA512.
  5. @@ -81,8 +81,9 @@

    NoteNote 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.
  6. Directory Transforms, which replace a parent directory of a source file's path - with another directory.
  7. + 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()); }