Merge remote-tracking branch 'origin/Ghidra_11.3'

This commit is contained in:
Ryan Kurtz 2025-01-27 13:01:37 -05:00
commit 2ec84c2fb4
6 changed files with 343 additions and 17 deletions

View file

@ -26,8 +26,8 @@
<BLOCKQUOTE> <BLOCKQUOTE>
<OL> <OL>
<LI>A path, which must be an absolute, normalized path using forward slashes (think file <LI>A path, which must be an absolute, normalized path using forward slashes.
URI).</LI> E.g., "/usr/src/main/file.c", "/C:/Users/Ghidra/sourceFile.cc".</LI>
<LI>A <I>SourceFileIdType</I>, which can be NONE, UNKNOWN, TIMESTAMP_64, MD5, SHA1, SHA256, <LI>A <I>SourceFileIdType</I>, which can be NONE, UNKNOWN, TIMESTAMP_64, MD5, SHA1, SHA256,
or SHA512.</LI> or SHA512.</LI>
@ -81,8 +81,9 @@
<BLOCKQUOTE> <BLOCKQUOTE>
<P><IMG src="help/shared/note.png" alt="Note" border="0">Note that adding source files, <P><IMG src="help/shared/note.png" alt="Note" border="0">Note that adding source files,
removing source files, or changing the source map requires exclusive access to a program. removing source files, or changing the source map requires an exclusive checkout if
Reading the source file list or source map does not require exclusive access.</P> the program is in a shared Ghidra repository. Reading the source file list or source map does
not require an exclusive checkout.</P>
<BR> <BR>
</BLOCKQUOTE><BR> </BLOCKQUOTE><BR>
<BR> <BR>
@ -111,7 +112,8 @@
path.</LI> path.</LI>
<LI><I>Directory Transforms</I>, which replace a parent directory of a source file's path <LI><I>Directory Transforms</I>, which replace a parent directory of a source file's path
with another directory.</LI> 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".</LI>
</OL> </OL>
</BLOCKQUOTE><BR> </BLOCKQUOTE><BR>
<BR> <BR>
@ -176,15 +178,54 @@
<P>This action removes the selected transform from the list of transforms.</P> <P>This action removes the selected transform from the list of transforms.</P>
<H3><A name="Edit_Transform"></A>Edit Transform</H3> <H3><A name="Edit_Transform"></A>Edit Transform</H3>
</BLOCKQUOTE>
<P>This action allows you to change the destination of a transform (but not the source).</P> <P>This action allows you to change the destination of a transform (but not the source).</P>
</BLOCKQUOTE>
<H2><A name="Source_Files_Table_Plugin_Actions"></A>Listing Actions</H2>
<BLOCKQUOTE>
<H3><A name="View_Source"></A>View Source</H3>
<P>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
<A href="#Source_Files_Table_Plugin_Options"> below</A>. 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.
</P>
</BLOCKQUOTE>
<H2><A name="Source_Files_Table_Plugin_Options"></A>Plugin Options</H2>
<BLOCKQUOTE>
<H3><A name="Use_Existing_As_Default"></A>Use Existing As Default</H3>
<P>If enabled, the SourcePathTransformer will just return a SourceFile's path if
no transform applies to the file.</P>
<H3><A name="Viewer_for_Source_Files"></A>Viewer for Source Files</H3>
<P> 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.
</BLOCKQUOTE>
<P class="relatedtopic">Related Topics:</P> <P class="relatedtopic">Related Topics:</P>
<UL> <UL>
<LI><A href="help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm#Source_Map_Field">Source <LI><A href="help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm#Source_Map_Field">Source
Map Field</A></LI> Map Field</A></LI>
<LI><A href="help/topics/EclipseIntegration/EclipseIntegration.htm"> Eclipse Integration</A>
</LI>
<LI><A href="help/topics/VSCodeIntegration/VSCodeIntegration.htm"> Visual Studio Code
Integration</A></LI>
</UL><BR> </UL><BR>
<BR> <BR>
<BR> <BR>

View file

@ -22,7 +22,8 @@ import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.database.sourcemap.*; import ghidra.program.database.sourcemap.*;
import ghidra.program.model.listing.Program; 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.datastruct.Accumulator;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -150,6 +151,12 @@ public class SourceFilesTableModel extends ThreadedTableModelStub<SourceFileRowO
@Override @Override
public String getValue(SourceFileRowObject rowObject, Settings settings, Object data, public String getValue(SourceFileRowObject rowObject, Settings settings, Object data,
ServiceProvider sProvider) throws IllegalArgumentException { ServiceProvider sProvider) throws IllegalArgumentException {
if (program == null) {
return null;
}
if (pathTransformer == null) {
pathTransformer = UserDataPathTransformer.getPathTransformer(program);
}
return pathTransformer.getTransformedPath(rowObject.getSourceFile(), return pathTransformer.getTransformedPath(rowObject.getSourceFile(),
useExistingAsDefault); useExistingAsDefault);
} }

View file

@ -17,16 +17,38 @@ package ghidra.app.plugin.core.sourcefilestable;
import static ghidra.program.util.ProgramEvent.*; import static ghidra.program.util.ProgramEvent.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.*;
import docking.DockingWindowManager;
import docking.action.builder.ActionBuilder;
import docking.options.editor.StringWithChoicesEditor;
import docking.widgets.values.GValuesMap;
import docking.widgets.values.ValuesMapDialog;
import ghidra.app.CorePluginPackage; import ghidra.app.CorePluginPackage;
import ghidra.app.context.ListingActionContext;
import ghidra.app.events.ProgramClosedPluginEvent; import ghidra.app.events.ProgramClosedPluginEvent;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.services.EclipseIntegrationService;
import ghidra.app.services.VSCodeIntegrationService;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.options.*;
import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginInfo;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.database.sourcemap.UserDataPathTransformer;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.sourcemap.*;
import ghidra.program.util.ProgramChangeRecord; import ghidra.program.util.ProgramChangeRecord;
import ghidra.program.util.SourceMapFieldLocation;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.bean.opteditor.OptionsVetoException;
import ghidra.util.task.MonitoredRunnable;
import ghidra.util.task.TaskBuilder;
//@formatter:off //@formatter:off
@PluginInfo( @PluginInfo(
@ -43,11 +65,23 @@ import ghidra.program.util.ProgramChangeRecord;
* A {@link ProgramPlugin} for displaying source file information about a program * A {@link ProgramPlugin} for displaying source file information about a program
* and for managing source file path transforms. * and for managing source file path transforms.
*/ */
public class SourceFilesTablePlugin extends ProgramPlugin { public class SourceFilesTablePlugin extends ProgramPlugin implements OptionsChangeListener {
private SourceFilesTableProvider provider; private SourceFilesTableProvider provider;
private DomainObjectListener listener; private DomainObjectListener listener;
private static final String USE_EXISTING_AS_DEFAULT_OPTION_NAME =
"Use Existing Path as Default";
private boolean useExistingAsDefault = true;
private static final String ECLIPSE = "Eclipse";
private static final String VS_CODE = "VS Code";
private static final String[] VIEWERS = { ECLIPSE, VS_CODE };
private static final String SELECTED_VIEWER_OPTION_NAME = "Viewer for Source Files";
private String selectedViewer = VS_CODE;
private File vscodeExecutable = null;
private File eclipseExecutable = null;
/** /**
* Constructor * Constructor
* @param plugintool tool * @param plugintool tool
@ -61,6 +95,24 @@ public class SourceFilesTablePlugin extends ProgramPlugin {
super.init(); super.init();
provider = new SourceFilesTableProvider(this); provider = new SourceFilesTableProvider(this);
listener = createDomainObjectListener(); listener = createDomainObjectListener();
initOptions(tool.getOptions("Source Files and Transforms"));
createAction();
}
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) throws OptionsVetoException {
switch (optionName) {
case USE_EXISTING_AS_DEFAULT_OPTION_NAME:
useExistingAsDefault =
options.getBoolean(USE_EXISTING_AS_DEFAULT_OPTION_NAME, true);
break;
case SELECTED_VIEWER_OPTION_NAME:
selectedViewer = options.getString(SELECTED_VIEWER_OPTION_NAME, VS_CODE);
break;
default:
break;
}
} }
@Override @Override
@ -95,4 +147,197 @@ public class SourceFilesTablePlugin extends ProgramPlugin {
.build(); .build();
// @formatter:on // @formatter:on
} }
private void createAction() {
new ActionBuilder("View Source", getName())
.popupMenuPath("View Source...")
.popupMenuGroup("ZZZ")
.helpLocation(new HelpLocation(getName(), "View_Source"))
.withContext(ListingActionContext.class)
.enabledWhen(c -> 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<SourceMapEntry> 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<String, SourceMapEntry> 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<String> 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<String> 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);
}
} }

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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); ElfCompressedSectionHeader.read(getRawSectionReader(), header);
if (!isSupportedCompressionType(result.getCh_type())) { if (!isSupportedCompressionType(result.getCh_type())) {
throw new IOException("Unknown ELF section compression type 0x%x for section %s" 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; return result;
} }
@ -465,7 +465,7 @@ public class ElfSectionHeader implements StructConverter, MemoryLoadable {
return new ByteProviderWrapper(reader.getByteProvider(), sh_offset, sh_size); return new ByteProviderWrapper(reader.getByteProvider(), sh_offset, sh_size);
} }
private BinaryReader getRawSectionReader() throws IOException { private BinaryReader getRawSectionReader() {
return new BinaryReader(getRawSectionByteProvider(), header.isLittleEndian()); return new BinaryReader(getRawSectionByteProvider(), header.isLittleEndian());
} }

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -591,6 +591,22 @@ public class FormatManager implements OptionsChangeListener {
rowElem.addContent(colElem); rowElem.addContent(colElem);
root.addContent(rowElem); 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"); rowElem = new Element("ROW");
colElem = new Element("FIELD"); colElem = new Element("FIELD");

View file

@ -281,7 +281,7 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
destination = focusOwner; destination = focusOwner;
} }
if (!(destination instanceof JTextComponent)) { if (!(destination instanceof JTextComponent textComponent)) {
return false; // we only handle text components 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. // widgets register actions for Escape and then check for that action.
int code = event.getKeyCode(); int code = event.getKeyCode();
if (code == KeyEvent.VK_ESCAPE) { 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 // 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 // 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;
} }
/** /**