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");