mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
Merge remote-tracking branch 'origin/GP-2970_Dan_emuCacheControl--SQUASHED'
This commit is contained in:
commit
fae95c7235
13 changed files with 309 additions and 83 deletions
|
@ -475,65 +475,6 @@ 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));
|
||||
}
|
||||
}
|
||||
|
||||
interface ConfigureEmulatorAction {
|
||||
String NAME = "Configure Emulator";
|
||||
String DESCRIPTION = "Choose and configure the current emulator";
|
||||
String GROUP = GROUP_MAINTENANCE;
|
||||
String HELP_ANCHOR = "configure_emulator";
|
||||
|
||||
static ToggleActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ToggleActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuGroup(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?
|
||||
|
|
|
@ -21,13 +21,17 @@ import java.util.Map.Entry;
|
|||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.ToggleDockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.action.builder.ToggleActionBuilder;
|
||||
import ghidra.app.context.ProgramLocationActionContext;
|
||||
import ghidra.app.events.ProgramActivatedPluginEvent;
|
||||
import ghidra.app.events.ProgramClosedPluginEvent;
|
||||
|
@ -35,13 +39,14 @@ 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.*;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
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.pcode.emu.PcodeMachine.*;
|
||||
import ghidra.pcode.emu.PcodeMachine.AccessKind;
|
||||
import ghidra.pcode.emu.PcodeMachine.SwiMode;
|
||||
import ghidra.pcode.exec.InjectionErrorPcodeExecutionException;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
@ -54,6 +59,7 @@ import ghidra.trace.model.thread.TraceThread;
|
|||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.trace.model.time.schedule.*;
|
||||
import ghidra.trace.model.time.schedule.Scheduler.RunResult;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
|
@ -83,6 +89,82 @@ import ghidra.util.task.TaskMonitor;
|
|||
public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEmulationService {
|
||||
protected static final int MAX_CACHE_SIZE = 5;
|
||||
|
||||
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 = DebuggerResources.ICON_EMULATE;
|
||||
String GROUP = DebuggerResources.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 = DebuggerResources.ICON_THREAD;
|
||||
String GROUP = DebuggerResources.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));
|
||||
}
|
||||
}
|
||||
|
||||
interface ConfigureEmulatorAction {
|
||||
String NAME = "Configure Emulator";
|
||||
String DESCRIPTION = "Choose and configure the current emulator";
|
||||
String GROUP = DebuggerResources.GROUP_GENERAL;
|
||||
String HELP_ANCHOR = "configure_emulator";
|
||||
|
||||
static ToggleActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ToggleActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface InvalidateEmulatorCacheAction {
|
||||
String NAME = "Invalidate Emulator Cache";
|
||||
String DESCRIPTION =
|
||||
"Prevent the emulation service from using cached snapshots from the current trace";
|
||||
String GROUP = DebuggerResources.GROUP_MAINTENANCE;
|
||||
String HELP_ANCHOR = "invalidate_cache";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuPath(DebuggerPluginPackage.NAME, ConfigureEmulatorAction.NAME, NAME)
|
||||
.menuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
protected static class CacheKey implements Comparable<CacheKey> {
|
||||
// TODO: Should key on platform, not trace
|
||||
protected final Trace trace;
|
||||
|
@ -273,6 +355,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
|
||||
DockingAction actionEmulateProgram;
|
||||
DockingAction actionEmulateAddThread;
|
||||
DockingAction actionInvalidateCache;
|
||||
Map<Class<? extends DebuggerPcodeEmulatorFactory>, ToggleDockingAction> //
|
||||
actionsChooseEmulatorFactory = new HashMap<>();
|
||||
|
||||
|
@ -302,6 +385,10 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
.popupWhen(this::emulateAddThreadEnabled)
|
||||
.onAction(this::emulateAddThreadActivated)
|
||||
.buildAndInstall(tool);
|
||||
actionInvalidateCache = InvalidateEmulatorCacheAction.builder(this)
|
||||
.enabledWhen(this::invalidateCacheEnabled)
|
||||
.onAction(this::invalidateCacheActivated)
|
||||
.buildAndInstall(tool);
|
||||
ClassSearcher.addChangeListener(classChangeListener);
|
||||
updateConfigureEmulatorStates();
|
||||
}
|
||||
|
@ -312,7 +399,8 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
|
||||
private ToggleDockingAction createActionChooseEmulator(DebuggerPcodeEmulatorFactory factory) {
|
||||
ToggleDockingAction action = ConfigureEmulatorAction.builder(this)
|
||||
.menuPath(DebuggerPluginPackage.NAME, "Configure Emulator", factory.getTitle())
|
||||
.menuPath(DebuggerPluginPackage.NAME, ConfigureEmulatorAction.NAME,
|
||||
factory.getTitle())
|
||||
.onAction(ctx -> configureEmulatorActivated(factory))
|
||||
.buildAndInstall(tool);
|
||||
String[] path = action.getMenuBarData().getMenuPath();
|
||||
|
@ -460,6 +548,23 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
}
|
||||
}
|
||||
|
||||
private boolean invalidateCacheEnabled(ActionContext ignored) {
|
||||
return traceManager.getCurrentTrace() != null;
|
||||
}
|
||||
|
||||
private void invalidateCacheActivated(ActionContext ignored) {
|
||||
DebuggerCoordinates current = traceManager.getCurrent();
|
||||
Trace trace = current.getTrace();
|
||||
long version = trace.getEmulatorCacheVersion();
|
||||
try (UndoableTransaction tid =
|
||||
UndoableTransaction.start(trace, "Invalidate Emulator Cache")) {
|
||||
trace.setEmulatorCacheVersion(version + 1);
|
||||
}
|
||||
// NB. Success should already display on screen, since it's current.
|
||||
// Failure should be reported by tool's task manager.
|
||||
traceManager.materialize(current);
|
||||
}
|
||||
|
||||
private void configureEmulatorActivated(DebuggerPcodeEmulatorFactory factory) {
|
||||
// TODO: Pull up config page. Tool Options? Program/Trace Options?
|
||||
setEmulatorFactory(factory);
|
||||
|
@ -493,7 +598,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
protected Map.Entry<CacheKey, CachedEmulator> findNearestPrefix(CacheKey key) {
|
||||
synchronized (cache) {
|
||||
Map.Entry<CacheKey, CachedEmulator> candidate = cache.floorEntry(key);
|
||||
if (candidate == null) {
|
||||
if (candidate == null || !candidate.getValue().isValid()) {
|
||||
return null;
|
||||
}
|
||||
if (!candidate.getKey().compareKey(key).related) {
|
||||
|
@ -725,7 +830,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
public DebuggerPcodeMachine<?> getCachedEmulator(Trace trace, TraceSchedule time) {
|
||||
CachedEmulator ce =
|
||||
cache.get(new CacheKey(trace.getPlatformManager().getHostPlatform(), time));
|
||||
return ce == null ? null : ce.emulator();
|
||||
return ce == null || !ce.isValid() ? null : ce.emulator();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -772,12 +772,13 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
if (coordinates.getTime().isSnapOnly()) {
|
||||
return coordinates.getSnap();
|
||||
}
|
||||
Collection<? extends TraceSnapshot> suitable = coordinates.getTrace()
|
||||
.getTimeManager()
|
||||
.getSnapshotsWithSchedule(coordinates.getTime());
|
||||
if (!suitable.isEmpty()) {
|
||||
TraceSnapshot found = suitable.iterator().next();
|
||||
return found.getKey();
|
||||
Trace trace = coordinates.getTrace();
|
||||
long version = trace.getEmulatorCacheVersion();
|
||||
for (TraceSnapshot snapshot : trace.getTimeManager()
|
||||
.getSnapshotsWithSchedule(coordinates.getTime())) {
|
||||
if (snapshot.getVersion() >= version) {
|
||||
return snapshot.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -58,7 +58,11 @@ public interface DebuggerEmulationService {
|
|||
/**
|
||||
* An emulator managed by this service
|
||||
*/
|
||||
record CachedEmulator(Trace trace, DebuggerPcodeMachine<?> emulator) {
|
||||
record CachedEmulator(Trace trace, DebuggerPcodeMachine<?> emulator, long version) {
|
||||
public CachedEmulator(Trace trace, DebuggerPcodeMachine<?> emulator) {
|
||||
this(trace, emulator, trace.getEmulatorCacheVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the trace to which the emulator is bound
|
||||
*
|
||||
|
@ -83,6 +87,15 @@ public interface DebuggerEmulationService {
|
|||
public DebuggerPcodeMachine<?> emulator() {
|
||||
return emulator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this cached emulator is still valid
|
||||
*
|
||||
* @return true if valid
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return version >= trace.getEmulatorCacheVersion();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,11 +30,13 @@ import generic.Unique;
|
|||
import generic.test.category.NightlyCategory;
|
||||
import ghidra.app.plugin.assembler.*;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOpinion;
|
||||
import ghidra.app.plugin.core.debug.service.platform.DebuggerPlatformServicePlugin;
|
||||
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
|
||||
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
|
||||
import ghidra.app.services.DebuggerStaticMappingService;
|
||||
import ghidra.pcode.exec.InterruptPcodeExecutionException;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
|
@ -592,4 +594,84 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU
|
|||
assertEquals(new BigInteger("0", 16),
|
||||
regs.getViewValue(scratch, regR2).getUnsignedValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheInvalidation() throws Throwable {
|
||||
createProgram();
|
||||
intoProject(program);
|
||||
Assembler asm = Assemblers.getAssembler(program);
|
||||
Memory memory = program.getMemory();
|
||||
Address addrText = addr(program, 0x00400000);
|
||||
Register regR0 = program.getRegister("r0");
|
||||
Register regR2 = program.getRegister("r2");
|
||||
Address addrI2;
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
|
||||
(byte) 0, TaskMonitor.DUMMY, false);
|
||||
blockText.setExecute(true);
|
||||
InstructionIterator ii = asm.assemble(addrText,
|
||||
"mov r1, r0",
|
||||
"mov r2, r1");
|
||||
ii.next();
|
||||
addrI2 = ii.next().getMinAddress();
|
||||
program.getProgramContext()
|
||||
.setValue(regR0, addrText, addrText, new BigInteger("1234", 16));
|
||||
}
|
||||
|
||||
programManager.openProgram(program);
|
||||
waitForSwing();
|
||||
codeBrowser.goTo(new ProgramLocation(program, addrText));
|
||||
waitForSwing();
|
||||
|
||||
performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionEmulateProgram, true);
|
||||
|
||||
DebuggerCoordinates current = traceManager.getCurrent();
|
||||
Trace trace = current.getTrace();
|
||||
assertNotNull(trace);
|
||||
|
||||
TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
|
||||
TraceMemorySpace regs = trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
|
||||
|
||||
// Step as written to fill the cache
|
||||
waitOn(traceManager.activateAndNotify(current.time(TraceSchedule.parse("0:t0-1")),
|
||||
ActivationCause.USER, false));
|
||||
waitForSwing();
|
||||
waitOn(traceManager.activateAndNotify(current.time(TraceSchedule.parse("0:t0-2")),
|
||||
ActivationCause.USER, false));
|
||||
waitForSwing();
|
||||
long scratch = traceManager.getCurrentView().getSnap();
|
||||
|
||||
// Sanity check
|
||||
assertEquals(new BigInteger("1234", 16),
|
||||
regs.getViewValue(scratch, regR2).getUnsignedValue());
|
||||
|
||||
// Inject some logic that would require a cache refresh to materialize
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add breakpoint")) {
|
||||
TraceBreakpoint tb = trace.getBreakpointManager()
|
||||
.addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), addrI2, Set.of(thread),
|
||||
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
|
||||
tb.setEmuSleigh("""
|
||||
r1 = 0x5678;
|
||||
emu_exec_decoded();
|
||||
""");
|
||||
}
|
||||
|
||||
// Check the cache is still valid
|
||||
waitOn(traceManager.activateAndNotify(current.time(TraceSchedule.parse("0:t0-1")),
|
||||
ActivationCause.USER, false));
|
||||
waitForSwing();
|
||||
waitOn(traceManager.activateAndNotify(current.time(TraceSchedule.parse("0:t0-2")),
|
||||
ActivationCause.USER, false));
|
||||
waitForSwing();
|
||||
assertEquals(scratch, traceManager.getCurrentView().getSnap());
|
||||
assertEquals(new BigInteger("1234", 16),
|
||||
regs.getViewValue(scratch, regR2).getUnsignedValue());
|
||||
|
||||
// Invalidate the cache. View should update immediately
|
||||
performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionInvalidateCache,
|
||||
true);
|
||||
waitForTasks();
|
||||
assertEquals(new BigInteger("5678", 16),
|
||||
regs.getViewValue(scratch, regR2).getUnsignedValue());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue