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

This commit is contained in:
Ryan Kurtz
2024-05-31 06:09:01 -04:00
16 changed files with 555 additions and 85 deletions
@@ -53,7 +53,8 @@
<attribute-alias from="_kinds" to="Kinds" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" />
<attribute name="Enabled" schema="BOOL" />
<attribute-alias from="_enabled" to="Enabled" />
<attribute name="Commands" schema="STRING" />
<attribute name="Condition" schema="STRING" />
<attribute name="Hit Count" schema="INT" />
@@ -132,6 +133,8 @@
<schema name="BreakpointLocation" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointLocation" />
<element schema="VOID" />
<attribute name="Enabled" schema="BOOL" />
<attribute-alias from="_enabled" to="Enabled" />
<attribute name="Range" schema="RANGE" />
<attribute-alias from="_range" to="Range" />
<attribute name="_display" schema="STRING" hidden="yes" />
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.modules;
package ghidra.debug.api.modules;
import java.util.Objects;
@@ -43,10 +43,9 @@ public class DebuggerMissingModuleActionContext extends DefaultActionContext {
if (this == obj) {
return true;
}
if (!(obj instanceof DebuggerMissingModuleActionContext)) {
if (!(obj instanceof DebuggerMissingModuleActionContext that)) {
return false;
}
DebuggerMissingModuleActionContext that = (DebuggerMissingModuleActionContext) obj;
if (!this.module.equals(that.module)) {
return false;
}
@@ -0,0 +1,98 @@
/* ###
* 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.debug.api.modules;
import java.util.Objects;
import docking.DefaultActionContext;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.InstructionIterator;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
public class DebuggerMissingProgramActionContext extends DefaultActionContext {
public static Address getMappingProbeAddress(Program program) {
if (program == null) {
return null;
}
AddressIterator eepi = program.getSymbolTable().getExternalEntryPointIterator();
if (eepi.hasNext()) {
return eepi.next();
}
InstructionIterator ii = program.getListing().getInstructions(true);
if (ii.hasNext()) {
return ii.next().getAddress();
}
AddressSetView es = program.getMemory().getExecuteSet();
if (!es.isEmpty()) {
return es.getMinAddress();
}
if (!program.getMemory().isEmpty()) {
return program.getMinAddress();
}
return null;
}
private final Trace trace;
private final Program program;
private final int hashCode;
private Address probe;
public DebuggerMissingProgramActionContext(Trace trace, Program program) {
this.trace = Objects.requireNonNull(trace);
this.program = Objects.requireNonNull(program);
this.hashCode = Objects.hash(getClass(), trace, program);
}
public Trace getTrace() {
return trace;
}
public Program getProgram() {
return program;
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof DebuggerMissingProgramActionContext that)) {
return false;
}
if (!this.trace.equals(that.trace)) {
return false;
}
if (!this.program.equals(that.program)) {
return false;
}
return true;
}
public Address getMappingProbeAddress() {
if (probe == null) {
probe = getMappingProbeAddress(program);
}
return probe;
}
}
@@ -17,15 +17,39 @@ package ghidra.debug.api.tracermi;
import java.io.IOException;
import ghidra.app.services.Terminal;
/**
* A terminal with some back-end element attached to it
*/
public interface TerminalSession extends AutoCloseable {
@Override
void close() throws IOException;
default void close() throws IOException {
terminate();
terminal().close();
}
/**
* The handle to the terminal
*
* @return the handle
*/
Terminal terminal();
/**
* Ensure the session is visible
*
* <p>
* The window should be displayed and brought to the front.
*/
default void show() {
terminal().toFront();
}
/**
* Terminate the session without closing the terminal
*
* @throws IOException if an I/O issue occurs during termination
*/
void terminate() throws IOException;
@@ -34,7 +58,9 @@ public interface TerminalSession extends AutoCloseable {
*
* @return true for terminated, false for active
*/
boolean isTerminated();
default boolean isTerminated() {
return terminal().isTerminated();
}
/**
* Provide a human-readable description of the session
@@ -42,4 +68,22 @@ public interface TerminalSession extends AutoCloseable {
* @return the description
*/
String description();
/**
* Get the terminal contents as a string (no attributes)
*
* @return the content
*/
default String content() {
return terminal().getFullText();
}
/**
* Get the current title of the terminal
*
* @return the title
*/
default String title() {
return terminal().getSubTitle();
}
}
@@ -71,6 +71,12 @@ public interface TraceRmiLaunchOffer {
this.exception = exception;
}
public void showTerminals() {
for (TerminalSession session : sessions.values()) {
session.show();
}
}
@Override
public void close() throws Exception {
if (connection != null) {
@@ -45,16 +45,14 @@ import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.AutoConfigState.ConfigStateField;
import ghidra.framework.plugintool.AutoConfigState.PathIsFile;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.InstructionIterator;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.pty.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceLocation;
import ghidra.trace.model.modules.TraceModule;
import ghidra.util.MessageType;
import ghidra.util.Msg;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
@@ -67,12 +65,6 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
protected record PtyTerminalSession(Terminal terminal, Pty pty, PtySession session,
Thread waiter) implements TerminalSession {
@Override
public void close() throws IOException {
terminate();
terminal.close();
}
@Override
public void terminate() throws IOException {
terminal.terminated();
@@ -81,11 +73,6 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
waiter.interrupt();
}
@Override
public boolean isTerminated() {
return terminal.isTerminated();
}
@Override
public String description() {
return session.description();
@@ -94,23 +81,12 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
protected record NullPtyTerminalSession(Terminal terminal, Pty pty, String name)
implements TerminalSession {
@Override
public void close() throws IOException {
terminate();
terminal.close();
}
@Override
public void terminate() throws IOException {
terminal.terminated();
pty.close();
}
@Override
public boolean isTerminated() {
return terminal.isTerminated();
}
@Override
public String description() {
return name;
@@ -171,25 +147,8 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
}
protected Address getMappingProbeAddress() {
if (program == null) {
return null;
}
AddressIterator eepi = program.getSymbolTable().getExternalEntryPointIterator();
if (eepi.hasNext()) {
return eepi.next();
}
InstructionIterator ii = program.getListing().getInstructions(true);
if (ii.hasNext()) {
return ii.next().getAddress();
}
AddressSetView es = program.getMemory().getExecuteSet();
if (!es.isEmpty()) {
return es.getMinAddress();
}
if (!program.getMemory().isEmpty()) {
return program.getMinAddress();
}
return null; // I guess we won't wait for a mapping, then
// May be null, in which case, we won't wait for a mapping
return DebuggerMissingProgramActionContext.getMappingProbeAddress(program);
}
protected CompletableFuture<Void> listenForMapping(DebuggerStaticMappingService mappingService,
@@ -686,6 +645,22 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
}
return new LaunchResult(program, Map.of(), null, null, null, lastExc);
}
catch (NoStaticMappingException e) {
DebuggerConsoleService consoleService =
tool.getService(DebuggerConsoleService.class);
if (consoleService == null) {
Msg.error(this, e.getMessage());
}
else {
consoleService.log(DebuggerResources.ICON_MODULES,
"<html>The trace <b>%s</b> has no mapping to its program <b>%s</b></html>"
.formatted(
HTMLUtilities.escapeHTML(trace.getDomainFile().getName()),
HTMLUtilities.escapeHTML(program.getDomainFile().getName())),
new DebuggerMissingProgramActionContext(trace, program));
}
return new LaunchResult(program, sessions, acceptor, connection, trace, e);
}
catch (Exception e) {
DebuggerConsoleService consoleService =
tool.getService(DebuggerConsoleService.class);
@@ -700,6 +675,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
if (prompt) {
switch (promptError(result)) {
case KEEP:
result.showTerminals();
return result;
case RETRY:
try {
@@ -15,7 +15,10 @@
*/
package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
import java.util.Arrays;
import java.util.List;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import docking.widgets.OptionDialog;
import ghidra.debug.api.tracermi.TerminalSession;
@@ -26,7 +29,7 @@ import ghidra.util.HelpLocation;
public class LaunchFailureDialog extends OptionDialog {
private static final String MSGPAT_PART_TOP = """
<html><body width="400px">
<h3>Failed to launch %s due to an exception:</h3>
<h3>Failed to launch '%s' due to an exception:</h3>
<tt>%s</tt>
@@ -56,6 +59,7 @@ public class LaunchFailureDialog extends OptionDialog {
""";
private static final String MSGPAT_WITH_RESOURCES = MSGPAT_PART_TOP + MSGPAT_PART_RESOURCES;
private static final String MSGPAT_WITHOUT_RESOURCES = MSGPAT_PART_TOP;
private static final int MAX_TERM_CONTENT_LINES = 2;
public enum ErrPromptResponse {
KEEP, RETRY, TERMINATE;
@@ -90,6 +94,25 @@ public class LaunchFailureDialog extends OptionDialog {
result.trace() != null;
}
protected static String htmlContent(TerminalSession session) {
String content = session.content().trim();
List<String> lines = Arrays.asList(content.split("\n"));
String note = "";
if (lines.size() >= MAX_TERM_CONTENT_LINES) {
note = "(last %d lines)".formatted(MAX_TERM_CONTENT_LINES);
content = lines.subList(lines.size() - MAX_TERM_CONTENT_LINES, lines.size())
.stream()
.collect(Collectors.joining("\n"));
}
return """
<div style="font:bold;">Title: %s</div>%s
<div style="background:black;color:white;">
<pre>%s</pre>""".formatted(
session.title(),
note,
content);
}
protected static String htmlResources(LaunchResult result) {
StringBuilder sb = new StringBuilder();
for (Entry<String, TerminalSession> ent : result.sessions().entrySet()) {
@@ -100,6 +123,9 @@ public class LaunchFailureDialog extends OptionDialog {
if (session.isTerminated()) {
sb.append(" (Terminated)");
}
sb.append("<div>\n");
sb.append(htmlContent(session));
sb.append("</div>");
sb.append("</li>\n");
}
if (result.acceptor() != null) {
@@ -152,14 +152,51 @@
<H3><A name="import_missing_module"></A><IMG alt="" src="icon.debugger.import"> Import Missing
Module</H3>
<P>This action is offered to resolve a "missing module" console message. It is equivalent to <A
href="#import_from_fs">Import From File System</A> on the missing module.</P>
<P>This action is offered to resolve a "missing module" console message. Such a message is
reported in the <A href="help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html">Debug
Console</A> when the cursor in the <A href=
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Dynamic Listing</A> cannot be
synchronized to the static listing for lack of a module mapping. This action is equivalent to
<A href="#import_from_fs">Import From File System</A> on the missing module.</P>
<H3><A name="map_missing_module"></A><IMG alt="" src="icon.debugger.map.modules"> Map Missing
Module</H3>
<P>This action is offered to resolve a "missing module" console message. It is equivalent to <A
href="#map_module_to">Map Module To</A> on the missing module.</P>
<P>This action is offered to resolve a "missing module" console message. Such a message is
reported in the <A href="help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html">Debug
Console</A> when the cursor in the <A href=
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Dynamic Listing</A> cannot be
synchronized to the static listing for lack of a module mapping. This action is equivalent to
<A href="#map_module_to">Map Module To</A> on the missing module.</P>
<H3><A name="map_missing_program_retry"></A><IMG alt="" src="icon.debugger.map.auto"> Retry Map
Missing Program</H3>
<P>This action is offered to resolve a "missing program" console message. Such a message is
reported in the <A href="help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html">Debug
Console</A> when the launcher fails to map the current program database to the launched trace.
This action is equivalent to <A href="#map_modules">Map Modules</A>, but considering only the
missing program and launched trace.</P>
<H3><A name="map_missing_program_current"></A><IMG alt="" src="icon.debugger.map.modules"> Map
Missing Program to Current Module</H3>
<P>This action is offered to resolve a "missing program" console message. Such a message is
reported in the <A href="help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html">Debug
Console</A> when the launcher fails to map the current program database to the launched trace.
This action is only available when the current trace is the launched trace. It finds the module
containing the cursor in the <A href=
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Dynamic Listing</A> and proposes
to map it to the missing program.</P>
<H3><A name="map_missing_program_identically"></A><IMG alt="" src=
"icon.debugger.map.identically"> Map Missing Program Identically</H3>
<P>This action is offered to resolve a "missing program" console message. Such a message is
reported in the <A href="help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html">Debug
Console</A> when the launcher fails to map the current program database to the launched trace.
This action is equivalent to <A href="#map_identically">Map Identically</A>, but for the
missing program and launched trace.</P>
<H3><A name="show_sections_table"></A><IMG alt="" src="icon.debugger.modules.table.sections">
Show Sections Table</H3>
@@ -179,12 +216,15 @@
Addresses</H3>
<P>This action is available when at least one module or section is selected. It selects all
addresses in the dynamic listing contained by the selected modules or sections.</P>
addresses in the <A href="help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Dynamic
Listing</A> contained by the selected modules or sections.</P>
<H3><A name="select_rows"></A><IMG alt="" src="icon.debugger.select.rows"> Select Rows</H3>
<P>This action is available when the dynamic listing's cursor is at a valid location. It
selects the module and section, if applicable, containing that cursor. If the dynamic listing
has a selection, it selects all modules and sections intersecting that selection.</P>
<P>This action is available when the <A href=
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Dynamic Listing</A>'s cursor is
at a valid location. It selects the module and section, if applicable, containing that cursor.
If the dynamic listing has a selection, it selects all modules and sections intersecting that
selection.</P>
</BODY>
</HTML>
@@ -56,7 +56,6 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.OpenProgramAction;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext;
import ghidra.app.plugin.core.debug.gui.thread.DebuggerTraceFileActionContext;
import ghidra.app.plugin.core.debug.gui.trace.DebuggerTraceTabPanel;
import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils;
@@ -74,6 +73,7 @@ import ghidra.debug.api.action.GoToInput;
import ghidra.debug.api.action.LocationTrackingSpec;
import ghidra.debug.api.control.ControlMode;
import ghidra.debug.api.listing.MultiBlendedListingBackgroundColorModel;
import ghidra.debug.api.modules.DebuggerMissingModuleActionContext;
import ghidra.debug.api.modules.DebuggerStaticMappingChangeListener;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.model.DomainFile;
@@ -20,6 +20,7 @@ import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.services.*;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
@@ -37,6 +38,7 @@ import ghidra.framework.plugintool.util.PluginStatus;
ProgramLocationPluginEvent.class,
ProgramClosedPluginEvent.class,
TraceActivatedPluginEvent.class,
TraceClosedPluginEvent.class,
},
servicesRequired = {
DebuggerStaticMappingService.class,
@@ -81,6 +83,9 @@ public class DebuggerModulesPlugin extends AbstractDebuggerPlugin {
else if (event instanceof TraceActivatedPluginEvent ev) {
provider.coordinatesActivated(ev.getActiveCoordinates());
}
else if (event instanceof TraceClosedPluginEvent ev) {
provider.traceClosed(ev.getTrace());
}
}
@Override
@@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.debug.gui.modules;
import static ghidra.framework.main.DataTreeDialogType.*;
import static ghidra.framework.main.DataTreeDialogType.OPEN;
import java.awt.event.MouseEvent;
import java.io.File;
@@ -67,15 +67,29 @@ import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.*;
import ghidra.trace.model.modules.*;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.util.TraceEvent;
import ghidra.trace.util.TraceEvents;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.*;
public class DebuggerModulesProvider extends ComponentProviderAdapter {
protected static final AutoConfigState.ClassHandler<DebuggerModulesProvider> CONFIG_STATE_HANDLER =
AutoConfigState.wireHandler(DebuggerModulesProvider.class, MethodHandles.lookup());
protected static final String NO_MODULES_PROPOSAL_SEL = """
Could not formulate a proposal for any selected module. \
You may need to import and/or open the destination images first.\
""";
protected static final String FMT_NO_MODULES_PROPOSAL_RETRY = """
Could not formulate a proposal for program '%s' to trace '%s'. \
The module may not be loaded yet, or the chosen image could be wrong.\
""";
protected static final String FMT_NO_MODULES_PROPOSAL_CURRENT = """
Could not formulate a proposal from module '%s' to program '%s'.\
""";
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
if (!Objects.equals(a.getTrace(), b.getTrace())) {
return false;
@@ -253,6 +267,57 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
}
}
interface MapMissingProgramRetryAction {
String NAME = "Retry Map Missing Program";
String DESCRIPTION = "Retry mapping the missing program by finding its module";
Icon ICON = DebuggerResources.ICON_MAP_AUTO;
String HELP_ANCHOR = "map_missing_program_retry";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarIcon(ICON)
.popupMenuIcon(ICON)
.popupMenuPath(NAME)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface MapMissingProgramToCurrentAction {
String NAME = "Map Missing Program to Current Module";
String DESCRIPTION = "Map the missing program to the current module";
Icon ICON = DebuggerResources.ICON_MAP_MODULES;
String HELP_ANCHOR = "map_missing_program_current";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarIcon(ICON)
.popupMenuIcon(ICON)
.popupMenuPath(NAME)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface MapMissingProgramIdenticallyAction {
String NAME = "Map Missing Program Identically";
String DESCRIPTION = "Map the missing program to its trace identically";
Icon ICON = DebuggerResources.ICON_MAP_IDENTICALLY;
String HELP_ANCHOR = "map_missing_program_identically";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarIcon(ICON)
.popupMenuIcon(ICON)
.popupMenuPath(NAME)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface ShowSectionsTableAction {
String NAME = "Show Sections Table";
Icon ICON = new GIcon("icon.debugger.modules.table.sections");
@@ -404,9 +469,17 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
}
}
protected class ForCleanupMappingChangeListener
implements DebuggerStaticMappingChangeListener {
@Override
public void mappingsChanged(Set<Trace> affectedTraces, Set<Program> affectedPrograms) {
Swing.runIfSwingOrRunLater(() -> cleanMissingProgramMessages(null, null));
}
}
final DebuggerModulesPlugin plugin;
@AutoServiceConsumed
//@AutoServiceConsumed via method
private DebuggerStaticMappingService staticMappingService;
@AutoServiceConsumed
private DebuggerTraceManagerService traceManager;
@@ -467,6 +540,13 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
DockingAction actionImportMissingModule;
DockingAction actionMapMissingModule;
DockingAction actionMapMissingProgramRetry;
DockingAction actionMapMissingProgramToCurrent;
DockingAction actionMapMissingProgramIdentically;
protected final ForCleanupMappingChangeListener mappingChangeListener =
new ForCleanupMappingChangeListener();
SelectAddressesAction actionSelectAddresses;
ImportFromFileSystemAction actionImportFromFileSystem;
ToggleDockingAction actionShowSectionsTable;
@@ -507,26 +587,37 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
importerService.importFile(root, file);
}
void addResolutionActionMaybe(DebuggerConsoleService consoleService, DockingActionIf action) {
if (action != null) {
consoleService.addResolutionAction(action);
}
}
void removeResolutionActionMaybe(DebuggerConsoleService consoleService,
DockingActionIf action) {
if (action != null) {
consoleService.removeResolutionAction(action);
}
}
@AutoServiceConsumed
private void setConsoleService(DebuggerConsoleService consoleService) {
if (consoleService != null) {
if (actionImportMissingModule != null) {
consoleService.addResolutionAction(actionImportMissingModule);
}
if (actionMapMissingModule != null) {
consoleService.addResolutionAction(actionMapMissingModule);
}
addResolutionActionMaybe(consoleService, actionImportMissingModule);
addResolutionActionMaybe(consoleService, actionMapMissingModule);
addResolutionActionMaybe(consoleService, actionMapMissingProgramRetry);
addResolutionActionMaybe(consoleService, actionMapMissingProgramToCurrent);
addResolutionActionMaybe(consoleService, actionMapMissingProgramIdentically);
}
}
protected void dispose() {
if (consoleService != null) {
if (actionImportMissingModule != null) {
consoleService.removeResolutionAction(actionImportMissingModule);
}
if (actionMapMissingModule != null) {
consoleService.removeResolutionAction(actionMapMissingModule);
}
removeResolutionActionMaybe(consoleService, actionImportMissingModule);
removeResolutionActionMaybe(consoleService, actionMapMissingModule);
removeResolutionActionMaybe(consoleService, actionMapMissingProgramRetry);
removeResolutionActionMaybe(consoleService, actionMapMissingProgramToCurrent);
removeResolutionActionMaybe(consoleService, actionMapMissingProgramIdentically);
}
blockChooserDialog.dispose();
@@ -637,6 +728,20 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
.onAction(this::activatedMapMissingModule)
.build();
actionMapMissingProgramRetry = MapMissingProgramRetryAction.builder(plugin)
.withContext(DebuggerMissingProgramActionContext.class)
.onAction(this::activatedMapMissingProgramRetry)
.build();
actionMapMissingProgramToCurrent = MapMissingProgramToCurrentAction.builder(plugin)
.withContext(DebuggerMissingProgramActionContext.class)
.enabledWhen(this::isEnabledMapMissingProgramToCurrent)
.onAction(this::activatedMapMissingProgramToCurrent)
.build();
actionMapMissingProgramIdentically = MapMissingProgramIdenticallyAction.builder(plugin)
.withContext(DebuggerMissingProgramActionContext.class)
.onAction(this::activatedMapMissingProgramIdentically)
.build();
actionSelectAddresses = new SelectAddressesAction();
actionImportFromFileSystem = new ImportFromFileSystemAction();
actionShowSectionsTable = ShowSectionsTableAction.builder(plugin)
@@ -799,6 +904,78 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
mapModuleTo(context.getModule());
}
private void activatedMapMissingProgramRetry(DebuggerMissingProgramActionContext context) {
if (staticMappingService == null) {
return;
}
Program program = context.getProgram();
Trace trace = context.getTrace();
Map<TraceModule, ModuleMapProposal> map = staticMappingService.proposeModuleMaps(
trace.getModuleManager().getAllModules(), List.of(program));
Collection<ModuleMapEntry> proposal = MapProposal.flatten(map.values());
promptModuleProposal(proposal, FMT_NO_MODULES_PROPOSAL_RETRY.formatted(
trace.getDomainFile().getName(), program.getDomainFile().getName()));
}
private boolean isEnabledMapMissingProgramToCurrent(
DebuggerMissingProgramActionContext context) {
if (staticMappingService == null || traceManager == null || listingService == null) {
return false;
}
ProgramLocation loc = listingService.getCurrentLocation();
if (loc == null) {
return false;
}
if (!(loc.getProgram() instanceof TraceProgramView view)) {
return false;
}
Trace trace = context.getTrace();
if (view.getTrace() != trace) {
return false;
}
long snap = traceManager.getCurrentFor(trace).getSnap();
Address address = loc.getAddress();
return !trace.getModuleManager().getModulesAt(snap, address).isEmpty();
}
private void activatedMapMissingProgramToCurrent(DebuggerMissingProgramActionContext context) {
if (staticMappingService == null || traceManager == null || listingService == null) {
return;
}
Trace trace = context.getTrace();
long snap = traceManager.getCurrentFor(trace).getSnap();
Address address = listingService.getCurrentLocation().getAddress();
TraceModule module = trace.getModuleManager().getModulesAt(snap, address).iterator().next();
Program program = context.getProgram();
ModuleMapProposal proposal =
staticMappingService.proposeModuleMap(module, program);
Map<TraceModule, ModuleMapEntry> map = proposal.computeMap();
promptModuleProposal(map.values(), FMT_NO_MODULES_PROPOSAL_CURRENT.formatted(
module.getName(), program.getDomainFile().getName()));
}
private void activatedMapMissingProgramIdentically(
DebuggerMissingProgramActionContext context) {
if (staticMappingService == null) {
return;
}
Trace trace = context.getTrace();
long snap = traceManager == null ? 0 : traceManager.getCurrentFor(trace).getSnap();
try {
staticMappingService.addIdentityMapping(trace, context.getProgram(),
Lifespan.nowOn(snap), true);
}
catch (TraceConflictedMappingException e) {
Msg.showError(this, null, "Map Identically", e.getMessage());
}
}
private void toggledShowSectionsTable(ActionContext ignored) {
setShowSectionsTable(actionShowSectionsTable.isSelected());
}
@@ -903,11 +1080,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
}
}
protected void promptModuleProposal(Collection<ModuleMapEntry> proposal) {
protected void promptModuleProposal(Collection<ModuleMapEntry> proposal, String emptyMsg) {
if (proposal.isEmpty()) {
Msg.showInfo(this, getComponent(), "Map Modules",
"Could not formulate a proposal for any selected module." +
" You may need to import and/or open the destination images first.");
Msg.showInfo(this, getComponent(), "Map Modules", emptyMsg);
return;
}
Collection<ModuleMapEntry> adjusted =
@@ -926,7 +1101,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
Map<TraceModule, ModuleMapProposal> map = staticMappingService.proposeModuleMaps(modules,
List.of(programManager.getAllOpenPrograms()));
Collection<ModuleMapEntry> proposal = MapProposal.flatten(map.values());
promptModuleProposal(proposal);
promptModuleProposal(proposal, NO_MODULES_PROPOSAL_SEL);
}
protected void mapModuleTo(TraceModule module) {
@@ -939,7 +1114,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
}
ModuleMapProposal proposal = staticMappingService.proposeModuleMap(module, program);
Map<TraceModule, ModuleMapEntry> map = proposal.computeMap();
promptModuleProposal(map.values());
promptModuleProposal(map.values(), NO_MODULES_PROPOSAL_SEL);
}
protected void promptSectionProposal(Collection<SectionMapEntry> proposal) {
@@ -1090,6 +1265,11 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
if (currentProgram == program) {
currentProgram = null;
}
cleanMissingProgramMessages(null, program);
}
public void traceClosed(Trace trace) {
cleanMissingProgramMessages(trace, null);
}
protected void addNewTraceListener() {
@@ -1232,4 +1412,62 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
doSetFilterSectionsByModules(filterSectionsByModules);
doSetShowSectionsTable(showSectionsTable);
}
protected boolean shouldKeepMessage(DebuggerMissingProgramActionContext ctx, Trace closedTrace,
Program closedProgram) {
Trace trace = ctx.getTrace();
if (trace == closedTrace) {
return false;
}
if (!traceManager.getOpenTraces().contains(trace)) {
return false;
}
Program program = ctx.getProgram();
if (program == closedProgram) {
return false;
}
if (programManager != null &&
!Arrays.asList(programManager.getAllOpenPrograms()).contains(program)) {
return false;
}
// Only do mapping probe on mapping changed events
if (closedTrace != null || closedProgram != null) {
return true;
}
TraceProgramView view = traceManager.getCurrentFor(trace).getView();
Address probe = ctx.getMappingProbeAddress();
ProgramLocation dyn = staticMappingService.getDynamicLocationFromStatic(view,
new ProgramLocation(program, probe));
if (dyn != null) {
return false;
}
return true;
}
protected void cleanMissingProgramMessages(Trace closedTrace, Program closedProgram) {
if (traceManager == null) {
return;
}
for (ActionContext ctx : consoleService.getActionContexts()) {
if (!(ctx instanceof DebuggerMissingProgramActionContext mpCtx)) {
continue;
}
if (!shouldKeepMessage(mpCtx, closedTrace, closedProgram)) {
consoleService.removeFromLog(mpCtx);
}
}
}
@AutoServiceConsumed
private void setStaticMappingService(DebuggerStaticMappingService staticMappingService) {
if (this.staticMappingService != null) {
this.staticMappingService.removeChangeListener(mappingChangeListener);
}
this.staticMappingService = staticMappingService;
if (this.staticMappingService != null) {
this.staticMappingService.addChangeListener(mappingChangeListener);
}
}
}
@@ -47,13 +47,13 @@ import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.ProgramManager;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.async.SwingExecutorService;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.debug.api.modules.DebuggerMissingModuleActionContext;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.model.*;
import ghidra.plugin.importer.ImporterPlugin;
@@ -139,4 +139,9 @@ public class DefaultTerminal implements Terminal {
public void setTerminateAction(Runnable action) {
Swing.runIfSwingOrRunLater(() -> provider.setTerminateAction(action));
}
@Override
public void toFront() {
provider.toFront();
}
}
@@ -802,7 +802,8 @@ public class VtBuffer {
i++;
}
}
for (int i = Math.max(sbSize, startRow); i <= endRow; i++) {
int lnSize = lines.size();
for (int i = Math.max(sbSize, startRow); i <= Math.min(endRow, sbSize + lnSize - 1); i++) {
VtLine line = lines.get(i - sbSize);
gatherLineText(buf, startRow, startCol, endRow, endCol, i, line, lineSep);
}
@@ -55,6 +55,8 @@ public interface Terminal extends AutoCloseable {
/**
* @see #injectDisplayOutput(ByteBuffer)
*
* @param arr the array of bytes to inject
*/
default void injectDisplayOutput(byte[] arr) {
injectDisplayOutput(ByteBuffer.wrap(arr));
@@ -87,6 +89,9 @@ public interface Terminal extends AutoCloseable {
/**
* @see #setFixedSize(short, short)
*
* @param cols the number of columns
* @param rows the number of rows
*/
default void setFixedSize(int cols, int rows) {
setFixedSize((short) cols, (short) rows);
@@ -102,6 +107,8 @@ public interface Terminal extends AutoCloseable {
*
* <p>
* This only affects the primary buffer. The alternate buffer has no scroll-back.
*
* @param rows the number of scroll-back rows
*/
void setMaxScrollBackRows(int rows);
@@ -210,4 +217,9 @@ public interface Terminal extends AutoCloseable {
* @return true for terminated, false for active
*/
boolean isTerminated();
/**
* Bring the terminal to the front of the UI
*/
void toFront();
}
@@ -482,6 +482,23 @@ public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerTest {
}
}
@Test
@SuppressWarnings("resource")
public void testGetFullText() throws Exception {
terminalService = addPlugin(tool, TerminalPlugin.class);
try (DefaultTerminal term = (DefaultTerminal) terminalService
.createNullTerminal(Charset.forName("UTF-8"), buf -> {
})) {
term.setFixedSize(80, 25);
term.injectDisplayOutput(TEST_CONTENTS);
assertEquals("""
term Term
noterm""", term.getFullText().trim());
}
}
protected String csi(char f, int... params) {
return "\033[" +
IntStream.of(params).mapToObj(Integer::toString).collect(Collectors.joining(";")) + f;