From 38798da71e0b34e54e15eaaaea49397ced2aab26 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Thu, 22 Jul 2021 13:36:21 -0400 Subject: [PATCH] GP-660: Added "Emulate Program" and "Add Emulated Thread" actions for loading a program into a purely-emulated trace. --- Ghidra/Debug/Debugger/certification.manifest | 1 + .../src/main/help/help/TOC_Source.xml | 4 + .../DebuggerEmulationServicePlugin.html | 44 +++ .../DebuggerStaticMappingPlugin.html | 6 +- .../DebuggerThreadsPlugin.html | 19 +- .../core/debug/gui/DebuggerResources.java | 46 +++ .../gui/register/DebuggerRegistersPlugin.java | 33 +- .../register/DebuggerRegistersProvider.java | 26 +- .../DebuggerEmulationServicePlugin.java | 222 ++++++++++- .../EmulatorOutOfMemoryException.java | 22 ++ .../emulation/ProgramEmulationUtils.java | 354 ++++++++++++++++++ .../ReadsTargetMemoryPcodeExecutorState.java | 48 ++- .../DebuggerModelServiceProxyPlugin.java | 44 ++- .../DebuggerStaticMappingServicePlugin.java | 58 ++- .../modules/DebuggerStaticMappingUtils.java | 141 +++++++ .../DebuggerStaticMappingService.java | 22 +- .../DebuggerBreakpointsPluginScreenShots.java | 5 +- .../stack/DebuggerStackPluginScreenShots.java | 3 +- .../DebuggerBreakpointMarkerPluginTest.java | 3 +- .../DebuggerBreakpointsProviderTest.java | 3 +- .../listing/DebuggerListingProviderTest.java | 11 +- .../gui/stack/DebuggerStackProviderTest.java | 3 +- .../DebuggerLogicalBreakpointServiceTest.java | 7 +- .../DebuggerEmulationServiceTest.java | 98 +++++ .../DebuggerStaticMappingServiceTest.java | 6 +- .../java/ghidra/trace/database/DBTrace.java | 9 +- 26 files changed, 1069 insertions(+), 169 deletions(-) create mode 100644 Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerEmulationServicePlugin/DebuggerEmulationServicePlugin.html create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/EmulatorOutOfMemoryException.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingUtils.java create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java diff --git a/Ghidra/Debug/Debugger/certification.manifest b/Ghidra/Debug/Debugger/certification.manifest index e0ef58ee77..d07f0be24b 100644 --- a/Ghidra/Debug/Debugger/certification.manifest +++ b/Ghidra/Debug/Debugger/certification.manifest @@ -41,6 +41,7 @@ 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/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| src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerGoToDialog.png||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 d21c44c30c..9019d4a84a 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml @@ -94,6 +94,10 @@ sortgroup="g" target="help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html" /> + + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerEmulationServicePlugin/DebuggerEmulationServicePlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerEmulationServicePlugin/DebuggerEmulationServicePlugin.html new file mode 100644 index 0000000000..e3f948553a --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerEmulationServicePlugin/DebuggerEmulationServicePlugin.html @@ -0,0 +1,44 @@ + + + + + + + Debugger: Model Service + + + + + +

Debugger: Emulation Service

+ +

This service plugin provides emulation to the Trace + Manager and provides actions for launching emulated traces from programs, i.e., without + requiring a connected debugger. Please note that "pure emulation" of a target program, while it + doesn't require a platform to execute the target natively, it typically does require + significant state initialization and dependency stubbing, except in limited circumstances.

+ +

Actions

+ +

Emulate Program

+ +

This action is available whenever a program is active. It will create a new trace suitable + for "pure emulation" of the current program starting at the current address. More precisely, it + will open a new blank trace, initializes it with the current program's memory map, allocates a + stack, and creates a thread whose program counter is initialized to the current address and + whose registers are initialized to the register context at the current address. Optionally, any + other initialization can be done by manually modifying the trace in the UI, or using a script. + The trace and/or p-code stepping actions can then be used to emulate.

+ +

Add Emulated Thread

+ +

This action is available whenever a "pure emulation" trace is active. It spawns a new thread + in the current trace suitable for emulating starting at the current address. More precisely, it + allocates another stack and creates a new thread whose program counter is initialized to the + current address and whose registers are initialized to the register context at the current + address. Optionally, other registers can be initialized via the UI or a script. The new thread + is activated so that stepping actions will affect it by default.

+ + 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..3939a016d7 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 @@ -169,6 +169,8 @@ public interface DebuggerResources { ImageIcon ICON_BLANK = ResourceManager.loadImage("images/blank.png"); ImageIcon ICON_PACKAGE = ResourceManager.loadImage("images/debugger32.png"); + ImageIcon ICON_EMULATE = ICON_PROCESS; // TODO + HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package"); String HELP_ANCHOR_PLUGIN = "plugin"; @@ -489,6 +491,50 @@ public interface DebuggerResources { } } + interface EmulateProgramAction { + String NAME = "Emulate Program in new Trace"; + String DESCRIPTION = "Emulate the current program in a new trace starting at the cursor"; + Icon ICON = ICON_EMULATE; + String GROUP = GROUP_GENERAL; + String HELP_ANCHOR = "emulate_program"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .toolBarIcon(ICON) + .toolBarGroup(GROUP) + .menuPath(DebuggerPluginPackage.NAME, NAME) + .menuIcon(ICON) + .menuGroup(GROUP) + .popupMenuPath(NAME) + .popupMenuIcon(ICON) + .popupMenuGroup(GROUP) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface EmulateAddThreadAction { + String NAME = "Add Emulated Thread to Trace"; + String DESCRIPTION = "Add an emulated thread to the current trace starting here"; + Icon ICON = ICON_THREAD; + String GROUP = GROUP_GENERAL; + String HELP_ANCHOR = "add_emulated_thread"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuPath(DebuggerPluginPackage.NAME, NAME) + .menuIcon(ICON) + .menuGroup(GROUP) + .popupMenuPath(NAME) + .popupMenuIcon(ICON) + .popupMenuGroup(GROUP) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + abstract class AbstractQuickLaunchAction extends DockingAction { public static final String NAME = "Quick Launch"; public static final Icon ICON = ICON_DEBUGGER; // TODO: A different icon? diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersPlugin.java index cf52e441f7..6a5166d39c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersPlugin.java @@ -59,8 +59,10 @@ public class DebuggerRegistersPlugin extends AbstractDebuggerPlugin { protected DebuggerRegistersProvider connectedProvider; - private final Map> selectionByCSpec = new HashMap<>(); - private final Map> favoritesByCSpec = new HashMap<>(); + private final Map> selectionByCSpec = + new HashMap<>(); + private final Map> favoritesByCSpec = + new HashMap<>(); private final Set disconnectedProviders = new HashSet<>(); public DebuggerRegistersPlugin(PluginTool tool) { @@ -119,13 +121,13 @@ public class DebuggerRegistersPlugin extends AbstractDebuggerPlugin { } } - public static String encodeSetsByCSpec(Map> setsByCSpec) { + public static String encodeSetsByCSpec( + Map> setsByCSpec) { return StringUtils.join(setsByCSpec.entrySet().stream().map(ent -> { - CompilerSpec cspec = ent.getKey(); + LanguageCompilerSpecPair lcsp = ent.getKey(); String regs = StringUtils.join( ent.getValue().stream().map(Register::getName).collect(Collectors.toList()), ','); - return cspec.getLanguage().getLanguageID() + "/" + cspec.getCompilerSpecID() + ":" + - regs; + return lcsp.languageID + "/" + lcsp.compilerSpecID + ":" + regs; }).collect(Collectors.toList()), ';'); } @@ -138,8 +140,8 @@ public class DebuggerRegistersPlugin extends AbstractDebuggerPlugin { saveState.putString(KEY_FAVORITES_BY_CSPEC, favoritesByCSpecString); } - public static void readSetsByCSpec(Map> setsByCSpec, - String encoded) { + public static void readSetsByCSpec( + Map> setsByCSpec, String encoded) { LanguageService langServ = DefaultLanguageService.getLanguageService(); if (encoded.length() == 0) { return; @@ -160,24 +162,17 @@ public class DebuggerRegistersPlugin extends AbstractDebuggerPlugin { "Bad lang-spec key: " + langCsPart + ". Ignoring."); continue; } + LanguageID lid = new LanguageID(langCsParts[0]); Language lang; try { - lang = langServ.getLanguage(new LanguageID(langCsParts[0])); + lang = langServ.getLanguage(lid); } catch (LanguageNotFoundException e) { Msg.warn(DebuggerRegistersPlugin.class, "Language " + langCsParts[0] + " does not exist. Ignoring."); continue; } - CompilerSpec cSpec; - try { - cSpec = lang.getCompilerSpecByID(new CompilerSpecID(langCsParts[1])); - } - catch (CompilerSpecNotFoundException e) { - Msg.warn(DebuggerRegistersPlugin.class, - "CompilerSpec " + langCsParts[1] + " does not exist. Ignoring."); - continue; - } + CompilerSpecID csid = new CompilerSpecID(langCsParts[1]); LinkedHashSet regs = new LinkedHashSet<>(); for (String regName : regsPart.split(",")) { @@ -190,7 +185,7 @@ public class DebuggerRegistersPlugin extends AbstractDebuggerPlugin { regs.add(register); } - setsByCSpec.put(cSpec, regs); + setsByCSpec.put(new LanguageCompilerSpecPair(lid, csid), regs); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java index 06aa6b86fc..5f5eb75a21 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java @@ -369,8 +369,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter } final DebuggerRegistersPlugin plugin; - private final Map> selectionByCSpec; - private final Map> favoritesByCSpec; + private final Map> selectionByCSpec; + private final Map> favoritesByCSpec; private final boolean isClone; DebuggerCoordinates previous = DebuggerCoordinates.NOWHERE; @@ -436,8 +436,9 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter AddressSetView viewKnown; protected DebuggerRegistersProvider(final DebuggerRegistersPlugin plugin, - Map> selectionByCSpec, - Map> favoritesByCSpec, boolean isClone) { + Map> selectionByCSpec, + Map> favoritesByCSpec, + boolean isClone) { super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_REGISTERS, plugin.getName()); this.plugin = plugin; this.selectionByCSpec = selectionByCSpec; @@ -1026,18 +1027,27 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter return result; } + protected LanguageCompilerSpecPair getLangCSpecPair(Trace trace) { + return new LanguageCompilerSpecPair(trace.getBaseLanguage().getLanguageID(), + trace.getBaseCompilerSpec().getCompilerSpecID()); + } + + protected LanguageCompilerSpecPair getLangCSpecPair(TraceThread thread) { + return getLangCSpecPair(thread.getTrace()); + } + protected Set getSelectionFor(TraceThread thread) { synchronized (selectionByCSpec) { - CompilerSpec cSpec = thread.getTrace().getBaseCompilerSpec(); - return selectionByCSpec.computeIfAbsent(cSpec, + LanguageCompilerSpecPair lcsp = getLangCSpecPair(thread); + return selectionByCSpec.computeIfAbsent(lcsp, __ -> computeDefaultRegisterSelection(thread)); } } protected Set getFavoritesFor(TraceThread thread) { synchronized (favoritesByCSpec) { - CompilerSpec cSpec = thread.getTrace().getBaseCompilerSpec(); - return favoritesByCSpec.computeIfAbsent(cSpec, + LanguageCompilerSpecPair lcsp = getLangCSpecPair(thread); + return favoritesByCSpec.computeIfAbsent(lcsp, __ -> computeDefaultRegisterFavorites(thread)); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java index 99474cc8ff..d5d530818d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java @@ -15,23 +15,40 @@ */ package ghidra.app.plugin.core.debug.service.emulation; +import java.io.IOException; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import org.apache.commons.lang3.exception.ExceptionUtils; +import com.google.common.collect.Range; + +import docking.action.DockingAction; +import ghidra.app.context.ProgramLocationActionContext; +import ghidra.app.events.ProgramActivatedPluginEvent; +import ghidra.app.events.ProgramClosedPluginEvent; import ghidra.app.plugin.PluginCategoryNames; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.EmulateAddThreadAction; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.EmulateProgramAction; import ghidra.app.services.*; import ghidra.async.AsyncLazyMap; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginStatus; -import ghidra.trace.model.Trace; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.trace.model.*; +import ghidra.trace.model.program.TraceProgramView; +import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSchedule; import ghidra.trace.model.time.TraceSchedule.CompareResult; import ghidra.trace.model.time.TraceSnapshot; +import ghidra.util.Msg; import ghidra.util.database.UndoableTransaction; import ghidra.util.exception.CancelledException; import ghidra.util.task.Task; @@ -44,10 +61,13 @@ import ghidra.util.task.TaskMonitor; packageName = DebuggerPluginPackage.NAME, status = PluginStatus.UNSTABLE, eventsConsumed = { - TraceClosedPluginEvent.class + TraceClosedPluginEvent.class, + ProgramActivatedPluginEvent.class, + ProgramClosedPluginEvent.class, }, servicesRequired = { - DebuggerTraceManagerService.class + DebuggerTraceManagerService.class, + DebuggerStaticMappingService.class }, servicesProvided = { DebuggerEmulationService.class @@ -153,23 +173,166 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm private DebuggerTraceManagerService traceManager; @AutoServiceConsumed private DebuggerModelService modelService; + @AutoServiceConsumed + private DebuggerStaticMappingService staticMappings; @SuppressWarnings("unused") private AutoService.Wiring autoServiceWiring; + DockingAction actionEmulateProgram; + DockingAction actionEmulateAddThread; + public DebuggerEmulationServicePlugin(PluginTool tool) { super(tool); autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this); } + @Override + protected void init() { + super.init(); + createActions(); + } + + protected void createActions() { + actionEmulateProgram = EmulateProgramAction.builder(this) + .withContext(ProgramLocationActionContext.class) + .enabledWhen(this::emulateProgramEnabled) + .popupWhen(this::emulateProgramEnabled) + .onAction(this::emulateProgramActivated) + .buildAndInstall(tool); + actionEmulateAddThread = EmulateAddThreadAction.builder(this) + .withContext(ProgramLocationActionContext.class) + .enabledWhen(this::emulateAddThreadEnabled) + .popupWhen(this::emulateAddThreadEnabled) + .onAction(this::emulateAddThreadActivated) + .buildAndInstall(tool); + } + + private boolean emulateProgramEnabled(ProgramLocationActionContext ctx) { + Program program = ctx.getProgram(); + // To avoid confusion of "forked from trace," only permit action from static context + if (program == null || program instanceof TraceProgramView) { + return false; + } + /*MemoryBlock block = program.getMemory().getBlock(ctx.getAddress()); + if (!block.isExecute()) { + return false; + }*/ + return true; + } + + private void emulateProgramActivated(ProgramLocationActionContext ctx) { + Program program = ctx.getProgram(); + if (program == null) { + return; + } + Trace trace = null; + try { + trace = ProgramEmulationUtils.launchEmulationTrace(program, ctx.getAddress(), this); + + traceManager.openTrace(trace); + traceManager.activateTrace(trace); + } + catch (IOException e) { + Msg.showError(this, null, actionEmulateProgram.getDescription(), + "Could not create trace for emulation", e); + } + finally { + if (trace != null) { + trace.release(this); + } + } + } + + private boolean emulateAddThreadEnabled(ProgramLocationActionContext ctx) { + Program programOrView = ctx.getProgram(); + if (programOrView instanceof TraceProgramView) { + TraceProgramView view = (TraceProgramView) programOrView; + if (!ProgramEmulationUtils.isEmulatedProgram(view.getTrace())) { + return false; + } + /*MemoryBlock block = view.getMemory().getBlock(ctx.getAddress()); + return block.isExecute();*/ + return true; + } + + // Action was probably activated in a static listing. + // Bail if current trace is not emulated. Otherwise map and check region. + DebuggerCoordinates current = traceManager.getCurrent(); + if (current.getTrace() == null || + !ProgramEmulationUtils.isEmulatedProgram(current.getTrace())) { + return false; + } + TraceLocation traceLoc = staticMappings.getOpenMappedLocation( + current.getTrace(), ctx.getLocation(), current.getSnap()); + if (traceLoc == null) { + return false; + } + /*TraceMemoryRegion region = current.getTrace() + .getMemoryManager() + .getRegionContaining(current.getSnap(), traceLoc.getAddress()); + return region != null && region.isExecute()*/; + return true; + } + + private void emulateAddThreadActivated(ProgramLocationActionContext ctx) { + + Program programOrView = ctx.getProgram(); + if (programOrView instanceof TraceProgramView) { + TraceProgramView view = (TraceProgramView) programOrView; + Trace trace = view.getTrace(); + Address tracePc = ctx.getAddress(); + + /*MemoryBlock block = view.getMemory().getBlock(tracePc); + if (!block.isExecute()) { + return; + }*/ + ProgramLocation progLoc = + staticMappings.getOpenMappedLocation(new DefaultTraceLocation(view.getTrace(), null, + Range.singleton(view.getSnap()), tracePc)); + Program program = progLoc == null ? null : progLoc.getProgram(); + Address programPc = progLoc == null ? null : progLoc.getAddress(); + + long snap = + view.getViewport().getOrderedSnaps().stream().filter(s -> s >= 0).findFirst().get(); + TraceThread thread = ProgramEmulationUtils.launchEmulationThread(trace, snap, program, + tracePc, programPc); + traceManager.activateThread(thread); + } + else { + Program program = programOrView; + Address programPc = ctx.getAddress(); + + DebuggerCoordinates current = traceManager.getCurrent(); + long snap = current.getSnap(); + Trace trace = current.getTrace(); + TraceLocation traceLoc = + staticMappings.getOpenMappedLocation(trace, ctx.getLocation(), snap); + if (traceLoc == null) { + return; + } + Address tracePc = traceLoc.getAddress(); + /*TraceMemoryRegion region = + trace.getMemoryManager().getRegionContaining(snap, tracePc); + if (region == null || !region.isExecute()) { + return; + }*/ + TraceThread thread = ProgramEmulationUtils.launchEmulationThread(trace, snap, program, + tracePc, programPc); + traceManager.activateThread(thread); + } + } + protected Map.Entry findNearestPrefix(CacheKey key) { - Map.Entry candidate = cache.floorEntry(key); - if (candidate == null) { - return null; + synchronized (cache) { + Map.Entry candidate = cache.floorEntry(key); + if (candidate == null) { + return null; + } + if (!candidate.getKey().compareKey(key).related) { + return null; + } + return candidate; } - if (!candidate.getKey().compareKey(key).related) { - return null; - } - return candidate; } protected CompletableFuture doBackgroundEmulate(CacheKey key) { @@ -220,8 +383,10 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm if (ancestor != null) { CacheKey prevKey = ancestor.getKey(); - cache.remove(prevKey); - eldest.remove(prevKey); + synchronized (cache) { + cache.remove(prevKey); + eldest.remove(prevKey); + } // TODO: Handle errors, and add to proper place in cache? // TODO: Finish partially-executed instructions? @@ -243,14 +408,15 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm emu.writeDown(trace, destSnap.getKey(), time.getSnap(), false); } - cache.put(key, ce); - eldest.add(key); - - assert cache.size() == eldest.size(); - while (cache.size() > MAX_CACHE_SIZE) { - CacheKey expired = eldest.iterator().next(); - eldest.remove(expired); - cache.remove(expired); + synchronized (cache) { + cache.put(key, ce); + eldest.add(key); + assert cache.size() == eldest.size(); + while (cache.size() > MAX_CACHE_SIZE) { + CacheKey expired = eldest.iterator().next(); + eldest.remove(expired); + cache.remove(expired); + } } return destSnap.getKey(); @@ -284,4 +450,20 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm private void setModelService(DebuggerModelService modelService) { cache.clear(); } + + @Override + public void processEvent(PluginEvent event) { + super.processEvent(event); + if (event instanceof TraceClosedPluginEvent) { + TraceClosedPluginEvent evt = (TraceClosedPluginEvent) event; + synchronized (cache) { + List toRemove = eldest.stream() + .filter(k -> k.trace == evt.getTrace()) + .collect(Collectors.toList()); + cache.keySet().removeAll(toRemove); + eldest.removeAll(toRemove); + assert cache.size() == eldest.size(); + } + } + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/EmulatorOutOfMemoryException.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/EmulatorOutOfMemoryException.java new file mode 100644 index 0000000000..a15262d5a8 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/EmulatorOutOfMemoryException.java @@ -0,0 +1,22 @@ +/* ### + * 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.service.emulation; + +/** + * Some emulator-related operation was unable to locate a suitable address in the trace's memory map + */ +public class EmulatorOutOfMemoryException extends RuntimeException { +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java new file mode 100644 index 0000000000..b0719d43cb --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java @@ -0,0 +1,354 @@ +/* ### + * 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.service.emulation; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.EnumSet; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.google.common.collect.Range; + +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; +import ghidra.app.services.DebuggerEmulationService; +import ghidra.framework.model.DomainFile; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.Program; +import ghidra.program.model.listing.ProgramContext; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.util.ProgramLocation; +import ghidra.trace.database.DBTrace; +import ghidra.trace.model.DefaultTraceLocation; +import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.*; +import ghidra.trace.model.modules.TraceConflictedMappingException; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.thread.TraceThreadManager; +import ghidra.trace.model.time.TraceSnapshot; +import ghidra.util.ComparatorMath; +import ghidra.util.DifferenceAddressSetView; +import ghidra.util.database.UndoableTransaction; +import ghidra.util.exception.DuplicateNameException; + +/** + * A set of utilities for emulating programs without necessarily having a debugger connection. + * + *

    + * Most of these are already integrated via the {@link DebuggerEmulationService}. Please see if that + * service satisfies your use case before employing these directly. + */ +public enum ProgramEmulationUtils { + ; + + /** + * Conventional prefix for first snapshot to identify "pure emulation" traces. + */ + public static final String EMULATION_STARTED_AT = "Emulation started at "; + + /** + * Suggests a name for a new trace for emulation of the given program + * + * @param program the program to emulate + * @return the suggested name + */ + public static String getTraceName(Program program) { + DomainFile df = program.getDomainFile(); + if (df != null) { + return "Emulate " + df.getName(); + } + return "Emulate " + program.getName(); + } + + /** + * Suggests the initial module name for loading a program into an emulated trace + * + * @param program the program comprising the module to "load" + * @return the suggested module name + */ + public static String getModuleName(Program program) { + String executablePath = program.getExecutablePath(); + if (executablePath != null) { + return executablePath; + } + DomainFile df = program.getDomainFile(); + if (df != null) { + return df.getName(); + } + return program.getName(); + } + + /** + * Convert permissions for a program memory block into flags for a trace memory region + * + * @param block the block whose permissions to convert + * @return the corresponding set of flags + */ + public static Set getRegionFlags(MemoryBlock block) { + Set result = EnumSet.noneOf(TraceMemoryFlag.class); + int mask = block.getPermissions(); + if ((mask & MemoryBlock.READ) != 0) { + result.add(TraceMemoryFlag.READ); + } + if ((mask & MemoryBlock.WRITE) != 0) { + result.add(TraceMemoryFlag.WRITE); + } + if ((mask & MemoryBlock.EXECUTE) != 0) { + result.add(TraceMemoryFlag.EXECUTE); + } + if ((mask & MemoryBlock.VOLATILE) != 0) { + result.add(TraceMemoryFlag.VOLATILE); + } + return result; + } + + /** + * Create regions for each block in a program, without relocation, and map the program in + * + *

    + * This creates a region for each loaded, non-overlay block in the program. Permissions/flags + * are assigned accordingly. A single static mapping is generated to cover the entire range of + * created regions. Note that no bytes are copied in, as that could be prohibitive for large + * programs. Instead, the emulator should load them, based on the static mapping, as needed. + * + *

    + * A transaction must already be started on the destination trace. + * + * @param snapshot the destination snapshot, usually 0 + * @param program the progam to load + */ + public static void loadExecutable(TraceSnapshot snapshot, Program program) { + Trace trace = snapshot.getTrace(); + Address min = null; + Address max = null; + try { + for (MemoryBlock block : program.getMemory().getBlocks()) { + if (!block.isLoaded()) { + continue; + } + if (block.isOverlay()) { + continue; + } + AddressRange range = new AddressRangeImpl(block.getStart(), block.getEnd()); + min = min == null ? range.getMinAddress() + : ComparatorMath.cmin(min, range.getMinAddress()); + max = max == null ? range.getMaxAddress() + : ComparatorMath.cmax(max, range.getMaxAddress()); + String modName = getModuleName(program); + + // TODO: Do I populate modules, since the mapping will already be done? + trace.getMemoryManager() + .createRegion( + "Modules[" + modName + "].Sections[" + block.getName() + "]", + snapshot.getKey(), range, getRegionFlags(block)); + } + DebuggerStaticMappingUtils.addMapping( + new DefaultTraceLocation(trace, null, Range.atLeast(snapshot.getKey()), min), + new ProgramLocation(program, min), + max.subtract(min), false); + } + catch (TraceOverlappedRegionException | DuplicateNameException + | TraceConflictedMappingException e) { + throw new AssertionError(e); + } + // N.B. Bytes will be loaded lazily + } + + /** + * Spawn a new thread in the given trace at the given creation snap + * + *

    + * This does not initialize the thread's state. It simply creates it. + * + * @param trace the trace to contain the new thread + * @param snap the creation shap of the new thread + * @return the new thread + */ + public static TraceThread spawnThread(Trace trace, long snap) { + TraceThreadManager tm = trace.getThreadManager(); + long next = tm.getAllThreads().size(); + while (!tm.getThreadsByPath("Threads[" + next + "]").isEmpty()) { + next++; + } + try { + return tm.createThread("Threads[" + next + "]", "[" + next + "]", snap); + } + catch (DuplicateNameException e) { + throw new AssertionError(e); + } + } + + /** + * Initialize a thread's registers using program context and an optional stack + * + * @param trace the trace containing the thread + * @param snap the destination snap for the register state + * @param thread the thread whose registers to initialize + * @param program the program whose context to use + * @param tracePc the program counter in the trace's memory map + * @param programPc the program counter in the program's memory map + * @param stack optionally, the region representing the thread's stack + */ + public static void initializeRegisters(Trace trace, long snap, TraceThread thread, + Program program, Address tracePc, Address programPc, TraceMemoryRegion stack) { + TraceMemoryRegisterSpace space = + trace.getMemoryManager().getMemoryRegisterSpace(thread, true); + if (program != null) { + ProgramContext ctx = program.getProgramContext(); + for (Register reg : Stream.of(ctx.getRegistersWithValues()) + .map(Register::getBaseRegister) + .collect(Collectors.toSet())) { + RegisterValue rv = ctx.getRegisterValue(reg, programPc); + if (!rv.hasAnyValue()) { + continue; + } + // Set all the mask bits + space.setValue(snap, new RegisterValue(reg, BigInteger.ZERO).combineValues(rv)); + } + } + space.setValue(snap, new RegisterValue(trace.getBaseLanguage().getProgramCounter(), + tracePc.getOffsetAsBigInteger())); + if (stack != null) { + CompilerSpec cSpec = trace.getBaseCompilerSpec(); + Address sp = cSpec.stackGrowsNegative() + ? stack.getMaxAddress() + : stack.getMinAddress(); + space.setValue(snap, + new RegisterValue(cSpec.getStackPointer(), sp.getOffsetAsBigInteger())); + } + } + + /** + * Attempt to allocate a new stack region for the given thread + * + * @param trace the trace containing the stack and thread + * @param snap the creation snap for the new region + * @param thread the thread for which the stack is being allocated + * @param size the desired size of the region + * @return the new region representing the allocated stack + * + * @throws EmulatorOutOfMemoryException if the stack cannot be allocated + */ + public static TraceMemoryRegion allocateStack(Trace trace, long snap, TraceThread thread, + long size) { + AddressSpace space = trace.getBaseCompilerSpec().getStackBaseSpace(); + AddressSet except0 = new AddressSet(space.getAddress(0x1000), space.getMaxAddress()); + TraceMemoryManager mm = trace.getMemoryManager(); + AddressSetView left = + new DifferenceAddressSetView(except0, mm.getRegionsAddressSet(snap)); + try { + for (AddressRange candidate : left) { + if (Long.compareUnsigned(candidate.getLength(), size) > 0) { + AddressRange alloc = new AddressRangeImpl(candidate.getMinAddress(), size); + return mm.createRegion(thread.getPath() + ".Stack", snap, alloc, + TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); + } + } + } + catch (AddressOverflowException | TraceOverlappedRegionException + | DuplicateNameException e) { + throw new AssertionError(e); + } + throw new EmulatorOutOfMemoryException(); + } + + /** + * Create a new trace with a single thread, ready for emulation of the given program + * + * @param program the program to emulate + * @param pc the initial program counter for the new single thread + * @param consumer the consumer of the new trace + * @return the new trace + * @throws IOException if the trace cannot be created + */ + public static Trace launchEmulationTrace(Program program, Address pc, Object consumer) + throws IOException { + Trace trace = null; + boolean success = false; + try { + trace = new DBTrace(getTraceName(program), program.getCompilerSpec(), consumer); + try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate", false)) { + TraceSnapshot initial = + trace.getTimeManager().createSnapshot(EMULATION_STARTED_AT + pc); + long snap = initial.getKey(); + loadExecutable(initial, program); + doLaunchEmulationThread(trace, snap, program, pc, pc); + tid.commit(); + } + success = true; + return trace; + } + catch (LanguageNotFoundException e) { + throw new AssertionError(e); + } + finally { + if (!success && trace != null) { + trace.release(consumer); + } + } + } + + /** + * Create a new emulated thread within an existing trace + * + * @param trace the trace to contain the new thread + * @param snap the creation snap for the new thread + * @param program the program whose context to use for initial register values + * @param tracePc the program counter in the trace's memory map + * @param programPc the program counter in the program's memory map + * @return the new thread + */ + public static TraceThread doLaunchEmulationThread(Trace trace, long snap, Program program, + Address tracePc, Address programPc) { + TraceThread thread = spawnThread(trace, snap); + TraceMemoryRegion stack = allocateStack(trace, snap, thread, 0x4000); + initializeRegisters(trace, snap, thread, program, tracePc, programPc, stack); + return thread; + } + + /** + * Same as {@link #doLaunchEmulationThread(Trace, long, Program, Address, Address)}, but within + * a transaction + */ + public static TraceThread launchEmulationThread(Trace trace, long snap, Program program, + Address tracePc, Address programPc) { + try (UndoableTransaction tid = + UndoableTransaction.start(trace, "Emulate new Thread", false)) { + TraceThread thread = doLaunchEmulationThread(trace, snap, program, tracePc, programPc); + tid.commit(); + return thread; + } + } + + /** + * Check if the given trace is for "pure emulation" + * + * @param trace the trace to check + * @return true if created for emulation, false otherwise + */ + public static boolean isEmulatedProgram(Trace trace) { + TraceSnapshot snapshot = trace.getTimeManager().getSnapshot(0, false); + if (snapshot == null) { + return false; + } + if (!snapshot.getDescription().startsWith(EMULATION_STARTED_AT)) { + return false; + } + return true; + } +} 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 808afb8bb8..21b139e0e7 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 @@ -44,30 +44,37 @@ public class ReadsTargetMemoryPcodeExecutorState @Override protected void fillUninitialized(AddressSet uninitialized) { - // TODO: fillUnknownWithStaticImages? - if (!isLive()) { - return; - } - AddressSet unknown = computeUnknown(uninitialized); - if (unknown.isEmpty()) { - return; - } - fillUnknownWithRecorder(unknown); + AddressSet unknown; unknown = computeUnknown(uninitialized); if (unknown.isEmpty()) { return; } + if (fillUnknownWithRecorder(unknown)) { + unknown = computeUnknown(uninitialized); + if (unknown.isEmpty()) { + return; + } + } + if (fillUnknownWithStaticImages(unknown)) { + unknown = computeUnknown(uninitialized); + if (unknown.isEmpty()) { + return; + } + } Msg.warn(this, "Emulator read from UNKNOWN state: " + unknown); } - protected void fillUnknownWithRecorder(AddressSet unknown) { + protected boolean fillUnknownWithRecorder(AddressSet unknown) { + if (!isLive()) { + return false; + } waitTimeout(recorder.captureProcessMemory(unknown, TaskMonitor.DUMMY)); + return true; } - private void fillUnknownWithStaticImages(AddressSet unknown) { - if (!space.isMemorySpace()) { - return; - } + private boolean fillUnknownWithStaticImages(AddressSet unknown) { + boolean result = false; + // TODO: Expand to block? DON'T OVERWRITE KNOWN! DebuggerStaticMappingService mappingService = tool.getService(DebuggerStaticMappingService.class); byte[] data = new byte[4096]; @@ -76,12 +83,15 @@ public class ReadsTargetMemoryPcodeExecutorState .entrySet()) { Program program = ent.getKey(); ShiftAndAddressSetView shifted = ent.getValue(); - Msg.warn(this, - "Filling in unknown trace memory in emulator using mapped image: " + - program + ": " + shifted.getAddressSetView()); long shift = shifted.getShift(); Memory memory = program.getMemory(); - for (AddressRange rng : shifted.getAddressSetView()) { + 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) { @@ -101,8 +111,10 @@ public class ReadsTargetMemoryPcodeExecutorState lower += len; fullLen -= len; } + result = true; } } + return result; } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java index 851c91e95d..29678510b0 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java @@ -63,22 +63,22 @@ import ghidra.util.datastruct.ListenerSet; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; -@PluginInfo( // - shortDescription = "Debugger models manager service (proxy to front-end)", // - description = "Manage debug sessions, connections, and trace recording", // - category = PluginCategoryNames.DEBUGGER, // - packageName = DebuggerPluginPackage.NAME, // - status = PluginStatus.RELEASED, // - eventsConsumed = { ProgramActivatedPluginEvent.class, // - ProgramClosedPluginEvent.class, // - }, // - servicesRequired = { // - DebuggerTraceManagerService.class, // - }, // - servicesProvided = { // - DebuggerModelService.class, // - } // -) +@PluginInfo( + shortDescription = "Debugger models manager service (proxy to front-end)", + description = "Manage debug sessions, connections, and trace recording", + category = PluginCategoryNames.DEBUGGER, + packageName = DebuggerPluginPackage.NAME, + status = PluginStatus.RELEASED, + eventsConsumed = { + ProgramActivatedPluginEvent.class, + ProgramClosedPluginEvent.class, + }, + servicesRequired = { + DebuggerTraceManagerService.class, + }, + servicesProvided = { + DebuggerModelService.class, + }) public class DebuggerModelServiceProxyPlugin extends Plugin implements DebuggerModelServiceInternal { @@ -401,11 +401,17 @@ public class DebuggerModelServiceProxyPlugin extends Plugin DockingAction action = it.next(); it.remove(); tool.removeAction(action); + String[] path = action.getMenuBarData().getMenuPath(); + tool.setMenuGroup(Arrays.copyOf(path, path.length - 1), null); } for (DebuggerProgramLaunchOffer offer : offers) { - actionDebugProgramMenus.add(DebugProgramAction.menuBuilder(offer, this, delegate) + DockingAction action = DebugProgramAction.menuBuilder(offer, this, delegate) .onAction(ctx -> debugProgramMenuActivated(offer)) - .buildAndInstall(tool)); + .build(); + actionDebugProgramMenus.add(action); + String[] path = action.getMenuBarData().getMenuPath(); + tool.setMenuGroup(Arrays.copyOf(path, path.length - 1), DebugProgramAction.GROUP, "zz"); + tool.addAction(action); } } @@ -449,7 +455,6 @@ public class DebuggerModelServiceProxyPlugin extends Plugin ProgramActivatedPluginEvent evt = (ProgramActivatedPluginEvent) event; currentProgram = evt.getActiveProgram(); currentProgramPath = getProgramPath(currentProgram); - updateActionDebugProgram(); } if (event instanceof ProgramClosedPluginEvent) { @@ -457,7 +462,6 @@ public class DebuggerModelServiceProxyPlugin extends Plugin if (currentProgram == evt.getProgram()) { currentProgram = null; currentProgramPath = null; - updateActionDebugProgram(); } } 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..1a22e6c920 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 @@ -63,26 +63,25 @@ import ghidra.util.exception.CancelledException; import ghidra.util.exception.VersionException; import ghidra.util.task.TaskMonitor; -@PluginInfo( // - shortDescription = "Debugger static mapping manager", // - description = "Track and manage static mappings (program-trace relocations)", // - category = PluginCategoryNames.DEBUGGER, // - packageName = DebuggerPluginPackage.NAME, // - status = PluginStatus.RELEASED, // +@PluginInfo( + shortDescription = "Debugger static mapping manager", + description = "Track and manage static mappings (program-trace relocations)", + category = PluginCategoryNames.DEBUGGER, + packageName = DebuggerPluginPackage.NAME, + status = PluginStatus.RELEASED, eventsConsumed = { - ProgramOpenedPluginEvent.class, // - ProgramClosedPluginEvent.class, // - TraceOpenedPluginEvent.class, // - TraceClosedPluginEvent.class, // - }, // - servicesRequired = { // - ProgramManager.class, // - DebuggerTraceManagerService.class, // - }, // - servicesProvided = { // - DebuggerStaticMappingService.class, // - } // -) + ProgramOpenedPluginEvent.class, + ProgramClosedPluginEvent.class, + TraceOpenedPluginEvent.class, + TraceClosedPluginEvent.class, + }, + servicesRequired = { + ProgramManager.class, + DebuggerTraceManagerService.class, + }, + servicesProvided = { + DebuggerStaticMappingService.class, + }) public class DebuggerStaticMappingServicePlugin extends Plugin implements DebuggerStaticMappingService, DomainFolderChangeAdapter { @@ -1013,8 +1012,8 @@ public class DebuggerStaticMappingServicePlugin extends Plugin for (ModuleMapEntry ent : entries) { monitor.checkCanceled(); try { - addModuleMapping(ent.getModule(), ent.getModuleRange().getLength(), - ent.getProgram(), truncateExisting); + DebuggerStaticMappingUtils.addModuleMapping(ent.getModule(), + ent.getModuleRange().getLength(), ent.getProgram(), truncateExisting); } catch (Exception e) { Msg.error(this, "Could not add mapping " + ent + ": " + e.getMessage()); @@ -1022,16 +1021,6 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } } - @Override - public void addSectionMapping(TraceSection from, Program toProgram, MemoryBlock to, - boolean truncateExisting) throws TraceConflictedMappingException { - TraceLocation fromLoc = new DefaultTraceLocation(from.getTrace(), null, - from.getModule().getLifespan(), from.getStart()); - ProgramLocation toLoc = new ProgramLocation(toProgram, to.getStart()); - long length = Math.min(from.getRange().getLength(), to.getSize()); - addMapping(fromLoc, toLoc, length, truncateExisting); - } - @Override public void addSectionMappings(Collection entries, TaskMonitor monitor, boolean truncateExisting) throws CancelledException { @@ -1056,8 +1045,8 @@ public class DebuggerStaticMappingServicePlugin extends Plugin for (SectionMapEntry ent : entries) { monitor.checkCanceled(); try { - addSectionMapping(ent.getSection(), ent.getProgram(), ent.getBlock(), - truncateExisting); + DebuggerStaticMappingUtils.addSectionMapping(ent.getSection(), ent.getProgram(), + ent.getBlock(), truncateExisting); } catch (Exception e) { Msg.error(this, "Could not add mapping " + ent + ": " + e.getMessage()); @@ -1078,8 +1067,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } protected T noProject() { - Msg.warn(this, "The given program does not exist in any project"); - return null; + return DebuggerStaticMappingUtils.noProject(this); } protected InfoPerTrace requireTrackedInfo(Trace trace) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingUtils.java new file mode 100644 index 0000000000..255ecbda02 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingUtils.java @@ -0,0 +1,141 @@ +/* ### + * 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.service.modules; + +import java.net.URL; +import java.util.List; + +import com.google.common.collect.Range; + +import ghidra.app.plugin.core.debug.utils.ProgramURLUtils; +import ghidra.app.services.DebuggerStaticMappingService; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.util.ProgramLocation; +import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.model.DefaultTraceLocation; +import ghidra.trace.model.TraceLocation; +import ghidra.trace.model.modules.*; +import ghidra.trace.model.program.TraceProgramView; +import ghidra.util.Msg; + +public enum DebuggerStaticMappingUtils { + ; + + protected static T noProject(Object originator) { + Msg.warn(originator, "The given program does not exist in any project"); + return null; + } + + /** + * Add a static mapping (relocation) from the given trace to the given program + * + *

    + * 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 + * @param truncateExisting true to delete or truncate the lifespan of overlapping entries + * @throws TraceConflictedMappingException if a conflicting mapping overlaps the source and + * {@code truncateExisting} is false. + */ + public static void addMapping(TraceLocation from, ProgramLocation to, long length, + boolean truncateExisting) + throws TraceConflictedMappingException { + Program tp = to.getProgram(); + if (tp instanceof TraceProgramView) { + throw new IllegalArgumentException( + "Mapping destination cannot be a " + TraceProgramView.class.getSimpleName()); + } + TraceStaticMappingManager manager = from.getTrace().getStaticMappingManager(); + URL toURL = ProgramURLUtils.getUrlFromProgram(tp); + if (toURL == null) { + noProject(DebuggerStaticMappingService.class); + } + try { + Address start = from.getAddress(); + Address end = start.addNoWrap(length - 1); + // Also check end in the destination + Address toAddress = to.getAddress(); + toAddress.addNoWrap(length - 1); // Anticipate possible AddressOverflow + AddressRangeImpl range = new AddressRangeImpl(start, end); + if (truncateExisting) { + long truncEnd = DBTraceUtils.lowerEndpoint(from.getLifespan()) - 1; + for (TraceStaticMapping existing : List + .copyOf(manager.findAllOverlapping(range, from.getLifespan()))) { + existing.delete(); + if (Long.compareUnsigned(existing.getStartSnap(), truncEnd) < 0) { + manager.add(existing.getTraceAddressRange(), + Range.closed(existing.getStartSnap(), truncEnd), + existing.getStaticProgramURL(), existing.getStaticAddress()); + } + } + } + manager.add(range, from.getLifespan(), toURL, + toAddress.toString(true)); + } + catch (AddressOverflowException e) { + throw new IllegalArgumentException("Length would cause address overflow", e); + } + } + + /** + * Add a static mapping (relocation) from the given module to the given program + * + *

    + * This is simply a shortcut and does not mean to imply that all mappings must represent module + * relocations. The lifespan is that of the module's. + * + * @param from the source module + * @param length the "size" of the module -- {@code max-min+1} as loaded/mapped in memory + * @param toProgram the destination program + * @see #addMapping(TraceLocation, ProgramLocation, long, boolean) + */ + public static void addModuleMapping(TraceModule from, long length, Program toProgram, + boolean truncateExisting) throws TraceConflictedMappingException { + TraceLocation fromLoc = + new DefaultTraceLocation(from.getTrace(), null, from.getLifespan(), from.getBase()); + ProgramLocation toLoc = new ProgramLocation(toProgram, toProgram.getImageBase()); + addMapping(fromLoc, toLoc, length, truncateExisting); + } + + /** + * Add a static mapping (relocation) from the given section to the given program memory block + * + *

    + * This is simply a shortcut and does not mean to imply that all mappings must represent section + * relocations. In most cases the lengths of the from and to objects match exactly, but this may + * not be the case. Whatever the case, the minimum length is computed, and the start addresses + * are used as the location. The lifespan is that of the section's containing module. + * + * @param from the source section + * @param toProgram the destination program + * @param to the destination memory block + * @see #addMapping(TraceLocation, ProgramLocation, long, boolean) + */ + public static void addSectionMapping(TraceSection from, Program toProgram, MemoryBlock to, + boolean truncateExisting) throws TraceConflictedMappingException { + TraceLocation fromLoc = new DefaultTraceLocation(from.getTrace(), null, + from.getModule().getLifespan(), from.getStart()); + ProgramLocation toLoc = new ProgramLocation(toProgram, to.getStart()); + long length = Math.min(from.getRange().getLength(), to.getSize()); + addMapping(fromLoc, toLoc, length, truncateExisting); + } +} 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..52a1f1b6cc 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,7 +487,7 @@ public interface DebuggerStaticMappingService { } /** - * A {@code (shift,view)} pair for describing sets of mapped addresses + * <<<<<<< HEAD A {@code (shift,view)} pair for describing sets of mapped addresses */ public class ShiftAndAddressSetView { private final long shift; @@ -559,7 +559,8 @@ public interface DebuggerStaticMappingService { boolean truncateExisting) throws TraceConflictedMappingException; /** - * Add several static mappings (relocations) + * ======= >>>>>>> d694542c5 (GP-660: Put program filler back in. Need to performance test.) Add + * several static mappings (relocations) * *

    * This will group the entries by trace and add each's entries in a single transaction. If any @@ -574,23 +575,6 @@ public interface DebuggerStaticMappingService { void addModuleMappings(Collection entries, TaskMonitor monitor, boolean truncateExisting) throws CancelledException; - /** - * Add a static mapping (relocation) from the given section to the given program memory block - * - *

    - * This is simply a shortcut and does not mean to imply that all mappings must represent section - * relocations. In most cases the lengths of the from and to objects match exactly, but this may - * not be the case. Whatever the case, the minimum length is computed, and the start addresses - * are used as the location. The lifespan is that of the section's containing module. - * - * @param from the source section - * @param toProgram the destination program - * @param to the destination memory block - * @see #addMapping(TraceLocation, ProgramLocation, long, boolean) - */ - void addSectionMapping(TraceSection from, Program toProgram, MemoryBlock to, - boolean truncateExisting) throws TraceConflictedMappingException; - /** * Add several static mappings (relocations) * diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java index 18b1923a3b..ab7125c811 100644 --- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java @@ -30,6 +30,7 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest.Test import ghidra.app.plugin.core.debug.service.breakpoint.DebuggerLogicalBreakpointServicePlugin; import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin; import ghidra.app.services.*; @@ -130,12 +131,12 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera mb.testProcess3.addRegion("echo:.text", mb.rng(0x7fac0000, 0x7fac0fff), "rx"); try (UndoableTransaction tid = UndoableTransaction.start(trace1, "Add mapping", true)) { - mappingService.addMapping( + DebuggerStaticMappingUtils.addMapping( new DefaultTraceLocation(trace1, null, Range.atLeast(0L), addr(trace1, 0x00400000)), new ProgramLocation(program, addr(program, 0x00400000)), 0x00210000, false); } try (UndoableTransaction tid = UndoableTransaction.start(trace3, "Add mapping", true)) { - mappingService.addMapping( + DebuggerStaticMappingUtils.addMapping( new DefaultTraceLocation(trace3, null, Range.atLeast(0L), addr(trace3, 0x7fac0000)), new ProgramLocation(program, addr(program, 0x00400000)), 0x00010000, false); } diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPluginScreenShots.java index 140c0308d2..c389c5889e 100644 --- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPluginScreenShots.java +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPluginScreenShots.java @@ -20,6 +20,7 @@ import org.junit.*; import com.google.common.collect.Range; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin; import ghidra.app.services.*; @@ -114,7 +115,7 @@ public class DebuggerStackPluginScreenShots extends GhidraScreenShotGenerator { root.createFile("trace", tb.trace, TaskMonitor.DUMMY); root.createFile("echo", program, TaskMonitor.DUMMY); try (UndoableTransaction tid = tb.startTransaction()) { - mappingService.addMapping( + DebuggerStaticMappingUtils.addMapping( new DefaultTraceLocation(tb.trace, null, Range.atLeast(snap), tb.addr(0x00400000)), new ProgramLocation(program, addr(program, 0x00400000)), 0x10000, false); } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java index 503ca5f715..f389a1051b 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java @@ -41,6 +41,7 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; import ghidra.app.services.*; import ghidra.app.services.LogicalBreakpoint.Enablement; import ghidra.app.util.viewer.listingpanel.ListingPanel; @@ -116,7 +117,7 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu protected void addMapping(Trace trace) throws Exception { try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add mapping", true)) { - mappingService.addMapping( + DebuggerStaticMappingUtils.addMapping( new DefaultTraceLocation(trace, null, Range.atLeast(0L), addr(trace, 0x55550123)), new ProgramLocation(program, addr(program, 0x00400123)), 0x1000, false); } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java index afa7879316..084373dafd 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java @@ -32,6 +32,7 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import ghidra.app.plugin.core.debug.gui.breakpoint.DebuggerBreakpointsProvider.LogicalBreakpointTableModel; import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin; +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; import ghidra.app.services.*; import ghidra.app.services.LogicalBreakpoint.Enablement; import ghidra.async.AsyncTestUtils; @@ -70,7 +71,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge protected void addMapping(Trace trace, Program prog) throws Exception { try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add mapping", true)) { - mappingService.addMapping( + DebuggerStaticMappingUtils.addMapping( new DefaultTraceLocation(trace, null, Range.atLeast(0L), addr(trace, 0x55550000)), new ProgramLocation(prog, addr(prog, 0x00400000)), 0x1000, false); } 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 e40336e992..ed502a991e 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 @@ -42,6 +42,7 @@ import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin; import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction; import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow; import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext; +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; import ghidra.app.services.*; import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.async.SwingExecutorService; @@ -461,7 +462,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceLocation from = new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000)); - mappingService.addMapping(from, to, 0x8000, false); + DebuggerStaticMappingUtils.addMapping(from, to, 0x8000, false); } waitForProgram(program); waitForDomainObject(tb.trace); @@ -512,7 +513,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceLocation from = new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000)); - mappingService.addMapping(from, to, 0x8000, false); + DebuggerStaticMappingUtils.addMapping(from, to, 0x8000, false); thread = tb.getOrAddThread("Thread1", 0); Register pc = tb.trace.getBaseLanguage().getProgramCounter(); @@ -552,7 +553,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceLocation from = new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000)); - mappingService.addMapping(from, to, 0x8000, false); + DebuggerStaticMappingUtils.addMapping(from, to, 0x8000, false); } waitForProgram(program); waitForDomainObject(tb.trace); @@ -643,7 +644,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceLocation from = new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000)); - mappingService.addMapping(from, to, 0x8000, false); + DebuggerStaticMappingUtils.addMapping(from, to, 0x8000, false); thread = tb.getOrAddThread("Thread1", 0); Register pc = tb.trace.getBaseLanguage().getProgramCounter(); @@ -906,7 +907,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceLocation from = new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000)); - mappingService.addMapping(from, to, 0x8000, false); + DebuggerStaticMappingUtils.addMapping(from, to, 0x8000, false); } waitForProgram(program); waitForDomainObject(tb.trace); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderTest.java index 04e86a3671..a85db35c31 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderTest.java @@ -28,6 +28,7 @@ import com.google.common.collect.Range; import generic.Unique; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSet; @@ -496,7 +497,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe TraceLocation dloc = new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); ProgramLocation sloc = new ProgramLocation(program, addr(program, 0x00600000)); - mappingService.addMapping(dloc, sloc, 0x1000, false); + DebuggerStaticMappingUtils.addMapping(dloc, sloc, 0x1000, false); } waitForDomainObject(tb.trace); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java index 10bbe5fc39..32c207a659 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java @@ -26,6 +26,7 @@ import org.junit.*; import generic.Unique; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; import ghidra.app.services.*; import ghidra.app.services.LogicalBreakpoint.Enablement; import ghidra.async.AsyncReference; @@ -273,8 +274,10 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe waitFor(() -> r.getTraceMemoryRegion(region), "Recorder missed region: " + region); try (UndoableTransaction tid = UndoableTransaction.start(t, "Add .text mapping", true)) { - mappingService.addMapping(new DefaultTraceLocation(t, null, textRegion.getLifespan(), - textRegion.getMinAddress()), new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, + DebuggerStaticMappingUtils.addMapping( + new DefaultTraceLocation(t, null, textRegion.getLifespan(), + textRegion.getMinAddress()), + new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, false); } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java new file mode 100644 index 0000000000..b8cb8d0530 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java @@ -0,0 +1,98 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.service.emulation; + +import static org.junit.Assert.*; + +import java.math.BigInteger; + +import org.junit.Test; + +import generic.Unique; +import ghidra.app.plugin.assembler.Assembler; +import ghidra.app.plugin.assembler.Assemblers; +import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; +import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.Register; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceMemoryRegisterSpace; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.time.TraceSchedule; +import ghidra.util.database.UndoableTransaction; +import ghidra.util.task.TaskMonitor; + +public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGUITest { + protected DebuggerEmulationServicePlugin emulationPlugin; + + @Test + public void testPureEmulation() throws Exception { + emulationPlugin = addPlugin(tool, DebuggerEmulationServicePlugin.class); + + // TODO: Action enablement doesn't work without CodeBrowser??? + // Probably missing some contextChanged, but I have no provider! + addPlugin(tool, CodeBrowserPlugin.class); + + createProgram(); + intoProject(program); + Assembler asm = Assemblers.getAssembler(program); + Memory memory = program.getMemory(); + Address addrText = addr(program, 0x00400000); + Register regPC = program.getRegister("pc"); + Register regR0 = program.getRegister("r0"); + Register regR1 = program.getRegister("r1"); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + MemoryBlock blockText = + memory.createInitializedBlock(".text", addrText, 0x1000, (byte) 0, + TaskMonitor.DUMMY, false); + blockText.setExecute(true); + memory.createInitializedBlock(".data", addr(program, 0x00600000), 0x1000, (byte) 0, + TaskMonitor.DUMMY, false); + asm.assemble(addrText, "mov r0, r1"); + program.getProgramContext() + .setValue(regR1, addrText, addrText, new BigInteger("1234", 16)); + } + + programManager.openProgram(program); + waitForSwing(); + + assertTrue(emulationPlugin.actionEmulateProgram.isEnabled()); + performAction(emulationPlugin.actionEmulateProgram); + + Trace trace = traceManager.getCurrentTrace(); + assertNotNull(trace); + + TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads()); + TraceMemoryRegisterSpace regs = + trace.getMemoryManager().getMemoryRegisterSpace(thread, false); + assertEquals(new BigInteger("00400000", 16), + regs.getViewValue(0, regPC).getUnsignedValue()); + assertEquals(new BigInteger("0000", 16), regs.getViewValue(0, regR0).getUnsignedValue()); + assertEquals(new BigInteger("1234", 16), regs.getViewValue(0, regR1).getUnsignedValue()); + + long scratch = + emulationPlugin.emulate(trace, TraceSchedule.parse("0:t0-1"), TaskMonitor.DUMMY); + + assertEquals(new BigInteger("00400002", 16), + regs.getViewValue(scratch, regPC).getUnsignedValue()); + assertEquals(new BigInteger("1234", 16), + regs.getViewValue(scratch, regR0).getUnsignedValue()); + assertEquals(new BigInteger("1234", 16), + regs.getViewValue(scratch, regR1).getUnsignedValue()); + } +} 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 d2fe5d5554..f42ff513f8 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 @@ -75,7 +75,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg dynSpace.getAddress(0x00100000)); ProgramLocation to = new ProgramLocation(program, stSpace.getAddress(0x00200000)); try (UndoableTransaction tid = tb.startTransaction()) { - mappingService.addMapping(from, to, 0x1000, false); + DebuggerStaticMappingUtils.addMapping(from, to, 0x1000, false); } waitForDomainObject(tb.trace); } @@ -85,7 +85,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg dynSpace.getAddress(0x00100800)); ProgramLocation to = new ProgramLocation(program, stSpace.getAddress(0x00300000)); try (UndoableTransaction tid = tb.startTransaction()) { - mappingService.addMapping(from, to, 0x1000, truncateExisting); + DebuggerStaticMappingUtils.addMapping(from, to, 0x1000, truncateExisting); } } @@ -104,7 +104,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg dynSpace.getAddress(0x00102000)); ProgramLocation to = new ProgramLocation(program, stSpace.getAddress(0x00200000)); try (UndoableTransaction tid = tb.startTransaction()) { - mappingService.addMapping(from, to, 0x800, false); + DebuggerStaticMappingUtils.addMapping(from, to, 0x800, false); } waitForDomainObject(tb.trace); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java index 4912377b78..e7f4eeace0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java @@ -134,10 +134,15 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace throws IOException, LanguageNotFoundException { super(new DBHandle(), DBOpenMode.CREATE, TaskMonitor.DUMMY, name, DB_TIME_INTERVAL, DB_BUFFER_SIZE, consumer); + this.storeFactory = new DBCachedObjectStoreFactory(this); this.baseLanguage = baseCompilerSpec.getLanguage(); - this.baseCompilerSpec = baseCompilerSpec; - this.baseAddressFactory = new ProgramAddressFactory(baseLanguage, baseCompilerSpec); + // Need to "downgrade" the compiler spec, so nothing program-specific seeps in + // TODO: Should there be a TraceCompilerSpec? + this.baseCompilerSpec = + baseLanguage.getCompilerSpecByID(baseCompilerSpec.getCompilerSpecID()); + this.baseAddressFactory = + new ProgramAddressFactory(this.baseLanguage, this.baseCompilerSpec); try (UndoableTransaction tid = UndoableTransaction.start(this, "Create", false)) { initOptions(DBOpenMode.CREATE);