diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/AbstractTracePatchInstructionAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/AbstractTracePatchInstructionAction.java index cfd3fd736d..e148abbb30 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/AbstractTracePatchInstructionAction.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/AbstractTracePatchInstructionAction.java @@ -25,6 +25,7 @@ import ghidra.app.plugin.core.assembler.AssemblyDualTextField; import ghidra.app.plugin.core.assembler.PatchInstructionAction; import ghidra.app.services.DebuggerControlService; import ghidra.app.services.DebuggerControlService.StateEditor; +import ghidra.framework.cmd.BackgroundCommand; import ghidra.program.model.address.*; import ghidra.program.model.lang.*; import ghidra.program.model.listing.CodeUnit; @@ -32,6 +33,8 @@ import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.util.DefaultLanguageService; import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.program.TraceProgramView; +import ghidra.util.Msg; +import ghidra.util.task.TaskMonitor; public abstract class AbstractTracePatchInstructionAction extends PatchInstructionAction { protected final DebuggerDisassemblerPlugin plugin; @@ -116,37 +119,55 @@ public abstract class AbstractTracePatchInstructionAction extends PatchInstructi return Assemblers.getAssembler(language); } + class PatchInstructionCommand extends BackgroundCommand { + private final byte[] data; + + public PatchInstructionCommand(byte[] data) { + this.data = data; + } + + @Override + public boolean applyTo(TraceProgramView view, TaskMonitor monitor) { + DebuggerControlService controlService = tool.getService(DebuggerControlService.class); + if (controlService == null) { + return true; + } + StateEditor editor = controlService.createStateEditor(view); + Address address = getAddress(); + + // Get code unit and dependencies before invalidating it. + CodeUnit cu = getCodeUnit(); + RegisterValue contextValue = getContextValue(cu); + TracePlatform platform = getPlatform(cu); + + try { + editor.setVariable(address, data).get(1, TimeUnit.SECONDS); + } + catch (InterruptedException | ExecutionException | TimeoutException e) { + setStatusMsg("Couldn't patch: " + e); + Msg.error(this, "Couldn't patch", e); + return false; + } + + AddressSetView set = new AddressSet(address, address.add(data.length - 1)); + TraceDisassembleCommand dis = new TraceDisassembleCommand(platform, address, set); + if (contextValue != null) { + dis.setInitialContext(contextValue); + } + dis.run(tool, view); + + return true; + } + } + @Override protected void applyPatch(byte[] data) throws MemoryAccessException { TraceProgramView view = getView(); if (view == null) { return; } - DebuggerControlService controlService = tool.getService(DebuggerControlService.class); - if (controlService == null) { - return; - } - StateEditor editor = controlService.createStateEditor(view); - Address address = getAddress(); - - // Get code unit and dependencies before invalidating it. - CodeUnit cu = getCodeUnit(); - RegisterValue contextValue = getContextValue(cu); - TracePlatform platform = getPlatform(cu); - - try { - editor.setVariable(address, data).get(1, TimeUnit.SECONDS); - } - catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new MemoryAccessException("Couldn't patch", e); - } - - AddressSetView set = new AddressSet(address, address.add(data.length - 1)); - TraceDisassembleCommand dis = new TraceDisassembleCommand(platform, address, set); - if (contextValue != null) { - dis.setInitialContext(contextValue); - } - dis.run(tool, view); + PatchInstructionCommand patch = new PatchInstructionCommand(data); + patch.run(tool, view); } protected TraceProgramView getView() { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/TracePatchDataAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/TracePatchDataAction.java index 57d4508837..682929b88f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/TracePatchDataAction.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/TracePatchDataAction.java @@ -20,6 +20,7 @@ import java.util.concurrent.*; import ghidra.app.plugin.core.assembler.PatchDataAction; import ghidra.app.services.DebuggerControlService; import ghidra.app.services.DebuggerControlService.StateEditor; +import ghidra.framework.cmd.BackgroundCommand; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressRange; import ghidra.program.model.listing.CodeUnit; @@ -27,6 +28,8 @@ import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.trace.model.listing.TraceData; import ghidra.trace.model.program.TraceProgramView; +import ghidra.util.Msg; +import ghidra.util.task.TaskMonitor; public class TracePatchDataAction extends PatchDataAction { protected final DebuggerDisassemblerPlugin plugin; @@ -41,25 +44,42 @@ public class TracePatchDataAction extends PatchDataAction { return super.isApplicableToUnit(cu) && cu instanceof TraceData; } + class PatchDataCommand extends BackgroundCommand { + private final byte[] encoded; + + public PatchDataCommand(byte[] encoded) { + this.encoded = encoded; + } + + @Override + public boolean applyTo(TraceProgramView view, TaskMonitor monitor) { + DebuggerControlService controlService = tool.getService(DebuggerControlService.class); + if (controlService == null) { + return true; + } + StateEditor editor = controlService.createStateEditor(view); + Address address = getAddress(); + + try { + editor.setVariable(address, encoded).get(1, TimeUnit.SECONDS); + // Let the trace do everything regarding existing units + return true; + } + catch (InterruptedException | ExecutionException | TimeoutException e) { + setStatusMsg("Couldn't patch: " + e); + Msg.error(this, "Couldn't patch", e); + return false; + } + } + } + @Override protected void applyPatch(AddressRange rng, byte[] encoded) throws MemoryAccessException, CodeUnitInsertionException { if (!(getProgram() instanceof TraceProgramView view)) { return; } - DebuggerControlService controlService = tool.getService(DebuggerControlService.class); - if (controlService == null) { - return; - } - StateEditor editor = controlService.createStateEditor(view); - Address address = getAddress(); - - try { - editor.setVariable(address, encoded).get(1, TimeUnit.SECONDS); - } - catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new MemoryAccessException("Couldn't patch", e); - } - // Let the trace do everything regarding existing units + PatchDataCommand patch = new PatchDataCommand(encoded); + patch.run(tool, view); } } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java index 1785086d3a..7d901bc4ce 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java @@ -466,15 +466,19 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerInteg assertTrue( helper.patchInstructionAction.isAddToPopup(listingProvider.getActionContext(null))); + long snapBefore = traceManager.getCurrent().getViewSnap(); Instruction ins = helper.patchInstructionAt(tb.addr(0x00400123), "imm r0,#0x0", "imm r0,#0x3d2"); assertEquals(2, ins.getLength()); + waitForPass(() -> assertNotEquals(snapBefore, traceManager.getCurrent().getViewSnap())); long snap = traceManager.getCurrent().getViewSnap(); assertTrue(Lifespan.isScratch(snap)); byte[] bytes = new byte[2]; - view.getMemory().getBytes(tb.addr(0x00400123), bytes); - assertArrayEquals(tb.arr(0x30, 0xd2), bytes); + waitForPass(noExc(() -> { + view.getMemory().getBytes(tb.addr(0x00400123), bytes); + assertArrayEquals(tb.arr(0x30, 0xd2), bytes); + })); } @Test @@ -517,6 +521,7 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerInteg goTo(listingProvider.getListingPanel(), new ProgramLocation(view, tb.addr(0x00400123))); assertTrue(helper.patchDataAction.isAddToPopup(listingProvider.getActionContext(null))); + long snapBefore = traceManager.getCurrent().getViewSnap(); /** * TODO: There's a bug in the trace forking: Data units are not replaced when bytes changed. @@ -525,11 +530,14 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerInteg /*Data data =*/ helper.patchDataAt(tb.addr(0x00400123), "0h", "5h"); // assertEquals(2, data.getLength()); + waitForPass(() -> assertNotEquals(snapBefore, traceManager.getCurrent().getViewSnap())); long snap = traceManager.getCurrent().getViewSnap(); assertTrue(Lifespan.isScratch(snap)); byte[] bytes = new byte[2]; - view.getMemory().getBytes(tb.addr(0x00400123), bytes); - assertArrayEquals(tb.arr(0, 5), bytes); + waitForPass(noExc(() -> { + view.getMemory().getBytes(tb.addr(0x00400123), bytes); + assertArrayEquals(tb.arr(0, 5), bytes); + })); } @Test