GP-5314: Destroy LiveMemoryHandler

This commit is contained in:
Dan 2025-02-18 18:23:30 +00:00
parent 7c74de60e6
commit bef0660e6a
32 changed files with 667 additions and 783 deletions

View file

@ -4,9 +4,9 @@
* 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.
@ -24,7 +24,6 @@ import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.mem.LiveMemoryHandler;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
@ -53,9 +52,6 @@ public interface DebuggerControlService {
}
}
interface StateEditingMemoryHandler extends StateEditor, LiveMemoryHandler {
}
interface ControlModeChangeListener {
void modeChanged(Trace trace, ControlMode mode);
}
@ -78,5 +74,5 @@ public interface DebuggerControlService {
*/
StateEditor createStateEditor(Trace trace);
StateEditingMemoryHandler createStateEditor(TraceProgramView view);
StateEditor createStateEditor(TraceProgramView view);
}

View file

@ -4,9 +4,9 @@
* 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.
@ -17,6 +17,9 @@ package ghidra.app.plugin.core.debug.service.tracermi;
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler.*;
import ghidra.debug.api.tracermi.TraceRmiError;
import ghidra.framework.data.DomainObjectAdapterDB;
import ghidra.framework.model.TransactionInfo;
import ghidra.framework.model.TransactionListener;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register;
import ghidra.rmi.trace.TraceRmi.*;
@ -29,12 +32,51 @@ class OpenTrace implements ValueDecoder {
final DoId doId;
final Trace trace;
final TraceRmiTarget target;
final CurrentTxListener txListener;
TraceSnapshot lastSnapshot;
class CurrentTxListener implements TransactionListener {
boolean undoable;
public void markNotUndoable() {
undoable = false;
}
@Override
public void transactionStarted(DomainObjectAdapterDB domainObj, TransactionInfo tx) {
undoable = true;
}
@Override
public void transactionEnded(DomainObjectAdapterDB domainObj) {
if (!undoable) {
trace.clearUndo();
}
}
@Override
public void undoStackChanged(DomainObjectAdapterDB domainObj) {
// NOP
}
@Override
public void undoRedoOccurred(DomainObjectAdapterDB domainObj) {
// NOP
}
}
OpenTrace(DoId doId, Trace trace, TraceRmiTarget target) {
this.doId = doId;
this.trace = trace;
this.target = target;
this.txListener = new CurrentTxListener();
trace.addTransactionListener(txListener);
}
public void dispose(TraceRmiHandler consumer) {
trace.removeTransactionListener(txListener);
trace.release(consumer);
}
public TraceSnapshot createSnapshot(Snap snap, String description) {
@ -55,7 +97,7 @@ class OpenTrace implements ValueDecoder {
TraceObject object =
trace.getObjectManager().getObjectByCanonicalPath(TraceRmiHandler.toKeyPath(path));
if (required && object == null) {
throw new InvalidObjPathError();
throw new InvalidObjPathError(path.getPath());
}
return object;
}
@ -78,7 +120,7 @@ class OpenTrace implements ValueDecoder {
public AddressSpace getSpace(String name, boolean required) {
AddressSpace space = trace.getBaseAddressFactory().getAddressSpace(name);
if (required && space == null) {
throw new NoSuchAddressSpaceError();
throw new NoSuchAddressSpaceError(name);
}
return space;
}

View file

@ -90,9 +90,15 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
}
protected static class InvalidObjPathError extends TraceRmiError {
public InvalidObjPathError(String path) {
super(path);
}
}
protected static class NoSuchAddressSpaceError extends TraceRmiError {
public NoSuchAddressSpaceError(String name) {
super(name);
}
}
protected static class InvalidSchemaError extends TraceRmiError {
@ -292,8 +298,8 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
synchronized (openTxes) {
while (!openTxes.isEmpty()) {
Tid nextKey = openTxes.keySet().iterator().next();
OpenTx open = openTxes.remove(nextKey);
open.tx.close();
OpenTx openTx = openTxes.remove(nextKey);
openTx.tx.close();
}
}
@ -309,7 +315,7 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
// OK. Move on
}
}
open.trace.release(this);
open.dispose(this);
}
closed.complete(null);
plugin.listeners.invoke().disconnected(this);
@ -840,7 +846,7 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
protected ReplyCloseTrace handleCloseTrace(RequestCloseTrace req) {
OpenTrace open = requireOpenTrace(req.getOid());
openTraces.removeById(open.doId);
open.trace.release(this);
open.dispose(this);
return ReplyCloseTrace.getDefaultInstance();
}
@ -968,13 +974,27 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
Msg.error(this, "Back-end debugger aborted a transaction!");
tx.tx.abortOnClose();
}
tx.tx.close();
OpenTrace open = requireOpenTrace(tx.txId.doId);
if (!tx.undoable) {
open.trace.clearUndo();
/**
* The listener is invoked via runLater, so we must do the same here, so that events are
* processed in the order emitted.
*/
Swing.runLater(() -> open.txListener.markNotUndoable());
}
tx.tx.close();
final boolean restoreEvents;
synchronized (openTxes) {
restoreEvents = openTxes.keySet()
.stream()
.noneMatch(id -> id.doId.domObjId == req.getOid().getId());
}
if (restoreEvents) {
open.trace.setEventsEnabled(true);
}
// TODO: Check for other transactions on the same trace?
open.trace.setEventsEnabled(true);
return ReplyEndTx.getDefaultInstance();
}
@ -1278,7 +1298,7 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
@Override
public void forceCloseTrace(Trace trace) {
OpenTrace open = openTraces.removeByTrace(trace);
open.trace.release(this);
open.dispose(this);
}
@Override

View file

@ -4,9 +4,9 @@
* 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.
@ -15,12 +15,16 @@
*/
package ghidra.app.plugin.core.debug.disassemble;
import java.util.concurrent.*;
import docking.ActionContext;
import ghidra.app.plugin.assembler.Assembler;
import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock;
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.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.CodeUnit;
@ -118,12 +122,25 @@ public abstract class AbstractTracePatchInstructionAction extends PatchInstructi
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, just in case.
// Get code unit and dependencies before invalidating it.
CodeUnit cu = getCodeUnit();
RegisterValue contextValue = getContextValue(cu);
TracePlatform platform = getPlatform(cu);
view.getMemory().setBytes(address, data); // This invalidates 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) {

View file

@ -4,9 +4,9 @@
* 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.
@ -29,10 +29,7 @@ public class CurrentPlatformTracePatchInstructionAction
@Override
protected boolean isApplicableToUnit(CodeUnit cu) {
if (!super.isApplicableToUnit(cu)) {
return false;
}
return cu instanceof TraceInstruction;
return super.isApplicableToUnit(cu) && cu instanceof TraceInstruction;
}
@Override

View file

@ -4,9 +4,9 @@
* 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.
@ -170,6 +170,7 @@ public class DebuggerDisassemblerPlugin extends Plugin implements PopupActionPro
CurrentPlatformTraceDisassembleAction actionDisassemble;
CurrentPlatformTracePatchInstructionAction actionPatchInstruction;
TracePatchDataAction actionPatchData;
public DebuggerDisassemblerPlugin(PluginTool tool) {
super(tool);
@ -185,9 +186,11 @@ public class DebuggerDisassemblerPlugin extends Plugin implements PopupActionPro
protected void createActions() {
actionDisassemble = new CurrentPlatformTraceDisassembleAction(this);
actionPatchInstruction = new CurrentPlatformTracePatchInstructionAction(this);
actionPatchData = new TracePatchDataAction(this);
tool.addAction(actionDisassemble);
tool.addAction(actionPatchInstruction);
tool.addAction(actionPatchData);
}
/**

View file

@ -0,0 +1,65 @@
/* ###
* 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.disassemble;
import java.util.concurrent.*;
import ghidra.app.plugin.core.assembler.PatchDataAction;
import ghidra.app.services.DebuggerControlService;
import ghidra.app.services.DebuggerControlService.StateEditor;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.trace.model.listing.TraceData;
import ghidra.trace.model.program.TraceProgramView;
public class TracePatchDataAction extends PatchDataAction {
protected final DebuggerDisassemblerPlugin plugin;
public TracePatchDataAction(DebuggerDisassemblerPlugin plugin) {
super(plugin);
this.plugin = plugin;
}
@Override
protected boolean isApplicableToUnit(CodeUnit cu) {
return super.isApplicableToUnit(cu) && cu instanceof TraceData;
}
@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
}
}

View file

@ -0,0 +1,77 @@
/* ###
* 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.gui;
import java.util.concurrent.*;
import ghidra.app.services.DebuggerConsoleService;
import ghidra.app.services.DebuggerControlService;
import ghidra.app.services.DebuggerControlService.StateEditor;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.util.Msg;
public interface PasteIntoTargetMixin {
default boolean doHasEnoughSpace(Program program, Address address, int byteCount) {
/**
* I don't care about code units. Just check that it's within the physical bounds and valid
* memory (considering Force Full View). FFV is handled within the trace view's memory.
*/
final Address end;
try {
end = address.addNoWrap(byteCount - 1);
}
catch (AddressOverflowException e) {
return false;
}
AddressSetView range = new AddressSet(address, end);
if (!program.getMemory().intersect(range).equals(range)) {
return false;
}
return true;
}
default boolean doPasteBytes(PluginTool tool, DebuggerControlService controlService,
DebuggerConsoleService consoleService, DebuggerCoordinates current,
ProgramLocation location, byte[] bytes) {
if (!(location.getProgram() instanceof TraceProgramView view)) {
tool.setStatusInfo("Not a trace?", true);
return false;
}
StateEditor editor = controlService.createStateEditor(current);
try {
editor.setVariable(location.getByteAddress(), bytes)
.get(1, TimeUnit.SECONDS);
return true;
}
catch (InterruptedException | ExecutionException | TimeoutException e) {
if (consoleService == null) {
Msg.showError(this, null, "Paste Error",
"Couldn't paste into " + location.getProgram(),
e);
}
else {
consoleService.log(DebuggerResources.ICON_LOG_ERROR,
"Couldn't paste into " + view, e);
}
return false;
}
}
}

View file

@ -291,6 +291,24 @@ public class DebuggerListingProvider extends CodeViewerProvider {
}
protected class ForListingClipboardProvider extends CodeBrowserClipboardProvider {
protected class PasteIntoTargetCommand extends PasteByteStringCommand
implements PasteIntoTargetMixin {
protected PasteIntoTargetCommand(String string) {
super(string);
}
@Override
protected boolean hasEnoughSpace(Program program, Address address, int byteCount) {
return doHasEnoughSpace(program, address, byteCount);
}
@Override
protected boolean pasteBytes(Program program, byte[] bytes) {
return doPasteBytes(tool, controlService, consoleService, current, currentLocation,
bytes);
}
}
protected ForListingClipboardProvider() {
super(DebuggerListingProvider.this.tool, DebuggerListingProvider.this);
}
@ -317,6 +335,11 @@ public class DebuggerListingProvider extends CodeViewerProvider {
}
return super.canPaste(availableFlavors);
}
@Override
protected boolean pasteByteString(String string) {
return tool.execute(new PasteIntoTargetCommand(string), currentProgram);
}
}
private final DebuggerListingPlugin plugin;

View file

@ -21,8 +21,10 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.invoke.MethodHandles;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.*;
import javax.swing.*;
@ -40,9 +42,9 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAc
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec;
import ghidra.app.plugin.core.format.ByteBlock;
import ghidra.app.services.DebuggerControlService;
import ghidra.app.plugin.core.format.ByteBlockAccessException;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerControlService.ControlModeChangeListener;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.debug.api.action.GoToInput;
import ghidra.debug.api.action.LocationTrackingSpec;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
@ -165,6 +167,50 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
}
}
protected class ForBytesClipboardProvider extends ByteViewerClipboardProvider {
protected class PasteIntoTargetCommand extends PasteByteStringCommand
implements PasteIntoTargetMixin {
protected PasteIntoTargetCommand(String string) {
super(string);
}
@Override
protected boolean hasEnoughSpace(Program program, Address address, int byteCount) {
return doHasEnoughSpace(program, address, byteCount);
}
@Override
protected boolean pasteBytes(Program program, byte[] bytes) {
return doPasteBytes(tool, controlService, consoleService, current, currentLocation,
bytes);
}
}
protected ForBytesClipboardProvider() {
super(DebuggerMemoryBytesProvider.this, DebuggerMemoryBytesProvider.this.tool);
}
@Override
public boolean canPaste(DataFlavor[] availableFlavors) {
if (controlService == null) {
return false;
}
Trace trace = current.getTrace();
if (trace == null) {
return false;
}
if (!controlService.getCurrentMode(trace).canEdit(current)) {
return false;
}
return super.canPaste(availableFlavors);
}
@Override
protected boolean pasteByteString(String string) {
return tool.execute(new PasteIntoTargetCommand(string), currentProgram);
}
}
private final AutoReadMemorySpec defaultReadMemorySpec =
AutoReadMemorySpec.fromConfigName(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME);
@ -172,6 +218,8 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
@AutoServiceConsumed
private DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
private DebuggerConsoleService consoleService;
//@AutoServiceConsumed via method
private DebuggerControlService controlService;
@SuppressWarnings("unused")
@ -272,23 +320,83 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
};
}
/**
* Override where edits are allowed and direct sets through the control service.
*/
class TargetByteBlock extends MemoryByteBlock {
protected TargetByteBlock(Program program, Memory memory, MemoryBlock block) {
super(program, memory, block);
}
/**
* {@inheritDoc}
*
* <p>
* Overridden to ignore existing instructions. Let them be clobbered!
*/
@Override
protected boolean editAllowed(Address addr, long length) {
return controlService != null;
}
protected ByteBuffer alloc(int size) {
return ByteBuffer.allocate(size)
.order(isBigEndian()
? ByteOrder.BIG_ENDIAN
: ByteOrder.LITTLE_ENDIAN);
}
protected void doSet(Address address, ByteBuffer buffer) throws ByteBlockAccessException {
checkEditsAllowed(address, buffer.capacity());
try {
controlService.createStateEditor(current)
.setVariable(address, buffer.array())
.get(1, TimeUnit.SECONDS);
}
catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new ByteBlockAccessException("Could not set target memory", e);
}
}
@Override
public void setByte(BigInteger index, byte value) throws ByteBlockAccessException {
doSet(getAddress(index), alloc(Byte.BYTES).put(value));
}
@Override
public void setShort(BigInteger index, short value) throws ByteBlockAccessException {
doSet(getAddress(index), alloc(Short.BYTES).putShort(value));
}
@Override
public void setInt(BigInteger index, int value) throws ByteBlockAccessException {
doSet(getAddress(index), alloc(Integer.BYTES).putInt(value));
}
@Override
public void setLong(BigInteger index, long value) throws ByteBlockAccessException {
doSet(getAddress(index), alloc(Long.BYTES).putLong(value));
}
}
class TargetByteBlockSet extends ProgramByteBlockSet {
protected TargetByteBlockSet(ByteBlockChangeManager changeManager) {
super(DebuggerMemoryBytesProvider.this, DebuggerMemoryBytesProvider.this.program,
changeManager);
}
@Override
protected MemoryByteBlock newMemoryByteBlock(Memory memory, MemoryBlock memBlock) {
return new TargetByteBlock(program, memory, memBlock);
}
}
@Override
protected ProgramByteBlockSet newByteBlockSet(ByteBlockChangeManager changeManager) {
if (program == null) {
return null;
}
// A bit of work to get it to ignore existing instructions. Let them be clobbered!
return new ProgramByteBlockSet(this, program, changeManager) {
@Override
protected MemoryByteBlock newMemoryByteBlock(Memory memory, MemoryBlock memBlock) {
return new MemoryByteBlock(program, memory, memBlock) {
@Override
protected boolean editAllowed(Address addr, long length) {
return true;
}
};
}
};
return new TargetByteBlockSet(changeManager);
}
/**
@ -373,22 +481,7 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
@Override
protected ByteViewerClipboardProvider newClipboardProvider() {
return new ByteViewerClipboardProvider(this, tool) {
@Override
public boolean canPaste(DataFlavor[] availableFlavors) {
if (controlService == null) {
return false;
}
Trace trace = current.getTrace();
if (trace == null) {
return false;
}
if (!controlService.getCurrentMode(trace).canEdit(current)) {
return false;
}
return super.canPaste(availableFlavors);
}
};
return new ForBytesClipboardProvider();
}
@AutoServiceConsumed

View file

@ -15,14 +15,13 @@
*/
package ghidra.app.plugin.core.debug.service.control;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.debug.api.control.ControlMode;
@ -31,11 +30,8 @@ import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.Address;
import ghidra.program.model.mem.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceProgramViewListener;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceProgramViewMemory;
import ghidra.util.datastruct.ListenerSet;
@PluginInfo(
@ -45,9 +41,7 @@ import ghidra.util.datastruct.ListenerSet;
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
eventsConsumed = {
TraceOpenedPluginEvent.class,
TraceActivatedPluginEvent.class,
TraceClosedPluginEvent.class,
},
servicesRequired = {
DebuggerTraceManagerService.class,
@ -115,8 +109,7 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
}
}
public class FollowsViewStateEditor extends AbstractStateEditor
implements StateEditingMemoryHandler {
public class FollowsViewStateEditor extends AbstractStateEditor {
private final TraceProgramView view;
public FollowsViewStateEditor(TraceProgramView view) {
@ -132,81 +125,11 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
public DebuggerCoordinates getCoordinates() {
return traceManager.resolveView(view);
}
@Override
public void clearCache() {
// Nothing to do
}
@Override
public byte getByte(Address addr) throws MemoryAccessException {
ByteBuffer buf = ByteBuffer.allocate(1);
view.getTrace().getMemoryManager().getViewBytes(view.getSnap(), addr, buf);
return buf.get(0);
}
@Override
public int getBytes(Address address, byte[] buffer, int startIndex, int size)
throws MemoryAccessException {
return view.getTrace()
.getMemoryManager()
.getViewBytes(view.getSnap(), address,
ByteBuffer.wrap(buffer, startIndex, size));
}
@Override
public void putByte(Address address, byte value) throws MemoryAccessException {
try {
setVariable(address, new byte[] { value }).get(1, TimeUnit.SECONDS);
}
catch (ExecutionException e) {
throw new MemoryAccessException("Failed to write " + address + ": " + e.getCause());
}
catch (TimeoutException | InterruptedException e) {
throw new MemoryAccessException("Failed to write " + address + ": " + e);
}
}
@Override
public int putBytes(Address address, byte[] source, int startIndex, int size)
throws MemoryAccessException {
try {
setVariable(address, Arrays.copyOfRange(source, startIndex, startIndex + size))
.get(1, TimeUnit.SECONDS);
}
catch (ExecutionException e) {
throw new MemoryAccessException("Failed to write " + address + ": " + e.getCause());
}
catch (TimeoutException | InterruptedException e) {
throw new MemoryAccessException("Failed to write " + address + ": " + e);
}
return size;
}
@Override
public void addLiveMemoryListener(LiveMemoryListener listener) {
throw new UnsupportedOperationException();
}
@Override
public void removeLiveMemoryListener(LiveMemoryListener listener) {
throw new UnsupportedOperationException();
}
}
protected class ListenerForEditorInstallation implements TraceProgramViewListener {
@Override
public void viewCreated(TraceProgramView view) {
installMemoryEditor(view);
}
}
//@AutoServiceConsumed // via method
@AutoServiceConsumed
private DebuggerTraceManagerService traceManager;
protected final ListenerForEditorInstallation listenerForEditorInstallation =
new ListenerForEditorInstallation();
private final Map<Trace, ControlMode> currentModes = new HashMap<>();
private final ListenerSet<ControlModeChangeListener> listeners =
@ -219,7 +142,6 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
@Override
protected void dispose() {
super.dispose();
uninstallAllMemoryEditors();
}
@Override
@ -265,7 +187,7 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
}
@Override
public StateEditingMemoryHandler createStateEditor(TraceProgramView view) {
public StateEditor createStateEditor(TraceProgramView view) {
return new FollowsViewStateEditor(view);
}
@ -292,78 +214,11 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
}
}
protected void installMemoryEditor(TraceProgramView view) {
TraceProgramViewMemory memory = view.getMemory();
if (memory.getLiveMemoryHandler() != null) {
return;
}
memory.setLiveMemoryHandler(createStateEditor(view));
}
protected void uninstallMemoryEditor(TraceProgramView view) {
TraceProgramViewMemory memory = view.getMemory();
LiveMemoryHandler handler = memory.getLiveMemoryHandler();
if (!(handler instanceof StateEditingMemoryHandler)) {
return;
}
StateEditingMemoryHandler editor = (StateEditingMemoryHandler) handler;
if (editor.getService() != this) {
return;
}
memory.setLiveMemoryHandler(null);
}
protected void installAllMemoryEditors(Trace trace) {
trace.addProgramViewListener(listenerForEditorInstallation);
for (TraceProgramView view : trace.getAllProgramViews()) {
installMemoryEditor(view);
}
}
protected void installAllMemoryEditors() {
if (traceManager == null) {
return;
}
for (Trace trace : traceManager.getOpenTraces()) {
installAllMemoryEditors(trace);
}
}
protected void uninstallAllMemoryEditors(Trace trace) {
trace.removeProgramViewListener(listenerForEditorInstallation);
for (TraceProgramView view : trace.getAllProgramViews()) {
uninstallMemoryEditor(view);
}
}
protected void uninstallAllMemoryEditors() {
if (traceManager == null) {
return;
}
for (Trace trace : traceManager.getOpenTraces()) {
uninstallAllMemoryEditors(trace);
}
}
@Override
public void processEvent(PluginEvent event) {
super.processEvent(event);
if (event instanceof TraceOpenedPluginEvent ev) {
installAllMemoryEditors(ev.getTrace());
}
else if (event instanceof TraceActivatedPluginEvent ev) {
if (event instanceof TraceActivatedPluginEvent ev) {
coordinatesActivated(ev.getActiveCoordinates(), ev.getCause());
}
else if (event instanceof TraceClosedPluginEvent ev) {
uninstallAllMemoryEditors(ev.getTrace());
}
}
@AutoServiceConsumed
private void setTraceManager(DebuggerTraceManagerService traceManager) {
uninstallAllMemoryEditors();
this.traceManager = traceManager;
installAllMemoryEditors();
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -22,6 +22,7 @@ import ghidra.program.model.listing.Program;
public class DebuggerDisassemblerPluginTestHelper extends AssemblerPluginTestHelper {
public DebuggerDisassemblerPluginTestHelper(DebuggerDisassemblerPlugin disassemblerPlugin,
CodeViewerProvider provider, Program program) {
super(disassemblerPlugin.actionPatchInstruction, null, provider, program);
super(disassemblerPlugin.actionPatchInstruction, disassemblerPlugin.actionPatchData,
provider, program);
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -225,16 +225,6 @@ public class DBTraceGuestPlatformMappedMemory implements Memory {
return guest.getLanguage().isBigEndian();
}
@Override
public void setLiveMemoryHandler(LiveMemoryHandler handler) {
throw new UnsupportedOperationException();
}
@Override
public LiveMemoryHandler getLiveMemoryHandler() {
return null;
}
@Override
public MemoryBlock createInitializedBlock(String name, Address start, InputStream is,
long length, TaskMonitor monitor, boolean overlay)

View file

@ -4,9 +4,9 @@
* 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.
@ -47,8 +47,6 @@ public abstract class AbstractDBTraceProgramViewMemory
protected boolean forceFullView = false;
protected long snap;
protected LiveMemoryHandler memoryWriteRedirect;
private static final int CACHE_PAGE_COUNT = 3;
protected final ByteCache cache = new ByteCache(CACHE_PAGE_COUNT) {
@Override
@ -162,16 +160,6 @@ public abstract class AbstractDBTraceProgramViewMemory
return program.getLanguage().isBigEndian();
}
@Override
public void setLiveMemoryHandler(LiveMemoryHandler handler) {
this.memoryWriteRedirect = handler;
}
@Override
public LiveMemoryHandler getLiveMemoryHandler() {
return memoryWriteRedirect;
}
@Override
public MemoryBlock createInitializedBlock(String name, Address start, InputStream is,
long length, TaskMonitor monitor, boolean overlay)
@ -339,10 +327,6 @@ public abstract class AbstractDBTraceProgramViewMemory
@Override
public void setByte(Address addr, byte value) throws MemoryAccessException {
if (memoryWriteRedirect != null) {
memoryWriteRedirect.putByte(addr, value);
return;
}
DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true);
if (space.putBytes(snap, addr, ByteBuffer.wrap(new byte[] { value })) != 1) {
throw new MemoryAccessException();
@ -352,10 +336,6 @@ public abstract class AbstractDBTraceProgramViewMemory
@Override
public void setBytes(Address addr, byte[] source, int sIndex, int size)
throws MemoryAccessException {
if (memoryWriteRedirect != null) {
memoryWriteRedirect.putBytes(addr, source, sIndex, size);
return;
}
DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true);
if (space.putBytes(snap, addr, ByteBuffer.wrap(source, sIndex, size)) != size) {
throw new MemoryAccessException();

View file

@ -15,7 +15,6 @@
*/
package ghidra.trace.model.program;
import ghidra.program.model.mem.LiveMemoryHandler;
import ghidra.program.model.mem.Memory;
public interface TraceProgramViewMemory extends Memory, SnapSpecificTraceView {
@ -25,13 +24,4 @@ public interface TraceProgramViewMemory extends Memory, SnapSpecificTraceView {
void setForceFullView(boolean forceFullView);
boolean isForceFullView();
/**
* {@inheritDoc}
*
* <p>
* For trace views, this only redirects memory writes.
*/
@Override
void setLiveMemoryHandler(LiveMemoryHandler handler);
}

View file

@ -4,9 +4,9 @@
* 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.
@ -39,13 +39,18 @@ import ghidra.program.model.mem.MemBuffer;
* The API for instruction assembly is available from {@link Assemblers}. For data assembly, the API
* is in {@link DataType#encodeRepresentation(String, MemBuffer, Settings, int)}.
*/
@PluginInfo(status = PluginStatus.RELEASED, packageName = CorePluginPackage.NAME, category = "Patching", shortDescription = "Assembler", description = "This plugin provides functionality for assembly patching. " +
"The assembler supports most processor languages also supported by the " +
"disassembler. Depending on the particular processor, your mileage may vary. " +
"We are in the process of testing and improving support for all our processors. " +
"You can access the assembler by pressing Ctrl-Shift-G, and then modifying the " +
"instruction in place. As you type, a content assist will guide you and provide " +
"assembled bytes when you have a complete instruction.")
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = CorePluginPackage.NAME,
category = "Patching",
shortDescription = "Assembler",
description = "This plugin provides functionality for assembly patching. " +
"The assembler supports most processor languages also supported by the " +
"disassembler. Depending on the particular processor, your mileage may vary. " +
"We are in the process of testing and improving support for all our processors. " +
"You can access the assembler by pressing Ctrl-Shift-G, and then modifying the " +
"instruction in place. As you type, a content assist will guide you and provide " +
"assembled bytes when you have a complete instruction.")
public class AssemblerPlugin extends ProgramPlugin {
public static final String ASSEMBLER_NAME = "Assembler";
@ -58,35 +63,39 @@ public class AssemblerPlugin extends ProgramPlugin {
}
private void createActions() {
// Debugger provides its own "Patch Instruction" action
// Debugger provides its own "Patch" actions
patchInstructionAction = new PatchInstructionAction(this) {
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!super.isEnabledForContext(context)) {
return false;
}
if (!(context instanceof ListingActionContext)) {
return false;
}
ListingActionContext lac = (ListingActionContext) context;
return !lac.getNavigatable().isDynamic();
return super.isEnabledForContext(context) &&
context instanceof ListingActionContext lac &&
!lac.getNavigatable().isDynamic();
}
@Override
public boolean isAddToPopup(ActionContext context) {
if (!super.isAddToPopup(context)) {
return false;
}
if (!(context instanceof ListingActionContext)) {
return false;
}
ListingActionContext lac = (ListingActionContext) context;
return !lac.getNavigatable().isDynamic();
return super.isAddToPopup(context) &&
context instanceof ListingActionContext lac &&
!lac.getNavigatable().isDynamic();
}
};
tool.addAction(patchInstructionAction);
patchDataAction = new PatchDataAction(this);
patchDataAction = new PatchDataAction(this) {
@Override
public boolean isEnabledForContext(ActionContext context) {
return super.isEnabledForContext(context) &&
context instanceof ListingActionContext lac &&
!lac.getNavigatable().isDynamic();
}
@Override
public boolean isAddToPopup(ActionContext context) {
return super.isAddToPopup(context) &&
context instanceof ListingActionContext lac &&
!lac.getNavigatable().isDynamic();
}
};
tool.addAction(patchDataAction);
}

View file

@ -4,9 +4,9 @@
* 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.
@ -73,14 +73,7 @@ public class PatchDataAction extends AbstractPatchAction {
@Override
protected boolean isApplicableToUnit(CodeUnit cu) {
if (!(cu instanceof Data)) {
return false;
}
Data data = (Data) cu;
if (!data.getBaseDataType().isEncodable()) {
return false;
}
return true;
return cu instanceof Data data && data.getBaseDataType().isEncodable();
}
protected Data getData() {
@ -113,6 +106,23 @@ public class PatchDataAction extends AbstractPatchAction {
input.setCaretPosition(repr.length());
}
protected void applyPatch(AddressRange rng, byte[] encoded)
throws MemoryAccessException, CodeUnitInsertionException {
Program program = getProgram();
Address address = getAddress();
Data data = getData();
DataType dt = data.getBaseDataType();
int oldLength = data.getLength();
if (encoded.length != oldLength) {
program.getListing().clearCodeUnits(address, rng.getMaxAddress(), false);
}
program.getMemory().setBytes(address, encoded);
if (encoded.length != oldLength) {
program.getListing().createData(address, dt, encoded.length);
}
}
@Override
public void accept() {
Program program = getProgram();
@ -135,14 +145,7 @@ public class PatchDataAction extends AbstractPatchAction {
}
try (Transaction tx =
program.openTransaction("Patch Data @" + address + ": " + input.getText())) {
int oldLength = data.getLength();
if (encoded.length != oldLength) {
program.getListing().clearCodeUnits(address, rng.getMaxAddress(), false);
}
program.getMemory().setBytes(address, encoded);
if (encoded.length != oldLength) {
program.getListing().createData(address, dt, encoded.length);
}
applyPatch(rng, encoded);
hide();
}
catch (MemoryAccessException e) {

View file

@ -4,9 +4,9 @@
* 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.
@ -25,7 +25,6 @@ import docking.dnd.GenericDataFlavor;
import docking.dnd.StringTransferable;
import docking.widgets.OptionDialog;
import ghidra.framework.cmd.Command;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
@ -38,8 +37,7 @@ import ghidra.util.*;
import ghidra.util.task.TaskMonitor;
/**
* Base class that can copy bytes into a Transferable object, and paste
* bytes into a program.
* Base class that can copy bytes into a Transferable object, and paste bytes into a program.
*
*/
public abstract class ByteCopier {
@ -381,125 +379,12 @@ public abstract class ByteCopier {
}
protected boolean pasteByteString(final String string) {
Command cmd = new Command() {
private String status = "Pasting";
@Override
public boolean applyTo(DomainObject domainObject) {
if (!(domainObject instanceof Program)) {
return false;
}
String validString = string;
if (!isOnlyAsciiBytes(string)) {
tool.setStatusInfo("Pasted string contained non-text ascii bytes. " +
"Only the ascii will be used.", true);
validString = keepOnlyAsciiBytes(string);
}
byte[] bytes = getBytes(validString);
if (bytes == null) {
status = "Improper data format. Expected sequence of hex bytes";
tool.beep();
return false;
}
// Ensure that we are not writing over instructions
Program program = (Program) domainObject;
Address address = currentLocation.getAddress();
if (!hasEnoughSpace(program, address, bytes.length)) {
status =
"Not enough space to paste all bytes. Encountered data or instructions.";
tool.beep();
return false;
}
// Ask the user before pasting a string into the program. Since having a string in
// the clipboard is so common, this is to prevent an accidental paste.
if (!confirmPaste(validString)) {
return true; // the user cancelled; the command is successful
}
boolean pastedAllBytes = pasteBytes(program, bytes);
if (!pastedAllBytes) {
tool.setStatusInfo("Not all bytes were pasted due to memory access issues",
true);
}
return true;
}
private boolean pasteBytes(Program program, byte[] bytes) {
// note: loop one byte at a time here, since Memory will validate all addresses
// before pasting any bytes
boolean foundError = false;
Address address = currentLocation.getAddress();
Memory memory = program.getMemory();
for (byte element : bytes) {
try {
memory.setByte(address, element);
}
catch (MemoryAccessException e) {
// Keep trying the remaining bytes. Should we just stop in this case?
foundError = true;
}
address = address.next();
}
return !foundError;
}
private boolean confirmPaste(String validString) {
// create a truncated version of the string to show in the dialog
String partialText = validString.length() < 40 ? validString
: validString.substring(0, 40) + " ...";
int result = OptionDialog.showYesNoDialog(null, "Paste String Into Program?",
"Are you sure you want to paste the string \"" + partialText +
"\"\n into the program's memory?");
return result != OptionDialog.NO_OPTION;
}
private boolean hasEnoughSpace(Program program, Address address, int byteCount) {
Listing listing = program.getListing();
for (int i = 0; i < byteCount;) {
if (address == null) {
status = "Not enough addresses to paste bytes";
tool.beep();
return false;
}
CodeUnit codeUnit = listing.getCodeUnitContaining(address);
if (!(codeUnit instanceof Data) || ((Data) codeUnit).isDefined()) {
status = "Cannot paste on top of defined instructions/data";
tool.beep();
return false;
}
int length = codeUnit.getLength();
i += length;
address = codeUnit.getMaxAddress().next();
}
return true;
}
@Override
public String getStatusMsg() {
return status;
}
@Override
public String getName() {
return "Paste";
}
};
return tool.execute(cmd, currentProgram);
return tool.execute(new PasteByteStringCommand(string), currentProgram);
}
/**
* Create a Transferable from the given text.
*
* @param text text used to create a Transferable
* @return a Transferable
*/
@ -511,8 +396,120 @@ public abstract class ByteCopier {
// Inner Classes
//==================================================================================================
protected class PasteByteStringCommand implements Command<Program> {
protected final String string;
private String status = "Pasting";
protected PasteByteStringCommand(String string) {
this.string = string;
}
@Override
public boolean applyTo(Program program) {
String validString = string;
if (!isOnlyAsciiBytes(string)) {
tool.setStatusInfo("Pasted string contained non-text ascii bytes. " +
"Only the ascii will be used.", true);
validString = keepOnlyAsciiBytes(string);
}
byte[] bytes = getBytes(validString);
if (bytes == null) {
status = "Improper data format. Expected sequence of hex bytes";
tool.beep();
return false;
}
// Ensure that we are not writing over instructions
Address address = currentLocation.getAddress();
if (!hasEnoughSpace(program, address, bytes.length)) {
status =
"Not enough space to paste all bytes. Encountered data or instructions.";
tool.beep();
return false;
}
// Ask the user before pasting a string into the program. Since having a string in
// the clipboard is so common, this is to prevent an accidental paste.
if (!confirmPaste(validString)) {
return true; // the user cancelled; the command is successful
}
boolean pastedAllBytes = pasteBytes(program, bytes);
if (!pastedAllBytes) {
tool.setStatusInfo("Not all bytes were pasted due to memory access issues",
true);
}
return true;
}
protected boolean pasteBytes(Program program, byte[] bytes) {
// note: loop one byte at a time here, since Memory will validate all addresses
// before pasting any bytes
boolean foundError = false;
Address address = currentLocation.getAddress();
Memory memory = program.getMemory();
for (byte element : bytes) {
try {
memory.setByte(address, element);
}
catch (MemoryAccessException e) {
// Keep trying the remaining bytes. Should we just stop in this case?
foundError = true;
}
address = address.next();
}
return !foundError;
}
protected boolean confirmPaste(String validString) {
// create a truncated version of the string to show in the dialog
String partialText = validString.length() < 40 ? validString
: validString.substring(0, 40) + " ...";
int result = OptionDialog.showYesNoDialog(null, "Paste String Into Program?",
"Are you sure you want to paste the string \"" + partialText +
"\"\n into the program's memory?");
return result != OptionDialog.NO_OPTION;
}
protected boolean hasEnoughSpace(Program program, Address address, int byteCount) {
Listing listing = program.getListing();
for (int i = 0; i < byteCount;) {
if (address == null) {
status = "Not enough addresses to paste bytes";
tool.beep();
return false;
}
CodeUnit codeUnit = listing.getCodeUnitContaining(address);
if (!(codeUnit instanceof Data data) || data.isDefined()) {
status = "Cannot paste on top of defined instructions/data";
tool.beep();
return false;
}
int length = codeUnit.getLength();
i += length;
address = codeUnit.getMaxAddress().next();
}
return true;
}
@Override
public String getStatusMsg() {
return status;
}
@Override
public String getName() {
return "Paste";
}
}
/**
* An iterator of bytes from memory. This class exists because the {@link MemoryByteIterator}
* An iterator of bytes from memory. This class exists because the {@link MemoryByteIterator}
* throws an exception from its next() method, which will not work for us.
*/
private static class ByteIterator implements Iterator<Byte> {

View file

@ -4,9 +4,9 @@
* 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.
@ -486,78 +486,6 @@ public class MemoryManagerTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals(block.isWrite(), newBlock.isWrite());
}
@Test
public void testLiveMemory() throws Exception {
mem.createInitializedBlock("Test", addr(0), 0x1000, (byte) 0x55, null, false);
LiveMemoryHandler testHandler = new LiveMemoryHandler() {
@Override
public void clearCache() {
}
@Override
public byte getByte(Address addr) throws MemoryAccessException {
return 0;
}
@Override
public int getBytes(Address addr, byte[] dest, int dIndex, int size)
throws MemoryAccessException {
for (int i = 0; i < size; ++i) {
dest[dIndex + i] = (byte) i;
}
return size;
}
@Override
public void putByte(Address addr, byte value) {
}
@Override
public int putBytes(Address address, byte[] source, int sIndex, int size)
throws MemoryAccessException {
return 0;
}
@Override
public void addLiveMemoryListener(LiveMemoryListener listener) {
// TODO Auto-generated method stub
}
@Override
public void removeLiveMemoryListener(LiveMemoryListener listener) {
// TODO Auto-generated method stub
}
};
assertEquals((byte) 0x55, mem.getByte(addr(0x500)));
mem.setLiveMemoryHandler(testHandler);
byte[] bytes = new byte[5];
mem.getBytes(addr(0x1000), bytes);
for (int i = 0; i < bytes.length; ++i) {
assertEquals(i, bytes[i]);
}
assertEquals((byte) 0, mem.getByte(addr(0x500)));
mem.setLiveMemoryHandler(null);
try {
mem.getBytes(addr(0x1000), bytes);
Assert.fail();
}
catch (MemoryAccessException e) {
}
assertEquals((byte) 0x55, mem.getByte(addr(0x500)));
}
@Test
public void testSplitBlock() throws Exception {
createBlock("Test", addr(0), 100);

View file

@ -4,9 +4,9 @@
* 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.
@ -67,16 +67,6 @@ public class MyTestMemory extends AddressSet implements Memory {
return false;
}
@Override
public void setLiveMemoryHandler(LiveMemoryHandler handler) {
throw new UnsupportedOperationException();
}
@Override
public LiveMemoryHandler getLiveMemoryHandler() {
throw new UnsupportedOperationException();
}
@Override
public MemoryBlock createInitializedBlock(String name, Address start, InputStream is,
long length, TaskMonitor monitor, boolean overlay)

View file

@ -18,26 +18,33 @@ package ghidra.app.plugin.core.format;
import ghidra.util.exception.UsrException;
/**
* <p>An ByteBlockAccessException indicates that the attempted
* access is not permitted. (i.e. Readable/Writeable)</p>
*
* A {@code ByteBlockAccessException} indicates that the attempted access is not permitted. (i.e.
* Readable/Writeable)
*/
public class ByteBlockAccessException extends UsrException {
/**
* <p>Constructs an ByteBlockAccessException with no detail message.
/**
* Construct an exception with no details
*/
public ByteBlockAccessException() {
super();
}
/**
* <p>Constructs an ByteBlockAccessException with the specified
* detail message.
public ByteBlockAccessException() {
super();
}
/**
* Construct an exception with the specified message
*
* @param message The message.
* @param message the message
*/
public ByteBlockAccessException(String message) {
super(message);
}
}
public ByteBlockAccessException(String message) {
super(message);
}
/**
* Construct an exception with the specified message and cause
*
* @param message the message
* @param cause the cause
*/
public ByteBlockAccessException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -49,19 +49,24 @@ public abstract class Transaction implements AutoCloseable {
}
/**
* End this transaction if currently active.
* @param commit true if changes should be commited, false if all changes in this transaction
* should be discarded (i.e., rollback). If this is a "sub-transaction" and commit is false,
* the larger transaction will rollback upon completion.
* @return true if changes have been commited or false if nothing to commit or commit parameter
* was specified as false.
* End this transaction if currently active.
*
* @param commit true if changes should be committed, false if all changes in this transaction
* should be discarded (i.e., rollback). If this is a "sub-transaction" and commit is
* false, the larger transaction will rollback upon completion.
* @return true if changes have been committed or false if nothing to commit or commit parameter
* was specified as false.
*/
abstract protected boolean endTransaction(@SuppressWarnings("hiding") boolean commit);
/**
* Determine if this is a sub-transaction to a larger transaction. If true is returned the
* larger transaction will not complete until all sub-transactions have ended. The larger
* transaction will rollback upon completion if any of the sub-transactions do not commit.
* Determine if this is a sub-transaction to a larger transaction.
*
* <p>
* If true is returned the larger transaction will not complete until all sub-transactions have
* ended. The larger transaction will rollback upon completion if any of the sub-transactions do
* not commit.
*
* @return true if this is a sub-transaction, else false.
*/
public boolean isSubTransaction() {
@ -69,16 +74,21 @@ public abstract class Transaction implements AutoCloseable {
}
/**
* Mark transaction for rollback/non-commit upon closing. A subsequent invocation of
* {@link #commitOnClose()} will alter this state prior to closing.
* Mark transaction for rollback/non-commit upon closing.
*
* <p>
* A subsequent invocation of {@link #commitOnClose()} will alter this state prior to closing.
*/
public void abortOnClose() {
commit = false;
}
/**
* Mark transaction for commit upon closing. This state is assumed by default. A subsequent
* invocation of {@link #abortOnClose()} will alter this state prior to closing.
* Mark transaction for commit upon closing.
*
* <p>
* This state is assumed by default. A subsequent invocation of {@link #abortOnClose()} will
* alter this state prior to closing.
*/
public void commitOnClose() {
commit = true;
@ -106,7 +116,9 @@ public abstract class Transaction implements AutoCloseable {
/**
* End this transaction if active using the current commit state.
* See {@link #commitOnClose()}, {@link #abortOnClose()}.
*
* @see #commitOnClose()
* @see #abortOnClose()
*/
@Override
public void close() {
@ -115,5 +127,4 @@ public abstract class Transaction implements AutoCloseable {
endTransaction(commit);
}
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -20,8 +20,7 @@ import java.util.*;
import ghidra.framework.model.*;
import ghidra.framework.model.TransactionInfo.Status;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.*;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
@ -192,9 +191,11 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
}
/**
* Returns the undo stack depth.
* (The number of items on the undo stack)
* Returns the undo stack depth (The number of items on the undo stack).
*
* <p>
* This method is for JUnits.
*
* @return the undo stack depth
*/
@Override
@ -333,7 +334,7 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
}
void notifyStartTransaction(TransactionInfo tx) {
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
for (TransactionListener listener : transactionListeners) {
listener.transactionStarted(domainObj, tx);
listener.undoStackChanged(domainObj);
@ -342,7 +343,7 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
}
void notifyEndTransaction() {
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
for (TransactionListener listener : transactionListeners) {
listener.transactionEnded(domainObj);
listener.undoStackChanged(domainObj);
@ -351,7 +352,7 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
}
void notifyUndoStackChanged() {
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
for (TransactionListener listener : transactionListeners) {
listener.undoStackChanged(domainObj);
}
@ -359,7 +360,7 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
}
void notifyUndoRedo() {
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
for (TransactionListener listener : transactionListeners) {
listener.undoRedoOccurred(domainObj);
listener.undoStackChanged(domainObj);

View file

@ -4,9 +4,9 @@
* 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.
@ -46,8 +46,8 @@ public interface TransactionInfo {
/**
* Determine if the corresponding transaction, and all of its sub-transactions, has been
* comitted to the underlying database.
* @return true if the corresponding transaction has been comitted, else false.
* committed to the underlying database.
* @return true if the corresponding transaction has been committed, else false.
*/
public boolean hasCommittedDBTransaction();

View file

@ -4,9 +4,9 @@
* 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.
@ -362,9 +362,6 @@ public class MemoryBlockDB implements MemoryBlock {
@Override
public byte getByte(Address addr) throws MemoryAccessException {
if (memMap.getLiveMemoryHandler() != null) {
return memMap.getByte(addr);
}
checkValid();
long offset = getBlockOffset(addr);
return getByte(offset);
@ -378,10 +375,6 @@ public class MemoryBlockDB implements MemoryBlock {
@Override
public int getBytes(Address addr, byte[] b, int off, int len)
throws IndexOutOfBoundsException, MemoryAccessException {
if (memMap.getLiveMemoryHandler() != null) {
return memMap.getBytes(addr, b, off, len);
}
checkValid();
long offset = getBlockOffset(addr);
return getBytes(offset, b, off, len);
@ -389,10 +382,6 @@ public class MemoryBlockDB implements MemoryBlock {
@Override
public void putByte(Address addr, byte b) throws MemoryAccessException {
if (memMap.getLiveMemoryHandler() != null) {
memMap.setByte(addr, b);
return;
}
long offset = getBlockOffset(addr);
memMap.lock.acquire();
try {
@ -414,10 +403,6 @@ public class MemoryBlockDB implements MemoryBlock {
@Override
public int putBytes(Address addr, byte[] b, int off, int len)
throws IndexOutOfBoundsException, MemoryAccessException {
if (memMap.getLiveMemoryHandler() != null) {
memMap.setBytes(addr, b, off, len);
return len;
}
memMap.lock.acquire();
try {
checkValid();

View file

@ -40,7 +40,7 @@ import ghidra.util.task.TaskMonitor;
/**
* The database memory map manager.
*/
public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener {
public class MemoryMapDB implements Memory, ManagerDB {
private ProgramDB program;
private AddressMapDB addrMap;
@ -68,7 +68,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener {
}
private MemoryBlock lastBlock;// the last accessed block
private LiveMemoryHandler liveMemory;
// lazy hashmap of block names to blocks, must be reloaded if blocks are removed or added
private HashMap<String, MemoryBlock> nameBlockMap = new HashMap<>();
@ -260,9 +259,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener {
initializeBlocks();
buildAddressSets(true);
}
if (liveMemory != null) {
liveMemory.clearCache();
}
addrMap.memoryMapChanged(this);
}
@ -359,10 +355,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener {
@Override
public AddressSetView getLoadedAndInitializedAddressSet() {
if (liveMemory != null) {
return this; // all memory is initialized!
}
MemoryAddressSetViews localAddrSetViews = buildAddressSets(false);
return new AddressSetViewAdapter(localAddrSetViews.initializedAndLoaded);
}
@ -586,29 +578,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener {
return defaultEndian == BIG_ENDIAN;
}
@Override
public void setLiveMemoryHandler(LiveMemoryHandler handler) {
lock.acquire();
try {
if (liveMemory != null) {
liveMemory.removeLiveMemoryListener(this);
}
liveMemory = handler;
if (liveMemory != null) {
liveMemory.addLiveMemoryListener(this);
}
program.invalidate();
}
finally {
lock.release();
}
}
@Override
public LiveMemoryHandler getLiveMemoryHandler() {
return liveMemory;
}
@Override
public MemoryBlock createInitializedBlock(String name, Address start, long size,
byte initialValue, TaskMonitor monitor, boolean overlay) throws LockException,
@ -976,10 +945,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener {
lock.acquire();
try {
program.checkExclusiveAccess();
if (liveMemory != null) {
throw new MemoryBlockException(
"Memory move operation not permitted while live memory is active");
}
checkBlock(block);
MemoryBlockDB memBlock = (MemoryBlockDB) block;
@ -1026,10 +991,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener {
lock.acquire();
try {
program.checkExclusiveAccess();
if (liveMemory != null) {
throw new MemoryBlockException(
"Memory split operation not permitted while live memory is active");
}
checkBlock(block);
MemoryBlockDB memBlock = (MemoryBlockDB) block;
if (!memBlock.contains(addr)) {
@ -1115,10 +1076,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener {
throws MemoryBlockException, LockException {
program.checkExclusiveAccess();
if (liveMemory != null) {
throw new MemoryBlockException(
"Memory join operation not permitted while live memory is active");
}
checkBlockForJoining(block1);
checkBlockForJoining(block2);
@ -1433,9 +1390,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener {
@Override
public byte getByte(Address addr) throws MemoryAccessException {
if (liveMemory != null) {
return liveMemory.getByte(addr);
}
MemoryBlock block = getBlockDB(addr);
if (block == null) {
throw new MemoryAccessException(
@ -1452,9 +1406,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener {
@Override
public int getBytes(Address addr, byte[] dest, int dIndex, int size)
throws MemoryAccessException {
if (liveMemory != null) {
return liveMemory.getBytes(addr, dest, dIndex, size);
}
int numRead = 0;
long lastRead = 0;
while (numRead < size) {
@ -1685,11 +1636,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener {
@Override
public void setByte(Address addr, byte value) throws MemoryAccessException {
if (liveMemory != null) {
liveMemory.putByte(addr, value);
fireBytesChanged(addr, 1);
return;
}
lock.acquire();
try {
MemoryBlock block = getBlock(addr);
@ -1714,12 +1660,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener {
@Override
public void setBytes(Address address, byte[] source, int sIndex, int size)
throws MemoryAccessException {
if (liveMemory != null) {
int cnt = liveMemory.putBytes(address, source, sIndex, size);
fireBytesChanged(address, cnt);
return;
}
lock.acquire();
try {
Address addr = address;
@ -2149,11 +2089,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener {
return super.hashCode();
}
@Override
public void memoryChanged(Address addr, int size) {
fireBytesChanged(addr, size);
}
@Override
public AddressRangeIterator getAddressRanges(Address start, boolean forward) {
return allAddrSet.getAddressRanges(start, forward);

View file

@ -1,81 +0,0 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* 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.program.model.mem;
import ghidra.program.model.address.Address;
/**
* Live memory handler interface.
*/
public interface LiveMemoryHandler {
/**
* Called when the memory map is re-initializing. Usually after an undo or redo.
*/
public void clearCache();
/**
* Gets the byte at the given address.
* @param addr the address of the byte to be retrieved
* @return the byte at the given address.
* @throws MemoryAccessException if the byte can't be read.
*/
public byte getByte(Address addr) throws MemoryAccessException;
/**
* Get the bytes at the given address and size and put them into the destination buffer.
* @param address the address of the first byte to be retrieved.
* @param buffer the byte buffer in which to place the bytes.
* @param startIndex the starting index in the buffer to put the first byte.
* @param size the number of bytes to retrieve and put in the buffer.
* @return the number of bytes placed into the given buffer.
* @throws MemoryAccessException if the bytes can't be read.
*/
public int getBytes(Address address, byte[] buffer, int startIndex, int size) throws MemoryAccessException;
/**
* Writes the given byte value to the address in memory.
* @param address the address whose byte is to be updated to the new value.
* @param value the value to set at the given address.
* @throws MemoryAccessException if the value can not be written to the memory.
*/
public void putByte(Address address, byte value) throws MemoryAccessException;
/**
* Writes the given bytes to memory starting at the given address.
* @param address the address in memory to write the bytes.
* @param source the buffer containing the byte values to be written to memory.
* @param startIndex the starting index in the buffer to get byte values.
* @param size the number of bytes to write to memory.
* @return the number of bytes written to memory.
* @throws MemoryAccessException if the bytes can't be written to memory.
*/
public int putBytes(Address address, byte[] source, int startIndex, int size) throws MemoryAccessException;
/**
* Adds a LiveMemoryListener to this handler. The listener will be notified when memory
* bytes change.
* @param listener the listener to be notified of memory byte value changes.
*/
public void addLiveMemoryListener(LiveMemoryListener listener);
/**
* Removes the LiveMemoryListener from this handler.
* @param listener the listener to be removed.
*/
public void removeLiveMemoryListener(LiveMemoryListener listener);
}

View file

@ -1,23 +0,0 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* 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.program.model.mem;
import ghidra.program.model.address.Address;
public interface LiveMemoryListener {
public void memoryChanged(Address addr, int size);
}

View file

@ -151,18 +151,6 @@ public interface Memory extends AddressSetView {
return block != null && block.isExternalBlock();
}
/**
* Sets the live memory handler
* @param handler the live memory handler
*/
public void setLiveMemoryHandler(LiveMemoryHandler handler);
/**
* Returns the live memory handler instance used by this memory.
* @return the live memory handler
*/
public LiveMemoryHandler getLiveMemoryHandler();
/**
* Create an initialized memory block based upon a data {@link InputStream} and add it to
* this Memory.

View file

@ -4,9 +4,9 @@
* 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.
@ -175,16 +175,6 @@ public class StubMemory extends AddressSet implements Memory {
throw new UnsupportedOperationException();
}
@Override
public void setLiveMemoryHandler(LiveMemoryHandler handler) {
throw new UnsupportedOperationException();
}
@Override
public LiveMemoryHandler getLiveMemoryHandler() {
throw new UnsupportedOperationException();
}
@Override
public MemoryBlock createInitializedBlock(String name, Address start, InputStream is,
long length, TaskMonitor monitor, boolean overlay) {

View file

@ -35,8 +35,6 @@ import docking.dnd.GClipboard;
import docking.widgets.OptionDialog;
import generic.Unique;
import ghidra.app.plugin.assembler.*;
import ghidra.app.plugin.core.assembler.AssemblerPlugin;
import ghidra.app.plugin.core.assembler.AssemblerPluginTestHelper;
import ghidra.app.plugin.core.clipboard.ClipboardPlugin;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin;
@ -465,7 +463,8 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerInteg
@Test
public void testPatchDataActionInDynamicListingEmu() throws Throwable {
AssemblerPlugin assemblerPlugin = addPlugin(tool, AssemblerPlugin.class);
DebuggerDisassemblerPlugin disassemblerPlugin =
addPlugin(tool, DebuggerDisassemblerPlugin.class);
assertFalse(controlPlugin.actionControlMode.isEnabled());
@ -482,8 +481,8 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerInteg
}
CodeViewerProvider listingProvider = listingPlugin.getProvider();
AssemblerPluginTestHelper helper =
new AssemblerPluginTestHelper(assemblerPlugin, listingProvider, view);
DebuggerDisassemblerPluginTestHelper helper =
new DebuggerDisassemblerPluginTestHelper(disassemblerPlugin, listingProvider, view);
traceManager.activateTrace(tb.trace);
waitForSwing();

View file

@ -1178,11 +1178,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
performAction(actionPaste, memBytesProvider, false);
OptionDialog confirm = waitForDialogComponent(OptionDialog.class);
pressButtonByText(confirm, "Yes");
// TODO: This shouldn't be separate calls per byte!
handleWriteMemInvocation(process, tb.addr(0x55550800), new Bytes(0x42));
handleWriteMemInvocation(process, tb.addr(0x55550801), new Bytes(0x53));
handleWriteMemInvocation(process, tb.addr(0x55550802), new Bytes(0x64));
handleWriteMemInvocation(process, tb.addr(0x55550803), new Bytes(0x75));
handleWriteMemInvocation(process, tb.addr(0x55550800), new Bytes(0x42, 0x53, 0x64, 0x75));
performAction(actionEdit);