diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html index dc7ae1517f..256c43690e 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html @@ -96,6 +96,13 @@ selected modules to Ghidra data types in the trace. These are often the same types that Ghidra imports from the static image, anyway.

+

Map Identically

+ +

This action is available when both a trace and a program are opened. It maps the current + trace to the current program using identical addresses. This action ignores the module list. It + is a suitable mapping when the current program is loaded in the trace without + relocation. It is also a fallback worth trying in the absence of a module list.

+

Map Modules

This action is available from the modules' or sections' pop-up menu. It searches the tool's diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html index 33dc843704..cb7711c878 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html @@ -28,9 +28,9 @@ automation, e.g., the Map Modules bot, or by higher-level user actions, e.g., the Map Modules - action. This under-the-hood static mapping window displays the mappings table, allowing users or - developers to diagnose image mapping issues and manually add mappings, regardless of reported - modules and/or sections. For most users, there is no reason to access this window.

+ action. This under-the-hood static mapping window displays the mappings table, allowing users + or developers to diagnose image mapping issues and manually add mappings, regardless of + reported modules and/or sections. For most users, there is no reason to access this window.

Table Columns

diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html index 92e6c1f23b..74dd1b38b1 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html @@ -50,10 +50,10 @@

Navigating Threads

-

Selecting a thread in the table will navigate to (or "activate" or "focus") - that thread. Windows which are sensitive to the current thread will update. Notably, the Registers window - will display the activated thread's register values. Selecting a thread in the table will navigate to (or "activate" or "focus") that thread. + Windows which are sensitive to the current thread will update. Notably, the Registers window will + display the activated thread's register values. Listing windows with configured location tracking will re-compute that location with the thread's context and navigate to it. The thread timeline plots all recorded threads. Threads which are alive will @@ -77,16 +77,17 @@ The state always reflects the present, or latest known, state.

  • Comment - a user-modifiable comment about the thread.
  • - -
  • Plot - a graphical representation of the thread's lifespan. Unlike other column headers, clicking and dragging - in this one will navigate through time. To rearrange this column, hold SHIFT while dragging.
  • + +
  • Plot - a graphical representation of the thread's lifespan. Unlike other column headers, + clicking and dragging in this one will navigate through time. To rearrange this column, hold + SHIFT while dragging.
  • Navigating Time

    The user can navigate through time within the current trace by using the caret in the plot - column header. There are also actions for "stepping the trace" forward and backward. See the - Time window for a way to + column header. There are also actions for "stepping the trace" forward and backward. See the Time window for a way to display and navigate to specific events in the trace's timeline. Note that stepping away from the present will prevent most windows from interacting with the live target. While some components and scripts may interact with the target and record things "into the present," such 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 1a3661221c..a6bf9b40b3 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 @@ -146,6 +146,7 @@ public interface DebuggerResources { //ResourceManager.loadImage("images/capture-memory.png"); // TODO: Draw an icon + ImageIcon ICON_MAP_IDENTICALLY = ResourceManager.loadImage("images/doubleArrow.png"); ImageIcon ICON_MAP_MODULES = ResourceManager.loadImage("images/modules.png"); ImageIcon ICON_MAP_SECTIONS = ICON_MAP_MODULES; // TODO ImageIcon ICON_BLOCK = ICON_MAP_SECTIONS; // TODO @@ -1209,6 +1210,23 @@ public interface DebuggerResources { } } + interface MapIdenticallyAction { + String NAME = "Map Identically"; + String DESCRIPTION = + "Map the current trace to the current program using identical addresses"; + Icon ICON = ICON_MAP_IDENTICALLY; + String GROUP = GROUP_MAPPING; + String HELP_ANCHOR = "map_identically"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName).description(DESCRIPTION) + .toolBarIcon(ICON) + .toolBarGroup(GROUP) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + interface MapModulesAction { String NAME = "Map Modules"; String DESCRIPTION = "Map selected modules to program images"; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java index c647351cd5..1fe24fe85f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java @@ -539,6 +539,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { private Program currentProgram; private ProgramLocation currentLocation; + DockingAction actionMapIdentically; DockingAction actionMapModules; DockingAction actionMapModuleTo; DockingAction actionMapSections; @@ -747,6 +748,10 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { } protected void createActions() { + actionMapIdentically = MapIdenticallyAction.builder(plugin) + .enabledWhen(ctx -> currentProgram != null && currentTrace != null) + .onAction(this::activatedMapIdentically) + .buildAndInstallLocal(this); actionMapModules = MapModulesAction.builder(plugin) .enabledWhen(this::isContextNonEmpty) .popupWhen(this::isContextNonEmpty) @@ -822,6 +827,14 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { return sel.stream().map(TraceSection::getModule).distinct().count() == 1; } + private void activatedMapIdentically(ActionContext ignored) { + if (currentProgram == null || currentTrace == null) { + return; + } + staticMappingService.addIdentityMapping(currentTrace, currentProgram, + Range.atLeast(traceManager.getCurrentSnap()), true); + } + private void activatedMapModules(ActionContext ignored) { Set sel = getSelectedModules(myActionContext); if (sel == null || sel.isEmpty()) { 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 3f695ff998..f217dc8f92 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 @@ -980,6 +980,69 @@ public class DebuggerStaticMappingServicePlugin extends Plugin manager.add(range, fromLifespan, toURL, toAddress.toString(true)); } + static protected AddressRange clippedRange(Trace trace, String spaceName, long min, + long max) { + AddressSpace space = trace.getBaseAddressFactory().getAddressSpace(spaceName); + if (space == null) { + return null; + } + Address spaceMax = space.getMaxAddress(); + if (Long.compareUnsigned(min, spaceMax.getOffset()) > 0) { + return null; + } + if (Long.compareUnsigned(max, spaceMax.getOffset()) > 0) { + return new AddressRangeImpl(space.getAddress(min), spaceMax); + } + return new AddressRangeImpl(space.getAddress(min), space.getAddress(max)); + } + + @Override + public void addIdentityMapping(Trace from, Program toProgram, Range lifespan, + boolean truncateExisting) { + try (UndoableTransaction tid = + UndoableTransaction.start(from, "Add identity mappings", false)) { + doAddIdentityMapping(from, toProgram, lifespan, truncateExisting); + tid.commit(); + } + } + + protected void doAddIdentityMapping(Trace from, Program toProgram, Range lifespan, + boolean truncateExisting) { + Map mins = new HashMap<>(); + Map maxs = new HashMap<>(); + for (AddressRange range : toProgram.getMemory().getAddressRanges()) { + mins.compute(range.getAddressSpace().getName(), (n, min) -> { + Address can = range.getMinAddress(); + if (min == null || can.compareTo(min) < 0) { + return can; + } + return min; + }); + maxs.compute(range.getAddressSpace().getName(), (n, max) -> { + Address can = range.getMaxAddress(); + if (max == null || can.compareTo(max) > 0) { + return can; + } + return max; + }); + } + for (String name : mins.keySet()) { + AddressRange range = clippedRange(from, name, mins.get(name).getOffset(), + maxs.get(name).getOffset()); + if (range == null) { + continue; + } + try { + addMapping(new DefaultTraceLocation(from, null, lifespan, range.getMinAddress()), + new ProgramLocation(toProgram, mins.get(name)), range.getLength(), + truncateExisting); + } + catch (TraceConflictedMappingException e) { + Msg.error(this, "Could not add identity mapping " + range + ": " + e.getMessage()); + } + } + } + @Override public void addModuleMapping(TraceModule from, long length, Program toProgram, boolean truncateExisting) throws TraceConflictedMappingException { 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 d0b0739975..62d15a4fa8 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 @@ -532,7 +532,6 @@ public interface DebuggerStaticMappingService { * Note if the trace is backed by a Ghidra database, the caller must already have started a * transaction on the relevant domain object. * - * * @param from the source trace location, including lifespan * @param to the destination program location * @param length the length of the mapped region, where 0 indicates {@code 1 << 64}. @@ -543,6 +542,18 @@ public interface DebuggerStaticMappingService { void addMapping(TraceLocation from, ProgramLocation to, long length, boolean truncateExisting) throws TraceConflictedMappingException; + /** + * Add a static mapping from the given trace to the given program, using identical addresses + * + * @param from the source trace + * @param toProgram the destination program + * @param lifespan the lifespan of the mapping + * @param truncateExisting true to delete or truncate the lifespan of overlapping entries. If + * false, overlapping entries are omitted. + */ + void addIdentityMapping(Trace from, Program toProgram, Range lifespan, + boolean truncateExisting); + /** * Add a static mapping (relocation) from the given module to the given program * diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderTest.java index 044a266bed..58f7e110de 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderTest.java @@ -327,6 +327,41 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI assertProviderEmpty(); } + @Test + public void testActionMapIdentically() throws Exception { + assertFalse(modulesProvider.actionMapIdentically.isEnabled()); + + createAndOpenTrace(); + createAndOpenProgramFromTrace(); + intoProject(tb.trace); + intoProject(program); + + // No modules necessary + traceManager.activateTrace(tb.trace); + waitForSwing(); + + assertTrue(modulesProvider.actionMapIdentically.isEnabled()); + + // Need some substance in the program + try (UndoableTransaction tid = UndoableTransaction.start(program, "Populate", true)) { + addBlock(); + } + waitForDomainObject(program); + + performAction(modulesProvider.actionMapIdentically); + waitForDomainObject(tb.trace); + + Collection mappings = + tb.trace.getStaticMappingManager().getAllEntries(); + assertEquals(1, mappings.size()); + + TraceStaticMapping sm = mappings.iterator().next(); + assertEquals(Range.atLeast(0L), sm.getLifespan()); + assertEquals("ram:00400000", sm.getStaticAddress()); + assertEquals(0x1000, sm.getLength()); // Block is 0x1000 in length + assertEquals(tb.addr(0x00400000), sm.getMinTraceAddress()); + } + @Test public void testActionMapModules() throws Exception { assertFalse(modulesProvider.actionMapModules.isEnabled());