Merge branch 'GP-660_ProgramEmulation'

This commit is contained in:
Ryan Kurtz 2021-07-28 09:37:32 -04:00
commit 4612e34053
26 changed files with 1069 additions and 169 deletions

View file

@ -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/DebuggerBreakpointsPlugin/images/breakpoints-make-effective.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html||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/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/DebuggerInterpreterPlugin/DebuggerInterpreterPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerGoToDialog.png||GHIDRA||||END| src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerGoToDialog.png||GHIDRA||||END|

View file

@ -94,6 +94,10 @@
sortgroup="g" sortgroup="g"
target="help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html" /> target="help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html" />
<tocdef id="DebuggerEmulationServicePlugin" text="Emulation"
sortgroup="g1"
target="help/topics/DebuggerEmulationServicePlugin/DebuggerEmulationServicePlugin.html" />
<tocdef id="DebuggerRegistersPlugin" text="Registers" <tocdef id="DebuggerRegistersPlugin" text="Registers"
sortgroup="h" sortgroup="h"
target="help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html" /> target="help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html" />

View file

@ -0,0 +1,44 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Debugger: Model Service</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
</HEAD>
<BODY lang="EN-US">
<H1><A name="plugin"></A>Debugger: Emulation Service</H1>
<P>This service plugin provides emulation to the <A href=
"help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html">Trace
Manager</A> 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.</P>
<H2>Actions</H2>
<H3><A name="emulate_program"></A> Emulate Program</H3>
<P>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.</P>
<H3><A name="add_emulated_thread"></A> Add Emulated Thread</H3>
<P>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.</P>
</BODY>
</HTML>

View file

@ -28,9 +28,9 @@
automation, e.g., the <A href="help/topics/DebuggerBots/DebuggerBots.html#map_modules">Map automation, e.g., the <A href="help/topics/DebuggerBots/DebuggerBots.html#map_modules">Map
Modules</A> bot, or by higher-level user actions, e.g., the <A href= Modules</A> bot, or by higher-level user actions, e.g., the <A href=
"help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html#map_modules">Map Modules</A> "help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html#map_modules">Map Modules</A>
action. This under-the-hood static mapping window displays the mappings table, allowing users or action. This under-the-hood static mapping window displays the mappings table, allowing users
developers to diagnose image mapping issues and manually add mappings, regardless of reported or developers to diagnose image mapping issues and manually add mappings, regardless of
modules and/or sections. For most users, there is no reason to access this window.</P> reported modules and/or sections. For most users, there is no reason to access this window.</P>
<H2>Table Columns</H2> <H2>Table Columns</H2>

View file

@ -50,10 +50,10 @@
<H2>Navigating Threads</H2> <H2>Navigating Threads</H2>
<P>Selecting a thread in the table will navigate to (or "activate" or "focus") <P>Selecting a thread in the table will navigate to (or "activate" or "focus") that thread.
that thread. Windows which are sensitive to the current thread will update. Notably, the <A Windows which are sensitive to the current thread will update. Notably, the <A href=
href="help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> window "help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> window will
will display the activated thread's register values. <A href= display the activated thread's register values. <A href=
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Listing</A> windows with "help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Listing</A> windows with
configured location tracking will re-compute that location with the thread's context and 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 navigate to it. The thread timeline plots all recorded threads. Threads which are alive will
@ -78,15 +78,16 @@
<LI>Comment - a user-modifiable comment about the thread.</LI> <LI>Comment - a user-modifiable comment about the thread.</LI>
<LI>Plot - a graphical representation of the thread's lifespan. Unlike other column headers, clicking and dragging <LI>Plot - a graphical representation of the thread's lifespan. Unlike other column headers,
in this one will navigate through time. To rearrange this column, hold SHIFT while dragging.</LI> clicking and dragging in this one will navigate through time. To rearrange this column, hold
SHIFT while dragging.</LI>
</UL> </UL>
<H2>Navigating Time</H2> <H2>Navigating Time</H2>
<P>The user can navigate through time within the current trace by using the caret in the plot <P>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 column header. There are also actions for "stepping the trace" forward and backward. See the <A
<A href="help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html">Time</A> window for a way to href="help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html">Time</A> window for a way to
display and navigate to specific events in the trace's timeline. Note that stepping away from 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 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 components and scripts may interact with the target and record things "into the present," such

View file

@ -169,6 +169,8 @@ public interface DebuggerResources {
ImageIcon ICON_BLANK = ResourceManager.loadImage("images/blank.png"); ImageIcon ICON_BLANK = ResourceManager.loadImage("images/blank.png");
ImageIcon ICON_PACKAGE = ResourceManager.loadImage("images/debugger32.png"); ImageIcon ICON_PACKAGE = ResourceManager.loadImage("images/debugger32.png");
ImageIcon ICON_EMULATE = ICON_PROCESS; // TODO
HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package"); HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package");
String HELP_ANCHOR_PLUGIN = "plugin"; 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 { abstract class AbstractQuickLaunchAction extends DockingAction {
public static final String NAME = "Quick Launch"; public static final String NAME = "Quick Launch";
public static final Icon ICON = ICON_DEBUGGER; // TODO: A different icon? public static final Icon ICON = ICON_DEBUGGER; // TODO: A different icon?

View file

@ -59,8 +59,10 @@ public class DebuggerRegistersPlugin extends AbstractDebuggerPlugin {
protected DebuggerRegistersProvider connectedProvider; protected DebuggerRegistersProvider connectedProvider;
private final Map<CompilerSpec, LinkedHashSet<Register>> selectionByCSpec = new HashMap<>(); private final Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> selectionByCSpec =
private final Map<CompilerSpec, LinkedHashSet<Register>> favoritesByCSpec = new HashMap<>(); new HashMap<>();
private final Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> favoritesByCSpec =
new HashMap<>();
private final Set<DebuggerRegistersProvider> disconnectedProviders = new HashSet<>(); private final Set<DebuggerRegistersProvider> disconnectedProviders = new HashSet<>();
public DebuggerRegistersPlugin(PluginTool tool) { public DebuggerRegistersPlugin(PluginTool tool) {
@ -119,13 +121,13 @@ public class DebuggerRegistersPlugin extends AbstractDebuggerPlugin {
} }
} }
public static String encodeSetsByCSpec(Map<CompilerSpec, LinkedHashSet<Register>> setsByCSpec) { public static String encodeSetsByCSpec(
Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> setsByCSpec) {
return StringUtils.join(setsByCSpec.entrySet().stream().map(ent -> { return StringUtils.join(setsByCSpec.entrySet().stream().map(ent -> {
CompilerSpec cspec = ent.getKey(); LanguageCompilerSpecPair lcsp = ent.getKey();
String regs = StringUtils.join( String regs = StringUtils.join(
ent.getValue().stream().map(Register::getName).collect(Collectors.toList()), ','); ent.getValue().stream().map(Register::getName).collect(Collectors.toList()), ',');
return cspec.getLanguage().getLanguageID() + "/" + cspec.getCompilerSpecID() + ":" + return lcsp.languageID + "/" + lcsp.compilerSpecID + ":" + regs;
regs;
}).collect(Collectors.toList()), ';'); }).collect(Collectors.toList()), ';');
} }
@ -138,8 +140,8 @@ public class DebuggerRegistersPlugin extends AbstractDebuggerPlugin {
saveState.putString(KEY_FAVORITES_BY_CSPEC, favoritesByCSpecString); saveState.putString(KEY_FAVORITES_BY_CSPEC, favoritesByCSpecString);
} }
public static void readSetsByCSpec(Map<CompilerSpec, LinkedHashSet<Register>> setsByCSpec, public static void readSetsByCSpec(
String encoded) { Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> setsByCSpec, String encoded) {
LanguageService langServ = DefaultLanguageService.getLanguageService(); LanguageService langServ = DefaultLanguageService.getLanguageService();
if (encoded.length() == 0) { if (encoded.length() == 0) {
return; return;
@ -160,24 +162,17 @@ public class DebuggerRegistersPlugin extends AbstractDebuggerPlugin {
"Bad lang-spec key: " + langCsPart + ". Ignoring."); "Bad lang-spec key: " + langCsPart + ". Ignoring.");
continue; continue;
} }
LanguageID lid = new LanguageID(langCsParts[0]);
Language lang; Language lang;
try { try {
lang = langServ.getLanguage(new LanguageID(langCsParts[0])); lang = langServ.getLanguage(lid);
} }
catch (LanguageNotFoundException e) { catch (LanguageNotFoundException e) {
Msg.warn(DebuggerRegistersPlugin.class, Msg.warn(DebuggerRegistersPlugin.class,
"Language " + langCsParts[0] + " does not exist. Ignoring."); "Language " + langCsParts[0] + " does not exist. Ignoring.");
continue; continue;
} }
CompilerSpec cSpec; CompilerSpecID csid = new CompilerSpecID(langCsParts[1]);
try {
cSpec = lang.getCompilerSpecByID(new CompilerSpecID(langCsParts[1]));
}
catch (CompilerSpecNotFoundException e) {
Msg.warn(DebuggerRegistersPlugin.class,
"CompilerSpec " + langCsParts[1] + " does not exist. Ignoring.");
continue;
}
LinkedHashSet<Register> regs = new LinkedHashSet<>(); LinkedHashSet<Register> regs = new LinkedHashSet<>();
for (String regName : regsPart.split(",")) { for (String regName : regsPart.split(",")) {
@ -190,7 +185,7 @@ public class DebuggerRegistersPlugin extends AbstractDebuggerPlugin {
regs.add(register); regs.add(register);
} }
setsByCSpec.put(cSpec, regs); setsByCSpec.put(new LanguageCompilerSpecPair(lid, csid), regs);
} }
} }

View file

@ -369,8 +369,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
} }
final DebuggerRegistersPlugin plugin; final DebuggerRegistersPlugin plugin;
private final Map<CompilerSpec, LinkedHashSet<Register>> selectionByCSpec; private final Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> selectionByCSpec;
private final Map<CompilerSpec, LinkedHashSet<Register>> favoritesByCSpec; private final Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> favoritesByCSpec;
private final boolean isClone; private final boolean isClone;
DebuggerCoordinates previous = DebuggerCoordinates.NOWHERE; DebuggerCoordinates previous = DebuggerCoordinates.NOWHERE;
@ -436,8 +436,9 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
AddressSetView viewKnown; AddressSetView viewKnown;
protected DebuggerRegistersProvider(final DebuggerRegistersPlugin plugin, protected DebuggerRegistersProvider(final DebuggerRegistersPlugin plugin,
Map<CompilerSpec, LinkedHashSet<Register>> selectionByCSpec, Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> selectionByCSpec,
Map<CompilerSpec, LinkedHashSet<Register>> favoritesByCSpec, boolean isClone) { Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> favoritesByCSpec,
boolean isClone) {
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_REGISTERS, plugin.getName()); super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_REGISTERS, plugin.getName());
this.plugin = plugin; this.plugin = plugin;
this.selectionByCSpec = selectionByCSpec; this.selectionByCSpec = selectionByCSpec;
@ -1026,18 +1027,27 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
return result; 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<Register> getSelectionFor(TraceThread thread) { protected Set<Register> getSelectionFor(TraceThread thread) {
synchronized (selectionByCSpec) { synchronized (selectionByCSpec) {
CompilerSpec cSpec = thread.getTrace().getBaseCompilerSpec(); LanguageCompilerSpecPair lcsp = getLangCSpecPair(thread);
return selectionByCSpec.computeIfAbsent(cSpec, return selectionByCSpec.computeIfAbsent(lcsp,
__ -> computeDefaultRegisterSelection(thread)); __ -> computeDefaultRegisterSelection(thread));
} }
} }
protected Set<Register> getFavoritesFor(TraceThread thread) { protected Set<Register> getFavoritesFor(TraceThread thread) {
synchronized (favoritesByCSpec) { synchronized (favoritesByCSpec) {
CompilerSpec cSpec = thread.getTrace().getBaseCompilerSpec(); LanguageCompilerSpecPair lcsp = getLangCSpecPair(thread);
return favoritesByCSpec.computeIfAbsent(cSpec, return favoritesByCSpec.computeIfAbsent(lcsp,
__ -> computeDefaultRegisterFavorites(thread)); __ -> computeDefaultRegisterFavorites(thread));
} }
} }

View file

@ -15,23 +15,40 @@
*/ */
package ghidra.app.plugin.core.debug.service.emulation; package ghidra.app.plugin.core.debug.service.emulation;
import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.apache.commons.lang3.exception.ExceptionUtils; 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.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; 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.app.services.*;
import ghidra.async.AsyncLazyMap; import ghidra.async.AsyncLazyMap;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus; 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;
import ghidra.trace.model.time.TraceSchedule.CompareResult; import ghidra.trace.model.time.TraceSchedule.CompareResult;
import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction; import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task; import ghidra.util.task.Task;
@ -44,10 +61,13 @@ import ghidra.util.task.TaskMonitor;
packageName = DebuggerPluginPackage.NAME, packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.UNSTABLE, status = PluginStatus.UNSTABLE,
eventsConsumed = { eventsConsumed = {
TraceClosedPluginEvent.class TraceClosedPluginEvent.class,
ProgramActivatedPluginEvent.class,
ProgramClosedPluginEvent.class,
}, },
servicesRequired = { servicesRequired = {
DebuggerTraceManagerService.class DebuggerTraceManagerService.class,
DebuggerStaticMappingService.class
}, },
servicesProvided = { servicesProvided = {
DebuggerEmulationService.class DebuggerEmulationService.class
@ -153,15 +173,157 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
private DebuggerTraceManagerService traceManager; private DebuggerTraceManagerService traceManager;
@AutoServiceConsumed @AutoServiceConsumed
private DebuggerModelService modelService; private DebuggerModelService modelService;
@AutoServiceConsumed
private DebuggerStaticMappingService staticMappings;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private AutoService.Wiring autoServiceWiring; private AutoService.Wiring autoServiceWiring;
DockingAction actionEmulateProgram;
DockingAction actionEmulateAddThread;
public DebuggerEmulationServicePlugin(PluginTool tool) { public DebuggerEmulationServicePlugin(PluginTool tool) {
super(tool); super(tool);
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this); 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<CacheKey, CachedEmulator> findNearestPrefix(CacheKey key) { protected Map.Entry<CacheKey, CachedEmulator> findNearestPrefix(CacheKey key) {
synchronized (cache) {
Map.Entry<CacheKey, CachedEmulator> candidate = cache.floorEntry(key); Map.Entry<CacheKey, CachedEmulator> candidate = cache.floorEntry(key);
if (candidate == null) { if (candidate == null) {
return null; return null;
@ -171,6 +333,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
} }
return candidate; return candidate;
} }
}
protected CompletableFuture<Long> doBackgroundEmulate(CacheKey key) { protected CompletableFuture<Long> doBackgroundEmulate(CacheKey key) {
EmulateTask task = new EmulateTask(key); EmulateTask task = new EmulateTask(key);
@ -220,8 +383,10 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
if (ancestor != null) { if (ancestor != null) {
CacheKey prevKey = ancestor.getKey(); CacheKey prevKey = ancestor.getKey();
synchronized (cache) {
cache.remove(prevKey); cache.remove(prevKey);
eldest.remove(prevKey); eldest.remove(prevKey);
}
// TODO: Handle errors, and add to proper place in cache? // TODO: Handle errors, and add to proper place in cache?
// TODO: Finish partially-executed instructions? // TODO: Finish partially-executed instructions?
@ -243,15 +408,16 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
emu.writeDown(trace, destSnap.getKey(), time.getSnap(), false); emu.writeDown(trace, destSnap.getKey(), time.getSnap(), false);
} }
synchronized (cache) {
cache.put(key, ce); cache.put(key, ce);
eldest.add(key); eldest.add(key);
assert cache.size() == eldest.size(); assert cache.size() == eldest.size();
while (cache.size() > MAX_CACHE_SIZE) { while (cache.size() > MAX_CACHE_SIZE) {
CacheKey expired = eldest.iterator().next(); CacheKey expired = eldest.iterator().next();
eldest.remove(expired); eldest.remove(expired);
cache.remove(expired); cache.remove(expired);
} }
}
return destSnap.getKey(); return destSnap.getKey();
} }
@ -284,4 +450,20 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
private void setModelService(DebuggerModelService modelService) { private void setModelService(DebuggerModelService modelService) {
cache.clear(); cache.clear();
} }
@Override
public void processEvent(PluginEvent event) {
super.processEvent(event);
if (event instanceof TraceClosedPluginEvent) {
TraceClosedPluginEvent evt = (TraceClosedPluginEvent) event;
synchronized (cache) {
List<CacheKey> toRemove = eldest.stream()
.filter(k -> k.trace == evt.getTrace())
.collect(Collectors.toList());
cache.keySet().removeAll(toRemove);
eldest.removeAll(toRemove);
assert cache.size() == eldest.size();
}
}
}
} }

View file

@ -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 {
}

View file

@ -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.
*
* <p>
* 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<TraceMemoryFlag> getRegionFlags(MemoryBlock block) {
Set<TraceMemoryFlag> 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
*
* <p>
* 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.
*
* <p>
* 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
*
* <p>
* 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;
}
}

View file

@ -44,30 +44,37 @@ public class ReadsTargetMemoryPcodeExecutorState
@Override @Override
protected void fillUninitialized(AddressSet uninitialized) { protected void fillUninitialized(AddressSet uninitialized) {
// TODO: fillUnknownWithStaticImages? AddressSet unknown;
if (!isLive()) {
return;
}
AddressSet unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
fillUnknownWithRecorder(unknown);
unknown = computeUnknown(uninitialized); unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) { if (unknown.isEmpty()) {
return; 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); 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)); waitTimeout(recorder.captureProcessMemory(unknown, TaskMonitor.DUMMY));
return true;
} }
private void fillUnknownWithStaticImages(AddressSet unknown) { private boolean fillUnknownWithStaticImages(AddressSet unknown) {
if (!space.isMemorySpace()) { boolean result = false;
return; // TODO: Expand to block? DON'T OVERWRITE KNOWN!
}
DebuggerStaticMappingService mappingService = DebuggerStaticMappingService mappingService =
tool.getService(DebuggerStaticMappingService.class); tool.getService(DebuggerStaticMappingService.class);
byte[] data = new byte[4096]; byte[] data = new byte[4096];
@ -76,12 +83,15 @@ public class ReadsTargetMemoryPcodeExecutorState
.entrySet()) { .entrySet()) {
Program program = ent.getKey(); Program program = ent.getKey();
ShiftAndAddressSetView shifted = ent.getValue(); ShiftAndAddressSetView shifted = ent.getValue();
Msg.warn(this,
"Filling in unknown trace memory in emulator using mapped image: " +
program + ": " + shifted.getAddressSetView());
long shift = shifted.getShift(); long shift = shifted.getShift();
Memory memory = program.getMemory(); 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 lower = rng.getMinAddress().getOffset();
long fullLen = rng.getLength(); long fullLen = rng.getLength();
while (fullLen > 0) { while (fullLen > 0) {
@ -101,8 +111,10 @@ public class ReadsTargetMemoryPcodeExecutorState
lower += len; lower += len;
fullLen -= len; fullLen -= len;
} }
result = true;
} }
} }
return result;
} }
} }

View file

@ -63,22 +63,22 @@ import ghidra.util.datastruct.ListenerSet;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@PluginInfo( // @PluginInfo(
shortDescription = "Debugger models manager service (proxy to front-end)", // shortDescription = "Debugger models manager service (proxy to front-end)",
description = "Manage debug sessions, connections, and trace recording", // description = "Manage debug sessions, connections, and trace recording",
category = PluginCategoryNames.DEBUGGER, // category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME, // packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED, // status = PluginStatus.RELEASED,
eventsConsumed = { ProgramActivatedPluginEvent.class, // eventsConsumed = {
ProgramClosedPluginEvent.class, // ProgramActivatedPluginEvent.class,
}, // ProgramClosedPluginEvent.class,
servicesRequired = { // },
DebuggerTraceManagerService.class, // servicesRequired = {
}, // DebuggerTraceManagerService.class,
servicesProvided = { // },
DebuggerModelService.class, // servicesProvided = {
} // DebuggerModelService.class,
) })
public class DebuggerModelServiceProxyPlugin extends Plugin public class DebuggerModelServiceProxyPlugin extends Plugin
implements DebuggerModelServiceInternal { implements DebuggerModelServiceInternal {
@ -401,11 +401,17 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
DockingAction action = it.next(); DockingAction action = it.next();
it.remove(); it.remove();
tool.removeAction(action); tool.removeAction(action);
String[] path = action.getMenuBarData().getMenuPath();
tool.setMenuGroup(Arrays.copyOf(path, path.length - 1), null);
} }
for (DebuggerProgramLaunchOffer offer : offers) { for (DebuggerProgramLaunchOffer offer : offers) {
actionDebugProgramMenus.add(DebugProgramAction.menuBuilder(offer, this, delegate) DockingAction action = DebugProgramAction.menuBuilder(offer, this, delegate)
.onAction(ctx -> debugProgramMenuActivated(offer)) .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; ProgramActivatedPluginEvent evt = (ProgramActivatedPluginEvent) event;
currentProgram = evt.getActiveProgram(); currentProgram = evt.getActiveProgram();
currentProgramPath = getProgramPath(currentProgram); currentProgramPath = getProgramPath(currentProgram);
updateActionDebugProgram(); updateActionDebugProgram();
} }
if (event instanceof ProgramClosedPluginEvent) { if (event instanceof ProgramClosedPluginEvent) {
@ -457,7 +462,6 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
if (currentProgram == evt.getProgram()) { if (currentProgram == evt.getProgram()) {
currentProgram = null; currentProgram = null;
currentProgramPath = null; currentProgramPath = null;
updateActionDebugProgram(); updateActionDebugProgram();
} }
} }

View file

@ -63,26 +63,25 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException; import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@PluginInfo( // @PluginInfo(
shortDescription = "Debugger static mapping manager", // shortDescription = "Debugger static mapping manager",
description = "Track and manage static mappings (program-trace relocations)", // description = "Track and manage static mappings (program-trace relocations)",
category = PluginCategoryNames.DEBUGGER, // category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME, // packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED, // status = PluginStatus.RELEASED,
eventsConsumed = { eventsConsumed = {
ProgramOpenedPluginEvent.class, // ProgramOpenedPluginEvent.class,
ProgramClosedPluginEvent.class, // ProgramClosedPluginEvent.class,
TraceOpenedPluginEvent.class, // TraceOpenedPluginEvent.class,
TraceClosedPluginEvent.class, // TraceClosedPluginEvent.class,
}, // },
servicesRequired = { // servicesRequired = {
ProgramManager.class, // ProgramManager.class,
DebuggerTraceManagerService.class, // DebuggerTraceManagerService.class,
}, // },
servicesProvided = { // servicesProvided = {
DebuggerStaticMappingService.class, // DebuggerStaticMappingService.class,
} // })
)
public class DebuggerStaticMappingServicePlugin extends Plugin public class DebuggerStaticMappingServicePlugin extends Plugin
implements DebuggerStaticMappingService, DomainFolderChangeAdapter { implements DebuggerStaticMappingService, DomainFolderChangeAdapter {
@ -1013,8 +1012,8 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
for (ModuleMapEntry ent : entries) { for (ModuleMapEntry ent : entries) {
monitor.checkCanceled(); monitor.checkCanceled();
try { try {
addModuleMapping(ent.getModule(), ent.getModuleRange().getLength(), DebuggerStaticMappingUtils.addModuleMapping(ent.getModule(),
ent.getProgram(), truncateExisting); ent.getModuleRange().getLength(), ent.getProgram(), truncateExisting);
} }
catch (Exception e) { catch (Exception e) {
Msg.error(this, "Could not add mapping " + ent + ": " + e.getMessage()); 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 @Override
public void addSectionMappings(Collection<SectionMapEntry> entries, public void addSectionMappings(Collection<SectionMapEntry> entries,
TaskMonitor monitor, boolean truncateExisting) throws CancelledException { TaskMonitor monitor, boolean truncateExisting) throws CancelledException {
@ -1056,8 +1045,8 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
for (SectionMapEntry ent : entries) { for (SectionMapEntry ent : entries) {
monitor.checkCanceled(); monitor.checkCanceled();
try { try {
addSectionMapping(ent.getSection(), ent.getProgram(), ent.getBlock(), DebuggerStaticMappingUtils.addSectionMapping(ent.getSection(), ent.getProgram(),
truncateExisting); ent.getBlock(), truncateExisting);
} }
catch (Exception e) { catch (Exception e) {
Msg.error(this, "Could not add mapping " + ent + ": " + e.getMessage()); Msg.error(this, "Could not add mapping " + ent + ": " + e.getMessage());
@ -1078,8 +1067,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
} }
protected <T> T noProject() { protected <T> T noProject() {
Msg.warn(this, "The given program does not exist in any project"); return DebuggerStaticMappingUtils.noProject(this);
return null;
} }
protected InfoPerTrace requireTrackedInfo(Trace trace) { protected InfoPerTrace requireTrackedInfo(Trace trace) {

View file

@ -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> 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
*
* <p>
* 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
*
* <p>
* 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
*
* <p>
* 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);
}
}

View file

@ -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 { public class ShiftAndAddressSetView {
private final long shift; private final long shift;
@ -559,7 +559,8 @@ public interface DebuggerStaticMappingService {
boolean truncateExisting) throws TraceConflictedMappingException; 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)
* *
* <p> * <p>
* This will group the entries by trace and add each's entries in a single transaction. If any * 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<ModuleMapEntry> entries, TaskMonitor monitor, void addModuleMappings(Collection<ModuleMapEntry> entries, TaskMonitor monitor,
boolean truncateExisting) throws CancelledException; boolean truncateExisting) throws CancelledException;
/**
* Add a static mapping (relocation) from the given section to the given program memory block
*
* <p>
* 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) * Add several static mappings (relocations)
* *

View file

@ -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.breakpoint.DebuggerLogicalBreakpointServicePlugin;
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin; 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.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin; import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.services.*; import ghidra.app.services.*;
@ -130,12 +131,12 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera
mb.testProcess3.addRegion("echo:.text", mb.rng(0x7fac0000, 0x7fac0fff), "rx"); mb.testProcess3.addRegion("echo:.text", mb.rng(0x7fac0000, 0x7fac0fff), "rx");
try (UndoableTransaction tid = UndoableTransaction.start(trace1, "Add mapping", true)) { try (UndoableTransaction tid = UndoableTransaction.start(trace1, "Add mapping", true)) {
mappingService.addMapping( DebuggerStaticMappingUtils.addMapping(
new DefaultTraceLocation(trace1, null, Range.atLeast(0L), addr(trace1, 0x00400000)), new DefaultTraceLocation(trace1, null, Range.atLeast(0L), addr(trace1, 0x00400000)),
new ProgramLocation(program, addr(program, 0x00400000)), 0x00210000, false); new ProgramLocation(program, addr(program, 0x00400000)), 0x00210000, false);
} }
try (UndoableTransaction tid = UndoableTransaction.start(trace3, "Add mapping", true)) { try (UndoableTransaction tid = UndoableTransaction.start(trace3, "Add mapping", true)) {
mappingService.addMapping( DebuggerStaticMappingUtils.addMapping(
new DefaultTraceLocation(trace3, null, Range.atLeast(0L), addr(trace3, 0x7fac0000)), new DefaultTraceLocation(trace3, null, Range.atLeast(0L), addr(trace3, 0x7fac0000)),
new ProgramLocation(program, addr(program, 0x00400000)), 0x00010000, false); new ProgramLocation(program, addr(program, 0x00400000)), 0x00010000, false);
} }

View file

@ -20,6 +20,7 @@ import org.junit.*;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; 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.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin; import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.services.*; import ghidra.app.services.*;
@ -114,7 +115,7 @@ public class DebuggerStackPluginScreenShots extends GhidraScreenShotGenerator {
root.createFile("trace", tb.trace, TaskMonitor.DUMMY); root.createFile("trace", tb.trace, TaskMonitor.DUMMY);
root.createFile("echo", program, TaskMonitor.DUMMY); root.createFile("echo", program, TaskMonitor.DUMMY);
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
mappingService.addMapping( DebuggerStaticMappingUtils.addMapping(
new DefaultTraceLocation(tb.trace, null, Range.atLeast(snap), tb.addr(0x00400000)), new DefaultTraceLocation(tb.trace, null, Range.atLeast(snap), tb.addr(0x00400000)),
new ProgramLocation(program, addr(program, 0x00400000)), 0x10000, false); new ProgramLocation(program, addr(program, 0x00400000)), 0x10000, false);
} }

View file

@ -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.DebuggerResources.*; import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; 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.*;
import ghidra.app.services.LogicalBreakpoint.Enablement; import ghidra.app.services.LogicalBreakpoint.Enablement;
import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.listingpanel.ListingPanel;
@ -116,7 +117,7 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
protected void addMapping(Trace trace) throws Exception { protected void addMapping(Trace trace) throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add mapping", true)) { try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add mapping", true)) {
mappingService.addMapping( DebuggerStaticMappingUtils.addMapping(
new DefaultTraceLocation(trace, null, Range.atLeast(0L), addr(trace, 0x55550123)), new DefaultTraceLocation(trace, null, Range.atLeast(0L), addr(trace, 0x55550123)),
new ProgramLocation(program, addr(program, 0x00400123)), 0x1000, false); new ProgramLocation(program, addr(program, 0x00400123)), 0x1000, false);
} }

View file

@ -33,6 +33,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.breakpoint.DebuggerBreakpointsProvider.LogicalBreakpointTableModel; 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.gui.console.DebuggerConsolePlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.services.LogicalBreakpoint.Enablement; import ghidra.app.services.LogicalBreakpoint.Enablement;
import ghidra.async.AsyncTestUtils; import ghidra.async.AsyncTestUtils;
@ -71,7 +72,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
protected void addMapping(Trace trace, Program prog) throws Exception { protected void addMapping(Trace trace, Program prog) throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add mapping", true)) { try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add mapping", true)) {
mappingService.addMapping( DebuggerStaticMappingUtils.addMapping(
new DefaultTraceLocation(trace, null, Range.atLeast(0L), addr(trace, 0x55550000)), new DefaultTraceLocation(trace, null, Range.atLeast(0L), addr(trace, 0x55550000)),
new ProgramLocation(prog, addr(prog, 0x00400000)), 0x1000, false); new ProgramLocation(prog, addr(prog, 0x00400000)), 0x1000, false);
} }

View file

@ -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.BoundAction;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow; 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.gui.modules.DebuggerMissingModuleActionContext;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.async.SwingExecutorService; import ghidra.async.SwingExecutorService;
@ -461,7 +462,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
TraceLocation from = TraceLocation from =
new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000));
ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000)); ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000));
mappingService.addMapping(from, to, 0x8000, false); DebuggerStaticMappingUtils.addMapping(from, to, 0x8000, false);
} }
waitForProgram(program); waitForProgram(program);
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
@ -512,7 +513,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
TraceLocation from = TraceLocation from =
new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000));
ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000)); 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); thread = tb.getOrAddThread("Thread1", 0);
Register pc = tb.trace.getBaseLanguage().getProgramCounter(); Register pc = tb.trace.getBaseLanguage().getProgramCounter();
@ -552,7 +553,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
TraceLocation from = TraceLocation from =
new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000));
ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000)); ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000));
mappingService.addMapping(from, to, 0x8000, false); DebuggerStaticMappingUtils.addMapping(from, to, 0x8000, false);
} }
waitForProgram(program); waitForProgram(program);
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
@ -643,7 +644,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
TraceLocation from = TraceLocation from =
new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000));
ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000)); 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); thread = tb.getOrAddThread("Thread1", 0);
Register pc = tb.trace.getBaseLanguage().getProgramCounter(); Register pc = tb.trace.getBaseLanguage().getProgramCounter();
@ -906,7 +907,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
TraceLocation from = TraceLocation from =
new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000));
ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000)); ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000));
mappingService.addMapping(from, to, 0x8000, false); DebuggerStaticMappingUtils.addMapping(from, to, 0x8000, false);
} }
waitForProgram(program); waitForProgram(program);
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);

View file

@ -28,6 +28,7 @@ import com.google.common.collect.Range;
import generic.Unique; import generic.Unique;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; 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.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSet;
@ -496,7 +497,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe
TraceLocation dloc = TraceLocation dloc =
new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000));
ProgramLocation sloc = new ProgramLocation(program, addr(program, 0x00600000)); ProgramLocation sloc = new ProgramLocation(program, addr(program, 0x00600000));
mappingService.addMapping(dloc, sloc, 0x1000, false); DebuggerStaticMappingUtils.addMapping(dloc, sloc, 0x1000, false);
} }
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);

View file

@ -26,6 +26,7 @@ import org.junit.*;
import generic.Unique; import generic.Unique;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; 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.*;
import ghidra.app.services.LogicalBreakpoint.Enablement; import ghidra.app.services.LogicalBreakpoint.Enablement;
import ghidra.async.AsyncReference; import ghidra.async.AsyncReference;
@ -273,8 +274,10 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
waitFor(() -> r.getTraceMemoryRegion(region), "Recorder missed region: " + region); waitFor(() -> r.getTraceMemoryRegion(region), "Recorder missed region: " + region);
try (UndoableTransaction tid = try (UndoableTransaction tid =
UndoableTransaction.start(t, "Add .text mapping", true)) { UndoableTransaction.start(t, "Add .text mapping", true)) {
mappingService.addMapping(new DefaultTraceLocation(t, null, textRegion.getLifespan(), DebuggerStaticMappingUtils.addMapping(
textRegion.getMinAddress()), new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, new DefaultTraceLocation(t, null, textRegion.getLifespan(),
textRegion.getMinAddress()),
new ProgramLocation(p, addr(p, 0x00400000)), 0x1000,
false); false);
} }
} }

View file

@ -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());
}
}

View file

@ -75,7 +75,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
dynSpace.getAddress(0x00100000)); dynSpace.getAddress(0x00100000));
ProgramLocation to = new ProgramLocation(program, stSpace.getAddress(0x00200000)); ProgramLocation to = new ProgramLocation(program, stSpace.getAddress(0x00200000));
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
mappingService.addMapping(from, to, 0x1000, false); DebuggerStaticMappingUtils.addMapping(from, to, 0x1000, false);
} }
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
} }
@ -85,7 +85,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
dynSpace.getAddress(0x00100800)); dynSpace.getAddress(0x00100800));
ProgramLocation to = new ProgramLocation(program, stSpace.getAddress(0x00300000)); ProgramLocation to = new ProgramLocation(program, stSpace.getAddress(0x00300000));
try (UndoableTransaction tid = tb.startTransaction()) { 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)); dynSpace.getAddress(0x00102000));
ProgramLocation to = new ProgramLocation(program, stSpace.getAddress(0x00200000)); ProgramLocation to = new ProgramLocation(program, stSpace.getAddress(0x00200000));
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
mappingService.addMapping(from, to, 0x800, false); DebuggerStaticMappingUtils.addMapping(from, to, 0x800, false);
} }
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
} }

View file

@ -134,10 +134,15 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
throws IOException, LanguageNotFoundException { throws IOException, LanguageNotFoundException {
super(new DBHandle(), DBOpenMode.CREATE, TaskMonitor.DUMMY, name, DB_TIME_INTERVAL, super(new DBHandle(), DBOpenMode.CREATE, TaskMonitor.DUMMY, name, DB_TIME_INTERVAL,
DB_BUFFER_SIZE, consumer); DB_BUFFER_SIZE, consumer);
this.storeFactory = new DBCachedObjectStoreFactory(this); this.storeFactory = new DBCachedObjectStoreFactory(this);
this.baseLanguage = baseCompilerSpec.getLanguage(); this.baseLanguage = baseCompilerSpec.getLanguage();
this.baseCompilerSpec = baseCompilerSpec; // Need to "downgrade" the compiler spec, so nothing program-specific seeps in
this.baseAddressFactory = new ProgramAddressFactory(baseLanguage, baseCompilerSpec); // 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)) { try (UndoableTransaction tid = UndoableTransaction.start(this, "Create", false)) {
initOptions(DBOpenMode.CREATE); initOptions(DBOpenMode.CREATE);