From 1b5384c00c905bad329a9fe48369f58186db92c5 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Mon, 29 Nov 2021 11:34:28 -0500 Subject: [PATCH] GP-1214: Adding copy-into-progarm actions (plugin). Moving export action. --- Ghidra/Debug/Debugger/certification.manifest | 2 + .../src/main/help/help/TOC_Source.xml | 165 ++-- .../DebuggerCopyActionsPlugin.html | 178 ++++ .../images/DebuggerCopyIntoProgramDialog.png | Bin 0 -> 38319 bytes .../DebuggerListingPlugin.html | 29 +- .../DebuggerMemoryBytesPlugin.html | 29 +- .../core/debug/gui/DebuggerResources.java | 57 +- .../gui/action/DebuggerReadsMemoryTrait.java | 28 +- .../copying/DebuggerCopyActionsPlugin.java | 153 ++++ .../DebuggerCopyIntoProgramDialog.java | 844 ++++++++++++++++++ .../debug/gui/copying/DebuggerCopyPlan.java | 449 ++++++++++ .../gui/listing/DebuggerListingProvider.java | 26 +- .../memory/DebuggerMemoryBytesProvider.java | 4 +- .../breakpoint/LogicalBreakpointInternal.java | 2 +- .../ReadsTargetMemoryPcodeExecutorState.java | 57 +- .../DebuggerStaticMappingServicePlugin.java | 40 +- .../DebuggerStaticMappingService.java | 151 +++- .../DebuggerCopyActionsPluginScreenShots.java | 157 ++++ .../AbstractGhidraHeadedDebuggerGUITest.java | 26 +- .../DebuggerCopyActionsPluginTest.java | 505 +++++++++++ .../gui/copying/DebuggerCopyPlanTests.java | 743 +++++++++++++++ .../listing/DebuggerListingProviderTest.java | 24 +- .../DebuggerMemoryBytesProviderTest.java | 26 +- .../DebuggerStaticMappingServiceTest.java | 69 +- .../database/data/DBTraceDataTypeManager.java | 4 +- .../database/listing/DBTraceInstruction.java | 19 +- .../listing/DBTraceInstructionsView.java | 49 +- .../AbstractDBTraceProgramViewListing.java | 24 +- ...actDBTraceProgramViewReferenceManager.java | 12 +- .../trace/database/ToyDBTraceBuilder.java | 9 +- .../app/merge/ProgramMergeManagerPlugin.java | 56 +- .../core/progmgr/ProgramManagerPlugin.java | 44 +- .../ghidra/app/services/ProgramManager.java | 177 ++-- .../app/services/TestDummyProgramManager.java | 24 +- .../plugin/core/diff/DiffProgramManager.java | 24 +- .../assembler/sleigh/SleighAssembler.java | 11 +- .../sem/AssemblyConstructorSemantic.java | 59 +- .../program/database/mem/MemoryMapDB.java | 84 +- 38 files changed, 3855 insertions(+), 505 deletions(-) create mode 100644 Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerCopyActionsPlugin/DebuggerCopyActionsPlugin.html create mode 100644 Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerCopyActionsPlugin/images/DebuggerCopyIntoProgramDialog.png create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPlugin.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlan.java create mode 100644 Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginScreenShots.java create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginTest.java create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlanTests.java diff --git a/Ghidra/Debug/Debugger/certification.manifest b/Ghidra/Debug/Debugger/certification.manifest index 2b87be0f13..c0fbb2759e 100644 --- a/Ghidra/Debug/Debugger/certification.manifest +++ b/Ghidra/Debug/Debugger/certification.manifest @@ -41,6 +41,8 @@ src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoints-enable-al src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoints-make-effective.png||GHIDRA||||END| src/main/help/help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerConsolePlugin/images/DebuggerConsolePlugin.png||GHIDRA||||END| +src/main/help/help/topics/DebuggerCopyActionsPlugin/DebuggerCopyActionsPlugin.html||GHIDRA||||END| +src/main/help/help/topics/DebuggerCopyActionsPlugin/images/DebuggerCopyIntoProgramDialog.png||GHIDRA||||END| src/main/help/help/topics/DebuggerEmulationServicePlugin/DebuggerEmulationServicePlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerInterpreterPlugin/DebuggerInterpreterPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html||GHIDRA||||END| diff --git a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml index df323e28ae..9e57f22ea4 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml @@ -52,109 +52,114 @@ - - - - - + - + + - - + + + + + + + sortgroup="c1" + target="help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html" /> - + - + - + + + + sortgroup="g" + target="help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html" /> - + - + - + - + - - - - + - - - - - - - + - + + + + + + + + + + - + sortgroup="n" + target="help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html" /> + - + sortgroup="o" + target="help/topics/DebuggerMemviewPlugin/DebuggerMemviewPlugin.html" /> + - - - + sortgroup="p" + target="help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html" /> + + + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerCopyActionsPlugin/DebuggerCopyActionsPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerCopyActionsPlugin/DebuggerCopyActionsPlugin.html new file mode 100644 index 0000000000..759f297caf --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerCopyActionsPlugin/DebuggerCopyActionsPlugin.html @@ -0,0 +1,178 @@ + + + + + + + Debugger: Copy Actions + + + + + +

Debugger: Copy Actions

+ +

In the course of debugging, the user may want to capture certain state and annotations from + the dynamic context into the static. This might include the contents of the stack, the heap, or + example data stored in uninitialized memory. The copy actions allow for the easy movement of + data and annotations from traces into programs. The actions are all accessed via the Debugger menu.

+ +

Actions

+ +

Copy Into Current Program

+ +

This action requires a selection of memory in a dynamic view. It copies selected contents + from the current trace (at the current time) into the current program. The Copy Dialog is presented with the current program set as the destination.

+ +

Copy Into New Program

+ +

This action requires a selection of memory in a dynamic view. It copies selected contents + from the current trace (at the current time) into a new program. The Copy + Dialog is presented with <New Program> set as the destination.

+ +

Export Trace View

+ +

This action is available whenever a trace is open. The Export Dialog is presented for + the current trace at the current time. This provides a mechanism for capturing a particular + point in time from a trace to a file. The exported image can be analyzed in Ghidra or another + tool.

+ +

Copy Dialog

+ +

The Copy Into... actions both present the same dialog: (The Export Trace View + action uses a different dialog.)

+ + + + + + + +
+ +

The dialog consists of several options, followed by a table that displays the proposed + ranges to copy. For selected ranges not contained in the destination program's memory, new + blocks are proposed. The source selection is always broken apart by regions defined in the + trace's memory manager.

+ +

Options

+ + + +

Table Columns

+ +

The table displays the proposal and allows for some adjustments. It has the following + columns:

+ + + +

The Copy button confirms the dialog and copies all proposed ranges in the + table. If successful, the dialog is closed. The Cancel button dismisses the dialog + without performing any operation. The Reset button resets the proposal, in case entries + were accidentally removed or modified.

+ + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerCopyActionsPlugin/images/DebuggerCopyIntoProgramDialog.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerCopyActionsPlugin/images/DebuggerCopyIntoProgramDialog.png new file mode 100644 index 0000000000000000000000000000000000000000..f3c51f59ff382ec0c0d7b4a98c088d05945f44a1 GIT binary patch literal 38319 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⋘8X}f`Y~DfAtY_ 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 literal 0 HcmV?d00001 diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html index 372c54f55f..b0850cb8aa 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html @@ -49,7 +49,7 @@ surprising. For example, disassembling some instructions and then stepping back in time will cause that disassembly to disappear.

-

Because not all memory is captured, some background coloring is used to indicate the state +

Because not all memory is recorded, some background coloring is used to indicate the state of attempted memory reads. Regardless of state, the most-recent contents, as recorded in the trace, are displayed in the listing, defaulting to 00. "Stale" memory, that is ranges of memory which have not been read at the current time, are displayed with a darker background. Where @@ -142,33 +142,32 @@ and/or needs a version upgrade. It will attempt to open the program, allowing Ghidra to prompt you about the situation.

-

Capture Memory

+

Read Memory

This action is available when the current trace is "at the present" with a live target, and - there is a selection of addresses in the dynamic listing. It will instruct the recorder to - capture the contents of memory for the selected range(s). Typically, the viewable addresses are - automatically captured — see the Auto-Read action.

+ there is a selection of addresses in the dynamic listing. It will instruct the recorder to read + and record the contents of memory for the selected range(s). Typically, the viewable addresses + are automatically read, anyway — see the Auto-Read action.

Auto-Read Memory

This action is always available on all dynamic listings. It configures whether or not the - memory range(s) displayed in the listing are automatically captured. Like the Capture Memory - action, capture can only occur when the current trace is "at the present" with a live target. - It occurs when the user scrolls the listing, or when the listing is otherwise navigated to a - new location. Note that other components may capture memory, regardless of this listing's - configuration. For example, the recorder typically captures the page of memory pointed to by - the program counter. In other words, this action cannot "disable all memory captures." - The options are pluggable, but currently consist of:

+ memory range(s) displayed in the listing are automatically read and recorded. Like the Read + Memory action, it is only permitted when the current trace is "at the present" with a live + target. It occurs when the user scrolls the listing, or when the listing is otherwise navigated + to a new location. Note that other components may read memory, regardless of this listing's + configuration. For example, the recorder typically reads the page of memory pointed to by the + program counter. In other words, this action cannot "disable all memory reads." The + options are pluggable, but currently consist of:

    -
  • Do Not Read Memory - disables automatic memory capture for this listing - only.
  • +
  • Do Not Read Memory - disables automatic memory reads for this listing only.
  • Read Visible Memory - automatically reads stale ranges that enter this listing's view.
  • Read Visible Memory, RO Once - (default) behaves like Read Visible Memory, except it will - neglect to capture read-only ranges that have been captured previously.
  • + neglect read-only ranges that have been read previously.

Tool Options: Colors

diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/DebuggerMemoryBytesPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/DebuggerMemoryBytesPlugin.html index 79d4a7fc04..e03884984f 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/DebuggerMemoryBytesPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/DebuggerMemoryBytesPlugin.html @@ -36,7 +36,7 @@ limitation is that you cannot use snapshots to display different points in time for the same trace.

-

Because not all memory is captured, some background coloring is used to indicate the state +

Because not all memory is recorded, some background coloring is used to indicate the state of attempted memory reads. Regardless of state, the most-recent contents, as recorded in the trace, are displayed in the window, defaulting to 00. "Stale" memory, that is ranges of memory which have not been read at the current time, are displayed with a darker background. Where @@ -103,33 +103,32 @@ -

Capture Memory

+

Read Memory

This action is available when the current trace is "at the present" with a live target, and - there is a selection of addresses in the memory window. It will instruct the recorder to - capture the contents of memory for the selected range(s). Typically, the viewable addresses are - automatically captured — see the Auto-Read action.

+ there is a selection of addresses in the memory window. It will instruct the recorder to read + and record the contents of memory for the selected range(s). Typically, the viewable addresses + are automatically read — see the Auto-Read action.

Auto-Read Memory

This action is always available on all memory windows. It configures whether or not the - memory range(s) displayed in the window are automatically captured. Like the Capture Memory - action, capture can only occur when the current trace is "at the present" with a live target. - It occurs when the user scrolls the window, or when the window is otherwise navigated to a new - location. Note that other components may capture memory, regardless of this windows's - configuration. For example, the recorder typically captures the page of memory pointed to by - the program counter. In other words, this action cannot "disable all memory captures." - The options are pluggable, but currently consist of:

+ memory range(s) displayed in the window are automatically read and recorded. Like the Read + Memory action, it is only permitted when the current trace is "at the present" with a live + target. It occurs when the user scrolls the window, or when the window is otherwise navigated + to a new location. Note that other components may read memory, regardless of this windows's + configuration. For example, the recorder typically reads the page of memory pointed to by the + program counter. In other words, this action cannot "disable all memory reads." The + options are pluggable, but currently consist of:

    -
  • Do Not Read Memory - disables automatic memory capture for this window - only.
  • +
  • Do Not Read Memory - disables automatic memory reads for this window only.
  • Read Visible Memory - automatically reads stale ranges that enter this window's view.
  • Read Visible Memory, RO Once - (default) behaves like Read Visible Memory, except it will - neglect to capture read-only ranges that have been captured previously.
  • + neglect read-only ranges that have been read previously.

Byte Viewer Options

diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java index 4c2f68e243..e7b06c2a39 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java @@ -144,8 +144,8 @@ public interface DebuggerResources { ImageIcon ICON_AUTOREAD = ResourceManager.loadImage("images/autoread.png"); // TODO: Draw a real icon. - ImageIcon ICON_CAPTURE_MEMORY = ICON_REGIONS; - //ResourceManager.loadImage("images/capture-memory.png"); + ImageIcon ICON_READ_MEMORY = ICON_REGIONS; + //ResourceManager.loadImage("images/read-memory.png"); // TODO: Draw an icon ImageIcon ICON_MAP_IDENTICALLY = ResourceManager.loadImage("images/doubleArrow.png"); @@ -777,14 +777,15 @@ public interface DebuggerResources { } } - abstract class AbstractCaptureSelectedMemoryAction extends DockingAction { - public static final String NAME = "Capture Selected Memory"; - public static final Icon ICON = ICON_CAPTURE_MEMORY; - public static final String HELP_ANCHOR = "capture_memory"; + abstract class AbstractReadSelectedMemoryAction extends DockingAction { + public static final String NAME = "Read Selected Memory"; + public static final Icon ICON = ICON_READ_MEMORY; + public static final String HELP_ANCHOR = "read_memory"; - public AbstractCaptureSelectedMemoryAction(Plugin owner) { + public AbstractReadSelectedMemoryAction(Plugin owner) { super(NAME, owner.getName()); - setDescription("Capture memory for the selected addresses into the trace database"); + setDescription( + "(Re-)read and record memory for the selected addresses into the trace database"); setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR)); } } @@ -885,7 +886,7 @@ public interface DebuggerResources { interface AutoReadMemoryAction { String NAME = "Auto-Read Target Memory"; - String DESCRIPTION = "Automatically capture visible memory from the live target"; + String DESCRIPTION = "Automatically read and record visible memory from the live target"; String HELP_ANCHOR = "auto_memory"; String NAME_VIS_RO_ONCE = "Read Visible Memory, RO Once"; @@ -1081,7 +1082,43 @@ public interface DebuggerResources { return new ActionBuilder(NAME, ownerName) .description(DESCRIPTION) .menuGroup(GROUP) - .menuPath(NAME) + .menuPath(DebuggerPluginPackage.NAME, NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface CopyIntoProgramAction { + String NAME_PAT = "Copy Into %s Program"; + String DESC_PAT = "Copy the current selection into %s program"; + String GROUP = GROUP_MAINTENANCE; + } + + interface CopyIntoCurrentProgramAction extends CopyIntoProgramAction { + String NAME = String.format(NAME_PAT, "Current"); + String DESCRIPTION = String.format(DESC_PAT, "the current"); + String HELP_ANCHOR = "copy_into_current"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuGroup(GROUP) + .menuPath(DebuggerPluginPackage.NAME, NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface CopyIntoNewProgramAction extends CopyIntoProgramAction { + String NAME = String.format(NAME_PAT, "New"); + String DESCRIPTION = String.format(DESC_PAT, "a new"); + String HELP_ANCHOR = "copy_into_new"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuGroup(GROUP) + .menuPath(DebuggerPluginPackage.NAME, NAME) .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java index f79d67a87a..d7857f63da 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java @@ -27,7 +27,7 @@ import docking.menu.MultiStateDockingAction; import docking.widgets.EventTrigger; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.gui.DebuggerResources; -import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractCaptureSelectedMemoryAction; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractReadSelectedMemoryAction; import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec; import ghidra.app.plugin.core.debug.utils.BackgroundUtils; import ghidra.app.services.TraceRecorder; @@ -49,10 +49,10 @@ public abstract class DebuggerReadsMemoryTrait { protected static final AutoConfigState.ClassHandler CONFIG_STATE_HANDLER = AutoConfigState.wireHandler(DebuggerReadsMemoryTrait.class, MethodHandles.lookup()); - protected class CaptureSelectedMemoryAction extends AbstractCaptureSelectedMemoryAction { + protected class ReadSelectedMemoryAction extends AbstractReadSelectedMemoryAction { public static final String GROUP = DebuggerResources.GROUP_GENERAL; - public CaptureSelectedMemoryAction() { + public ReadSelectedMemoryAction() { super(plugin); setToolBarData(new ToolBarData(ICON, GROUP)); setEnabled(false); @@ -89,14 +89,14 @@ public abstract class DebuggerReadsMemoryTrait { } } - protected class ForCaptureTraceListener extends TraceDomainObjectListener { - public ForCaptureTraceListener() { + protected class ForReadsTraceListener extends TraceDomainObjectListener { + public ForReadsTraceListener() { listenFor(TraceSnapshotChangeType.ADDED, this::snapshotAdded); listenFor(TraceMemoryStateChangeType.CHANGED, this::memStateChanged); } private void snapshotAdded(TraceSnapshot snapshot) { - actionCaptureSelected.updateEnabled(null); + actionReadSelected.updateEnabled(null); } private void memStateChanged(TraceAddressSnapRange range, TraceMemoryState oldIsNull, @@ -120,7 +120,7 @@ public abstract class DebuggerReadsMemoryTrait { @Override public void processMemoryAccessibilityChanged(TraceRecorder recorder) { Swing.runIfSwingOrRunLater(() -> { - actionCaptureSelected.updateEnabled(null); + actionReadSelected.updateEnabled(null); }); } } @@ -137,7 +137,7 @@ public abstract class DebuggerReadsMemoryTrait { } protected MultiStateDockingAction actionAutoRead; - protected CaptureSelectedMemoryAction actionCaptureSelected; + protected ReadSelectedMemoryAction actionReadSelected; private final AutoReadMemorySpec defaultAutoSpec = AutoReadMemorySpec.fromConfigName(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME); @@ -149,8 +149,8 @@ public abstract class DebuggerReadsMemoryTrait { protected final Plugin plugin; protected final ComponentProvider provider; - protected final ForCaptureTraceListener traceListener = - new ForCaptureTraceListener(); + protected final ForReadsTraceListener traceListener = + new ForReadsTraceListener(); protected final ForAccessRecorderListener recorderListener = new ForAccessRecorderListener(); protected final ForVisibilityListener displayListener = new ForVisibilityListener(); @@ -257,10 +257,10 @@ public abstract class DebuggerReadsMemoryTrait { } } - public DockingAction installCaptureSelectedAction() { - actionCaptureSelected = new CaptureSelectedMemoryAction(); - provider.addLocalAction(actionCaptureSelected); - return actionCaptureSelected; + public DockingAction installReadSelectedAction() { + actionReadSelected = new ReadSelectedMemoryAction(); + provider.addLocalAction(actionReadSelected); + return actionReadSelected; } public AddressSetDisplayListener getDisplayListener() { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPlugin.java new file mode 100644 index 0000000000..97053afc18 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPlugin.java @@ -0,0 +1,153 @@ +/* ### + * 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.app.plugin.core.debug.gui.copying; + +import docking.ActionContext; +import docking.action.DockingAction; +import ghidra.app.context.ProgramLocationActionContext; +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.gui.DebuggerResources.*; +import ghidra.app.plugin.core.exporter.ExporterDialog; +import ghidra.app.services.*; +import ghidra.framework.plugintool.PluginInfo; +import ghidra.framework.plugintool.PluginTool; +import ghidra.framework.plugintool.annotation.AutoServiceConsumed; +import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.program.util.ProgramSelection; +import ghidra.trace.model.program.TraceProgramView; +import ghidra.trace.model.program.TraceVariableSnapProgramView; + +@PluginInfo( + shortDescription = "Copy and export trace data", + description = "Provides tool actions for moving data from traces to various destinations.", + category = PluginCategoryNames.DEBUGGER, + packageName = DebuggerPluginPackage.NAME, + status = PluginStatus.RELEASED, + eventsConsumed = {}, + eventsProduced = {}, + servicesRequired = { + DebuggerStaticMappingService.class, + ProgramManager.class, + }, + servicesProvided = {}) +public class DebuggerCopyActionsPlugin extends AbstractDebuggerPlugin { + + protected static ProgramSelection getSelectionFromContext(ActionContext context) { + if (!(context instanceof ProgramLocationActionContext)) { + return null; + } + ProgramLocationActionContext ctx = (ProgramLocationActionContext) context; + return ctx.hasSelection() ? ctx.getSelection() : null; + } + + protected DebuggerCopyIntoProgramDialog copyDialog = new DebuggerCopyIntoProgramDialog(); + + protected DockingAction actionExportView; + protected DockingAction actionCopyIntoCurrentProgram; + protected DockingAction actionCopyIntoNewProgram; + + @AutoServiceConsumed + private ProgramManager programManager; + @AutoServiceConsumed + private DebuggerStaticMappingService mappingService; + @AutoServiceConsumed + private DebuggerModelService modelService; + + public DebuggerCopyActionsPlugin(PluginTool tool) { + super(tool); + + createActions(); + } + + protected void createActions() { + actionExportView = ExportTraceViewAction.builder(this) + .enabled(false) + .withContext(ProgramLocationActionContext.class) + .enabledWhen(this::checkTrace) + .onAction(this::activatedExportView) + .buildAndInstall(tool); + + // Using programManager here depends on it calling tool.updateContext() + actionCopyIntoCurrentProgram = CopyIntoCurrentProgramAction.builder(this) + .enabled(false) + .withContext(ProgramLocationActionContext.class) + .enabledWhen( + ctx -> checkTraceSelection(ctx) && programManager.getCurrentProgram() != null) + .onAction(this::activatedCopyIntoCurrentProgram) + .buildAndInstall(tool); + + actionCopyIntoNewProgram = CopyIntoNewProgramAction.builder(this) + .enabled(false) + .withContext(ProgramLocationActionContext.class) + .enabledWhen(this::checkTraceSelection) + .onAction(this::activatedCopyIntoNewProgram) + .buildAndInstall(tool); + } + + protected boolean checkTrace(ProgramLocationActionContext context) { + return context.getProgram() instanceof TraceProgramView; + } + + protected boolean checkTraceSelection(ProgramLocationActionContext context) { + return checkTrace(context) && context.hasSelection(); + } + + protected void activatedExportView(ProgramLocationActionContext context) { + if (!checkTrace(context)) { + return; + } + TraceProgramView view = (TraceProgramView) context.getProgram(); + // Avoid odd race conditions by fixing the snap + TraceProgramView fixed = view instanceof TraceVariableSnapProgramView + ? view.getTrace().getFixedProgramView(view.getSnap()) + : view; + + ExporterDialog dialog = + new ExporterDialog(tool, fixed.getDomainFile(), fixed, + getSelectionFromContext(context)); + tool.showDialog(dialog); + } + + protected void activatedCopyIntoCurrentProgram(ProgramLocationActionContext context) { + if (!checkTraceSelection(context)) { + return; + } + copyDialog.setSource((TraceProgramView) context.getProgram(), context.getSelection()); + copyDialog.setProgramManager(programManager); + copyDialog.setStaticMappingService(mappingService); + copyDialog.setModelService(modelService); + copyDialog.setDestination(programManager.getCurrentProgram()); + copyDialog.reset(); + copyDialog.setStatusText(""); + tool.showDialog(copyDialog); + } + + protected void activatedCopyIntoNewProgram(ProgramLocationActionContext context) { + if (!checkTraceSelection(context)) { + return; + } + copyDialog.setSource((TraceProgramView) context.getProgram(), context.getSelection()); + copyDialog.setProgramManager(programManager); + copyDialog.setStaticMappingService(mappingService); + copyDialog.setModelService(modelService); + copyDialog.setDestination(copyDialog.NEW_PROGRAM); + copyDialog.reset(); + copyDialog.setStatusText(""); + tool.showDialog(copyDialog); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java new file mode 100644 index 0000000000..7313ace853 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java @@ -0,0 +1,844 @@ +/* ### + * 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.app.plugin.core.debug.gui.copying; + +import java.awt.*; +import java.io.IOException; +import java.util.*; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.*; +import java.util.stream.Collectors; + +import javax.swing.*; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; + +import com.google.common.collect.Range; + +import docking.DialogComponentProvider; +import docking.widgets.table.*; +import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.plugin.core.debug.gui.copying.DebuggerCopyPlan.Copier; +import ghidra.app.services.*; +import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; +import ghidra.program.database.ProgramDB; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.trace.model.memory.TraceMemoryManager; +import ghidra.trace.model.memory.TraceMemoryRegion; +import ghidra.trace.model.modules.*; +import ghidra.trace.model.program.TraceProgramView; +import ghidra.util.*; +import ghidra.util.database.UndoableTransaction; +import ghidra.util.exception.CancelledException; +import ghidra.util.table.GhidraTableFilterPanel; +import ghidra.util.task.*; + +public class DebuggerCopyIntoProgramDialog extends DialogComponentProvider { + static final int GAP = 5; + static final int BUTTON_SIZE = 32; + + protected static class RangeEntry { + private final String regionName; + private final String moduleNames; + private final String sectionNames; + private final AddressRange srcRange; + private String blockName; + private final boolean create; + private final boolean overlay; + private final AddressRange dstRange; + + protected RangeEntry(String regionName, String moduleNames, String sectionNames, + AddressRange srcRange, String blockName, boolean create, boolean overlay, + AddressRange dstRange) { + this.regionName = regionName; + this.moduleNames = moduleNames; + this.sectionNames = sectionNames; + this.srcRange = srcRange; + this.blockName = blockName; + this.create = create; + this.overlay = overlay; + this.dstRange = dstRange; + } + + public String getRegionName() { + return regionName; + } + + public String getModuleNames() { + return moduleNames; + } + + public String getSectionNames() { + return sectionNames; + } + + public AddressRange getSrcRange() { + return srcRange; + } + + public Address getSrcMinAddress() { + return srcRange.getMinAddress(); + } + + public Address getSrcMaxAddress() { + return srcRange.getMaxAddress(); + } + + public AddressRange getDstRange() { + return dstRange; + } + + public String getBlockName() { + return create ? blockName : (blockName + " *"); + } + + public void setBlockName(String blockName) { + if (!create) { + throw new IllegalStateException("Cannot modify name of existing block"); + } + this.blockName = blockName; + } + + public boolean isCreate() { + return create; + } + + public boolean isOverlay() { + return overlay; + } + + public Address getDstMinAddress() { + return dstRange.getMinAddress(); + } + + public Address getDstMaxAddress() { + return dstRange.getMaxAddress(); + } + } + + protected enum RangeTableColumns + implements EnumeratedTableColumn { + REMOVE("Remove", String.class, e -> "Remove Range", (e, v) -> nop(), null), + REGION("Region", String.class, RangeEntry::getRegionName), + MODULES("Modules", String.class, RangeEntry::getModuleNames), + SECTIONS("Sections", String.class, RangeEntry::getSectionNames), + SRC_MIN("SrcMin", Address.class, RangeEntry::getSrcMinAddress), + SRC_MAX("SrcMax", Address.class, RangeEntry::getSrcMaxAddress), + BLOCK("Block", String.class, RangeEntry::getBlockName, RangeEntry::setBlockName, // + RangeEntry::isCreate), + OVERLAY("Overlay", Boolean.class, RangeEntry::isOverlay), + DST_MIN("DstMin", Address.class, RangeEntry::getDstMinAddress), + DST_MAX("DstMax", Address.class, RangeEntry::getDstMaxAddress); + + private static void nop() { + } + + private final String header; + private final Class cls; + private final Function getter; + private final BiConsumer setter; + private final Predicate editable; + + @SuppressWarnings("unchecked") + RangeTableColumns(String header, Class cls, Function getter, + BiConsumer setter, Predicate editable) { + this.header = header; + this.cls = cls; + this.getter = getter; + this.setter = (BiConsumer) setter; + this.editable = editable; + } + + RangeTableColumns(String header, Class cls, Function getter) { + this(header, cls, getter, null, null); + } + + @Override + public String getHeader() { + return header; + } + + @Override + public Class getValueClass() { + return cls; + } + + @Override + public Object getValueOf(RangeEntry row) { + return getter.apply(row); + } + + @Override + public boolean isEditable(RangeEntry row) { + return setter != null && (editable == null || editable.test(row)); + } + + @Override + public void setValueOf(RangeEntry row, Object value) { + setter.accept(row, value); + } + } + + protected static class RangeTableModel + extends DefaultEnumeratedColumnTableModel { + public RangeTableModel() { + super("Ranges", RangeTableColumns.class); + } + + @Override + public List defaultSortOrder() { + return List.of(RangeTableColumns.SRC_MIN); + } + } + + protected interface CopyDestination { + default Program getExistingProgram() { + return null; + } + + default boolean isExisting() { + return getExistingProgram() != null; + } + + Program getOrCreateProgram(TraceProgramView source, Object consumer) throws IOException; + + default void saveIfApplicable(Program program) { + } + } + + protected static final CopyDestination TEMP_PROGRAM = new CopyDestination() { + @Override + public String toString() { + return ""; + } + + @Override + public Program getOrCreateProgram(TraceProgramView source, Object consumer) + throws IOException { + return new ProgramDB(source.getName(), source.getLanguage(), source.getCompilerSpec(), + consumer); + } + }; + + protected final CopyDestination NEW_PROGRAM = new CopyDestination() { + @Override + public String toString() { + return ""; + } + + @Override + public Program getOrCreateProgram(TraceProgramView source, Object consumer) + throws IOException { + return new ProgramDB(source.getName(), source.getLanguage(), source.getCompilerSpec(), + consumer); + } + + @Override + public void saveIfApplicable(Program program) { + programManager.saveProgramAs(program); + } + }; + + protected static class OpenProgramDestination implements CopyDestination { + private final Program program; + + public OpenProgramDestination(Program program) { + this.program = program; + } + + @Override + public String toString() { + return program.getName(); + } + + @Override + public Program getExistingProgram() { + return program; + } + + @Override + public Program getOrCreateProgram(TraceProgramView source, Object consumer) { + return program; + } + } + + protected DebuggerModelService modelService; + protected ProgramManager programManager; + protected DebuggerStaticMappingService staticMappingService; + + protected TraceProgramView source; + protected AddressSetView set; + + protected CompletableFuture lastTask; + protected CompletableFuture captureTask; + + protected final DefaultComboBoxModel comboDestinationModel = + new DefaultComboBoxModel<>(); + protected JComboBox comboDestination; + protected final Map programDestinations = new HashMap<>(); + + // TODO: Save these options to tool state? + protected JCheckBox cbCapture; + protected JCheckBox cbRelocate; + protected JCheckBox cbUseOverlays; + protected DebuggerCopyPlan plan = new DebuggerCopyPlan(); + + protected final RangeTableModel tableModel = new RangeTableModel(); + protected GTable table; + protected GhidraTableFilterPanel filterPanel; + + protected JButton resetButton; + + public DebuggerCopyIntoProgramDialog() { + super("Copy Into Program", true, true, true, true); + + populateComponents(); + } + + protected void populateComponents() { + plan.selectAll(); + JPanel panel = new JPanel(new BorderLayout()); + + { + JPanel opts = new JPanel(); + opts.setLayout(new BoxLayout(opts, BoxLayout.Y_AXIS)); + + { + Box progBox = Box.createHorizontalBox(); + progBox.setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP)); + progBox.add(new JLabel("Destination:")); + comboDestination = new JComboBox<>(comboDestinationModel); + comboDestination.setBorder(BorderFactory.createEmptyBorder(0, GAP, 0, 0)); + comboDestination.addActionListener(e -> { + if (!isVisible()) { + return; + } + syncCbRelocateEnabled(getDestination()); + reset(); + }); + progBox.add(comboDestination); + opts.add(progBox); + } + + { + // Avoid Swing's automatic indentation + JPanel inner = new JPanel(new BorderLayout()); + inner.setBorder(BorderFactory.createEmptyBorder(0, GAP, GAP, GAP)); + cbCapture = + new JCheckBox("Read live target's memory"); + cbCapture.addActionListener(e -> { + if (!isVisible()) { + return; + } + reset(); + }); + inner.add(cbCapture); + opts.add(inner); + } + + { + // Avoid Swing's automatic indentation + JPanel inner = new JPanel(new BorderLayout()); + inner.setBorder(BorderFactory.createEmptyBorder(0, GAP, GAP, GAP)); + cbRelocate = + new JCheckBox("Relocate via Mappings. WARNING: No fixups"); + cbRelocate.addActionListener(e -> { + if (!isVisible()) { + return; + } + reset(); + }); + inner.add(cbRelocate); + opts.add(inner); + } + + { + // No swing indentation + JPanel inner = new JPanel(new BorderLayout()); + inner.setBorder(BorderFactory.createEmptyBorder(0, GAP, GAP, GAP)); + cbUseOverlays = new JCheckBox("Use overlays where blocks already exist"); + cbUseOverlays.addActionListener(e -> { + if (!isVisible()) { + return; + } + reset(); + }); + inner.add(cbUseOverlays); + opts.add(inner); + } + + { + JPanel panelInclude = new JPanel(new GridLayout(0, 2, GAP, GAP)); + panelInclude.setBorder(BorderFactory.createTitledBorder("Include:")); + JButton buttonSelectNone = new JButton("Select None"); + buttonSelectNone.addActionListener(e -> plan.selectNone()); + panelInclude.add(buttonSelectNone); + JButton buttonSelectAll = new JButton("Select All"); + buttonSelectAll.addActionListener(e -> plan.selectAll()); + panelInclude.add(buttonSelectAll); + for (Copier copier : plan.getAllCopiers()) { + panelInclude.add(plan.getCheckBox(copier)); + } + opts.add(panelInclude); + } + panel.add(opts, BorderLayout.NORTH); + } + + { + JPanel tablePanel = new JPanel(new BorderLayout()); + table = new GTable(tableModel); + table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + tablePanel.add(new JScrollPane(table)); + filterPanel = new GhidraTableFilterPanel<>(table, tableModel); + tablePanel.add(filterPanel, BorderLayout.SOUTH); + panel.add(tablePanel, BorderLayout.CENTER); + } + + panel.setMinimumSize(new Dimension(600, 600)); + addWorkPanel(panel); + + addOKButton(); + okButton.setText("Copy"); + addCancelButton(); + addResetButton(); + + TableColumnModel columnModel = table.getColumnModel(); + + TableColumn removeCol = columnModel.getColumn(RangeTableColumns.REMOVE.ordinal()); + CellEditorUtils.installButton(table, filterPanel, removeCol, DebuggerResources.ICON_DELETE, + BUTTON_SIZE, this::removeEntry); + } + + protected void addResetButton() { + resetButton = new JButton("Reset"); + resetButton.setMnemonic('R'); + resetButton.setName("Reset"); + resetButton.addActionListener(e -> resetCallback()); + addButton(resetButton); + } + + @Override + protected void cancelCallback() { + synchronized (this) { + if (captureTask != null) { + captureTask.cancel(false); + } + } + super.cancelCallback(); + } + + @Override + protected void okCallback() { + super.okCallback(); + + lastTask = new CompletableFuture<>(); + Task task = new Task("Copy Into Program", true, true, false) { + @Override + public void run(TaskMonitor monitor) throws CancelledException { + try { + executePlan(monitor); + Swing.runLater(() -> { + setStatusText(""); + close(); + }); + } + catch (Exception e) { + Msg.error(this, "Error copying into program", e); + setStatusText("Error: " + e.getMessage()); + } + } + }; + task.addTaskListener(new TaskListener() { + @Override + public void taskCancelled(Task task) { + lastTask.cancel(false); + } + + @Override + public void taskCompleted(Task task) { + lastTask.complete(null); + } + }); + executeProgressTask(task, 500); + } + + protected void resetCallback() { + reset(); + } + + protected void removeEntry(RangeEntry entry) { + tableModel.delete(entry); + } + + protected TraceRecorder getRecorderIfReadsPresent() { + if (modelService == null) { + return null; + } + TraceRecorder recorder = modelService.getRecorder(source.getTrace()); + if (recorder == null) { + return null; + } + if (!DebuggerCoordinates.view(source).withRecorder(recorder).isAliveAndReadsPresent()) { + return null; + } + return recorder; + } + + protected void checkCbCaptureEnabled() { + boolean en = getRecorderIfReadsPresent() != null; + cbCapture.setEnabled(en); + cbCapture.setSelected(en); + } + + public void setModelService(DebuggerModelService modelService) { + this.modelService = modelService; + checkCbCaptureEnabled(); + } + + public void setSource(TraceProgramView source, AddressSetView set) { + this.source = source; + this.set = set; + checkCbCaptureEnabled(); + } + + public void setProgramManager(ProgramManager programManager) { + this.programManager = programManager; + setSelectablePrograms(programManager.getAllOpenPrograms()); + } + + protected void setSelectablePrograms(Program[] programs) { + setSelectablePrograms(Arrays.asList(programs)); + } + + protected void setSelectablePrograms(Collection programs) { + programDestinations.clear(); + comboDestinationModel.removeAllElements(); + comboDestinationModel.addElement(NEW_PROGRAM); + comboDestinationModel.addElement(TEMP_PROGRAM); + for (Program program : new LinkedHashSet<>(programs)) { + OpenProgramDestination destination = new OpenProgramDestination(program); + programDestinations.put(program, destination); + comboDestinationModel.addElement(destination); + } + } + + public void setDestination(Program program) { + setDestination(programDestinations.get(program)); + } + + protected void syncCbRelocateEnabled(CopyDestination dest) { + cbRelocate.setEnabled(dest.getExistingProgram() != null); + } + + public void setDestination(CopyDestination dest) { + Objects.requireNonNull(dest); + syncCbRelocateEnabled(dest); + comboDestinationModel.setSelectedItem(dest); + } + + public CopyDestination getDestination() { + return (CopyDestination) comboDestinationModel.getSelectedItem(); + } + + public void setCapture(boolean capture) { + if (capture && getRecorderIfReadsPresent() == null) { + throw new IllegalStateException( + "Cannot enable capture unless live and reading the present"); + } + this.cbCapture.setSelected(capture); + } + + public boolean isCapture() { + return (cbCapture.isSelected() && getRecorderIfReadsPresent() != null); + } + + public void setRelocate(boolean relocate) { + if (relocate && !getDestination().isExisting()) { + throw new IllegalStateException("Cannot relocate when creating a new program"); + } + this.cbRelocate.setSelected(relocate); + } + + public boolean isRelocate() { + return cbRelocate.isSelected() && staticMappingService != null && + getDestination().isExisting(); + } + + public void setUseOverlays(boolean useOverlays) { + if (useOverlays && !getDestination().isExisting()) { + // Technically, you can, but why would you? + throw new IllegalStateException("Cannot use overlays when creating a new program"); + } + this.cbUseOverlays.setSelected(useOverlays); + } + + public boolean isUseOverlays() { + return cbUseOverlays.isSelected() && getDestination().isExisting(); + } + + public void setStaticMappingService(DebuggerStaticMappingService staticMappingService) { + this.staticMappingService = staticMappingService; + cbRelocate.setEnabled(staticMappingService != null); + } + + /** + * Re-populate the table based on destination and relocation settings + */ + public void reset() { + Program dest = getDestination().getExistingProgram(); + plan.syncCopiersEnabled(source, dest); + if (isRelocate()) { + resetWithRelocation(isUseOverlays(), dest); + } + else { + resetWithoutRelocation(isUseOverlays(), dest); + } + } + + protected String createName(String desired, Set taken) { + if (taken.add(desired)) { + return desired; + } + String candidate = desired; + for (int i = 2;; i++) { + candidate = desired + "_" + i; + if (taken.add(candidate)) { + return candidate; + } + } + } + + protected String computeRegionString(AddressRange rng) { + TraceMemoryManager mm = source.getTrace().getMemoryManager(); + Collection regions = + mm.getRegionsIntersecting(Range.singleton(source.getSnap()), rng); + return regions.isEmpty() ? "UNKNOWN" : regions.iterator().next().getName(); + } + + protected String computeModulesString(AddressRange rng) { + TraceModuleManager mm = source.getTrace().getModuleManager(); + Collection modules = + mm.getModulesIntersecting(Range.singleton(source.getSnap()), rng); + return modules.stream().map(m -> m.getName()).collect(Collectors.joining(",")); + } + + protected String computeSectionsString(AddressRange rng) { + TraceModuleManager mm = source.getTrace().getModuleManager(); + Collection sections = + mm.getSectionsIntersecting(Range.singleton(source.getSnap()), rng); + return sections.stream().map(s -> s.getName()).collect(Collectors.joining(",")); + } + + protected void createEntry(Collection result, AddressRange srcRange, + AddressRange dstRange, boolean overlay, Set taken, MemoryBlock dstBlock) { + String srcName = computeRegionString(srcRange); + String dstName = dstBlock != null ? dstBlock.getName() : createName(srcName, taken); + String srcModules = computeModulesString(srcRange); + String srcSections = computeSectionsString(srcRange); + result.add(new RangeEntry(srcName, srcModules, srcSections, srcRange, dstName, + dstBlock == null, overlay, dstRange)); + } + + protected void createEntries(Collection result, boolean useOverlays, + MappedAddressRange mappedRng, AddressRange srcRange, AddressRange dstRange, + Set taken, Program dest) { + if (dest == null) { + createEntry(result, srcRange, dstRange, false, taken, null); + return; + } + + Memory memory = dest.getMemory(); + AddressSetView hits = + memory.intersectRange(dstRange.getMinAddress(), dstRange.getMaxAddress()); + if (!hits.isEmpty() && useOverlays) { + createEntry(result, srcRange, dstRange, true, taken, null); + return; + } + + AddressSetView misses = new AddressSet(dstRange).subtract(hits); + for (AddressRange miss : misses) { + createEntry(result, mappedRng.mapDestinationToSource(miss), miss, false, taken, null); + } + for (AddressRange hit : hits) { + Address next = hit.getMinAddress(); + while (next != null && hit.contains(next)) { + MemoryBlock block = memory.getBlock(next); + AddressRange dr = hit.intersectRange(block.getStart(), block.getEnd()); + createEntry(result, mappedRng.mapDestinationToSource(dr), dr, false, taken, block); + next = block.getEnd().next(); + } + } + } + + protected void collectBlockNames(Collection result, Program program) { + if (program == null) { + return; + } + for (MemoryBlock b : program.getMemory().getBlocks()) { + result.add(b.getName()); + } + } + + protected List breakRangeByRegions(AddressRange srcRange) { + AddressSet remains = new AddressSet(srcRange); + List result = new ArrayList<>(); + for (TraceMemoryRegion region : source.getTrace() + .getMemoryManager() + .getRegionsIntersecting(Range.singleton(source.getSnap()), srcRange)) { + AddressRange range = region.getRange().intersect(srcRange); + result.add(range); + remains.delete(range); + } + remains.iterator().forEachRemaining(result::add); + return result; + } + + protected void resetWithRelocation(boolean useOverlays, Program dest) { + Objects.requireNonNull(dest); + tableModel.clear(); + List result = new ArrayList<>(); + Set taken = new HashSet<>(); + collectBlockNames(taken, dest); + Collection mappedSet = staticMappingService + .getOpenMappedViews(source.getTrace(), set, source.getSnap()) + .get(dest); + if (mappedSet == null) { + return; + } + for (MappedAddressRange mappedRng : mappedSet) { + for (AddressRange src : breakRangeByRegions(mappedRng.getSourceAddressRange())) { + AddressRange dst = mappedRng.mapSourceToDestination(src); + createEntries(result, useOverlays, mappedRng, src, dst, taken, dest); + } + } + tableModel.addAll(result); + } + + protected MappedAddressRange identityMapped(AddressRange srng, Program dest) { + if (dest == null) { // New program + return new MappedAddressRange(srng, srng); + } + AddressSpace srcSpace = srng.getAddressSpace(); + AddressSpace dstSpace = dest.getAddressFactory().getAddressSpace(srcSpace.getName()); + if (dstSpace == null) { + return null; + } + long minOff = MathUtilities.unsignedMax(srng.getMinAddress().getOffset(), + dstSpace.getMinAddress().getOffset()); + long maxOff = MathUtilities.unsignedMin(srng.getMaxAddress().getOffset(), + dstSpace.getMaxAddress().getOffset()); + if (Long.compareUnsigned(minOff, maxOff) > 0) { + return null; + } + return new MappedAddressRange( + new AddressRangeImpl(srcSpace.getAddress(minOff), srcSpace.getAddress(maxOff)), + new AddressRangeImpl(dstSpace.getAddress(minOff), dstSpace.getAddress(maxOff))); + } + + protected void resetWithoutRelocation(boolean useOverlays, Program dest) { + tableModel.clear(); + List result = new ArrayList<>(); + Set taken = new HashSet<>(); + collectBlockNames(taken, dest); + for (AddressRange rng : set) { + for (AddressRange src : breakRangeByRegions(rng)) { + MappedAddressRange id = identityMapped(src, dest); + if (id == null) { + continue; + } + createEntries(result, useOverlays, id, id.getSourceAddressRange(), + id.getDestinationAddressRange(), taken, dest); + } + } + tableModel.addAll(result); + } + + protected MemoryBlock executeEntryBlock(RangeEntry entry, Program dest, TaskMonitor monitor) + throws Exception { + if (entry.isCreate()) { + return dest.getMemory() + .createInitializedBlock(entry.getBlockName(), entry.getDstMinAddress(), + entry.getDstRange().getLength(), (byte) 0, monitor, entry.isOverlay()); + } + MemoryBlock block = dest.getMemory().getBlock(entry.getDstMinAddress()); + if (plan.isRequiresInitializedMemory(source, dest) && !block.isInitialized()) { + return dest.getMemory().convertToInitialized(block, (byte) 0); + } + return block; + } + + protected void executeEntry(RangeEntry entry, Program dest, TraceRecorder recorder, + TaskMonitor monitor) + throws Exception { + MemoryBlock block = executeEntryBlock(entry, dest, monitor); + Address dstMin = entry.getDstRange().getMinAddress(); + if (block.isOverlay()) { + dstMin = block.getStart().getAddressSpace().getAddress(dstMin.getOffset()); + } + if (recorder != null) { + executeCapture(entry.getSrcRange(), recorder, monitor); + } + plan.execute(source, entry.getSrcRange(), dest, dstMin, monitor); + } + + protected TraceRecorder getRecorderIfEnabledAndReadsPresent() { + if (!cbCapture.isSelected()) { + return null; + } + return getRecorderIfReadsPresent(); + } + + protected void executeCapture(AddressRange range, TraceRecorder recorder, TaskMonitor monitor) + throws Exception { + synchronized (this) { + monitor.checkCanceled(); + this.captureTask = recorder.captureProcessMemory(new AddressSet(range), monitor, false); + } + try { + captureTask.get(); // Not a fan, but whatever. + } + finally { + captureTask = null; + } + } + + protected void executePlan(TaskMonitor monitor) throws Exception { + Program dest = getDestination().getOrCreateProgram(source, this); + boolean doRelease = !Arrays.asList(programManager.getAllOpenPrograms()).contains(dest); + TraceRecorder recorder = getRecorderIfEnabledAndReadsPresent(); + try (UndoableTransaction tid = UndoableTransaction.start(dest, "Copy From Trace", true)) { + monitor.initialize(tableModel.getRowCount()); + for (RangeEntry entry : tableModel.getModelData()) { + monitor.setMessage("Copying into " + entry.getDstRange()); + executeEntry(entry, dest, recorder, monitor); + monitor.incrementProgress(1); + } + programManager.openProgram(dest); + } + finally { + if (doRelease) { + dest.release(this); + } + } + getDestination().saveIfApplicable(dest); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlan.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlan.java new file mode 100644 index 0000000000..4c30b0aa37 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlan.java @@ -0,0 +1,449 @@ +/* ### + * 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.app.plugin.core.debug.gui.copying; + +import java.util.*; + +import javax.swing.JCheckBox; + +import com.google.common.collect.Range; + +import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.plugin.core.debug.service.breakpoint.LogicalBreakpointInternal.ProgramBreakpoint; +import ghidra.app.util.viewer.listingpanel.PropertyBasedBackgroundColorModel; +import ghidra.program.database.IntRangeMap; +import ghidra.program.model.address.*; +import ghidra.program.model.data.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.*; +import ghidra.trace.model.breakpoint.TraceBreakpoint; +import ghidra.trace.model.memory.TraceMemoryManager; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.program.TraceProgramView; +import ghidra.util.exception.InvalidInputException; +import ghidra.util.task.TaskMonitor; + +public class DebuggerCopyPlan { + public interface Copier { + String getName(); + + boolean isAvailable(TraceProgramView from, Program into); + + Collection getRequires(); + + Collection getRequiredBy(); + + boolean isRequiresInitializedMemory(); + + void copy(TraceProgramView from, AddressRange fromRange, Program into, Address intoAddress, + TaskMonitor monitor) throws Exception; + } + + public enum AllCopiers implements Copier { + BYTES("Bytes", List.of()) { + @Override + public boolean isRequiresInitializedMemory() { + return true; + } + + @Override + public void copy(TraceProgramView from, AddressRange fromRange, Program into, + Address intoAddress, TaskMonitor monitor) throws Exception { + // This is perhaps too heavy handed.... + into.getListing() + .clearCodeUnits(intoAddress, intoAddress.add(fromRange.getLength() - 1), + false); + byte[] buf = new byte[4096]; + AddressRangeChunker chunker = new AddressRangeChunker(fromRange, buf.length); + for (AddressRange chunk : chunker) { + monitor.checkCanceled(); + Address addr = chunk.getMinAddress(); + int len = (int) chunk.getLength(); + from.getMemory().getBytes(addr, buf, 0, len); + long off = addr.subtract(fromRange.getMinAddress()); + Address dest = intoAddress.add(off); + into.getMemory().setBytes(dest, buf, 0, len); + } + } + }, + STATE("State (as colors)", List.of()) { + @Override + public void copy(TraceProgramView from, AddressRange fromRange, Program into, + Address intoAddress, TaskMonitor monitor) throws Exception { + IntRangeMap map = + into.getIntRangeMap(PropertyBasedBackgroundColorModel.COLOR_PROPERTY_NAME); + if (map == null) { + map = into.createIntRangeMap( + PropertyBasedBackgroundColorModel.COLOR_PROPERTY_NAME); + } + AddressSet rngAsSet = new AddressSet(fromRange); + TraceMemoryManager mm = from.getTrace().getMemoryManager(); + AddressSetView knownSet = mm.getAddressesWithState(from.getSnap(), rngAsSet, + s -> s == TraceMemoryState.KNOWN); + AddressSetView errorSet = mm.getAddressesWithState(from.getSnap(), rngAsSet, + s -> s == TraceMemoryState.ERROR); + AddressSetView staleSet = rngAsSet.subtract(knownSet).subtract(errorSet); + setShifted(map, fromRange.getMinAddress(), intoAddress, errorSet, + DebuggerResources.DEFAULT_COLOR_BACKGROUND_ERROR.getRGB()); + setShifted(map, fromRange.getMinAddress(), intoAddress, staleSet, + DebuggerResources.DEFAULT_COLOR_BACKGROUND_STALE.getRGB()); + } + + public void setShifted(IntRangeMap map, Address src, Address dst, AddressSetView set, + int value) { + for (AddressRange rng : set) { + long offMin = rng.getMinAddress().subtract(src); + long offMax = rng.getMaxAddress().subtract(src); + Address dMin = dst.add(offMin); + Address dMax = dst.add(offMax); + map.setValue(dMin, dMax, value); + } + } + }, + INSTRUCTIONS("Instructions", List.of(BYTES)) { + @Override + protected boolean checkAvailable(TraceProgramView from, Program into) { + return into == null || from.getLanguage() == into.getLanguage(); + } + + @Override + public void copy(TraceProgramView from, AddressRange fromRange, Program into, + Address intoAddress, TaskMonitor monitor) throws Exception { + Listing intoListing = into.getListing(); + for (Instruction ins : from.getListing() + .getInstructions(new AddressSet(fromRange), true)) { + monitor.checkCanceled(); + if (!ins.getPrototype().getLanguage().equals(into.getLanguage())) { + // Filter out "guest" instructions + continue; + } + long off = ins.getMinAddress().subtract(fromRange.getMinAddress()); + Address dest = intoAddress.add(off); + intoListing.createInstruction(dest, ins.getPrototype(), ins, ins); + } + } + }, + DATA("Data", List.of()) { + @Override + protected boolean checkAvailable(TraceProgramView from, Program into) { + return into == null || sameDataOrganization(from, into); + } + + @Override + public void copy(TraceProgramView from, AddressRange fromRange, Program into, + Address intoAddress, TaskMonitor monitor) + throws Exception { + Listing intoListing = into.getListing(); + for (Data data : from.getListing() + .getDefinedData(new AddressSet(fromRange), true)) { + monitor.checkCanceled(); + long off = data.getMinAddress().subtract(fromRange.getMinAddress()); + Address dest = intoAddress.add(off); + DataType dt = data.getDataType(); + if (!(dt instanceof DynamicDataType)) { + intoListing.createData(dest, dt, data.getLength()); + } + } + } + }, + DYNAMIC_DATA("Dynamic Data", List.of(BYTES)) { + @Override + protected boolean checkAvailable(TraceProgramView from, Program into) { + return into == null || sameDataOrganization(from, into); + } + + @Override + public void copy(TraceProgramView from, AddressRange fromRange, Program into, + Address intoAddress, TaskMonitor monitor) throws Exception { + Listing intoListing = into.getListing(); + for (Data data : from.getListing() + .getDefinedData(new AddressSet(fromRange), true)) { + monitor.checkCanceled(); + long off = data.getMinAddress().subtract(fromRange.getMinAddress()); + Address dest = intoAddress.add(off); + DataType dt = data.getDataType(); + if (dt instanceof DynamicDataType) { + intoListing.createData(dest, dt, data.getLength()); + } + } + } + }, + LABELS("Labels", List.of()) { + @Override + public void copy(TraceProgramView from, AddressRange fromRange, Program into, + Address intoAddress, TaskMonitor monitor) throws Exception { + SymbolTable intoTable = into.getSymbolTable(); + for (Symbol label : from.getSymbolTable() + .getSymbols(new AddressSet(fromRange), SymbolType.LABEL, true)) { + monitor.checkCanceled(); + if (label.getSource() == SourceType.DEFAULT) { + continue; + } + long off = label.getAddress().subtract(fromRange.getMinAddress()); + Address dest = intoAddress.add(off); + Namespace destNs = + findOrCopyNamespace(label.getParentNamespace(), intoTable, into); + try { + intoTable.createLabel(dest, label.getName(), destNs, label.getSource()); + } + catch (InvalidInputException e) { + throw new AssertionError(e); + } + } + } + + private Namespace findOrCopyNamespace(Namespace ns, SymbolTable intoTable, + Program into) throws Exception { + if (ns.isGlobal()) { + return into.getGlobalNamespace(); + } + Namespace destParent = + findOrCopyNamespace(ns.getParentNamespace(), intoTable, into); + return intoTable.getOrCreateNameSpace(destParent, ns.getName(), + ns.getSymbol().getSource()); + } + }, + BREAKPOINTS("Breakpoints (as bookmarks)", List.of()) { + @Override + public void copy(TraceProgramView from, AddressRange fromRange, Program into, + Address intoAddress, TaskMonitor monitor) throws Exception { + for (TraceBreakpoint bpt : from.getTrace() + .getBreakpointManager() + .getBreakpointsIntersecting(Range.singleton(from.getSnap()), fromRange)) { + monitor.checkCanceled(); + long off = bpt.getMinAddress().subtract(fromRange.getMinAddress()); + Address dest = intoAddress.add(off); + ProgramBreakpoint pb = + new ProgramBreakpoint(into, dest, bpt.getLength(), bpt.getKinds()); + if (bpt.isEnabled()) { + pb.enable(); + } + else { + pb.disable(); + } + } + } + }, + BOOKMARKS("Bookmarks", List.of()) { + @Override + public void copy(TraceProgramView from, AddressRange fromRange, Program into, + Address intoAddress, TaskMonitor monitor) throws Exception { + BookmarkManager intoBookmarks = into.getBookmarkManager(); + Iterator bit = + from.getBookmarkManager().getBookmarksIterator(fromRange.getMinAddress(), true); + while (bit.hasNext()) { + monitor.checkCanceled(); + Bookmark bm = bit.next(); + if (bm.getAddress().compareTo(fromRange.getMaxAddress()) > 0) { + break; + } + BookmarkType type = bm.getType(); + long off = bm.getAddress().subtract(fromRange.getMinAddress()); + Address dest = intoAddress.add(off); + BookmarkType destType = intoBookmarks.getBookmarkType(type.getTypeString()); + if (destType == null) { + destType = intoBookmarks.defineType(type.getTypeString(), type.getIcon(), + type.getMarkerColor(), type.getMarkerPriority()); + } + intoBookmarks.setBookmark(dest, destType.getTypeString(), bm.getCategory(), + bm.getComment()); + } + } + }, + REFERENCES("References (memory only)", List.of()) { + @Override + public void copy(TraceProgramView from, AddressRange fromRange, Program into, + Address intoAddress, TaskMonitor monitor) throws Exception { + ReferenceManager intoRefs = into.getReferenceManager(); + for (Reference ref : from.getReferenceManager() + .getReferenceIterator(fromRange.getMinAddress())) { + monitor.checkCanceled(); + if (ref.getFromAddress().compareTo(fromRange.getMaxAddress()) > 0) { + break; + } + if (ref.getSource() == SourceType.DEFAULT) { + continue; + } + // TODO: Other kinds of references? + if (!ref.isMemoryReference()) { + continue; + } + // Requiring both ends to be in copied range + if (!fromRange.contains(ref.getToAddress())) { + continue; + } + + // NB. "from" is overloaded here + long offFrom = ref.getFromAddress().subtract(fromRange.getMinAddress()); + long offTo = ref.getToAddress().subtract(fromRange.getMinAddress()); + Address destFrom = intoAddress.add(offFrom); + Address destTo = intoAddress.add(offTo); + intoRefs.addMemoryReference(destFrom, destTo, ref.getReferenceType(), + ref.getSource(), ref.getOperandIndex()); + } + } + }, + COMMENTS("Comments", List.of()) { + @Override + public void copy(TraceProgramView from, AddressRange fromRange, Program into, + Address intoAddress, TaskMonitor monitor) throws Exception { + Listing fromListing = from.getListing(); + Listing intoListing = into.getListing(); + for (Address addr : fromListing.getCommentAddressIterator(new AddressSet(fromRange), + true)) { + monitor.checkCanceled(); + long off = addr.subtract(fromRange.getMinAddress()); + Address dest = intoAddress.add(off); + // Ugly, but there's not MAX/MIN_COMMENT_TYPE + for (int i = CodeUnit.EOL_COMMENT; i <= CodeUnit.REPEATABLE_COMMENT; i++) { + String comment = fromListing.getComment(i, addr); + if (comment == null) { + continue; + } + intoListing.setComment(dest, i, comment); + } + } + } + }; + + protected boolean sameDataOrganization(Program p1, Program p2) { + DataOrganization dataOrg1 = p1.getDataTypeManager().getDataOrganization(); + DataOrganization dataOrg2 = p2.getDataTypeManager().getDataOrganization(); + return dataOrg1.equals(dataOrg2); + } + + public static final List VALUES; + static { + List asList = Arrays.asList(values()); + Collections.sort(asList, Comparator.comparing(AllCopiers::getName)); + VALUES = Collections.unmodifiableList(asList); + } + + final String name; + final Collection requires; + final Collection requiredBy = new HashSet<>(); + + private AllCopiers(String name, Collection requires) { + this.name = name; + this.requires = Collections.unmodifiableCollection(requires); + for (AllCopiers req : requires) { + req.requiredBy.add(this); + } + } + + protected boolean checkAvailable(TraceProgramView from, Program into) { + return true; + } + + @Override + public boolean isAvailable(TraceProgramView from, Program into) { + return checkAvailable(from, into) && + getRequires().stream().allMatch(c -> c.isAvailable(from, into)); + } + + @Override + public String getName() { + return name; + } + + @Override + public Collection getRequires() { + return requires; + } + + @Override + public Collection getRequiredBy() { + return requiredBy; + } + + @Override + public boolean isRequiresInitializedMemory() { + return false; + } + } + + protected final Map checkBoxes = new LinkedHashMap<>(); + + public DebuggerCopyPlan() { + for (Copier copier : getAllCopiers()) { + JCheckBox cb = new JCheckBox(copier.getName()); + Collection requires = copier.getRequires(); + Collection requiredBy = copier.getRequiredBy(); + if (!requires.isEmpty() || !requiredBy.isEmpty()) { + cb.addActionListener(e -> { + if (cb.isSelected()) { + for (Copier req : requires) { + checkBoxes.get(req).setSelected(true); + } + } + else { + for (Copier dep : requiredBy) { + checkBoxes.get(dep).setSelected(false); + } + } + }); + } + checkBoxes.put(copier, cb); + } + } + + public Collection getAllCopiers() { + return AllCopiers.VALUES; + } + + public JCheckBox getCheckBox(Copier copier) { + return checkBoxes.get(copier); + } + + public void selectAll() { + for (JCheckBox cb : checkBoxes.values()) { + cb.setSelected(true); + } + } + + public void selectNone() { + for (JCheckBox cb : checkBoxes.values()) { + cb.setSelected(false); + } + } + + public void execute(TraceProgramView from, AddressRange fromRange, Program into, + Address intoAddress, TaskMonitor monitor) throws Exception { + for (Copier copier : getAllCopiers()) { + if (!copier.isAvailable(from, into)) { + continue; + } + if (!checkBoxes.get(copier).isSelected()) { + continue; + } + copier.copy(from, fromRange, into, intoAddress, monitor); + } + } + + public void syncCopiersEnabled(TraceProgramView from, Program dest) { + for (Map.Entry ent : checkBoxes.entrySet()) { + ent.getValue().setEnabled(ent.getKey().isAvailable(from, dest)); + } + } + + public boolean isRequiresInitializedMemory(TraceProgramView from, Program dest) { + return checkBoxes.entrySet().stream().anyMatch(ent -> { + Copier copier = ent.getKey(); + return copier.isRequiresInitializedMemory() && + copier.isAvailable(from, dest) && ent.getValue().isSelected(); + }); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java index 70c600d489..d63c9adf3d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java @@ -52,7 +52,6 @@ import ghidra.app.plugin.core.debug.gui.action.*; import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext; import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils; import ghidra.app.plugin.core.debug.utils.ProgramURLUtils; -import ghidra.app.plugin.core.exporter.ExporterDialog; import ghidra.app.services.*; import ghidra.app.util.viewer.format.FormatManager; import ghidra.app.util.viewer.listingpanel.ListingPanel; @@ -72,7 +71,6 @@ import ghidra.program.util.ProgramSelection; import ghidra.trace.model.Trace; import ghidra.trace.model.modules.*; import ghidra.trace.model.program.TraceProgramView; -import ghidra.trace.model.program.TraceVariableSnapProgramView; import ghidra.util.HTMLUtilities; import ghidra.util.Swing; import ghidra.util.exception.CancelledException; @@ -239,8 +237,7 @@ public class DebuggerListingProvider extends CodeViewerProvider { protected SyncToStaticListingAction actionSyncToStaticListing; protected FollowsCurrentThreadAction actionFollowsCurrentThread; protected MultiStateDockingAction actionAutoReadMemory; - protected DockingAction actionCaptureSelectedMemory; - protected DockingAction actionExportView; + protected DockingAction actionReadSelectedMemory; protected DockingAction actionOpenProgram; protected MultiStateDockingAction actionTrackLocation; @@ -608,12 +605,7 @@ public class DebuggerListingProvider extends CodeViewerProvider { actionGoTo = goToTrait.installAction(); actionTrackLocation = trackingTrait.installAction(); actionAutoReadMemory = readsMemTrait.installAutoReadAction(); - actionCaptureSelectedMemory = readsMemTrait.installCaptureSelectedAction(); - - actionExportView = ExportTraceViewAction.builder(plugin) - .enabledWhen(ctx -> current.getView() != null) - .onAction(this::activatedExportView) - .buildAndInstallLocal(this); + actionReadSelectedMemory = readsMemTrait.installReadSelectedAction(); actionOpenProgram = OpenProgramAction.builder(plugin) .withContext(DebuggerOpenProgramActionContext.class) @@ -623,20 +615,6 @@ public class DebuggerListingProvider extends CodeViewerProvider { contextChanged(); } - private void activatedExportView(ActionContext context) { - if (current.getView() == null) { - return; - } - // Avoid odd race conditions by fixing the snap - TraceProgramView fixed = current.getView() instanceof TraceVariableSnapProgramView - ? current.getTrace().getFixedProgramView(current.getSnap()) - : current.getView(); - - ExporterDialog dialog = - new ExporterDialog(tool, fixed.getDomainFile(), fixed, getSelection()); - tool.showDialog(dialog); - } - private void activatedOpenProgram(DebuggerOpenProgramActionContext context) { programManager.openProgram(context.getDomainFile(), DomainFile.DEFAULT_VERSION, ProgramManager.OPEN_CURRENT); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java index 3d8050eaf1..222edf3693 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java @@ -148,7 +148,7 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi protected DockingAction actionGoTo; protected FollowsCurrentThreadAction actionFollowsCurrentThread; protected MultiStateDockingAction actionAutoReadMemory; - protected DockingAction actionCaptureSelectedMemory; + protected DockingAction actionReadSelectedMemory; protected MultiStateDockingAction actionTrackLocation; protected ForMemoryBytesGoToTrait goToTrait; @@ -257,7 +257,7 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi actionGoTo = goToTrait.installAction(); actionTrackLocation = trackingTrait.installAction(); actionAutoReadMemory = readsMemTrait.installAutoReadAction(); - actionCaptureSelectedMemory = readsMemTrait.installCaptureSelectedAction(); + actionReadSelectedMemory = readsMemTrait.installReadSelectedAction(); } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LogicalBreakpointInternal.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LogicalBreakpointInternal.java index 9d17df1b79..40d542c548 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LogicalBreakpointInternal.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LogicalBreakpointInternal.java @@ -37,7 +37,7 @@ import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; import utilities.util.IDHashed; -interface LogicalBreakpointInternal extends LogicalBreakpoint { +public interface LogicalBreakpointInternal extends LogicalBreakpoint { public static class ProgramBreakpoint { public static Set kindsFromBookmark(Bookmark mark) { String[] parts = mark.getCategory().split(";"); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorState.java index 8700bbdc66..020c0f46d1 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorState.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorState.java @@ -15,10 +15,11 @@ */ package ghidra.app.plugin.core.debug.service.emulation; +import java.util.Collection; import java.util.Map.Entry; import ghidra.app.services.DebuggerStaticMappingService; -import ghidra.app.services.DebuggerStaticMappingService.ShiftAndAddressSetView; +import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; import ghidra.app.services.TraceRecorder; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.*; @@ -78,40 +79,44 @@ public class ReadsTargetMemoryPcodeExecutorState DebuggerStaticMappingService mappingService = tool.getService(DebuggerStaticMappingService.class); byte[] data = new byte[4096]; - for (Entry ent : mappingService + for (Entry> ent : mappingService .getOpenMappedViews(trace, unknown, snap) .entrySet()) { Program program = ent.getKey(); - ShiftAndAddressSetView shifted = ent.getValue(); - long shift = shifted.getShift(); Memory memory = program.getMemory(); AddressSetView initialized = memory.getLoadedAndInitializedAddressSet(); - AddressSetView toRead = shifted.getAddressSetView().intersect(initialized); - Msg.warn(this, - "Filling in unknown trace memory in emulator using mapped image: " + - program + ": " + toRead); - for (AddressRange rng : toRead) { - long lower = rng.getMinAddress().getOffset(); - long fullLen = rng.getLength(); - while (fullLen > 0) { - int len = MathUtilities.unsignedMin(data.length, fullLen); - try { - int read = - memory.getBytes(space.getAddress(lower), data, 0, len); - if (read < len) { - Msg.warn(this, - " Partial read of " + rng + ". Got " + read + " bytes"); + Collection mappedSet = ent.getValue(); + for (MappedAddressRange mappedRng : mappedSet) { + AddressRange srng = mappedRng.getSourceAddressRange(); + long shift = mappedRng.getShift(); + for (AddressRange subsrng : initialized.intersectRange(srng.getMinAddress(), + srng.getMaxAddress())) { + Msg.warn(this, + "Filling in unknown trace memory in emulator using mapped image: " + + program + ": " + subsrng); + long lower = subsrng.getMinAddress().getOffset(); + long fullLen = subsrng.getLength(); + while (fullLen > 0) { + int len = MathUtilities.unsignedMin(data.length, fullLen); + try { + int read = + memory.getBytes(space.getAddress(lower), data, 0, len); + if (read < len) { + Msg.warn(this, + " Partial read of " + subsrng + ". Got " + read + + " bytes"); + } + cache.putData(lower - shift, data, 0, read); } - cache.putData(lower - shift, data, 0, read); + catch (MemoryAccessException | AddressOutOfBoundsException e) { + throw new AssertionError(e); + } + lower += len; + fullLen -= len; } - catch (MemoryAccessException | AddressOutOfBoundsException e) { - throw new AssertionError(e); - } - lower += len; - fullLen -= len; + result = true; } - result = true; } } return result; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java index 9348c19b52..9a397b5d70 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java @@ -283,7 +283,6 @@ public class DebuggerStaticMappingServicePlugin extends Plugin private Program program; private AddressRange staticRange; - private Long shift; // from static image to trace public MappingEntry(TraceStaticMapping mapping) { this.mapping = mapping; @@ -309,8 +308,6 @@ public class DebuggerStaticMappingServicePlugin extends Plugin Address minAddr = opened.getAddressFactory().getAddress(mapping.getStaticAddress()); Address maxAddr = addOrMax(minAddr, mapping.getLength() - 1); this.staticRange = new AddressRangeImpl(minAddr, maxAddr); - this.shift = mapping.getMinTraceAddress().getOffset() - - staticRange.getMinAddress().getOffset(); return true; } return false; @@ -320,7 +317,6 @@ public class DebuggerStaticMappingServicePlugin extends Plugin if (this.program == closed) { this.program = null; this.staticRange = null; - this.shift = null; return true; } return false; @@ -565,7 +561,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } protected void collectOpenMappedPrograms(AddressRange rng, Range span, - Map result) { + Map> result) { TraceAddressSnapRange tatr = new ImmutableTraceAddressSnapRange(rng, span); for (Entry out : outbound.entrySet()) { MappingEntry me = out.getValue(); @@ -575,16 +571,16 @@ public class DebuggerStaticMappingServicePlugin extends Plugin if (!out.getKey().intersects(tatr)) { continue; } - - ShiftAndAddressSetView set = result.computeIfAbsent(me.program, - p -> new ShiftAndAddressSetView(-me.shift, new AddressSet())); - ((AddressSet) set.getAddressSetView()).add(me.mapTraceRangeToProgram(rng)); + AddressRange srcRng = out.getKey().getRange().intersect(rng); + AddressRange dstRng = me.mapTraceRangeToProgram(rng); + result.computeIfAbsent(me.program, p -> new TreeSet<>()) + .add(new MappedAddressRange(srcRng, dstRng)); } } - public Map getOpenMappedViews(AddressSetView set, + public Map> getOpenMappedViews(AddressSetView set, Range span) { - Map result = new HashMap<>(); + Map> result = new HashMap<>(); for (AddressRange rng : set) { collectOpenMappedPrograms(rng, span, result); } @@ -715,7 +711,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } protected void collectOpenMappedViews(AddressRange rng, - Map result) { + Map> result) { for (Entry inPreceeding : inbound.headMapByValue( rng.getMaxAddress(), true).entrySet()) { Address start = inPreceeding.getValue(); @@ -726,14 +722,17 @@ public class DebuggerStaticMappingServicePlugin extends Plugin if (!me.isInProgramRange(rng)) { continue; } - ShiftAndAddressSetView set = result.computeIfAbsent(me.getTraceSnap(), - p -> new ShiftAndAddressSetView(me.shift, new AddressSet())); - ((AddressSet) set.getAddressSetView()).add(me.mapProgramRangeToTrace(rng)); + + AddressRange srcRange = me.staticRange.intersect(rng); + AddressRange dstRange = me.mapProgramRangeToTrace(rng); + result.computeIfAbsent(me.getTraceSnap(), p -> new TreeSet<>()) + .add(new MappedAddressRange(srcRange, dstRange)); } } - public Map getOpenMappedViews(AddressSetView set) { - Map result = new HashMap<>(); + public Map> getOpenMappedViews( + AddressSetView set) { + Map> result = new HashMap<>(); for (AddressRange rng : set) { collectOpenMappedViews(rng, result); } @@ -1219,9 +1218,8 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } @Override - public Map getOpenMappedViews(Trace trace, - AddressSetView set, - long snap) { + public Map> getOpenMappedViews(Trace trace, + AddressSetView set, long snap) { InfoPerTrace info = requireTrackedInfo(trace); if (info == null) { return null; @@ -1230,7 +1228,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } @Override - public Map getOpenMappedViews(Program program, + public Map> getOpenMappedViews(Program program, AddressSetView set) { InfoPerProgram info = requireTrackedInfo(program); if (info == null) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStaticMappingService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStaticMappingService.java index 264b55447c..4a5ed5a2e2 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStaticMappingService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStaticMappingService.java @@ -487,27 +487,38 @@ public interface DebuggerStaticMappingService { } /** - * <<<<<<< HEAD A {@code (shift,view)} pair for describing sets of mapped addresses + * A pair for describing sets of mapped addresses + * + *

+ * Note, the natural order is by the destination address. */ - public class ShiftAndAddressSetView { - private final long shift; - private final AddressSetView view; + public class MappedAddressRange implements Comparable { - public ShiftAndAddressSetView(long shift, AddressSetView view) { - this.shift = shift; - this.view = view; + private final AddressRange srcRange; + private final AddressRange dstRange; + private final int hashCode; + private final long shift; + + public MappedAddressRange(AddressRange srcRange, AddressRange dstRange) { + this.srcRange = srcRange; + this.dstRange = dstRange; + this.hashCode = Objects.hash(dstRange, srcRange); + this.shift = dstRange.getMinAddress().getOffset() - + srcRange.getMinAddress().getOffset(); + } + + @Override + public String toString() { + return ""; } /** - * Get the shift from the source address set to this address set + * Get the shift from the source address range to this address range * *

- * The meaning depends on what returned this view. If this view is the "static" set, then + * The meaning depends on what returned this view. If this view is the "static" range, then * this shift describes what was added to the offset of the "dynamic" address to get a - * particular address in this set. Note that since not all addresses from the requested - * source set may have been mapped, you cannot simply compare min addresses to obtain this - * shift. To "map back" to the source address from a destination address in this set, - * subtract this shift. + * particular address in the "static" range. * * @return the shift */ @@ -516,12 +527,107 @@ public interface DebuggerStaticMappingService { } /** - * Get the destination address set view as mapped from the source address set + * Map an address in the source range to the corresponding address in the destination range * - * @return the address set + * @param saddr the source address (not validated) + * @return the destination address */ - public AddressSetView getAddressSetView() { - return view; + public Address mapSourceToDestination(Address saddr) { + return dstRange.getAddressSpace().getAddress(saddr.getOffset() + shift); + } + + /** + * Map an address in the destination range to the corresponding address in the source range + * + * @param daddr the destination address (not validated) + * @return the source address + */ + public Address mapDestinationToSource(Address daddr) { + return srcRange.getAddressSpace().getAddress(daddr.getOffset() - shift); + } + + /** + * Map a sub-range of the source to the corresponding sub-range of the destination + * + * @param srng the source sub-range + * @return the destination sub-range + */ + public AddressRange mapSourceToDestination(AddressRange srng) { + try { + return new AddressRangeImpl(mapSourceToDestination(srng.getMinAddress()), + srng.getLength()); + } + catch (AddressOverflowException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Map a sub-range of the destination to the corresponding sub-range of the source + * + * @param drng the destination sub-range + * @return the source sub-range + */ + public AddressRange mapDestinationToSource(AddressRange drng) { + try { + return new AddressRangeImpl(mapDestinationToSource(drng.getMinAddress()), + drng.getLength()); + } + catch (AddressOverflowException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get the source address range + * + * @return the address range + */ + public AddressRange getSourceAddressRange() { + return srcRange; + } + + /** + * Get the destination address range + * + * @return the address range + */ + public AddressRange getDestinationAddressRange() { + return dstRange; + } + + @Override + public int compareTo(MappedAddressRange that) { + int c; + c = this.dstRange.compareTo(that.dstRange); + if (c != 0) { + return c; + } + c = this.srcRange.compareTo(that.srcRange); + if (c != 0) { + return c; + } + return 0; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof MappedAddressRange)) { + return false; + } + MappedAddressRange that = (MappedAddressRange) obj; + if (!this.dstRange.equals(that.dstRange)) { + return false; + } + if (!this.srcRange.equals(that.srcRange)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return hashCode; } } @@ -570,8 +676,7 @@ public interface DebuggerStaticMappingService { boolean truncateExisting) throws TraceConflictedMappingException; /** - * ======= >>>>>>> d694542c5 (GP-660: Put program filler back in. Need to performance test.) Add - * several static mappings (relocations) + * Add several static mappings (relocations) * *

* This will group the entries by trace and add each's entries in a single transaction. If any @@ -669,9 +774,9 @@ public interface DebuggerStaticMappingService { * @param trace the source trace * @param set the source address set * @param snap the source snap - * @return a map of destination programs to corresponding computed destination address sets + * @return a map of destination programs to corresponding computed destination address ranges */ - Map getOpenMappedViews(Trace trace, + Map> getOpenMappedViews(Trace trace, AddressSetView set, long snap); /** @@ -679,9 +784,9 @@ public interface DebuggerStaticMappingService { * * @param program the destination program, from which we are mapping back * @param set the destination address set, from which we are mapping back - * @return a map of source traces to corresponding computed source address sets + * @return a map of source traces to corresponding computed source address ranges */ - Map getOpenMappedViews(Program program, + Map> getOpenMappedViews(Program program, AddressSetView set); /** diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginScreenShots.java new file mode 100644 index 0000000000..da6c33a62d --- /dev/null +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginScreenShots.java @@ -0,0 +1,157 @@ +/* ### + * 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.app.plugin.core.debug.gui.copying; + +import java.util.Set; + +import org.junit.*; + +import com.google.common.collect.Range; + +import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest.TestDebuggerTargetTraceMapper; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider; +import ghidra.app.plugin.core.debug.service.model.DebuggerModelServicePlugin; +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; +import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; +import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin; +import ghidra.app.services.*; +import ghidra.dbg.model.TestDebuggerModelBuilder; +import ghidra.framework.model.DomainFolder; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.mem.Memory; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; +import ghidra.trace.database.ToyDBTraceBuilder; +import ghidra.trace.database.memory.DBTraceMemoryManager; +import ghidra.trace.database.module.DBTraceModuleManager; +import ghidra.trace.model.DefaultTraceLocation; +import ghidra.trace.model.memory.TraceMemoryFlag; +import ghidra.trace.model.modules.TraceModule; +import ghidra.util.database.UndoableTransaction; +import ghidra.util.task.TaskMonitor; +import help.screenshot.GhidraScreenShotGenerator; + +public class DebuggerCopyActionsPluginScreenShots extends GhidraScreenShotGenerator { + + ProgramManager programManager; + DebuggerTraceManagerService traceManager; + DebuggerModelService modelService; + DebuggerStaticMappingServicePlugin mappingService; + DebuggerListingPlugin listingPlugin; + DebuggerListingProvider listingProvider; + DebuggerCopyActionsPlugin copyPlugin; + TestDebuggerModelBuilder mb; + ToyDBTraceBuilder tb; + + @Before + public void setUpMine() throws Throwable { + programManager = addPlugin(tool, ProgramManagerPlugin.class); + traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class); + modelService = addPlugin(tool, DebuggerModelServicePlugin.class); + mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class); + listingPlugin = addPlugin(tool, DebuggerListingPlugin.class); + copyPlugin = addPlugin(tool, DebuggerCopyActionsPlugin.class); + + listingProvider = waitForComponentProvider(DebuggerListingProvider.class); + + mb = new TestDebuggerModelBuilder(); + mb.createTestModel(); + mb.createTestProcessesAndThreads(); + TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, + new TestDebuggerTargetTraceMapper(mb.testProcess1)); + tb = new ToyDBTraceBuilder(recorder.getTrace()); + } + + @After + public void tearDownMine() { + tb.close(); + + if (program != null) { + program.release(this); + } + } + + @Test + public void testCaptureDebuggerCopyIntoProgramDialog() throws Throwable { + long snap; + try (UndoableTransaction tid = tb.startTransaction()) { + snap = tb.trace.getTimeManager().createSnapshot("First").getKey(); + DBTraceMemoryManager mem = tb.trace.getMemoryManager(); + mem.createRegion(".text", snap, tb.range(0x55550000, 0x5555ffff), + Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE)); + mem.createRegion(".data", snap, tb.range(0x55560000, 0x5556ffff), + Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.WRITE)); + mem.createRegion("[stack]", snap, tb.range(0x00100000, 0x001fffff), + Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.WRITE)); + + DBTraceModuleManager mods = tb.trace.getModuleManager(); + + TraceModule modEcho = mods.addLoadedModule("Modules[/bin/echo]", "/bin/echo", + tb.range(0x55550000, 0x5556ffff), snap); + modEcho.addSection("Modules[/bin/echo].Sections[.text]", ".text", + tb.range(0x55550000, 0x5555ffff)); + modEcho.addSection("Modules[/bin/echo].Sections[.data]", ".data", + tb.range(0x55560000, 0x5556ffff)); + } + + program = createDefaultProgram("echo", "Toy:BE:64:default", this); + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + + try (UndoableTransaction tid = UndoableTransaction.start(program, "Add memory", true)) { + program.setImageBase(tb.addr(stSpace, 0x00400000), true); + Memory memory = program.getMemory(); + memory.createInitializedBlock(".text", tb.addr(stSpace, 0x00400000), 0x10000, (byte) 0, + TaskMonitor.DUMMY, false); + memory.createInitializedBlock(".data", tb.addr(stSpace, 0x00600000), 0x10000, (byte) 0, + TaskMonitor.DUMMY, false); + } + + DomainFolder root = tool.getProject().getProjectData().getRootFolder(); + root.createFile(tb.trace.getName(), tb.trace, TaskMonitor.DUMMY); + root.createFile(program.getName(), program, TaskMonitor.DUMMY); + + try (UndoableTransaction tid = tb.startTransaction()) { + mappingService.addMapping( + new DefaultTraceLocation(tb.trace, null, Range.atLeast(snap), tb.addr(0x55550000)), + new ProgramLocation(program, tb.addr(stSpace, 0x00400000)), 0x10000, true); + mappingService.addMapping( + new DefaultTraceLocation(tb.trace, null, Range.atLeast(snap), tb.addr(0x55560000)), + new ProgramLocation(program, tb.addr(stSpace, 0x00600000)), 0x10000, true); + } + + traceManager.openTrace(tb.trace); + traceManager.activateTrace(tb.trace); + + programManager.openProgram(program); + + listingProvider.requestFocus(); + waitForSwing(); + + listingProvider.setSelection( + new ProgramSelection(tb.trace.getMemoryManager().getRegionsAddressSet(snap))); + + waitForCondition(() -> copyPlugin.actionCopyIntoCurrentProgram.isEnabled()); + performAction(copyPlugin.actionCopyIntoCurrentProgram, false); + DebuggerCopyIntoProgramDialog dialog = + waitForDialogComponent(DebuggerCopyIntoProgramDialog.class); + dialog.setRelocate(true); + dialog.reset(); + waitForSwing(); + + captureDialog(DebuggerCopyIntoProgramDialog.class, 700, 600); + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java index 5d58809104..a8906486d3 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java @@ -495,8 +495,12 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest tool.getProject().getProjectData().getRootFolder().createFile(obj.getName(), obj, monitor); } + protected void createSnaplessTrace(String langID) throws IOException { + tb = new ToyDBTraceBuilder("dynamic-" + name.getMethodName(), langID); + } + protected void createSnaplessTrace() throws IOException { - tb = new ToyDBTraceBuilder("dynamic-" + name.getMethodName(), LANGID_TOYBE64); + createSnaplessTrace(LANGID_TOYBE64); } protected void addSnapshot(String desc) throws IOException { @@ -505,16 +509,28 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest } } - protected void createTrace() throws IOException { - createSnaplessTrace(); + protected void createTrace(String langID) throws IOException { + createSnaplessTrace(langID); addSnapshot("First snap"); } - protected void createAndOpenTrace() throws IOException { - createTrace(); + protected void createTrace() throws IOException { + createTrace(LANGID_TOYBE64); + } + + protected void useTrace(Trace trace) { + tb = new ToyDBTraceBuilder(trace); + } + + protected void createAndOpenTrace(String langID) throws IOException { + createTrace(langID); traceManager.openTrace(tb.trace); } + protected void createAndOpenTrace() throws IOException { + createAndOpenTrace(LANGID_TOYBE64); + } + protected String getProgramName() { return "static-" + getClass().getCanonicalName() + "." + name.getMethodName(); } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginTest.java new file mode 100644 index 0000000000..8b532e3c18 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginTest.java @@ -0,0 +1,505 @@ +/* ### + * 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.app.plugin.core.debug.gui.copying; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.Range; + +import generic.Unique; +import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; +import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec; +import ghidra.app.plugin.core.debug.gui.action.NoneAutoReadMemorySpec; +import ghidra.app.plugin.core.debug.gui.copying.DebuggerCopyIntoProgramDialog.RangeEntry; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider; +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; +import ghidra.app.services.DebuggerStaticMappingService; +import ghidra.app.services.TraceRecorder; +import ghidra.dbg.DebuggerModelListener; +import ghidra.dbg.target.TargetObject; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; +import ghidra.test.ToyProgramBuilder; +import ghidra.trace.database.memory.DBTraceMemoryManager; +import ghidra.trace.model.DefaultTraceLocation; +import ghidra.trace.model.TraceLocation; +import ghidra.trace.model.memory.TraceMemoryFlag; +import ghidra.util.database.UndoableTransaction; + +public class DebuggerCopyActionsPluginTest extends AbstractGhidraHeadedDebuggerGUITest { + + DebuggerCopyActionsPlugin copyActionsPlugin; + DebuggerListingPlugin listingPlugin; + DebuggerStaticMappingService mappingService; + + DebuggerListingProvider listingProvider; + + @Before + public void setupCopyActionsPluginTest() throws Exception { + mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class); + copyActionsPlugin = addPlugin(tool, DebuggerCopyActionsPlugin.class); + listingPlugin = addPlugin(tool, DebuggerListingPlugin.class); + + listingProvider = waitForComponentProvider(DebuggerListingProvider.class); + } + + @Test + public void testActionCopyIntoCurrentProgramWithoutRelocationCreateBlocks() throws Exception { + assertFalse(copyActionsPlugin.actionCopyIntoCurrentProgram.isEnabled()); + + createProgram(); + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + programManager.openProgram(program); + assertFalse(copyActionsPlugin.actionCopyIntoCurrentProgram.isEnabled()); + + createAndOpenTrace(); + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getMemoryManager() + .createRegion(".text", 0, tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + } + traceManager.activateTrace(tb.trace); + assertFalse(copyActionsPlugin.actionCopyIntoCurrentProgram.isEnabled()); + + listingProvider.requestFocus(); + listingProvider + .setSelection(new ProgramSelection(tb.addr(0x00400000), tb.addr(0x0040ffff))); + + waitForPass(() -> assertTrue(copyActionsPlugin.actionCopyIntoCurrentProgram.isEnabled())); + performAction(copyActionsPlugin.actionCopyIntoCurrentProgram, false); + DebuggerCopyIntoProgramDialog dialog = + waitForDialogComponent(DebuggerCopyIntoProgramDialog.class); + dialog.setRelocate(false); + dialog.reset(); + + RangeEntry entry = Unique.assertOne(dialog.tableModel.getModelData()); + + assertEquals(tb.range(stSpace, 0x00400000, 0x0040ffff), entry.getSrcRange()); + assertEquals(tb.range(stSpace, 0x00400000, 0x0040ffff), entry.getDstRange()); + assertEquals(".text", entry.getRegionName()); + assertEquals(".text", entry.getBlockName()); + assertTrue(entry.isCreate()); + dialog.okCallback(); + dialog.lastTask.get(1000, TimeUnit.MILLISECONDS); + waitForSwing(); + + MemoryBlock text = Unique.assertOne(Arrays.asList(program.getMemory().getBlocks())); + assertEquals(".text", text.getName()); + } + + @Test + public void testActionCopyIntoCurrentProgramWithoutRelocationCrossLanguage() throws Exception { + assertFalse(copyActionsPlugin.actionCopyIntoCurrentProgram.isEnabled()); + + createProgram(getSLEIGH_X86_LANGUAGE()); + createAndOpenTrace(ToyProgramBuilder._X64); + assertFalse(copyActionsPlugin.actionCopyIntoCurrentProgram.isEnabled()); + + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + + try (UndoableTransaction tid = UndoableTransaction.start(program, "Add blocks", true)) { + program.getMemory() + .createInitializedBlock(".text", tb.addr(stSpace, 0x00400000), 0x8000, + (byte) 0, monitor, false); + program.getMemory() + .createInitializedBlock(".text2", tb.addr(stSpace, 0x00408000), 0x8000, + (byte) 0, monitor, false); + } + + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager mm = tb.trace.getMemoryManager(); + mm.createRegion(".text", 0, tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + mm.putBytes(0, tb.addr(0x00401234), tb.buf(1, 2, 3, 4)); + + // This region should be excluded, since it cannot be mapped identically into 32-bits + mm.createRegion("lib:.text", 0, tb.range(0x7fff00400000L, 0x7fff0040ffffL), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + + // This region should be partially excluded, because 32-bits + // This is not likely to ever happen in practice, but be prepared + mm.createRegion(".straddle", 0, tb.range(0xfffff000L, 0x100000fffL), + TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); + } + + programManager.openProgram(program); + traceManager.activateTrace(tb.trace); + assertFalse(copyActionsPlugin.actionCopyIntoCurrentProgram.isEnabled()); + + listingProvider.requestFocus(); + listingProvider.setSelection(new ProgramSelection(tb.set( + tb.range(0x00400000, 0x0040ffff), + tb.range(0x7fff00400000L, 0x7fff0040ffffL), + tb.range(0xfffff000L, 0x100000fffL)))); + + waitForPass(() -> assertTrue(copyActionsPlugin.actionCopyIntoCurrentProgram.isEnabled())); + performAction(copyActionsPlugin.actionCopyIntoCurrentProgram, false); + DebuggerCopyIntoProgramDialog dialog = + waitForDialogComponent(DebuggerCopyIntoProgramDialog.class); + dialog.setRelocate(false); + dialog.reset(); + + List entries = List.copyOf(dialog.tableModel.getModelData()); + assertEquals(3, entries.size()); + RangeEntry entry; + + entry = entries.get(0); + assertEquals(tb.range(0x00400000, 0x00407fff), entry.getSrcRange()); + assertEquals(tb.range(stSpace, 0x00400000, 0x00407fff), entry.getDstRange()); + assertEquals(".text", entry.getRegionName()); + assertEquals(".text *", entry.getBlockName()); + assertFalse(entry.isCreate()); + + entry = entries.get(1); + assertEquals(tb.range(0x00408000, 0x0040ffff), entry.getSrcRange()); + assertEquals(tb.range(stSpace, 0x00408000, 0x0040ffff), entry.getDstRange()); + assertEquals(".text", entry.getRegionName()); + assertEquals(".text2 *", entry.getBlockName()); + assertFalse(entry.isCreate()); + + entry = entries.get(2); + assertEquals(tb.range(0xfffff000L, 0xffffffffL), entry.getSrcRange()); + assertEquals(tb.range(stSpace, 0xfffff000L, 0xffffffffL), entry.getDstRange()); + assertEquals(".straddle", entry.getRegionName()); + assertEquals(".straddle", entry.getBlockName()); + assertTrue(entry.isCreate()); + + dialog.okCallback(); + dialog.lastTask.get(1000, TimeUnit.MILLISECONDS); + waitForSwing(); + + byte[] dest = new byte[4]; + program.getMemory().getBytes(tb.addr(stSpace, 0x00401234), dest); + assertArrayEquals(tb.arr(1, 2, 3, 4), dest); + } + + @Test + public void testActionCopyIntoCurrentProgramWithRelocationExistingBlocks() throws Exception { + assertFalse(copyActionsPlugin.actionCopyIntoCurrentProgram.isEnabled()); + + createAndOpenTrace(); + createProgramFromTrace(); + intoProject(program); + intoProject(tb.trace); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getMemoryManager() + .createRegion(".text", 0, tb.range(0x55550000, 0x5555ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + } + traceManager.activateTrace(tb.trace); + assertFalse(copyActionsPlugin.actionCopyIntoCurrentProgram.isEnabled()); + + programManager.openProgram(program); + assertFalse(copyActionsPlugin.actionCopyIntoCurrentProgram.isEnabled()); + + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + MemoryBlock block; + try (UndoableTransaction tid = UndoableTransaction.start(program, "Create block", true)) { + block = program.getMemory() + .createUninitializedBlock(".text", tb.addr(stSpace, 0x00400000), 0x10000, + false); + } + + TraceLocation tloc = + new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x55550000)); + ProgramLocation ploc = new ProgramLocation(program, tb.addr(stSpace, 0x00400000)); + try (UndoableTransaction tid = tb.startTransaction()) { + mappingService.addMapping(tloc, ploc, 0x10000, true); + } + + waitForValue(() -> mappingService + .getOpenMappedViews(tb.trace, tb.set(tb.range(0x55550000, 0x5555ffff)), 0) + .get(program)); + + listingProvider.requestFocus(); + listingProvider + .setSelection(new ProgramSelection(tb.addr(0x55550000), tb.addr(0x5555ffff))); + + waitForPass(() -> assertTrue(copyActionsPlugin.actionCopyIntoCurrentProgram.isEnabled())); + performAction(copyActionsPlugin.actionCopyIntoCurrentProgram, false); + DebuggerCopyIntoProgramDialog dialog = + waitForDialogComponent(DebuggerCopyIntoProgramDialog.class); + dialog.setRelocate(true); + dialog.reset(); + + RangeEntry entry = Unique.assertOne(dialog.tableModel.getModelData()); + + assertEquals(tb.range(stSpace, 0x55550000, 0x5555ffff), entry.getSrcRange()); + assertEquals(tb.range(stSpace, 0x00400000, 0x0040ffff), entry.getDstRange()); + assertEquals(".text", entry.getRegionName()); + assertEquals(".text *", entry.getBlockName()); + assertFalse(entry.isCreate()); + dialog.okCallback(); + dialog.lastTask.get(1000, TimeUnit.MILLISECONDS); + waitForSwing(); + + MemoryBlock text = Unique.assertOne(Arrays.asList(program.getMemory().getBlocks())); + assertEquals(block, text); + } + + @Test + public void testActionCopyIntoCurrentProgramWithRelocationOverlayBlocks() throws Exception { + assertFalse(copyActionsPlugin.actionCopyIntoCurrentProgram.isEnabled()); + + createAndOpenTrace(); + createProgramFromTrace(); + intoProject(program); + intoProject(tb.trace); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getMemoryManager() + .createRegion(".text", 0, tb.range(0x55550000, 0x5555ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + } + traceManager.activateTrace(tb.trace); + assertFalse(copyActionsPlugin.actionCopyIntoCurrentProgram.isEnabled()); + + programManager.openProgram(program); + assertFalse(copyActionsPlugin.actionCopyIntoCurrentProgram.isEnabled()); + + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + MemoryBlock block; + try (UndoableTransaction tid = UndoableTransaction.start(program, "Create block", true)) { + block = program.getMemory() + .createUninitializedBlock(".text", tb.addr(stSpace, 0x00400000), 0x10000, + false); + } + + TraceLocation tloc = + new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x55550000)); + ProgramLocation ploc = new ProgramLocation(program, tb.addr(stSpace, 0x00400000)); + try (UndoableTransaction tid = tb.startTransaction()) { + mappingService.addMapping(tloc, ploc, 0x10000, true); + } + + waitForValue(() -> mappingService + .getOpenMappedViews(tb.trace, tb.set(tb.range(0x55550000, 0x5555ffff)), 0) + .get(program)); + + listingProvider.requestFocus(); + listingProvider + .setSelection(new ProgramSelection(tb.addr(0x55550000), tb.addr(0x5555ffff))); + + waitForPass(() -> assertTrue(copyActionsPlugin.actionCopyIntoCurrentProgram.isEnabled())); + performAction(copyActionsPlugin.actionCopyIntoCurrentProgram, false); + DebuggerCopyIntoProgramDialog dialog = + waitForDialogComponent(DebuggerCopyIntoProgramDialog.class); + dialog.setRelocate(true); + dialog.setUseOverlays(true); + dialog.reset(); + + RangeEntry entry = Unique.assertOne(dialog.tableModel.getModelData()); + + assertEquals(tb.range(stSpace, 0x55550000, 0x5555ffff), entry.getSrcRange()); + assertEquals(tb.range(stSpace, 0x00400000, 0x0040ffff), entry.getDstRange()); + assertEquals(".text", entry.getRegionName()); + assertEquals(".text_2", entry.getBlockName()); + assertTrue(entry.isCreate()); + dialog.okCallback(); + dialog.lastTask.get(1000, TimeUnit.MILLISECONDS); + waitForSwing(); + + MemoryBlock text2 = + Unique.assertOne(Arrays.asList(program.getMemory().getBlock(".text_2"))); + assertNotEquals(block, text2); + assertTrue(text2.isOverlay()); + } + + @Test + public void testActionCopyIntoNewProgram() throws Exception { + assertFalse(copyActionsPlugin.actionCopyIntoNewProgram.isEnabled()); + + createAndOpenTrace(); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getMemoryManager() + .createRegion(".text", 0, tb.range(0x55550000, 0x5555ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + } + traceManager.activateTrace(tb.trace); + assertFalse(copyActionsPlugin.actionCopyIntoNewProgram.isEnabled()); + + listingProvider.requestFocus(); + listingProvider + .setSelection(new ProgramSelection(tb.addr(0x55550000), tb.addr(0x5555ffff))); + + waitForPass(() -> assertTrue(copyActionsPlugin.actionCopyIntoNewProgram.isEnabled())); + performAction(copyActionsPlugin.actionCopyIntoNewProgram, false); + DebuggerCopyIntoProgramDialog dialog = + waitForDialogComponent(DebuggerCopyIntoProgramDialog.class); + dialog.setDestination(DebuggerCopyIntoProgramDialog.TEMP_PROGRAM); + + RangeEntry entry = Unique.assertOne(dialog.tableModel.getModelData()); + + assertEquals(tb.range(0x55550000, 0x5555ffff), entry.getSrcRange()); + assertEquals(tb.range(0x55550000, 0x5555ffff), entry.getDstRange()); + assertEquals(".text", entry.getRegionName()); + assertEquals(".text", entry.getBlockName()); + assertTrue(entry.isCreate()); + entry.setBlockName(".my_text"); + dialog.okCallback(); + dialog.lastTask.get(1000, TimeUnit.MILLISECONDS); + waitForSwing(); + + // Declare my own, or the @After will try to release it erroneously + Program program = waitForValue(() -> programManager.getCurrentProgram()); + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + + MemoryBlock text = Unique.assertOne(Arrays.asList(program.getMemory().getBlocks())); + assertEquals(tb.addr(stSpace, 0x55550000), text.getStart()); + assertEquals(".my_text", text.getName()); + } + + @Test + public void testActionCopyIntoNewProgramAdjacentRegions() throws Exception { + assertFalse(copyActionsPlugin.actionCopyIntoNewProgram.isEnabled()); + + createAndOpenTrace(); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getMemoryManager() + .createRegion(".text", 0, tb.range(0x55550000, 0x5555ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + tb.trace.getMemoryManager() + .createRegion(".data", 0, tb.range(0x55560000, 0x5556ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); + } + traceManager.activateTrace(tb.trace); + assertFalse(copyActionsPlugin.actionCopyIntoNewProgram.isEnabled()); + + listingProvider.requestFocus(); + listingProvider + .setSelection(new ProgramSelection(tb.addr(0x55550000), tb.addr(0x5556ffff))); + + waitForPass(() -> assertTrue(copyActionsPlugin.actionCopyIntoNewProgram.isEnabled())); + performAction(copyActionsPlugin.actionCopyIntoNewProgram, false); + DebuggerCopyIntoProgramDialog dialog = + waitForDialogComponent(DebuggerCopyIntoProgramDialog.class); + assertFalse(dialog.cbCapture.isEnabled()); + assertFalse(dialog.cbCapture.isSelected()); + dialog.setDestination(DebuggerCopyIntoProgramDialog.TEMP_PROGRAM); + + assertEquals(2, dialog.tableModel.getRowCount()); + RangeEntry entry; + + entry = dialog.tableModel.getRowObject(0); + assertEquals(tb.range(0x55550000, 0x5555ffff), entry.getSrcRange()); + assertEquals(tb.range(0x55550000, 0x5555ffff), entry.getDstRange()); + assertEquals(".text", entry.getRegionName()); + assertEquals(".text", entry.getBlockName()); + assertTrue(entry.isCreate()); + + entry = dialog.tableModel.getRowObject(1); + assertEquals(tb.range(0x55560000, 0x5556ffff), entry.getSrcRange()); + assertEquals(tb.range(0x55560000, 0x5556ffff), entry.getDstRange()); + assertEquals(".data", entry.getRegionName()); + assertEquals(".data", entry.getBlockName()); + assertTrue(entry.isCreate()); + + dialog.okCallback(); + dialog.lastTask.get(1000, TimeUnit.MILLISECONDS); + waitForSwing(); + + // Declare my own, or the @After will try to release it erroneously + Program program = waitForValue(() -> programManager.getCurrentProgram()); + assertEquals(2, program.getMemory().getBlocks().length); + } + + @Test + public void testActionCopyIntoNewProgramCaptureLive() throws Exception { + assertFalse(copyActionsPlugin.actionCopyIntoNewProgram.isEnabled()); + + createTestModel(); + + var listener = new DebuggerModelListener() { + int count = 0; + + @Override + public void memoryUpdated(TargetObject memory, Address address, byte[] data) { + count++; + } + }; + mb.testModel.addModelListener(listener); + + mb.createTestProcessesAndThreads(); + TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, + new TestDebuggerTargetTraceMapper(mb.testProcess1)); + useTrace(recorder.getTrace()); + mb.testProcess1.memory.addRegion(".text", mb.rng(0x55550000, 0x5555ffff), "rx"); + mb.testProcess1.memory.setMemory(mb.addr(0x55550000), mb.arr(1, 2, 3, 4, 5, 6, 7, 8)); + waitForPass(() -> { + assertEquals(1, tb.trace.getMemoryManager().getAllRegions().size()); + }); + + listingProvider.setAutoReadMemorySpec( + AutoReadMemorySpec.fromConfigName(NoneAutoReadMemorySpec.CONFIG_NAME)); + + traceManager.openTrace(tb.trace); + traceManager.activateTrace(tb.trace); + assertFalse(copyActionsPlugin.actionCopyIntoNewProgram.isEnabled()); + + listingProvider.requestFocus(); + listingProvider + .setSelection(new ProgramSelection(tb.addr(0x55550000), tb.addr(0x5555ffff))); + + waitForPass(() -> assertTrue(copyActionsPlugin.actionCopyIntoNewProgram.isEnabled())); + performAction(copyActionsPlugin.actionCopyIntoNewProgram, false); + DebuggerCopyIntoProgramDialog dialog = + waitForDialogComponent(DebuggerCopyIntoProgramDialog.class); + assertTrue(dialog.cbCapture.isEnabled()); + assertTrue(dialog.cbCapture.isSelected()); + dialog.setDestination(DebuggerCopyIntoProgramDialog.TEMP_PROGRAM); + + RangeEntry entry = Unique.assertOne(dialog.tableModel.getModelData()); + + assertEquals(tb.range(0x55550000, 0x5555ffff), entry.getSrcRange()); + assertEquals(tb.range(0x55550000, 0x5555ffff), entry.getDstRange()); + assertEquals("[.text]", entry.getRegionName()); + assertEquals("[.text]", entry.getBlockName()); + assertTrue(entry.isCreate()); + entry.setBlockName(".my_text"); + + assertEquals(0, listener.count); + dialog.okCallback(); + dialog.lastTask.get(10000, TimeUnit.MILLISECONDS); + waitForSwing(); + assertEquals(16, listener.count); + + // Declare my own, or the @After will try to release it erroneously + Program program = waitForValue(() -> programManager.getCurrentProgram()); + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + + MemoryBlock text = Unique.assertOne(Arrays.asList(program.getMemory().getBlocks())); + assertEquals(tb.addr(stSpace, 0x55550000), text.getStart()); + assertEquals(".my_text", text.getName()); + byte[] arr = new byte[8]; + text.getBytes(tb.addr(stSpace, 0x55550000), arr); + assertArrayEquals(tb.arr(1, 2, 3, 4, 5, 6, 7, 8), arr); + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlanTests.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlanTests.java new file mode 100644 index 0000000000..c2b21ee2ce --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlanTests.java @@ -0,0 +1,743 @@ +/* ### + * 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.app.plugin.core.debug.gui.copying; + +import static org.junit.Assert.*; + +import java.awt.Color; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.*; + +import javax.swing.JCheckBox; + +import org.junit.Test; + +import com.google.common.collect.Range; + +import ghidra.app.plugin.assembler.Assembler; +import ghidra.app.plugin.assembler.Assemblers; +import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; +import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.plugin.core.debug.gui.copying.DebuggerCopyPlan.AllCopiers; +import ghidra.app.util.viewer.listingpanel.PropertyBasedBackgroundColorModel; +import ghidra.program.database.IntRangeMap; +import ghidra.program.disassemble.Disassembler; +import ghidra.program.disassemble.DisassemblerMessageListener; +import ghidra.program.model.address.*; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.RegisterValue; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.*; +import ghidra.trace.database.breakpoint.DBTraceBreakpointManager; +import ghidra.trace.database.memory.DBTraceMemoryManager; +import ghidra.trace.database.program.DBTraceVariableSnapProgramView; +import ghidra.trace.database.symbol.*; +import ghidra.trace.model.breakpoint.TraceBreakpointKind; +import ghidra.trace.model.memory.TraceMemoryFlag; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.util.database.UndoableTransaction; +import ghidra.util.task.TaskMonitor; + +public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest { + public static class TestDynamicDataType extends CountedDynamicDataType { + public static final TestDynamicDataType dataType = new TestDynamicDataType(); + + public TestDynamicDataType() { + super("test_dyn", "A test dynamic type", ShortDataType.dataType, ByteDataType.dataType, + 0, 2, 0xffff); + } + } + + @Test + public void testBytes() throws Exception { + createTrace(); + createProgram(getSLEIGH_X86_64_LANGUAGE()); + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + + DBTraceVariableSnapProgramView view = tb.trace.getProgramView(); + assertTrue(AllCopiers.BYTES.isAvailable(view, program)); + + Random r = new Random(); + byte src[] = new byte[0x10000]; + r.nextBytes(src); + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.createRegion(".text", 0, tb.range(0x55550000, 0x5555ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + memory.putBytes(0, tb.addr(0x55550000), ByteBuffer.wrap(src)); + } + + Address paddr = tb.addr(stSpace, 0x00400000); + assertTrue(AllCopiers.BYTES.isRequiresInitializedMemory()); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy", true)) { + program.getMemory() + .createInitializedBlock(".text", paddr, 0x10000, (byte) 0, TaskMonitor.DUMMY, + false); + AllCopiers.BYTES.copy(view, tb.range(0x55550000, 0x5555ffff), program, paddr, + TaskMonitor.DUMMY); + } + + byte dst[] = new byte[0x10000]; + program.getMemory().getBytes(paddr, dst); + + assertArrayEquals(src, dst); + } + + @Test + public void testState() throws Exception { + createTrace(); + createProgram(getSLEIGH_X86_64_LANGUAGE()); + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + + DBTraceVariableSnapProgramView view = tb.trace.getProgramView(); + assertTrue(AllCopiers.STATE.isAvailable(view, program)); + + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.createRegion(".text", 0, tb.range(0x55550000, 0x5555ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + memory.putBytes(0, tb.addr(0x55550000), ByteBuffer.allocate(4096)); + memory.setState(0, tb.addr(0x55551000), TraceMemoryState.ERROR); + } + + Address paddr = tb.addr(stSpace, 0x00400000); + assertFalse(AllCopiers.STATE.isRequiresInitializedMemory()); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy", true)) { + program.getMemory() + .createInitializedBlock(".text", paddr, 0x10000, (byte) 0, TaskMonitor.DUMMY, + false); + AllCopiers.STATE.copy(view, tb.range(0x55550000, 0x5555ffff), program, paddr, + TaskMonitor.DUMMY); + } + + IntRangeMap map = + program.getIntRangeMap(PropertyBasedBackgroundColorModel.COLOR_PROPERTY_NAME); + AddressSet staleSet = + map.getAddressSet(DebuggerResources.DEFAULT_COLOR_BACKGROUND_STALE.getRGB()); + assertEquals(tb.set(tb.range(stSpace, 0x00401001, 0x0040ffff)), staleSet); + AddressSet errorSet = + map.getAddressSet(DebuggerResources.DEFAULT_COLOR_BACKGROUND_ERROR.getRGB()); + assertEquals(tb.set(tb.range(stSpace, 0x00401000, 0x00401000)), errorSet); + } + + @Test + public void testInstructionsMismatched() throws Exception { + createTrace(); + createProgram(getSLEIGH_X86_64_LANGUAGE()); + + assertFalse(AllCopiers.INSTRUCTIONS.isAvailable(tb.trace.getProgramView(), program)); + } + + @Test + public void testInstructionsDepBytes() throws Exception { + DebuggerCopyPlan plan = new DebuggerCopyPlan(); + JCheckBox cbInstructions = plan.getCheckBox(AllCopiers.INSTRUCTIONS); + JCheckBox cbBytes = plan.getCheckBox(AllCopiers.BYTES); + assertFalse(cbInstructions.isSelected()); + assertFalse(cbBytes.isSelected()); + + cbInstructions.doClick(); + assertTrue(cbInstructions.isSelected()); + assertTrue(cbBytes.isSelected()); + + cbInstructions.doClick(); + assertFalse(cbInstructions.isSelected()); + assertTrue(cbBytes.isSelected()); + + cbInstructions.doClick(); + assertTrue(cbInstructions.isSelected()); + assertTrue(cbBytes.isSelected()); + + cbBytes.doClick(); + assertFalse(cbInstructions.isSelected()); + assertFalse(cbBytes.isSelected()); + + cbBytes.doClick(); + assertFalse(cbInstructions.isSelected()); + assertTrue(cbBytes.isSelected()); + } + + @Test + public void testInstructions() throws Exception { + createTrace(); + createProgram(); + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + + DBTraceVariableSnapProgramView view = tb.trace.getProgramView(); + assertTrue(AllCopiers.BYTES.isAvailable(view, program)); + assertTrue(AllCopiers.INSTRUCTIONS.isAvailable(view, program)); + + AddressRange trng = tb.range(0x55550000, 0x5555ffff); + Assembler asm = Assemblers.getAssembler(view); + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.createRegion(".text", 0, trng, TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + InstructionIterator iit = asm.assemble(tb.addr(0x55550000), + "imm r0, #1234", + "imm r1, #2045", + "add r0, r1"); + assertTrue(iit.hasNext()); + } + + try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy", true)) { + Address paddr = tb.addr(stSpace, 0x00400000); + program.getMemory() + .createInitializedBlock(".text", paddr, 0x10000, (byte) 0, TaskMonitor.DUMMY, + false); + AllCopiers.BYTES.copy(view, trng, program, paddr, TaskMonitor.DUMMY); + AllCopiers.INSTRUCTIONS.copy(view, trng, program, paddr, TaskMonitor.DUMMY); + } + + List instructions = new ArrayList<>(); + program.getListing().getInstructions(true).forEachRemaining(instructions::add); + + assertEquals(3, instructions.size()); + Instruction ins; + + ins = instructions.get(0); + assertEquals(tb.addr(stSpace, 0x00400000), ins.getAddress()); + assertEquals("imm r0,#0x4d2", ins.toString()); + ins = instructions.get(1); + assertEquals(tb.addr(stSpace, 0x00400002), ins.getAddress()); + assertEquals("imm r1,#0x7fd", ins.toString()); + ins = instructions.get(2); + assertEquals(tb.addr(stSpace, 0x00400004), ins.getAddress()); + assertEquals("add r0,r1", ins.toString()); + } + + @Test + public void testInstructionsWithDefaultContext() throws Exception { + createTrace("x86:LE:64:default"); + createProgram(getSLEIGH_X86_64_LANGUAGE()); + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + + DBTraceVariableSnapProgramView view = tb.trace.getProgramView(); + assertTrue(AllCopiers.BYTES.isAvailable(view, program)); + assertTrue(AllCopiers.INSTRUCTIONS.isAvailable(view, program)); + + AddressRange trng = tb.range(0x55550000, 0x5555ffff); + Assembler asm = Assemblers.getAssembler(view); + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.createRegion(".text", 0, trng, TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + InstructionIterator iit = asm.assemble(tb.addr(0x55550000), + "MOV RAX, 1234", + "MOV RCX, 2345", + "ADD RAX, RCX"); + assertTrue(iit.hasNext()); + } + + try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy", true)) { + Address paddr = tb.addr(stSpace, 0x00400000); + program.getMemory() + .createInitializedBlock(".text", paddr, 0x10000, (byte) 0, TaskMonitor.DUMMY, + false); + AllCopiers.BYTES.copy(view, trng, program, paddr, TaskMonitor.DUMMY); + AllCopiers.INSTRUCTIONS.copy(view, trng, program, paddr, TaskMonitor.DUMMY); + } + + List instructions = new ArrayList<>(); + program.getListing().getInstructions(true).forEachRemaining(instructions::add); + + assertEquals(3, instructions.size()); + Instruction ins; + + ins = instructions.get(0); + assertEquals(tb.addr(stSpace, 0x00400000), ins.getAddress()); + assertEquals("MOV RAX,0x4d2", ins.toString()); + ins = instructions.get(1); + assertEquals(tb.addr(stSpace, 0x00400007), ins.getAddress()); + assertEquals("MOV RCX,0x929", ins.toString()); + ins = instructions.get(2); + assertEquals(tb.addr(stSpace, 0x0040000e), ins.getAddress()); + assertEquals("ADD RAX,RCX", ins.toString()); + } + + @Test + public void testInstructionsWithContext() throws Exception { + createTrace("x86:LE:64:default"); + createProgram(getSLEIGH_X86_64_LANGUAGE()); + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + + DBTraceVariableSnapProgramView view = tb.trace.getProgramView(); + assertTrue(AllCopiers.BYTES.isAvailable(view, program)); + assertTrue(AllCopiers.INSTRUCTIONS.isAvailable(view, program)); + + AddressRange trng = tb.range(0x55550000, 0x5555ffff); + // Assembler asm = Assemblers.getAssembler(view); + + Register contextReg = tb.language.getContextBaseRegister(); + Register longMode = tb.language.getRegister("longMode"); + RegisterValue rv = tb.trace.getRegisterContextManager() + .getValueWithDefault(tb.language, contextReg, 0, tb.addr(0x55550000)); + rv = rv.assign(longMode, BigInteger.ZERO); + Instruction checkCtx; + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.createRegion(".text", 0, trng, TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + tb.trace.getRegisterContextManager().setValue(tb.language, rv, Range.atLeast(0L), trng); + + // TODO: Once GP-1426 is resolved, use the assembler + /* + InstructionIterator iit = asm.assemble(tb.addr(0x55550000), + "MOV EAX, 1234", + "MOV ECX, 2345", + "ADD EAX, ECX"); + checkCtx = iit.next(); + */ + memory.putBytes(0, tb.addr(0x55550000), tb.buf( + 0xb8, 0xd2, 0x04, 0x00, 0x00, // MOV EAX,1234 + 0xb9, 0x29, 0x09, 0x00, 0x00, // MOV ECX,2345 + 0x01, 0xc8 // ADD EAX,ECX + )); + Disassembler + .getDisassembler(view, TaskMonitor.DUMMY, DisassemblerMessageListener.IGNORE) + .disassemble(tb.addr(0x55550000), tb.set(tb.range(0x55550000, 0x5555000b))); + checkCtx = tb.trace.getCodeManager().instructions().getAt(0, tb.addr(0x55550000)); + } + // Sanity pre-check + RegisterValue insCtx = checkCtx.getRegisterValue(contextReg); + assertFalse(insCtx.equals(tb.trace.getRegisterContextManager() + .getDefaultValue(tb.language, contextReg, checkCtx.getAddress()))); + + try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy", true)) { + Address paddr = tb.addr(stSpace, 0x00400000); + program.getMemory() + .createInitializedBlock(".text", paddr, 0x10000, (byte) 0, TaskMonitor.DUMMY, + false); + AllCopiers.BYTES.copy(view, trng, program, paddr, TaskMonitor.DUMMY); + AllCopiers.INSTRUCTIONS.copy(view, trng, program, paddr, TaskMonitor.DUMMY); + } + + List instructions = new ArrayList<>(); + program.getListing().getInstructions(true).forEachRemaining(instructions::add); + + assertEquals(3, instructions.size()); + Instruction ins; + + ins = instructions.get(0); + assertEquals(tb.addr(stSpace, 0x00400000), ins.getAddress()); + assertEquals("MOV EAX,0x4d2", ins.toString()); + ins = instructions.get(1); + assertEquals(tb.addr(stSpace, 0x00400005), ins.getAddress()); + assertEquals("MOV ECX,0x929", ins.toString()); + ins = instructions.get(2); + assertEquals(tb.addr(stSpace, 0x0040000a), ins.getAddress()); + assertEquals("ADD EAX,ECX", ins.toString()); + } + + @Test + public void testDataMismatched() throws Exception { + createTrace(); + createProgram(getSLEIGH_X86_64_LANGUAGE()); + + assertFalse(AllCopiers.DATA.isAvailable(tb.trace.getProgramView(), program)); + } + + @Test + public void testData() throws Exception { + createTrace(); + createProgram(); + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + + DBTraceVariableSnapProgramView view = tb.trace.getProgramView(); + assertTrue(AllCopiers.DATA.isAvailable(view, program)); + + AddressRange trng = tb.range(0x55560000, 0x5556ffff); + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.createRegion(".data", 0, trng, TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); + tb.addData(0, tb.addr(0x55560000), ByteDataType.dataType, tb.buf(0x12)); + tb.addData(0, tb.addr(0x55560001), ShortDataType.dataType, tb.buf(0x12, 0x34)); + tb.addData(0, tb.addr(0x55560003), IntegerDataType.dataType, + tb.buf(0x12, 0x34, 0x56, 0x78)); + tb.addData(0, tb.addr(0x55560007), LongLongDataType.dataType, + tb.buf(0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0)); + tb.addData(0, tb.addr(0x5556000f), TestDynamicDataType.dataType, + tb.buf(0x00, 0x03, 0x00, 0x01, 0x02)); + } + + try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy", true)) { + Address paddr = tb.addr(stSpace, 0x00600000); + program.getMemory() + .createInitializedBlock(".data", paddr, 0x10000, (byte) 0, TaskMonitor.DUMMY, + false); + AllCopiers.DATA.copy(view, trng, program, paddr, TaskMonitor.DUMMY); + } + + List data = new ArrayList<>(); + program.getListing().getDefinedData(true).forEachRemaining(data::add); + + // NB. Bytes were not copied. Dynamic omitted. + assertEquals(4, data.size()); + Data dat; + + dat = data.get(0); + assertEquals(tb.addr(stSpace, 0x00600000), dat.getAddress()); + assertEquals("db 0h", dat.toString()); + dat = data.get(1); + assertEquals(tb.addr(stSpace, 0x00600001), dat.getAddress()); + assertEquals("short 0h", dat.toString()); + dat = data.get(2); + assertEquals(tb.addr(stSpace, 0x00600003), dat.getAddress()); + assertEquals("int 0h", dat.toString()); + dat = data.get(3); + assertEquals(tb.addr(stSpace, 0x00600007), dat.getAddress()); + assertEquals("longlong 0h", dat.toString()); + } + + @Test + public void testDynamicDataMismatched() throws Exception { + createTrace(); + createProgram(getSLEIGH_X86_64_LANGUAGE()); + + assertFalse(AllCopiers.DYNAMIC_DATA.isAvailable(tb.trace.getProgramView(), program)); + } + + @Test + public void testDynamicData() throws Exception { + createTrace(); + createProgram(); + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + + DBTraceVariableSnapProgramView view = tb.trace.getProgramView(); + assertTrue(AllCopiers.DYNAMIC_DATA.isAvailable(view, program)); + + AddressRange trng = tb.range(0x55560000, 0x5556ffff); + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.createRegion(".data", 0, trng, TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); + tb.addData(0, tb.addr(0x55560000), ByteDataType.dataType, tb.buf(0x12)); + tb.addData(0, tb.addr(0x55560001), ShortDataType.dataType, tb.buf(0x12, 0x34)); + tb.addData(0, tb.addr(0x55560003), IntegerDataType.dataType, + tb.buf(0x12, 0x34, 0x56, 0x78)); + tb.addData(0, tb.addr(0x55560007), LongLongDataType.dataType, + tb.buf(0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0)); + tb.addData(0, tb.addr(0x5556000f), TestDynamicDataType.dataType, + tb.buf(0x00, 0x03, 0x00, 0x01, 0x02)); + } + + try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy", true)) { + Address paddr = tb.addr(stSpace, 0x00600000); + program.getMemory() + .createInitializedBlock(".data", paddr, 0x10000, (byte) 0, TaskMonitor.DUMMY, + false); + AllCopiers.BYTES.copy(view, trng, program, paddr, TaskMonitor.DUMMY); + AllCopiers.DATA.copy(view, trng, program, paddr, TaskMonitor.DUMMY); + AllCopiers.DYNAMIC_DATA.copy(view, trng, program, paddr, TaskMonitor.DUMMY); + } + + List data = new ArrayList<>(); + program.getListing().getDefinedData(true).forEachRemaining(data::add); + + // NB. Bytes were not copied. Dynamic omitted. + assertEquals(5, data.size()); + Data dat; + Data cmp; + + dat = data.get(0); + assertEquals(tb.addr(stSpace, 0x00600000), dat.getAddress()); + assertEquals("db 12h", dat.toString()); + dat = data.get(1); + assertEquals(tb.addr(stSpace, 0x00600001), dat.getAddress()); + assertEquals("short 1234h", dat.toString()); + dat = data.get(2); + assertEquals(tb.addr(stSpace, 0x00600003), dat.getAddress()); + assertEquals("int 12345678h", dat.toString()); + dat = data.get(3); + assertEquals(tb.addr(stSpace, 0x00600007), dat.getAddress()); + assertEquals("longlong 123456789ABCDEF0h", dat.toString()); + + dat = data.get(4); + assertEquals(tb.addr(stSpace, 0x0060000f), dat.getAddress()); + assertEquals("test_dyn ", dat.toString()); + assertEquals(4, dat.getNumComponents()); // count + 3 elements + cmp = dat.getComponent(0); + assertEquals("short 3h", cmp.toString()); + cmp = dat.getComponent(1); + assertEquals("db 0h", cmp.toString()); + cmp = dat.getComponent(2); + assertEquals("db 1h", cmp.toString()); + cmp = dat.getComponent(3); + assertEquals("db 2h", cmp.toString()); + } + + @Test + public void testLabels() throws Exception { + createTrace(); + createProgram(getSLEIGH_X86_64_LANGUAGE()); + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + + DBTraceVariableSnapProgramView view = tb.trace.getProgramView(); + assertTrue(AllCopiers.LABELS.isAvailable(view, program)); + + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.createRegion(".text", 0, tb.range(0x55550000, 0x5555ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + DBTraceNamespaceSymbol global = tb.trace.getSymbolManager().getGlobalNamespace(); + + DBTraceLabelSymbolView labels = tb.trace.getSymbolManager().labels(); + labels.create(0, null, tb.addr(0x55550000), "test_label1", global, SourceType.IMPORTED); + labels.create(0, null, tb.addr(0x55550005), "test_label2", global, + SourceType.USER_DEFINED); + DBTraceNamespaceSymbolView namespaces = tb.trace.getSymbolManager().namespaces(); + DBTraceNamespaceSymbol testNs = namespaces.add("test_ns", global, SourceType.ANALYSIS); + DBTraceNamespaceSymbol testNsChild = + namespaces.add("test_ns_child", testNs, SourceType.USER_DEFINED); + labels.create(0, null, tb.addr(0x55550800), "test_label3", testNsChild, + SourceType.ANALYSIS); + } + + Address paddr = tb.addr(stSpace, 0x00400000); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy", true)) { + program.getMemory() + .createInitializedBlock(".text", paddr, 0x10000, (byte) 0, TaskMonitor.DUMMY, + false); + AllCopiers.LABELS.copy(view, tb.range(0x55550000, 0x5555ffff), program, paddr, + TaskMonitor.DUMMY); + } + + List symbols = new ArrayList<>(); + program.getSymbolTable().getSymbolIterator(true).forEachRemaining(symbols::add); + + assertEquals(3, symbols.size()); + Symbol sym; + Namespace ns; + + sym = symbols.get(0); + assertEquals("test_label1", sym.getName()); + assertEquals(tb.addr(stSpace, 0x00400000), sym.getAddress()); + assertEquals(SourceType.IMPORTED, sym.getSource()); + assertTrue(sym.isGlobal()); + sym = symbols.get(1); + assertEquals("test_label2", sym.getName()); + assertEquals(tb.addr(stSpace, 0x00400005), sym.getAddress()); + assertEquals(SourceType.USER_DEFINED, sym.getSource()); + assertTrue(sym.isGlobal()); + + sym = symbols.get(2); + assertEquals("test_label3", sym.getName()); + assertEquals(tb.addr(stSpace, 0x00400800), sym.getAddress()); + assertEquals(SourceType.ANALYSIS, sym.getSource()); + assertFalse(sym.isGlobal()); + ns = sym.getParentNamespace(); + assertEquals("test_ns_child", ns.getName()); + assertEquals(SourceType.USER_DEFINED, ns.getSymbol().getSource()); + assertFalse(ns.isGlobal()); + ns = ns.getParentNamespace(); + assertEquals("test_ns", ns.getName()); + assertEquals(SourceType.ANALYSIS, ns.getSymbol().getSource()); + assertFalse(ns.isGlobal()); + ns = ns.getParentNamespace(); + assertTrue(ns.isGlobal()); + } + + @Test + public void testBreakpoints() throws Exception { + createTrace(); + createProgram(getSLEIGH_X86_64_LANGUAGE()); + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + + DBTraceVariableSnapProgramView view = tb.trace.getProgramView(); + assertTrue(AllCopiers.BREAKPOINTS.isAvailable(view, program)); + + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.createRegion(".text", 0, tb.range(0x55550000, 0x5555ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + + DBTraceBreakpointManager breakpoints = tb.trace.getBreakpointManager(); + breakpoints.placeBreakpoint("[1]", 0, tb.addr(0x55550123), List.of(), + Set.of(TraceBreakpointKind.SW_EXECUTE), true, "Test-1"); + breakpoints.placeBreakpoint("[2]", 0, tb.addr(0x55550321), List.of(), + Set.of(TraceBreakpointKind.SW_EXECUTE), false, "Test-2"); + } + + Address paddr = tb.addr(stSpace, 0x55550000); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Init", true)) { + program.getMemory() + .createInitializedBlock(".text", paddr, 0x10000, + (byte) 0, TaskMonitor.DUMMY, false); + // Set up a collision. This is normal with relocations + program.getBookmarkManager() + .setBookmark(tb.addr(stSpace, 0x55550123), "BreakpointDisabled", "SW_EXECUTE;1", + ""); + + AllCopiers.BREAKPOINTS.copy(view, tb.range(0x55550000, 0x5555ffff), program, paddr, + TaskMonitor.DUMMY); + } + + List bookmarks = new ArrayList<>(); + program.getBookmarkManager().getBookmarksIterator().forEachRemaining(bookmarks::add); + + assertEquals(2, bookmarks.size()); + Collections.sort(bookmarks, Comparator.comparing(Bookmark::getAddress)); + Bookmark bm; + + bm = bookmarks.get(0); + assertEquals(tb.addr(stSpace, 0x55550123), bm.getAddress()); + assertEquals("BreakpointEnabled", bm.getTypeString()); + assertEquals("SW_EXECUTE;1", bm.getCategory()); + + bm = bookmarks.get(1); + assertEquals(tb.addr(stSpace, 0x55550321), bm.getAddress()); + assertEquals("BreakpointDisabled", bm.getTypeString()); + assertEquals("SW_EXECUTE;1", bm.getCategory()); + } + + @Test + public void testBookmarks() throws Exception { + createTrace(); + createProgram(getSLEIGH_X86_64_LANGUAGE()); + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + + DBTraceVariableSnapProgramView view = tb.trace.getProgramView(); + assertTrue(AllCopiers.BOOKMARKS.isAvailable(view, program)); + + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.createRegion(".text", 0, tb.range(0x55550000, 0x5555ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + + BookmarkManager bookmarks = view.getBookmarkManager(); + bookmarks.defineType("TestType", DebuggerResources.ICON_DEBUGGER, Color.BLUE, 1); + bookmarks.setBookmark(tb.addr(0x55550123), "TestType", "TestCategory", "Test Comment"); + } + + Address paddr = tb.addr(stSpace, 0x55550000); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Init", true)) { + program.getMemory() + .createInitializedBlock(".text", paddr, 0x10000, + (byte) 0, TaskMonitor.DUMMY, false); + + AllCopiers.BOOKMARKS.copy(view, tb.range(0x55550000, 0x5555ffff), program, paddr, + TaskMonitor.DUMMY); + } + + List bookmarks = new ArrayList<>(); + program.getBookmarkManager().getBookmarksIterator().forEachRemaining(bookmarks::add); + + assertEquals(1, bookmarks.size()); + Bookmark bm; + + bm = bookmarks.get(0); + assertEquals(tb.addr(stSpace, 0x55550123), bm.getAddress()); + BookmarkType type = program.getBookmarkManager().getBookmarkType("TestType"); + assertNotNull(type); + assertEquals(type.getTypeString(), bm.getTypeString()); + assertEquals("TestCategory", bm.getCategory()); + assertEquals("Test Comment", bm.getComment()); + + assertEquals(DebuggerResources.ICON_DEBUGGER, type.getIcon()); + assertEquals(Color.BLUE, type.getMarkerColor()); + assertEquals(1, type.getMarkerPriority()); + } + + @Test + public void testReferences() throws Exception { + createTrace(); + createProgram(getSLEIGH_X86_64_LANGUAGE()); + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + + DBTraceVariableSnapProgramView view = tb.trace.getProgramView(); + assertTrue(AllCopiers.REFERENCES.isAvailable(view, program)); + + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.createRegion(".text", 0, tb.range(0x55550000, 0x5555ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + memory.createRegion(".data", 0, tb.range(0x55560000, 0x5556ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); + + ReferenceManager references = view.getReferenceManager(); + references.addMemoryReference(tb.addr(0x55550123), + tb.addr(0x55550321), RefType.COMPUTED_CALL, SourceType.USER_DEFINED, -1); + references.addMemoryReference(tb.addr(0x55550123), + tb.addr(0x55560321), RefType.READ, SourceType.USER_DEFINED, -1); + references.addMemoryReference(tb.addr(0x55560123), + tb.addr(0x55550321), RefType.PARAM, SourceType.USER_DEFINED, -1); + references.addMemoryReference(tb.addr(0x55560123), + tb.addr(0x55560321), RefType.DATA, SourceType.USER_DEFINED, -1); + } + + Address paddr = tb.addr(stSpace, 0x55550000); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Init", true)) { + program.getMemory() + .createInitializedBlock(".text", paddr, 0x10000, + (byte) 0, TaskMonitor.DUMMY, false); + + AllCopiers.REFERENCES.copy(view, tb.range(0x55550000, 0x5555ffff), program, paddr, + TaskMonitor.DUMMY); + } + + List references = new ArrayList<>(); + program.getReferenceManager().getReferenceIterator(paddr).forEachRemaining(references::add); + + assertEquals(1, references.size()); + Reference ref; + + ref = references.get(0); + assertEquals(tb.addr(stSpace, 0x55550123), ref.getFromAddress()); + assertEquals(tb.addr(stSpace, 0x55550321), ref.getToAddress()); + assertEquals(RefType.COMPUTED_CALL, ref.getReferenceType()); + assertEquals(SourceType.USER_DEFINED, ref.getSource()); + assertEquals(-1, ref.getOperandIndex()); + } + + @Test + public void testComments() throws Exception { + createTrace(); + createProgram(getSLEIGH_X86_64_LANGUAGE()); + AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace(); + + DBTraceVariableSnapProgramView view = tb.trace.getProgramView(); + assertTrue(AllCopiers.COMMENTS.isAvailable(view, program)); + + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.createRegion(".text", 0, tb.range(0x55550000, 0x5555ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + + Listing listing = view.getListing(); + listing.setComment(tb.addr(0x55550123), CodeUnit.EOL_COMMENT, "Test EOL Comment"); + listing.setComment(tb.addr(0x55550321), CodeUnit.PLATE_COMMENT, "Test Plate Comment"); + } + + Address paddr = tb.addr(stSpace, 0x55550000); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Init", true)) { + program.getMemory() + .createInitializedBlock(".text", paddr, 0x10000, + (byte) 0, TaskMonitor.DUMMY, false); + + AllCopiers.COMMENTS.copy(view, tb.range(0x55550000, 0x5555ffff), program, paddr, + TaskMonitor.DUMMY); + } + + Set

addresses = new HashSet<>(); + Listing listing = program.getListing(); + listing.getCommentAddressIterator(program.getMemory(), true) + .forEachRemaining(addresses::add); + + assertEquals(Set.of(tb.addr(stSpace, 0x55550123), tb.addr(stSpace, 0x55550321)), addresses); + assertEquals("Test EOL Comment", + listing.getComment(CodeUnit.EOL_COMMENT, tb.addr(stSpace, 0x55550123))); + assertEquals("Test Plate Comment", + listing.getComment(CodeUnit.PLATE_COMMENT, tb.addr(stSpace, 0x55550321))); + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java index 04fca7415d..ab4e610e18 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java @@ -983,11 +983,11 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI @Test @Ignore("TODO") // Needs attention, but low priority - public void testActionCaptureSelectedMemory() throws Exception { + public void testActionReadSelectedMemory() throws Exception { byte[] data = incBlock(); byte[] zero = new byte[data.length]; ByteBuffer buf = ByteBuffer.allocate(data.length); - assertFalse(listingProvider.actionCaptureSelectedMemory.isEnabled()); + assertFalse(listingProvider.actionReadSelectedMemory.isEnabled()); listingProvider.setAutoReadMemorySpec(readNone); // To verify enabled requires live target @@ -1002,12 +1002,12 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI traceManager.activateTrace(tb.trace); waitForSwing(); // Still - assertFalse(listingProvider.actionCaptureSelectedMemory.isEnabled()); + assertFalse(listingProvider.actionReadSelectedMemory.isEnabled()); listingProvider.setSelection(sel); waitForSwing(); // Still - assertFalse(listingProvider.actionCaptureSelectedMemory.isEnabled()); + assertFalse(listingProvider.actionReadSelectedMemory.isEnabled()); // Now, simulate the sequence that typically enables the action createTestModel(); @@ -1024,12 +1024,12 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI // NOTE: recordTargetContainerAndOpenTrace has already activated the trace // Action is still disabled, because it requires a selection - assertFalse(listingProvider.actionCaptureSelectedMemory.isEnabled()); + assertFalse(listingProvider.actionReadSelectedMemory.isEnabled()); listingProvider.setSelection(sel); waitForSwing(); // Now, it should be enabled - assertTrue(listingProvider.actionCaptureSelectedMemory.isEnabled()); + assertTrue(listingProvider.actionReadSelectedMemory.isEnabled()); // First check nothing captured yet buf.clear(); @@ -1038,7 +1038,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI assertArrayEquals(zero, buf.array()); // Verify that the action performs the expected task - performAction(listingProvider.actionCaptureSelectedMemory); + performAction(listingProvider.actionReadSelectedMemory); waitForBusyTool(tool); waitForDomainObject(trace); @@ -1053,28 +1053,28 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI // Verify that setting the memory inaccessible disables the action mb.testProcess1.memory.setAccessible(false); - waitForPass(() -> assertFalse(listingProvider.actionCaptureSelectedMemory.isEnabled())); + waitForPass(() -> assertFalse(listingProvider.actionReadSelectedMemory.isEnabled())); // Verify that setting it accessible re-enables it (assuming we still have selection) mb.testProcess1.memory.setAccessible(true); - waitForPass(() -> assertTrue(listingProvider.actionCaptureSelectedMemory.isEnabled())); + waitForPass(() -> assertTrue(listingProvider.actionReadSelectedMemory.isEnabled())); // Verify that moving into the past disables the action TraceSnapshot forced = recorder.forceSnapshot(); waitForSwing(); // UI Wants to sync with new snap. Wait.... traceManager.activateSnap(forced.getKey() - 1); waitForSwing(); - assertFalse(listingProvider.actionCaptureSelectedMemory.isEnabled()); + assertFalse(listingProvider.actionReadSelectedMemory.isEnabled()); // Verify that advancing to the present enables the action (assuming a selection) traceManager.activateSnap(forced.getKey()); waitForSwing(); - assertTrue(listingProvider.actionCaptureSelectedMemory.isEnabled()); + assertTrue(listingProvider.actionReadSelectedMemory.isEnabled()); // Verify that stopping the recording disables the action recorder.stopRecording(); waitForSwing(); - assertFalse(listingProvider.actionCaptureSelectedMemory.isEnabled()); + assertFalse(listingProvider.actionReadSelectedMemory.isEnabled()); // TODO: When resume recording is implemented, verify action is enabled with selection } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java index 1078c1b838..d41dfb4a59 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java @@ -741,11 +741,11 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge @Test @Ignore("TODO") // Needs attention, but low priority // Accessibility listener does not seem to work - public void testActionCaptureSelectedMemory() throws Exception { + public void testActionReadSelectedMemory() throws Exception { byte[] data = incBlock(); byte[] zero = new byte[data.length]; ByteBuffer buf = ByteBuffer.allocate(data.length); - assertFalse(memBytesProvider.actionCaptureSelectedMemory.isEnabled()); + assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled()); memBytesProvider.setAutoReadMemorySpec(readNone); // To verify enabled requires live target @@ -760,12 +760,12 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge traceManager.activateTrace(tb.trace); waitForSwing(); // Still - assertFalse(memBytesProvider.actionCaptureSelectedMemory.isEnabled()); + assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled()); memBytesProvider.setSelection(sel); waitForSwing(); // Still - assertFalse(memBytesProvider.actionCaptureSelectedMemory.isEnabled()); + assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled()); // Now, simulate the sequence that typically enables the action createTestModel(); @@ -782,21 +782,21 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge // NOTE: recordTargetContainerAndOpenTrace has already activated the trace // Action is still disabled, because it requires a selection - assertFalse(memBytesProvider.actionCaptureSelectedMemory.isEnabled()); + assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled()); memBytesProvider.setSelection(sel); waitForSwing(); // Now, it should be enabled - assertTrue(memBytesProvider.actionCaptureSelectedMemory.isEnabled()); + assertTrue(memBytesProvider.actionReadSelectedMemory.isEnabled()); - // First check nothing captured yet + // First check nothing recorded yet buf.clear(); assertEquals(data.length, trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf)); assertArrayEquals(zero, buf.array()); // Verify that the action performs the expected task - performAction(memBytesProvider.actionCaptureSelectedMemory); + performAction(memBytesProvider.actionReadSelectedMemory); waitForBusyTool(tool); waitForDomainObject(trace); @@ -811,28 +811,28 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge // Verify that setting the memory inaccessible disables the action mb.testProcess1.memory.setAccessible(false); - waitForPass(() -> assertFalse(memBytesProvider.actionCaptureSelectedMemory.isEnabled())); + waitForPass(() -> assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled())); // Verify that setting it accessible re-enables it (assuming we still have selection) mb.testProcess1.memory.setAccessible(true); - waitForPass(() -> assertTrue(memBytesProvider.actionCaptureSelectedMemory.isEnabled())); + waitForPass(() -> assertTrue(memBytesProvider.actionReadSelectedMemory.isEnabled())); // Verify that moving into the past disables the action TraceSnapshot forced = recorder.forceSnapshot(); waitForSwing(); // UI Wants to sync with new snap. Wait.... traceManager.activateSnap(forced.getKey() - 1); waitForSwing(); - assertFalse(memBytesProvider.actionCaptureSelectedMemory.isEnabled()); + assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled()); // Verify that advancing to the present enables the action (assuming a selection) traceManager.activateSnap(forced.getKey()); waitForSwing(); - assertTrue(memBytesProvider.actionCaptureSelectedMemory.isEnabled()); + assertTrue(memBytesProvider.actionReadSelectedMemory.isEnabled()); // Verify that stopping the recording disables the action recorder.stopRecording(); waitForSwing(); - assertFalse(memBytesProvider.actionCaptureSelectedMemory.isEnabled()); + assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled()); // TODO: When resume recording is implemented, verify action is enabled with selection } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServiceTest.java index f42ff513f8..3434ff071a 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServiceTest.java @@ -27,7 +27,7 @@ import com.google.common.collect.Range; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.services.DebuggerStaticMappingService; -import ghidra.app.services.DebuggerStaticMappingService.ShiftAndAddressSetView; +import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; import ghidra.framework.model.DomainFile; import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; @@ -339,7 +339,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg public void testAddMappingThenTranslateTraceViewToStaticEmpty() throws Exception { addMapping(); - Map views = + Map> views = mappingService.getOpenMappedViews(tb.trace, new AddressSet(), 0); assertTrue(views.isEmpty()); } @@ -360,18 +360,19 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg // After set.add(dynSpace.getAddress(0xbadbadbadL), dynSpace.getAddress(0xbadbadbadL + 0xff)); - Map views = + Map> views = mappingService.getOpenMappedViews(tb.trace, set, 0); assertEquals(1, views.size()); - ShiftAndAddressSetView shifted = views.get(program); - assertEquals(0x100000, shifted.getShift()); - AddressSetView inStatic = shifted.getAddressSetView(); - assertEquals(3, inStatic.getNumAddressRanges()); - AddressSet expected = new AddressSet(); - expected.add(stSpace.getAddress(0x00200000), stSpace.getAddress(0x002000ff)); - expected.add(stSpace.getAddress(0x00200c0d), stSpace.getAddress(0x00200ccc)); - expected.add(stSpace.getAddress(0x00201000 - 0x100), stSpace.getAddress(0x00200fff)); - assertEquals(expected, inStatic); + Collection mappedSet = views.get(program); + + assertEquals(Set.of( + new MappedAddressRange(tb.range(0x00100000, 0x001000ff), + tb.range(stSpace, 0x00200000, 0x002000ff)), + new MappedAddressRange(tb.range(0x00100c0d, 0x00100ccc), + tb.range(stSpace, 0x00200c0d, 0x00200ccc)), + new MappedAddressRange(tb.range(0x00100f00, 0x00100fff), + tb.range(stSpace, 0x00200f00, 0x00200fff))), + mappedSet); } @Test @@ -380,7 +381,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg copyTrace(); add2ndMapping(); - Map views = + Map> views = mappingService.getOpenMappedViews(program, new AddressSet()); assertTrue(views.isEmpty()); } @@ -403,30 +404,34 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg // After set.add(stSpace.getAddress(0xbadbadbadL), stSpace.getAddress(0xbadbadbadL + 0xff)); - Map views = + Map> views = mappingService.getOpenMappedViews(program, set); Msg.info(this, views); assertEquals(2, views.size()); - ShiftAndAddressSetView shifted1 = views.get(new DefaultTraceSnap(tb.trace, 0)); - assertEquals(-0x100000, shifted1.getShift()); - AddressSetView in1st = shifted1.getAddressSetView(); - assertEquals(5, in1st.getNumAddressRanges()); - AddressSetView in2nd = views.get(new DefaultTraceSnap(copy, 0)).getAddressSetView(); - assertEquals(3, in2nd.getNumAddressRanges()); + Collection mappedSet1 = views.get(new DefaultTraceSnap(tb.trace, 0)); + Collection mappedSet2 = views.get(new DefaultTraceSnap(copy, 0)); - AddressSet expectedIn1st = new AddressSet(); - AddressSet expectedIn2nd = new AddressSet(); - expectedIn1st.add(dynSpace.getAddress(0x00100000), dynSpace.getAddress(0x001000ff)); - expectedIn1st.add(dynSpace.getAddress(0x00100800 - 0x10), - dynSpace.getAddress(0x00100800 + 0xf)); - expectedIn1st.add(dynSpace.getAddress(0x00101000 - 0x100), dynSpace.getAddress(0x00100fff)); - expectedIn2nd.add(expectedIn1st); + assertEquals(Set.of( + new MappedAddressRange(tb.range(stSpace, 0x00200000, 0x002000ff), + tb.range(0x00100000, 0x001000ff)), + new MappedAddressRange(tb.range(stSpace, 0x002007f0, 0x0020080f), + tb.range(0x001007f0, 0x0010080f)), + new MappedAddressRange(tb.range(stSpace, 0x00200f00, 0x00200fff), + tb.range(0x00100f00, 0x00100fff)), + new MappedAddressRange(tb.range(stSpace, 0x00200000, 0x002000ff), + tb.range(0x00102000, 0x001020ff)), + new MappedAddressRange(tb.range(stSpace, 0x002007f0, 0x002007ff), + tb.range(0x001027f0, 0x001027ff))), + mappedSet1); - expectedIn1st.add(dynSpace.getAddress(0x00102000), dynSpace.getAddress(0x001020ff)); - expectedIn1st.add(dynSpace.getAddress(0x00102800 - 0x10), dynSpace.getAddress(0x001027ff)); - - assertEquals(expectedIn1st, in1st); - assertEquals(expectedIn2nd, in2nd); + assertEquals(Set.of( + new MappedAddressRange(tb.range(stSpace, 0x00200000, 0x002000ff), + tb.range(0x00100000, 0x001000ff)), + new MappedAddressRange(tb.range(stSpace, 0x002007f0, 0x0020080f), + tb.range(0x001007f0, 0x0010080f)), + new MappedAddressRange(tb.range(stSpace, 0x00200f00, 0x00200fff), + tb.range(0x00100f00, 0x00100fff))), + mappedSet2); } @Test diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataTypeManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataTypeManager.java index c2e8c9ef56..5d5ab618d9 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataTypeManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataTypeManager.java @@ -220,9 +220,7 @@ public class DBTraceDataTypeManager extends DataTypeManagerDB @Override public DataOrganization getDataOrganization() { if (dataOrganization == null) { - // TODO: Do I need to have a base compiler spec? - dataOrganization = - trace.getBaseLanguage().getDefaultCompilerSpec().getDataOrganization(); + dataOrganization = trace.getBaseCompilerSpec().getDataOrganization(); } return dataOrganization; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstruction.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstruction.java index 6812ecf3a5..83f32ce23e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstruction.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstruction.java @@ -30,6 +30,7 @@ import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.symbol.*; import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.database.context.DBTraceRegisterContextManager; import ghidra.trace.database.context.DBTraceRegisterContextSpace; import ghidra.trace.database.language.DBTraceGuestLanguage; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree; @@ -621,13 +622,12 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit lifespan, Address min, Address max, - ProcessorContextView context) { - Language language = space.baseLanguage; - Register contextReg = language.getContextBaseRegister(); - if (contextReg == null) { - return; - } - RegisterValue newValue = context.getRegisterValue(contextReg); - DBTraceRegisterContextManager ctxMgr = space.trace.getRegisterContextManager(); - if (Objects.equals(ctxMgr.getDefaultValue(language, contextReg, min), newValue)) { - DBTraceRegisterContextSpace ctxSpace = ctxMgr.get(space, false); - if (ctxSpace == null) { - return; - } - ctxSpace.setValue(language, null, lifespan, new AddressRangeImpl(min, max)); - return; - } - DBTraceRegisterContextSpace ctxSpace = ctxMgr.get(space, true); - // TODO: Do not save non-flowing context beyond??? - ctxSpace.setValue(language, newValue, lifespan, new AddressRangeImpl(min, max)); - } - protected Instruction doCreateInstruction(Range lifespan, Address address, InstructionPrototype prototype, Instruction protoInstr) { try { - doSetContexts(lifespan, address, address.addNoWrap(prototype.getLength() - 1), - protoInstr); - Instruction created = doCreate(lifespan, address, prototype, protoInstr); // copy override settings to replacement instruction if (protoInstr.isFallThroughOverridden()) { @@ -183,6 +159,27 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView super(space, space.instructionMapSpace); } + protected void doSetContexts(TraceAddressSnapRange tasr, Language language, + ProcessorContextView context) { + Register contextReg = language.getContextBaseRegister(); + if (contextReg == null || contextReg == Register.NO_CONTEXT) { + return; + } + RegisterValue newValue = context.getRegisterValue(contextReg); + DBTraceRegisterContextManager ctxMgr = space.trace.getRegisterContextManager(); + if (Objects.equals(ctxMgr.getDefaultValue(language, contextReg, tasr.getX1()), newValue)) { + DBTraceRegisterContextSpace ctxSpace = ctxMgr.get(space, false); + if (ctxSpace == null) { + return; + } + ctxSpace.setValue(language, null, tasr.getLifespan(), tasr.getRange()); + return; + } + DBTraceRegisterContextSpace ctxSpace = ctxMgr.get(space, true); + // TODO: Do not save non-flowing context beyond??? + ctxSpace.setValue(language, newValue, tasr.getLifespan(), tasr.getRange()); + } + protected DBTraceInstruction doCreate(Range lifespan, Address address, InstructionPrototype prototype, ProcessorContextView context) throws CodeUnitInsertionException, AddressOverflowException { @@ -214,6 +211,8 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView throw new CodeUnitInsertionException("Code units cannot overlap"); } + doSetContexts(tasr, prototype.getLanguage(), context); + DBTraceInstruction created = space.instructionMapSpace.put(tasr, null); created.set(prototype, context); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewListing.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewListing.java index fe45f80d9f..92de69852e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewListing.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewListing.java @@ -415,25 +415,33 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV forward))); } + protected AddressSetView getCommentAddresses(int commentType, AddressSetView addrSet) { + return new IntersectionAddressSetView(addrSet, program.viewport.unionedAddresses( + s -> program.trace.getCommentAdapter() + .getAddressSetView(Range.singleton(s), e -> e.getType() == commentType))); + } + + protected AddressSetView getCommentAddresses(AddressSetView addrSet) { + return new IntersectionAddressSetView(addrSet, program.viewport.unionedAddresses( + s -> program.trace.getCommentAdapter() + .getAddressSetView(Range.singleton(s)))); + } + @Override public CodeUnitIterator getCommentCodeUnitIterator(int commentType, AddressSetView addrSet) { - // TODO Auto-generated method stub - return null; + return new WrappingCodeUnitIterator( + getCodeUnitIterator(getCommentAddresses(commentType, addrSet), true)); } @Override public AddressIterator getCommentAddressIterator(int commentType, AddressSetView addrSet, boolean forward) { - return new IntersectionAddressSetView(addrSet, program.viewport.unionedAddresses( - s -> program.trace.getCommentAdapter() - .getAddressSetView(Range.singleton(s), e -> e.getType() == commentType))) - .getAddresses(forward); + return getCommentAddresses(commentType, addrSet).getAddresses(forward); } @Override public AddressIterator getCommentAddressIterator(AddressSetView addrSet, boolean forward) { - // TODO Auto-generated method stub - return null; + return getCommentAddresses(addrSet).getAddresses(forward); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewReferenceManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewReferenceManager.java index 9e55f25e49..583420507f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewReferenceManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewReferenceManager.java @@ -25,6 +25,7 @@ import javax.help.UnsupportedOperationException; import com.google.common.collect.Range; +import generic.NestedIterator; import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; import ghidra.program.model.listing.Variable; @@ -231,13 +232,22 @@ public abstract class AbstractDBTraceProgramViewReferenceManager implements Refe : (r1, r2) -> -r1.getFromAddress().compareTo(r2.getFromAddress()); } + protected Iterator getReferenceIteratorForSnap(long snap, Address startAddr) { + AddressIterator addresses = + refs.getReferenceSources(Range.singleton(snap)).getAddresses(startAddr, true); + return NestedIterator.start(addresses, a -> { + return refs.getReferencesFrom(snap, a).iterator(); + }); + } + @Override public ReferenceIterator getReferenceIterator(Address startAddr) { if (refs(false) == null) { return new ReferenceIteratorAdapter(Collections.emptyIterator()); } + // TODO: This will fail to occlude on equal (src,dst,opIndex) keys return new ReferenceIteratorAdapter( - program.viewport.mergedIterator(s -> refs.getReferencesFrom(s, startAddr).iterator(), + program.viewport.mergedIterator(s -> getReferenceIteratorForSnap(s, startAddr), getReferenceFromComparator(true))); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java index 0079f96350..6658797d2a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java @@ -49,8 +49,7 @@ import ghidra.trace.database.memory.DBTraceMemoryManager; import ghidra.trace.database.symbol.DBTraceReference; import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.database.thread.DBTraceThreadManager; -import ghidra.trace.model.ImmutableTraceAddressSnapRange; -import ghidra.trace.model.TraceAddressSnapRange; +import ghidra.trace.model.*; import ghidra.trace.model.language.TraceGuestLanguage; import ghidra.util.Msg; import ghidra.util.database.DBOpenMode; @@ -76,6 +75,12 @@ public class ToyDBTraceBuilder implements AutoCloseable { this.trace = new DBTrace(name, language.getDefaultCompilerSpec(), this); } + public ToyDBTraceBuilder(Trace trace) { + this.language = trace.getBaseLanguage(); + this.trace = (DBTrace) trace; + trace.addConsumer(this); + } + public Address addr(AddressSpace space, long offset) { return space.getAddress(offset); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/ProgramMergeManagerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/ProgramMergeManagerPlugin.java index 73b89bac4d..f130ba0db2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/ProgramMergeManagerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/ProgramMergeManagerPlugin.java @@ -50,6 +50,7 @@ public class ProgramMergeManagerPlugin extends MergeManagerPlugin implements Pro /** * Constructor for plugin that handles multi-user merge of programs. + * * @param tool the tool with the active program to be merged * @param mergeManager the merge manager that will control the merge process * @param program the current program @@ -61,7 +62,8 @@ public class ProgramMergeManagerPlugin extends MergeManagerPlugin implements Pro @Override public MergeManagerProvider createProvider() { - return new MergeManagerProvider(this, "Merge Programs for " + currentDomainObject.getName()); + return new MergeManagerProvider(this, + "Merge Programs for " + currentDomainObject.getName()); } @Override @@ -82,6 +84,7 @@ public class ProgramMergeManagerPlugin extends MergeManagerPlugin implements Pro /** * Gets the merge manager associated with this plug-in. + * * @return the merge manager */ @Override @@ -91,6 +94,7 @@ public class ProgramMergeManagerPlugin extends MergeManagerPlugin implements Pro /** * Defines and displays a component for resolving merge conflicts. + * * @param component the component * @param componentID the identifier for this component */ @@ -101,6 +105,7 @@ public class ProgramMergeManagerPlugin extends MergeManagerPlugin implements Pro /** * Sets the merge description at the top of the merge tool. + * * @param mergeDescription the new description */ @Override @@ -110,7 +115,9 @@ public class ProgramMergeManagerPlugin extends MergeManagerPlugin implements Pro /** * Sets the message below the progress meter in the current phase progress area. - * @param progressDescription the new text message to display. If null, then the default message is displayed. + * + * @param progressDescription the new text message to display. If null, then the default message + * is displayed. */ @Override void updateProgressDetails(String progressDescription) { @@ -118,7 +125,9 @@ public class ProgramMergeManagerPlugin extends MergeManagerPlugin implements Pro } /** - * Sets the percentage of the progress meter that is filled in for the current phase progress area. + * Sets the percentage of the progress meter that is filled in for the current phase progress + * area. + * * @param currentPercentProgress the percentage of the progress bar to fill in from 0 to 100. */ @Override @@ -135,8 +144,9 @@ public class ProgramMergeManagerPlugin extends MergeManagerPlugin implements Pro } /** - * Enables/disables the Apply button at the bottom of the merge tool. - * The Apply button is for applying conflicts. + * Enables/disables the Apply button at the bottom of the merge tool. The Apply button is for + * applying conflicts. + * * @param state true means enable the button. false means disable it. */ @Override @@ -146,6 +156,7 @@ public class ProgramMergeManagerPlugin extends MergeManagerPlugin implements Pro /** * Gets the provider for the merge. + * * @return the provider */ @Override @@ -153,22 +164,27 @@ public class ProgramMergeManagerPlugin extends MergeManagerPlugin implements Pro return provider; } + @Override public boolean closeOtherPrograms(boolean ignoreChanges) { return false; } + @Override public boolean closeAllPrograms(boolean ignoreChanges) { return false; } + @Override public boolean closeProgram() { return false; } + @Override public boolean closeProgram(Program program, boolean ignoreChanges) { return false; } + @Override public Program[] getAllOpenPrograms() { ProgramMultiUserMergeManager programMergeManager = (ProgramMultiUserMergeManager) mergeManager; @@ -178,10 +194,12 @@ public class ProgramMergeManagerPlugin extends MergeManagerPlugin implements Pro programMergeManager.getProgram(MergeConstants.ORIGINAL) }; } + @Override public Program getCurrentProgram() { return (Program) currentDomainObject; } + @Override public Program getProgram(Address addr) { return null; } @@ -190,6 +208,7 @@ public class ProgramMergeManagerPlugin extends MergeManagerPlugin implements Pro return 0; } + @Override public boolean isVisible(Program program) { return false; } @@ -199,6 +218,7 @@ public class ProgramMergeManagerPlugin extends MergeManagerPlugin implements Pro return null; } + @Override public Program openProgram(DomainFile domainFile) { return null; } @@ -208,29 +228,53 @@ public class ProgramMergeManagerPlugin extends MergeManagerPlugin implements Pro return null; } + @Override public Program openProgram(DomainFile df, int version) { return null; } + @Override public Program openProgram(DomainFile domainFile, int version, int state) { return null; } + @Override public void openProgram(Program program) { } + @Override public void openProgram(Program program, boolean current) { } + @Override public void openProgram(Program program, int state) { } + @Override public void releaseProgram(Program program, Object persistentOwner) { } + @Override + public void saveProgram() { + } + + @Override + public void saveProgram(Program program) { + } + + @Override + public void saveProgramAs() { + } + + @Override + public void saveProgramAs(Program program) { + } + + @Override public void setCurrentProgram(Program p) { } + @Override public boolean setPersistentOwner(Program program, Object owner) { return false; } @@ -238,10 +282,12 @@ public class ProgramMergeManagerPlugin extends MergeManagerPlugin implements Pro public void setSearchPriority(Program p, int priority) { } + @Override public boolean isLocked() { return false; } + @Override public void lockDown(boolean state) { } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java index 81b78ea51b..53a4e79bf3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java @@ -102,8 +102,7 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager { /** * Method called if the plugin supports this domain file. * - * @param data - * the data to be used by the running tool + * @param data the data to be used by the running tool * @return false if data is not a Program object. */ @Override @@ -447,9 +446,9 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager { } /** - * This method notifies listening plugins that a programs has been added to - * the program manager. This is not used for actually opening a program from - * the database and will act strangely if given a closed Program object. + * This method notifies listening plugins that a programs has been added to the program manager. + * This is not used for actually opening a program from the database and will act strangely if + * given a closed Program object. * * @see ghidra.app.services.ProgramManager#openProgram(ghidra.program.model.listing.Program) */ @@ -585,7 +584,7 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager { /** * Set the string chooser property editor on the property that is a filename. * - * @param options property list + * @param options property list * @param filePropertyName name of the property that is a filename */ private void setPropertyEditor(Options options, String filePropertyName) { @@ -597,8 +596,8 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager { } /** - * Start a transaction if one has not been started; needed when program - * properties are about to change from the options editor. + * Start a transaction if one has not been started; needed when program properties are about to + * change from the options editor. */ private void startTransaction(Program currentProgram) { if (transactionID < 0) { @@ -685,6 +684,26 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager { return openProgram; } + @Override + public void saveProgram() { + saveProgram(getCurrentProgram()); + } + + @Override + public void saveProgram(Program program) { + Swing.runIfSwingOrRunLater(() -> programSaveMgr.saveProgram(program)); + } + + @Override + public void saveProgramAs() { + saveProgramAs(getCurrentProgram()); + } + + @Override + public void saveProgramAs(Program program) { + Swing.runIfSwingOrRunLater(() -> programSaveMgr.saveAs(program)); + } + /** * Write out my data state. */ @@ -1040,13 +1059,4 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager { public boolean isManaged(Program program) { return programMgr.contains(program); } - - public void saveProgram(Program program) { - programSaveMgr.saveProgram(program); - } - - public void saveProgramAs(Program program) { - programSaveMgr.saveAs(program); - } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/ProgramManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/ProgramManager.java index c7dea6e54e..1191106483 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/ProgramManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/ProgramManager.java @@ -26,16 +26,17 @@ import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; /** - * Service for managing programs. Multiple programs may be open in a tool, but only one is active - * at any given time. + * Service for managing programs. Multiple programs may be open in a tool, but only one is active at + * any given time. */ -@ServiceInfo(defaultProvider = ProgramManagerPlugin.class, description = "Get the currently open program") +@ServiceInfo( + defaultProvider = ProgramManagerPlugin.class, + description = "Get the currently open program") public interface ProgramManager { /** - * Program will be open in a Hidden state if not already open. - * This mode is generally used in conjunction with a persistent - * program owner. + * Program will be open in a Hidden state if not already open. This mode is generally used in + * conjunction with a persistent program owner. */ public static final int OPEN_HIDDEN = 0; @@ -45,20 +46,21 @@ public interface ProgramManager { public static final int OPEN_CURRENT = 1; /** - * Program will be open within the tool but no change will be made - * to the currently active program. If this is the only program - * open, it will become the currently active program. + * Program will be open within the tool but no change will be made to the currently active + * program. If this is the only program open, it will become the currently active program. */ public static final int OPEN_VISIBLE = 2; /** * Return the program that is currently active. + * * @return may return null if no program is open */ public Program getCurrentProgram(); /** * Returns true if the specified program is open and considered visible to the user. + * * @param program the program * @return true if the specified program is open and considered visible to the user */ @@ -66,47 +68,51 @@ public interface ProgramManager { /** * Closes the currently active program - * @return true if the close is successful. - * false if the close fails or if there is no program currently active. + * + * @return true if the close is successful. false if the close fails or if there is no program + * currently active. */ public boolean closeProgram(); /** - * Open the program corresponding to the given url. + * Open the program corresponding to the given url. + * * @param ghidraURL valid server-based program URL - * @param state initial open state (OPEN_HIDDEN, OPEN_CURRENT, OPEN_VISIBLE). - * The visibility states will be ignored if the program is already open. - * @return null if the user canceled the "open" for the new program or an error - * occurred and was displayed. + * @param state initial open state (OPEN_HIDDEN, OPEN_CURRENT, OPEN_VISIBLE). The visibility + * states will be ignored if the program is already open. + * @return null if the user canceled the "open" for the new program or an error occurred and was + * displayed. * @see GhidraURL */ public Program openProgram(URL ghidraURL, int state); /** - * Open the program for the given domainFile. Once open it will - * become the active program. + * Open the program for the given domainFile. Once open it will become the active program. + * * @param domainFile domain file that has the program * @return null if the user canceled the "open" for the new program */ public Program openProgram(DomainFile domainFile); /** - * Open the program for the given domainFile. Once open it will become the active program. + * Open the program for the given domainFile. Once open it will become the active program. * - *

Note: this method functions exactly as {@link #openProgram(DomainFile)} + *

+ * Note: this method functions exactly as {@link #openProgram(DomainFile)} * * @param domainFile domain file that has the program * @param dialogParent unused * @return the program - * @deprecated deprecated for 10.1; removal for 10.3 or later; use {@link #openProgram(DomainFile)} + * @deprecated deprecated for 10.1; removal for 10.3 or later; use + * {@link #openProgram(DomainFile)} */ @Deprecated public Program openProgram(DomainFile domainFile, Component dialogParent); /** - * Opens the specified version of the program represented by the given DomainFile. This - * method should be used for shared DomainFiles. The newly opened file will be made the - * active program. + * Opens the specified version of the program represented by the given DomainFile. This method + * should be used for shared DomainFiles. The newly opened file will be made the active program. + * * @param df the DomainFile to open * @param version the version of the Program to open * @return the opened program or null if the given version does not exist. @@ -115,29 +121,32 @@ public interface ProgramManager { /** * Open the program for the given domainFile + * * @param domainFile domain file that has the program - * @param version the version of the Program to open. Specify - * DomainFile.DEFAULT_VERSION for file update mode. - * @param state initial open state (OPEN_HIDDEN, OPEN_CURRENT, OPEN_VISIBLE). - * The visibility states will be ignored if the program is already open. - * @return null if the user canceled the "open" for the new program or an error - * occurred and was displayed. + * @param version the version of the Program to open. Specify DomainFile.DEFAULT_VERSION for + * file update mode. + * @param state initial open state (OPEN_HIDDEN, OPEN_CURRENT, OPEN_VISIBLE). The visibility + * states will be ignored if the program is already open. + * @return null if the user canceled the "open" for the new program or an error occurred and was + * displayed. */ public Program openProgram(DomainFile domainFile, int version, int state); /** - * Opens the program to the tool. In this case the program is already open, but this tool - * may not have it registered as open. The program is made the active program. + * Opens the program to the tool. In this case the program is already open, but this tool may + * not have it registered as open. The program is made the active program. + * * @param program the program to register as open with the tool. */ public void openProgram(Program program); /** - * Opens the program to the tool. In this case the program is already open, but this tool - * may not have it registered as open. The program is made the active program. + * Opens the program to the tool. In this case the program is already open, but this tool may + * not have it registered as open. The program is made the active program. + * * @param program the program to register as open with the tool. - * @param current if true, the program is made the current active program. If false, then - * the program is made active only if it the first open program in the tool. + * @param current if true, the program is made the current active program. If false, then the + * program is made active only if it the first open program in the tool. * @deprecated use openProgram(Program program, int state) instead. */ @Deprecated @@ -145,19 +154,45 @@ public interface ProgramManager { /** * Open the specified program in the tool. + * * @param program the program - * @param state initial open state (OPEN_HIDDEN, OPEN_CURRENT, OPEN_VISIBLE). - * The visibility states will be ignored if the program is already open. + * @param state initial open state (OPEN_HIDDEN, OPEN_CURRENT, OPEN_VISIBLE). The visibility + * states will be ignored if the program is already open. */ public void openProgram(Program program, int state); /** - * Establish a persistent owner on an open program. This will cause the program manager to - * imply make a program hidden if it is closed. + * Saves the current program, possibly prompting the user for a new name. + */ + public void saveProgram(); + + /** + * Saves the specified program, possibly prompting the user for a new name. + * + * @param program the program + */ + public void saveProgram(Program program); + + /** + * Prompts the user to save the current program to a selected file. + */ + public void saveProgramAs(); + + /** + * Prompts the user to save the specified program to a selected file. + * + * @param program the program + */ + public void saveProgramAs(Program program); + + /** + * Establish a persistent owner on an open program. This will cause the program manager to imply + * make a program hidden if it is closed. + * * @param program the program * @param owner the owner - * @return true if program is open and another object is not already the owner, - * or the specified owner is already the owner. + * @return true if program is open and another object is not already the owner, or the specified + * owner is already the owner. * @see #releaseProgram(Program, Object) */ public boolean setPersistentOwner(Program program, Object owner); @@ -165,62 +200,67 @@ public interface ProgramManager { /** * Release the persistent ownership of a program. *

- * The program will automatically be closed if it is hidden or was marked as temporary. If - * any of these closures corresponds to a program with changes the user will be given an - * opportunity to save or keep the program open. + * The program will automatically be closed if it is hidden or was marked as temporary. If any + * of these closures corresponds to a program with changes the user will be given an opportunity + * to save or keep the program open. *

* If persistentOwner is not the correct owner, the method will have no affect. + * * @param program the program * @param persistentOwner the owner defined by {@link #setPersistentOwner(Program, Object)} */ public void releaseProgram(Program program, Object persistentOwner); /** - * Closes the given program with the option of saving any changes. The exact behavior of - * this method depends on several factors. First of all, if any other tool has this program - * open, then the program is closed for this tool only and the user is not prompted to - * save the program regardless of the ignoreChanges flag. Otherwise, if ignoreChanges is - * false and changes have been made, the user is prompted to save the program. + * Closes the given program with the option of saving any changes. The exact behavior of this + * method depends on several factors. First of all, if any other tool has this program open, + * then the program is closed for this tool only and the user is not prompted to save the + * program regardless of the ignoreChanges flag. Otherwise, if ignoreChanges is false and + * changes have been made, the user is prompted to save the program. + * * @param program the program to close. * @param ignoreChanges if true, the program is closed without saving any changes. - * @return true if the program was closed. Returns false if the user canceled the close - * while being prompted to save. Also returns false if the program passed in as a parameter - * is null. + * @return true if the program was closed. Returns false if the user canceled the close while + * being prompted to save. Also returns false if the program passed in as a parameter is + * null. */ boolean closeProgram(Program program, boolean ignoreChanges); /** - * Closes all open programs in this tool except the current program. - * If this tool is the only tool with a program open and that program has changes, - * then the user will be prompted to close each such file. - * (Providing the ignoreChanges flag is false) + * Closes all open programs in this tool except the current program. If this tool is the only + * tool with a program open and that program has changes, then the user will be prompted to + * close each such file. (Providing the ignoreChanges flag is false) + * * @param ignoreChanges if true, the programs will be closed without saving changes. * @return true if all other programs were closed. Returns false if the user canceled the close - * while being prompted to save. + * while being prompted to save. */ public boolean closeOtherPrograms(boolean ignoreChanges); /** - * Closes all open programs in this tool. If this tool is the only tool with a program - * open and that program has changes, then the user will be prompted to close each such file. - * (Providing the ignoreChanges flag is false) + * Closes all open programs in this tool. If this tool is the only tool with a program open and + * that program has changes, then the user will be prompted to close each such file. (Providing + * the ignoreChanges flag is false) + * * @param ignoreChanges if true, the programs will be closed without saving changes. - * @return true if all programs were closed. Returns false if the user canceled the close - * while being prompted to save. + * @return true if all programs were closed. Returns false if the user canceled the close while + * being prompted to save. */ public boolean closeAllPrograms(boolean ignoreChanges); /** * Sets the given program to be the current active program in the tool. + * * @param p the program to make active. */ public void setCurrentProgram(Program p); /** * Returns the first program in the list of open programs that contains the given address. - * Programs are searched in the order they were opened within a given priority. - * Program are initially opened with the PRIORITY_NORMAL priority, but can be set to have - * PRIORITY_HIGH or PRIORITY_LOW. + * Programs are searched in the order they were opened within a given priority. Program are + * initially opened with the PRIORITY_NORMAL priority, but can be set to have PRIORITY_HIGH or + * PRIORITY_LOW. + * * @param addr the address for which to search. * @return the first program that can be found to contain the given address. */ @@ -228,13 +268,15 @@ public interface ProgramManager { /** * Returns a list of all open program. + * * @return the programs */ public Program[] getAllOpenPrograms(); /** - * Allows program manager state to be locked/unlocked. While locked, the program manager will + * Allows program manager state to be locked/unlocked. While locked, the program manager will * not support opening additional programs. + * * @param state locked if true, unlocked if false * @deprecated deprecated for 10.1; removal for 10.3 or later */ @@ -243,6 +285,7 @@ public interface ProgramManager { /** * Returns true if program manager is in the locked state + * * @return true if program manager is in the locked state * @deprecated deprecated for 10.1; removal for 10.3 or later */ diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/services/TestDummyProgramManager.java b/Ghidra/Features/Base/src/test/java/ghidra/app/services/TestDummyProgramManager.java index 22c31fe08f..798502615a 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/services/TestDummyProgramManager.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/services/TestDummyProgramManager.java @@ -23,8 +23,8 @@ import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; /** - * A stub of the {@link ProgramManager} interface. This can be used to supply a test program - * manager or to spy on system internals by overriding methods as needed. + * A stub of the {@link ProgramManager} interface. This can be used to supply a test program manager + * or to spy on system internals by overriding methods as needed. */ public class TestDummyProgramManager implements ProgramManager { @@ -91,6 +91,26 @@ public class TestDummyProgramManager implements ProgramManager { // stub } + @Override + public void saveProgram() { + // stub + } + + @Override + public void saveProgram(Program program) { + // stub + } + + @Override + public void saveProgramAs() { + // stub + } + + @Override + public void saveProgramAs(Program program) { + // stub + } + @Override public boolean setPersistentOwner(Program program, Object owner) { // stub diff --git a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffProgramManager.java b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffProgramManager.java index 49221b6816..4c73890b22 100644 --- a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffProgramManager.java +++ b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffProgramManager.java @@ -24,8 +24,8 @@ import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; /** - * A stubbed {@link ProgramManager} that used the 'second program' at the current program. This - * is used to secondary views in order to install the right program. + * A stubbed {@link ProgramManager} that used the 'second program' at the current program. This is + * used to secondary views in order to install the right program. */ public class DiffProgramManager implements ProgramManager { ProgramDiffPlugin programDiffPlugin; @@ -119,6 +119,26 @@ public class DiffProgramManager implements ProgramManager { // stub } + @Override + public void saveProgram() { + // stub + } + + @Override + public void saveProgram(Program program) { + // stub + } + + @Override + public void saveProgramAs() { + // stub + } + + @Override + public void saveProgramAs(Program program) { + // stub + } + @Override public void setCurrentProgram(Program p) { // stub diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssembler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssembler.java index c2b2c32487..ac4cb0c714 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssembler.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssembler.java @@ -49,7 +49,6 @@ public class SleighAssembler implements Assembler { protected Program program; protected Listing listing; protected Memory memory; - protected Disassembler dis; protected AssemblyParser parser; protected AssemblyDefaultContext defaultContext; protected AssemblyContextGraph ctxGraph; @@ -71,8 +70,6 @@ public class SleighAssembler implements Assembler { this.listing = program.getListing(); this.memory = program.getMemory(); - this.dis = Disassembler.getDisassembler(program, TaskMonitor.DUMMY, - DisassemblerMessageListener.IGNORE); } /** @@ -113,8 +110,12 @@ public class SleighAssembler implements Assembler { Address end = at.add(insbytes.length - 1); listing.clearCodeUnits(at, end, false); memory.setBytes(at, insbytes); - dis.disassemble(at, new AddressSet(at)); - return listing.getInstructions(new AddressSet(at, end), true); + AddressSet set = new AddressSet(at, end); + // Creating this at construction causes it to assess memory flags too early. + Disassembler dis = Disassembler.getDisassembler(program, TaskMonitor.DUMMY, + DisassemblerMessageListener.IGNORE); + dis.disassemble(at, set); + return listing.getInstructions(set, true); } @Override diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyConstructorSemantic.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyConstructorSemantic.java index ac2c5e1350..38f4c5be69 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyConstructorSemantic.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyConstructorSemantic.java @@ -46,9 +46,10 @@ public class AssemblyConstructorSemantic implements Comparable indices) { this.cons = cons; @@ -73,6 +74,7 @@ public class AssemblyConstructorSemantic implements Comparable getPatterns() { @@ -92,6 +95,7 @@ public class AssemblyConstructorSemantic implements Comparable getOperandIndices() { @@ -111,8 +116,9 @@ public class AssemblyConstructorSemantic implements Comparable getOperandIndexIterator() { @@ -142,17 +148,17 @@ public class AssemblyConstructorSemantic implements ComparableallInitializedAddrSet and initializedLoadedAddrSet - * with relevant initialized addresses from the specified memory block. If block is not - * a mapped-block and it may be a source to existing mapped-blocks then - * scanAllMappedBlocksIfNeeded should be passed as true unless - * all mapped blocks will be processed separately. + * Update the allInitializedAddrSet and initializedLoadedAddrSet with + * relevant initialized addresses from the specified memory block. If block is not a + * mapped-block and it may be a source to existing mapped-blocks then + * scanAllMappedBlocksIfNeeded should be passed as true unless all + * mapped blocks will be processed separately. + * * @param block memory block - * @param scanAllMappedBlocksIfNeeded if true and block is initialized and not a mapped block all - * mapped blocks will be processed for possible introduction of newly initialized mapped regions. + * @param scanAllMappedBlocksIfNeeded if true and block is initialized and not a mapped block + * all mapped blocks will be processed for possible introduction of newly initialized + * mapped regions. */ private void addBlockAddresses(MemoryBlockDB block, boolean scanAllMappedBlocksIfNeeded) { AddressSet blockSet = new AddressSet(block.getStart(), block.getEnd()); @@ -193,11 +196,12 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { } /** - * Update initialized address set for those mapped blocks which map onto the - * specified block which has just completed a transition of its' initialized state. + * Update initialized address set for those mapped blocks which map onto the specified block + * which has just completed a transition of its' initialized state. + * * @param block block whose initialized state has changed - * @param isInitialized true if block transitioned from uninitialized to initialized, - * else transition is from initialized to uninitialized. + * @param isInitialized true if block transitioned from uninitialized to initialized, else + * transition is from initialized to uninitialized. */ private void updateMappedAddresses(MemoryBlockDB block, boolean isInitialized) { @@ -285,6 +289,7 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { /** * Returns the address factory for the program. + * * @return program address factory */ AddressFactory getAddressFactory() { @@ -293,6 +298,7 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { /** * Returns the AddressMap from the program. + * * @return program address map */ AddressMapDB getAddressMap() { @@ -331,7 +337,7 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { throw new MemoryAccessException(block.getName() + " does not contain range " + start.toString(true) + "-" + endAddr); } - + if (block.isMapped()) { checkMemoryWriteMappedBlock(block, start, endAddr); } @@ -368,7 +374,7 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { mappedEndAddress = byteMappingScheme.getMappedSourceAddress(mappedRangeMinAddr, endOffset); } - + for (MemoryBlockDB b : getBlocks(mappedStartAddress, mappedEndAddress)) { Address minAddr = Address.min(b.getEnd(), mappedEndAddress); Address maxAddr = Address.max(b.getStart(), mappedStartAddress); @@ -381,9 +387,9 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { throws MemoryAccessException { // TODO: could contain uninitialized region which is illegal to write to although block.isInitialized // may not be of much help since it reflects the first sub-block only - seems like mixing is a bad idea - + checkRangeForInstructions(start, endAddr); - + // Check all mapped-block address ranges which map onto the range to be modified Collection mappedBlocks = nonMappedBlock.getMappedBlocks(); if (mappedBlocks != null) { @@ -480,8 +486,9 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { } /** - * Two blocks have been joined producing newBlock. The block which was - * eliminated can be identified using the oldBlockStartAddr. + * Two blocks have been joined producing newBlock. The block which was eliminated can be + * identified using the oldBlockStartAddr. + * * @param newBlock new joined memory block * @param oldBlockStartAddr original start address of affected block */ @@ -798,6 +805,7 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { /** * Check new block name for validity + * * @param name new block name * @throws IllegalArgumentException if invalid block name specified */ @@ -1246,25 +1254,19 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { } /** - * Tests if the memory contains a sequence of contiguous bytes that match the - * given byte array at all bit positions where the mask contains an "on" bit. - * The test will be something like + * Tests if the memory contains a sequence of contiguous bytes that match the given byte array + * at all bit positions where the mask contains an "on" bit. The test will be something like * - * for(int i=0;i= n) { break; } + n -= advanced; try { addr = block.getEnd().addNoWrap(1); } @@ -1879,8 +1882,9 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { } /** - * Tests if the given addressSpace (overlay space) is used by any blocks. If not, it - * removes the space. + * Tests if the given addressSpace (overlay space) is used by any blocks. If not, it removes the + * space. + * * @param addressSpace overlay address space to be removed */ private void checkRemoveAddressSpace(AddressSpace addressSpace) { @@ -1930,8 +1934,8 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { } /** - * Gets the intersected set of addresses between a mapped memory block, and some other - * address set. + * Gets the intersected set of addresses between a mapped memory block, and some other address + * set. * * @param mappedBlock The mapped memory block to use in the intersection. * @param set Some other address set to use in the intersection. @@ -1954,9 +1958,10 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { } /** - * Converts the given address range back from the source range back to the mapped range. - * NOTE: It is important that the specified mappedSourceRange is restricted to the - * mapped source area of the specified mappedBlock. + * Converts the given address range back from the source range back to the mapped range. NOTE: + * It is important that the specified mappedSourceRange is restricted to the mapped source area + * of the specified mappedBlock. + * * @param mappedBlock mapped memory block * @param mappedSourceRange source range which maps into mappedBlock. * @return mapped range or null if source range not mapped to block @@ -2225,9 +2230,10 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { /** * Returns a list of all memory blocks that contain any addresses in the given range + * * @param start the start address * @param end the end address - * @return a list of all memory blocks that contain any addresses in the given range + * @return a list of all memory blocks that contain any addresses in the given range */ List getBlocks(Address start, Address end) { List list = new ArrayList<>();