Merge remote-tracking branch 'origin/patch'

This commit is contained in:
Ryan Kurtz
2025-12-22 11:11:16 -05:00
3 changed files with 92 additions and 43 deletions

View File

@@ -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<TraceProgramView> {
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() {

View File

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

View File

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