mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
Merge branch 'GP-660_ProgramEmulation'
This commit is contained in:
commit
4612e34053
26 changed files with 1069 additions and 169 deletions
|
@ -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|
|
||||
|
|
|
@ -94,6 +94,10 @@
|
|||
sortgroup="g"
|
||||
target="help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html" />
|
||||
|
||||
<tocdef id="DebuggerEmulationServicePlugin" text="Emulation"
|
||||
sortgroup="g1"
|
||||
target="help/topics/DebuggerEmulationServicePlugin/DebuggerEmulationServicePlugin.html" />
|
||||
|
||||
<tocdef id="DebuggerRegistersPlugin" text="Registers"
|
||||
sortgroup="h"
|
||||
target="help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html" />
|
||||
|
|
|
@ -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>
|
|
@ -28,9 +28,9 @@
|
|||
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=
|
||||
"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
|
||||
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.</P>
|
||||
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.</P>
|
||||
|
||||
<H2>Table Columns</H2>
|
||||
|
||||
|
|
|
@ -50,10 +50,10 @@
|
|||
|
||||
<H2>Navigating Threads</H2>
|
||||
|
||||
<P>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 <A
|
||||
href="help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> window
|
||||
will display the activated thread's register values. <A href=
|
||||
<P>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 <A href=
|
||||
"help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> window will
|
||||
display the activated thread's register values. <A href=
|
||||
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Listing</A> 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.</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
|
||||
in this one will navigate through time. To rearrange this column, hold SHIFT while dragging.</LI>
|
||||
|
||||
<LI>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.</LI>
|
||||
</UL>
|
||||
|
||||
<H2>Navigating Time</H2>
|
||||
|
||||
<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
|
||||
<A href="help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html">Time</A> window for a way to
|
||||
column header. There are also actions for "stepping the trace" forward and backward. See the <A
|
||||
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
|
||||
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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -59,8 +59,10 @@ public class DebuggerRegistersPlugin extends AbstractDebuggerPlugin {
|
|||
|
||||
protected DebuggerRegistersProvider connectedProvider;
|
||||
|
||||
private final Map<CompilerSpec, LinkedHashSet<Register>> selectionByCSpec = new HashMap<>();
|
||||
private final Map<CompilerSpec, LinkedHashSet<Register>> favoritesByCSpec = new HashMap<>();
|
||||
private final Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> selectionByCSpec =
|
||||
new HashMap<>();
|
||||
private final Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> favoritesByCSpec =
|
||||
new HashMap<>();
|
||||
private final Set<DebuggerRegistersProvider> disconnectedProviders = new HashSet<>();
|
||||
|
||||
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 -> {
|
||||
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<CompilerSpec, LinkedHashSet<Register>> setsByCSpec,
|
||||
String encoded) {
|
||||
public static void readSetsByCSpec(
|
||||
Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> 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<Register> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -369,8 +369,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
}
|
||||
|
||||
final DebuggerRegistersPlugin plugin;
|
||||
private final Map<CompilerSpec, LinkedHashSet<Register>> selectionByCSpec;
|
||||
private final Map<CompilerSpec, LinkedHashSet<Register>> favoritesByCSpec;
|
||||
private final Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> selectionByCSpec;
|
||||
private final Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> 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<CompilerSpec, LinkedHashSet<Register>> selectionByCSpec,
|
||||
Map<CompilerSpec, LinkedHashSet<Register>> favoritesByCSpec, boolean isClone) {
|
||||
Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> selectionByCSpec,
|
||||
Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> 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<Register> 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<Register> getFavoritesFor(TraceThread thread) {
|
||||
synchronized (favoritesByCSpec) {
|
||||
CompilerSpec cSpec = thread.getTrace().getBaseCompilerSpec();
|
||||
return favoritesByCSpec.computeIfAbsent(cSpec,
|
||||
LanguageCompilerSpecPair lcsp = getLangCSpecPair(thread);
|
||||
return favoritesByCSpec.computeIfAbsent(lcsp,
|
||||
__ -> computeDefaultRegisterFavorites(thread));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<CacheKey, CachedEmulator> findNearestPrefix(CacheKey key) {
|
||||
Map.Entry<CacheKey, CachedEmulator> candidate = cache.floorEntry(key);
|
||||
if (candidate == null) {
|
||||
return null;
|
||||
synchronized (cache) {
|
||||
Map.Entry<CacheKey, CachedEmulator> 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<Long> 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<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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<SectionMapEntry> 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> 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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
*
|
||||
* <p>
|
||||
* 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,
|
||||
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)
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.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;
|
||||
|
@ -71,7 +72,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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue