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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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.address.Address;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue; import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.mem.LiveMemoryHandler;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.program.TraceProgramView;
@ -53,9 +52,6 @@ public interface DebuggerControlService {
} }
} }
interface StateEditingMemoryHandler extends StateEditor, LiveMemoryHandler {
}
interface ControlModeChangeListener { interface ControlModeChangeListener {
void modeChanged(Trace trace, ControlMode mode); void modeChanged(Trace trace, ControlMode mode);
} }
@ -78,5 +74,5 @@ public interface DebuggerControlService {
*/ */
StateEditor createStateEditor(Trace trace); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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.app.plugin.core.debug.service.tracermi.TraceRmiHandler.*;
import ghidra.debug.api.tracermi.TraceRmiError; 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.address.*;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
import ghidra.rmi.trace.TraceRmi.*; import ghidra.rmi.trace.TraceRmi.*;
@ -29,12 +32,51 @@ class OpenTrace implements ValueDecoder {
final DoId doId; final DoId doId;
final Trace trace; final Trace trace;
final TraceRmiTarget target; final TraceRmiTarget target;
final CurrentTxListener txListener;
TraceSnapshot lastSnapshot; 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) { OpenTrace(DoId doId, Trace trace, TraceRmiTarget target) {
this.doId = doId; this.doId = doId;
this.trace = trace; this.trace = trace;
this.target = target; 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) { public TraceSnapshot createSnapshot(Snap snap, String description) {
@ -55,7 +97,7 @@ class OpenTrace implements ValueDecoder {
TraceObject object = TraceObject object =
trace.getObjectManager().getObjectByCanonicalPath(TraceRmiHandler.toKeyPath(path)); trace.getObjectManager().getObjectByCanonicalPath(TraceRmiHandler.toKeyPath(path));
if (required && object == null) { if (required && object == null) {
throw new InvalidObjPathError(); throw new InvalidObjPathError(path.getPath());
} }
return object; return object;
} }
@ -78,7 +120,7 @@ class OpenTrace implements ValueDecoder {
public AddressSpace getSpace(String name, boolean required) { public AddressSpace getSpace(String name, boolean required) {
AddressSpace space = trace.getBaseAddressFactory().getAddressSpace(name); AddressSpace space = trace.getBaseAddressFactory().getAddressSpace(name);
if (required && space == null) { if (required && space == null) {
throw new NoSuchAddressSpaceError(); throw new NoSuchAddressSpaceError(name);
} }
return space; return space;
} }

View file

@ -90,9 +90,15 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
} }
protected static class InvalidObjPathError extends TraceRmiError { protected static class InvalidObjPathError extends TraceRmiError {
public InvalidObjPathError(String path) {
super(path);
}
} }
protected static class NoSuchAddressSpaceError extends TraceRmiError { protected static class NoSuchAddressSpaceError extends TraceRmiError {
public NoSuchAddressSpaceError(String name) {
super(name);
}
} }
protected static class InvalidSchemaError extends TraceRmiError { protected static class InvalidSchemaError extends TraceRmiError {
@ -292,8 +298,8 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
synchronized (openTxes) { synchronized (openTxes) {
while (!openTxes.isEmpty()) { while (!openTxes.isEmpty()) {
Tid nextKey = openTxes.keySet().iterator().next(); Tid nextKey = openTxes.keySet().iterator().next();
OpenTx open = openTxes.remove(nextKey); OpenTx openTx = openTxes.remove(nextKey);
open.tx.close(); openTx.tx.close();
} }
} }
@ -309,7 +315,7 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
// OK. Move on // OK. Move on
} }
} }
open.trace.release(this); open.dispose(this);
} }
closed.complete(null); closed.complete(null);
plugin.listeners.invoke().disconnected(this); plugin.listeners.invoke().disconnected(this);
@ -840,7 +846,7 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
protected ReplyCloseTrace handleCloseTrace(RequestCloseTrace req) { protected ReplyCloseTrace handleCloseTrace(RequestCloseTrace req) {
OpenTrace open = requireOpenTrace(req.getOid()); OpenTrace open = requireOpenTrace(req.getOid());
openTraces.removeById(open.doId); openTraces.removeById(open.doId);
open.trace.release(this); open.dispose(this);
return ReplyCloseTrace.getDefaultInstance(); return ReplyCloseTrace.getDefaultInstance();
} }
@ -968,13 +974,27 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
Msg.error(this, "Back-end debugger aborted a transaction!"); Msg.error(this, "Back-end debugger aborted a transaction!");
tx.tx.abortOnClose(); tx.tx.abortOnClose();
} }
tx.tx.close();
OpenTrace open = requireOpenTrace(tx.txId.doId); OpenTrace open = requireOpenTrace(tx.txId.doId);
if (!tx.undoable) { 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(); return ReplyEndTx.getDefaultInstance();
} }
@ -1278,7 +1298,7 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
@Override @Override
public void forceCloseTrace(Trace trace) { public void forceCloseTrace(Trace trace) {
OpenTrace open = openTraces.removeByTrace(trace); OpenTrace open = openTraces.removeByTrace(trace);
open.trace.release(this); open.dispose(this);
} }
@Override @Override

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -15,12 +15,16 @@
*/ */
package ghidra.app.plugin.core.debug.disassemble; package ghidra.app.plugin.core.debug.disassemble;
import java.util.concurrent.*;
import docking.ActionContext; import docking.ActionContext;
import ghidra.app.plugin.assembler.Assembler; import ghidra.app.plugin.assembler.Assembler;
import ghidra.app.plugin.assembler.Assemblers; import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock; import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock;
import ghidra.app.plugin.core.assembler.AssemblyDualTextField; import ghidra.app.plugin.core.assembler.AssemblyDualTextField;
import ghidra.app.plugin.core.assembler.PatchInstructionAction; 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.address.*;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.listing.CodeUnit;
@ -118,12 +122,25 @@ public abstract class AbstractTracePatchInstructionAction extends PatchInstructi
if (view == null) { if (view == null) {
return; return;
} }
DebuggerControlService controlService = tool.getService(DebuggerControlService.class);
if (controlService == null) {
return;
}
StateEditor editor = controlService.createStateEditor(view);
Address address = getAddress(); 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(); CodeUnit cu = getCodeUnit();
RegisterValue contextValue = getContextValue(cu); RegisterValue contextValue = getContextValue(cu);
TracePlatform platform = getPlatform(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)); AddressSetView set = new AddressSet(address, address.add(data.length - 1));
TraceDisassembleCommand dis = new TraceDisassembleCommand(platform, address, set); TraceDisassembleCommand dis = new TraceDisassembleCommand(platform, address, set);
if (contextValue != null) { if (contextValue != null) {

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -29,10 +29,7 @@ public class CurrentPlatformTracePatchInstructionAction
@Override @Override
protected boolean isApplicableToUnit(CodeUnit cu) { protected boolean isApplicableToUnit(CodeUnit cu) {
if (!super.isApplicableToUnit(cu)) { return super.isApplicableToUnit(cu) && cu instanceof TraceInstruction;
return false;
}
return cu instanceof TraceInstruction;
} }
@Override @Override

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -170,6 +170,7 @@ public class DebuggerDisassemblerPlugin extends Plugin implements PopupActionPro
CurrentPlatformTraceDisassembleAction actionDisassemble; CurrentPlatformTraceDisassembleAction actionDisassemble;
CurrentPlatformTracePatchInstructionAction actionPatchInstruction; CurrentPlatformTracePatchInstructionAction actionPatchInstruction;
TracePatchDataAction actionPatchData;
public DebuggerDisassemblerPlugin(PluginTool tool) { public DebuggerDisassemblerPlugin(PluginTool tool) {
super(tool); super(tool);
@ -185,9 +186,11 @@ public class DebuggerDisassemblerPlugin extends Plugin implements PopupActionPro
protected void createActions() { protected void createActions() {
actionDisassemble = new CurrentPlatformTraceDisassembleAction(this); actionDisassemble = new CurrentPlatformTraceDisassembleAction(this);
actionPatchInstruction = new CurrentPlatformTracePatchInstructionAction(this); actionPatchInstruction = new CurrentPlatformTracePatchInstructionAction(this);
actionPatchData = new TracePatchDataAction(this);
tool.addAction(actionDisassemble); tool.addAction(actionDisassemble);
tool.addAction(actionPatchInstruction); 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 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() { protected ForListingClipboardProvider() {
super(DebuggerListingProvider.this.tool, DebuggerListingProvider.this); super(DebuggerListingProvider.this.tool, DebuggerListingProvider.this);
} }
@ -317,6 +335,11 @@ public class DebuggerListingProvider extends CodeViewerProvider {
} }
return super.canPaste(availableFlavors); return super.canPaste(availableFlavors);
} }
@Override
protected boolean pasteByteString(String string) {
return tool.execute(new PasteIntoTargetCommand(string), currentProgram);
}
} }
private final DebuggerListingPlugin plugin; private final DebuggerListingPlugin plugin;

View file

@ -21,8 +21,10 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.*;
import javax.swing.*; 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.*;
import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec; import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec;
import ghidra.app.plugin.core.format.ByteBlock; 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.DebuggerControlService.ControlModeChangeListener;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.debug.api.action.GoToInput; import ghidra.debug.api.action.GoToInput;
import ghidra.debug.api.action.LocationTrackingSpec; import ghidra.debug.api.action.LocationTrackingSpec;
import ghidra.debug.api.tracemgr.DebuggerCoordinates; 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 = private final AutoReadMemorySpec defaultReadMemorySpec =
AutoReadMemorySpec.fromConfigName(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME); AutoReadMemorySpec.fromConfigName(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME);
@ -172,6 +218,8 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
@AutoServiceConsumed @AutoServiceConsumed
private DebuggerTraceManagerService traceManager; private DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
private DebuggerConsoleService consoleService;
//@AutoServiceConsumed via method //@AutoServiceConsumed via method
private DebuggerControlService controlService; private DebuggerControlService controlService;
@SuppressWarnings("unused") @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 @Override
protected ProgramByteBlockSet newByteBlockSet(ByteBlockChangeManager changeManager) { protected ProgramByteBlockSet newByteBlockSet(ByteBlockChangeManager changeManager) {
if (program == null) { if (program == null) {
return null; return null;
} }
// A bit of work to get it to ignore existing instructions. Let them be clobbered! return new TargetByteBlockSet(changeManager);
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;
}
};
}
};
} }
/** /**
@ -373,22 +481,7 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
@Override @Override
protected ByteViewerClipboardProvider newClipboardProvider() { protected ByteViewerClipboardProvider newClipboardProvider() {
return new ByteViewerClipboardProvider(this, tool) { return new ForBytesClipboardProvider();
@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);
}
};
} }
@AutoServiceConsumed @AutoServiceConsumed

View file

@ -15,14 +15,13 @@
*/ */
package ghidra.app.plugin.core.debug.service.control; package ghidra.app.plugin.core.debug.service.control;
import java.nio.ByteBuffer;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin; import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; 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.*;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.debug.api.control.ControlMode; import ghidra.debug.api.control.ControlMode;
@ -31,11 +30,8 @@ import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.mem.*;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceProgramViewListener;
import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceProgramViewMemory;
import ghidra.util.datastruct.ListenerSet; import ghidra.util.datastruct.ListenerSet;
@PluginInfo( @PluginInfo(
@ -45,9 +41,7 @@ import ghidra.util.datastruct.ListenerSet;
packageName = DebuggerPluginPackage.NAME, packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED, status = PluginStatus.RELEASED,
eventsConsumed = { eventsConsumed = {
TraceOpenedPluginEvent.class,
TraceActivatedPluginEvent.class, TraceActivatedPluginEvent.class,
TraceClosedPluginEvent.class,
}, },
servicesRequired = { servicesRequired = {
DebuggerTraceManagerService.class, DebuggerTraceManagerService.class,
@ -115,8 +109,7 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
} }
} }
public class FollowsViewStateEditor extends AbstractStateEditor public class FollowsViewStateEditor extends AbstractStateEditor {
implements StateEditingMemoryHandler {
private final TraceProgramView view; private final TraceProgramView view;
public FollowsViewStateEditor(TraceProgramView view) { public FollowsViewStateEditor(TraceProgramView view) {
@ -132,81 +125,11 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
public DebuggerCoordinates getCoordinates() { public DebuggerCoordinates getCoordinates() {
return traceManager.resolveView(view); 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 { @AutoServiceConsumed
@Override
public void viewCreated(TraceProgramView view) {
installMemoryEditor(view);
}
}
//@AutoServiceConsumed // via method
private DebuggerTraceManagerService traceManager; private DebuggerTraceManagerService traceManager;
protected final ListenerForEditorInstallation listenerForEditorInstallation =
new ListenerForEditorInstallation();
private final Map<Trace, ControlMode> currentModes = new HashMap<>(); private final Map<Trace, ControlMode> currentModes = new HashMap<>();
private final ListenerSet<ControlModeChangeListener> listeners = private final ListenerSet<ControlModeChangeListener> listeners =
@ -219,7 +142,6 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
@Override @Override
protected void dispose() { protected void dispose() {
super.dispose(); super.dispose();
uninstallAllMemoryEditors();
} }
@Override @Override
@ -265,7 +187,7 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
} }
@Override @Override
public StateEditingMemoryHandler createStateEditor(TraceProgramView view) { public StateEditor createStateEditor(TraceProgramView view) {
return new FollowsViewStateEditor(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 @Override
public void processEvent(PluginEvent event) { public void processEvent(PluginEvent event) {
super.processEvent(event); super.processEvent(event);
if (event instanceof TraceOpenedPluginEvent ev) { if (event instanceof TraceActivatedPluginEvent ev) {
installAllMemoryEditors(ev.getTrace());
}
else if (event instanceof TraceActivatedPluginEvent ev) {
coordinatesActivated(ev.getActiveCoordinates(), ev.getCause()); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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 class DebuggerDisassemblerPluginTestHelper extends AssemblerPluginTestHelper {
public DebuggerDisassemblerPluginTestHelper(DebuggerDisassemblerPlugin disassemblerPlugin, public DebuggerDisassemblerPluginTestHelper(DebuggerDisassemblerPlugin disassemblerPlugin,
CodeViewerProvider provider, Program program) { 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -225,16 +225,6 @@ public class DBTraceGuestPlatformMappedMemory implements Memory {
return guest.getLanguage().isBigEndian(); return guest.getLanguage().isBigEndian();
} }
@Override
public void setLiveMemoryHandler(LiveMemoryHandler handler) {
throw new UnsupportedOperationException();
}
@Override
public LiveMemoryHandler getLiveMemoryHandler() {
return null;
}
@Override @Override
public MemoryBlock createInitializedBlock(String name, Address start, InputStream is, public MemoryBlock createInitializedBlock(String name, Address start, InputStream is,
long length, TaskMonitor monitor, boolean overlay) long length, TaskMonitor monitor, boolean overlay)

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -47,8 +47,6 @@ public abstract class AbstractDBTraceProgramViewMemory
protected boolean forceFullView = false; protected boolean forceFullView = false;
protected long snap; protected long snap;
protected LiveMemoryHandler memoryWriteRedirect;
private static final int CACHE_PAGE_COUNT = 3; private static final int CACHE_PAGE_COUNT = 3;
protected final ByteCache cache = new ByteCache(CACHE_PAGE_COUNT) { protected final ByteCache cache = new ByteCache(CACHE_PAGE_COUNT) {
@Override @Override
@ -162,16 +160,6 @@ public abstract class AbstractDBTraceProgramViewMemory
return program.getLanguage().isBigEndian(); return program.getLanguage().isBigEndian();
} }
@Override
public void setLiveMemoryHandler(LiveMemoryHandler handler) {
this.memoryWriteRedirect = handler;
}
@Override
public LiveMemoryHandler getLiveMemoryHandler() {
return memoryWriteRedirect;
}
@Override @Override
public MemoryBlock createInitializedBlock(String name, Address start, InputStream is, public MemoryBlock createInitializedBlock(String name, Address start, InputStream is,
long length, TaskMonitor monitor, boolean overlay) long length, TaskMonitor monitor, boolean overlay)
@ -339,10 +327,6 @@ public abstract class AbstractDBTraceProgramViewMemory
@Override @Override
public void setByte(Address addr, byte value) throws MemoryAccessException { public void setByte(Address addr, byte value) throws MemoryAccessException {
if (memoryWriteRedirect != null) {
memoryWriteRedirect.putByte(addr, value);
return;
}
DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true); DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true);
if (space.putBytes(snap, addr, ByteBuffer.wrap(new byte[] { value })) != 1) { if (space.putBytes(snap, addr, ByteBuffer.wrap(new byte[] { value })) != 1) {
throw new MemoryAccessException(); throw new MemoryAccessException();
@ -352,10 +336,6 @@ public abstract class AbstractDBTraceProgramViewMemory
@Override @Override
public void setBytes(Address addr, byte[] source, int sIndex, int size) public void setBytes(Address addr, byte[] source, int sIndex, int size)
throws MemoryAccessException { throws MemoryAccessException {
if (memoryWriteRedirect != null) {
memoryWriteRedirect.putBytes(addr, source, sIndex, size);
return;
}
DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true); DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true);
if (space.putBytes(snap, addr, ByteBuffer.wrap(source, sIndex, size)) != size) { if (space.putBytes(snap, addr, ByteBuffer.wrap(source, sIndex, size)) != size) {
throw new MemoryAccessException(); throw new MemoryAccessException();

View file

@ -15,7 +15,6 @@
*/ */
package ghidra.trace.model.program; package ghidra.trace.model.program;
import ghidra.program.model.mem.LiveMemoryHandler;
import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.Memory;
public interface TraceProgramViewMemory extends Memory, SnapSpecificTraceView { public interface TraceProgramViewMemory extends Memory, SnapSpecificTraceView {
@ -25,13 +24,4 @@ public interface TraceProgramViewMemory extends Memory, SnapSpecificTraceView {
void setForceFullView(boolean forceFullView); void setForceFullView(boolean forceFullView);
boolean isForceFullView(); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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 * The API for instruction assembly is available from {@link Assemblers}. For data assembly, the API
* is in {@link DataType#encodeRepresentation(String, MemBuffer, Settings, int)}. * 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. " + @PluginInfo(
"The assembler supports most processor languages also supported by the " + status = PluginStatus.RELEASED,
"disassembler. Depending on the particular processor, your mileage may vary. " + packageName = CorePluginPackage.NAME,
"We are in the process of testing and improving support for all our processors. " + category = "Patching",
"You can access the assembler by pressing Ctrl-Shift-G, and then modifying the " + shortDescription = "Assembler",
"instruction in place. As you type, a content assist will guide you and provide " + description = "This plugin provides functionality for assembly patching. " +
"assembled bytes when you have a complete instruction.") "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 class AssemblerPlugin extends ProgramPlugin {
public static final String ASSEMBLER_NAME = "Assembler"; public static final String ASSEMBLER_NAME = "Assembler";
@ -58,35 +63,39 @@ public class AssemblerPlugin extends ProgramPlugin {
} }
private void createActions() { private void createActions() {
// Debugger provides its own "Patch Instruction" action // Debugger provides its own "Patch" actions
patchInstructionAction = new PatchInstructionAction(this) { patchInstructionAction = new PatchInstructionAction(this) {
@Override @Override
public boolean isEnabledForContext(ActionContext context) { public boolean isEnabledForContext(ActionContext context) {
if (!super.isEnabledForContext(context)) { return super.isEnabledForContext(context) &&
return false; context instanceof ListingActionContext lac &&
} !lac.getNavigatable().isDynamic();
if (!(context instanceof ListingActionContext)) {
return false;
}
ListingActionContext lac = (ListingActionContext) context;
return !lac.getNavigatable().isDynamic();
} }
@Override @Override
public boolean isAddToPopup(ActionContext context) { public boolean isAddToPopup(ActionContext context) {
if (!super.isAddToPopup(context)) { return super.isAddToPopup(context) &&
return false; context instanceof ListingActionContext lac &&
} !lac.getNavigatable().isDynamic();
if (!(context instanceof ListingActionContext)) {
return false;
}
ListingActionContext lac = (ListingActionContext) context;
return !lac.getNavigatable().isDynamic();
} }
}; };
tool.addAction(patchInstructionAction); 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); tool.addAction(patchDataAction);
} }

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -73,14 +73,7 @@ public class PatchDataAction extends AbstractPatchAction {
@Override @Override
protected boolean isApplicableToUnit(CodeUnit cu) { protected boolean isApplicableToUnit(CodeUnit cu) {
if (!(cu instanceof Data)) { return cu instanceof Data data && data.getBaseDataType().isEncodable();
return false;
}
Data data = (Data) cu;
if (!data.getBaseDataType().isEncodable()) {
return false;
}
return true;
} }
protected Data getData() { protected Data getData() {
@ -113,6 +106,23 @@ public class PatchDataAction extends AbstractPatchAction {
input.setCaretPosition(repr.length()); 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 @Override
public void accept() { public void accept() {
Program program = getProgram(); Program program = getProgram();
@ -135,14 +145,7 @@ public class PatchDataAction extends AbstractPatchAction {
} }
try (Transaction tx = try (Transaction tx =
program.openTransaction("Patch Data @" + address + ": " + input.getText())) { program.openTransaction("Patch Data @" + address + ": " + input.getText())) {
int oldLength = data.getLength(); applyPatch(rng, encoded);
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);
}
hide(); hide();
} }
catch (MemoryAccessException e) { catch (MemoryAccessException e) {

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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.dnd.StringTransferable;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import ghidra.framework.cmd.Command; import ghidra.framework.cmd.Command;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
@ -38,8 +37,7 @@ import ghidra.util.*;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/** /**
* Base class that can copy bytes into a Transferable object, and paste * Base class that can copy bytes into a Transferable object, and paste bytes into a program.
* bytes into a program.
* *
*/ */
public abstract class ByteCopier { public abstract class ByteCopier {
@ -381,125 +379,12 @@ public abstract class ByteCopier {
} }
protected boolean pasteByteString(final String string) { protected boolean pasteByteString(final String string) {
Command cmd = new Command() { return tool.execute(new PasteByteStringCommand(string), currentProgram);
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);
} }
/** /**
* Create a Transferable from the given text. * Create a Transferable from the given text.
*
* @param text text used to create a Transferable * @param text text used to create a Transferable
* @return a Transferable * @return a Transferable
*/ */
@ -511,8 +396,120 @@ public abstract class ByteCopier {
// Inner Classes // 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. * throws an exception from its next() method, which will not work for us.
*/ */
private static class ByteIterator implements Iterator<Byte> { private static class ByteIterator implements Iterator<Byte> {

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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()); 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 @Test
public void testSplitBlock() throws Exception { public void testSplitBlock() throws Exception {
createBlock("Test", addr(0), 100); createBlock("Test", addr(0), 100);

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -67,16 +67,6 @@ public class MyTestMemory extends AddressSet implements Memory {
return false; return false;
} }
@Override
public void setLiveMemoryHandler(LiveMemoryHandler handler) {
throw new UnsupportedOperationException();
}
@Override
public LiveMemoryHandler getLiveMemoryHandler() {
throw new UnsupportedOperationException();
}
@Override @Override
public MemoryBlock createInitializedBlock(String name, Address start, InputStream is, public MemoryBlock createInitializedBlock(String name, Address start, InputStream is,
long length, TaskMonitor monitor, boolean overlay) long length, TaskMonitor monitor, boolean overlay)

View file

@ -18,26 +18,33 @@ package ghidra.app.plugin.core.format;
import ghidra.util.exception.UsrException; import ghidra.util.exception.UsrException;
/** /**
* <p>An ByteBlockAccessException indicates that the attempted * A {@code ByteBlockAccessException} indicates that the attempted access is not permitted. (i.e.
* access is not permitted. (i.e. Readable/Writeable)</p> * Readable/Writeable)
*
*/ */
public class ByteBlockAccessException extends UsrException { public class ByteBlockAccessException extends UsrException {
/** /**
* <p>Constructs an ByteBlockAccessException with no detail message. * Construct an exception with no details
*/ */
public ByteBlockAccessException() { public ByteBlockAccessException() {
super(); super();
} }
/**
/** * Construct an exception with the specified message
* <p>Constructs an ByteBlockAccessException with the specified
* detail message.
* *
* @param message The message. * @param message the message
*/ */
public ByteBlockAccessException(String message) { public ByteBlockAccessException(String message) {
super(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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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. * 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, * @param commit true if changes should be committed, false if all changes in this transaction
* the larger transaction will rollback upon completion. * should be discarded (i.e., rollback). If this is a "sub-transaction" and commit is
* @return true if changes have been commited or false if nothing to commit or commit parameter * false, the larger transaction will rollback upon completion.
* was specified as false. * @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); abstract protected boolean endTransaction(@SuppressWarnings("hiding") boolean commit);
/** /**
* Determine if this is a sub-transaction to a larger transaction. If true is returned the * Determine if this is a sub-transaction to a larger transaction.
* 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. * <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. * @return true if this is a sub-transaction, else false.
*/ */
public boolean isSubTransaction() { 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 * Mark transaction for rollback/non-commit upon closing.
* {@link #commitOnClose()} will alter this state prior to closing. *
* <p>
* A subsequent invocation of {@link #commitOnClose()} will alter this state prior to closing.
*/ */
public void abortOnClose() { public void abortOnClose() {
commit = false; commit = false;
} }
/** /**
* Mark transaction for commit upon closing. This state is assumed by default. A subsequent * Mark transaction for commit upon closing.
* invocation of {@link #abortOnClose()} will alter this state prior to closing. *
* <p>
* This state is assumed by default. A subsequent invocation of {@link #abortOnClose()} will
* alter this state prior to closing.
*/ */
public void commitOnClose() { public void commitOnClose() {
commit = true; commit = true;
@ -106,7 +116,9 @@ public abstract class Transaction implements AutoCloseable {
/** /**
* End this transaction if active using the current commit state. * End this transaction if active using the current commit state.
* See {@link #commitOnClose()}, {@link #abortOnClose()}. *
* @see #commitOnClose()
* @see #abortOnClose()
*/ */
@Override @Override
public void close() { public void close() {
@ -115,5 +127,4 @@ public abstract class Transaction implements AutoCloseable {
endTransaction(commit); endTransaction(commit);
} }
} }
} }

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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.*;
import ghidra.framework.model.TransactionInfo.Status; import ghidra.framework.model.TransactionInfo.Status;
import ghidra.util.Msg; import ghidra.util.*;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory; import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet; import ghidra.util.datastruct.WeakSet;
@ -192,9 +191,11 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
} }
/** /**
* Returns the undo stack depth. * Returns the undo stack depth (The number of items on the undo stack).
* (The number of items on the undo stack) *
* <p>
* This method is for JUnits. * This method is for JUnits.
*
* @return the undo stack depth * @return the undo stack depth
*/ */
@Override @Override
@ -333,7 +334,7 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
} }
void notifyStartTransaction(TransactionInfo tx) { void notifyStartTransaction(TransactionInfo tx) {
SystemUtilities.runSwingLater(() -> { Swing.runLater(() -> {
for (TransactionListener listener : transactionListeners) { for (TransactionListener listener : transactionListeners) {
listener.transactionStarted(domainObj, tx); listener.transactionStarted(domainObj, tx);
listener.undoStackChanged(domainObj); listener.undoStackChanged(domainObj);
@ -342,7 +343,7 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
} }
void notifyEndTransaction() { void notifyEndTransaction() {
SystemUtilities.runSwingLater(() -> { Swing.runLater(() -> {
for (TransactionListener listener : transactionListeners) { for (TransactionListener listener : transactionListeners) {
listener.transactionEnded(domainObj); listener.transactionEnded(domainObj);
listener.undoStackChanged(domainObj); listener.undoStackChanged(domainObj);
@ -351,7 +352,7 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
} }
void notifyUndoStackChanged() { void notifyUndoStackChanged() {
SystemUtilities.runSwingLater(() -> { Swing.runLater(() -> {
for (TransactionListener listener : transactionListeners) { for (TransactionListener listener : transactionListeners) {
listener.undoStackChanged(domainObj); listener.undoStackChanged(domainObj);
} }
@ -359,7 +360,7 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
} }
void notifyUndoRedo() { void notifyUndoRedo() {
SystemUtilities.runSwingLater(() -> { Swing.runLater(() -> {
for (TransactionListener listener : transactionListeners) { for (TransactionListener listener : transactionListeners) {
listener.undoRedoOccurred(domainObj); listener.undoRedoOccurred(domainObj);
listener.undoStackChanged(domainObj); listener.undoStackChanged(domainObj);

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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 * Determine if the corresponding transaction, and all of its sub-transactions, has been
* comitted to the underlying database. * committed to the underlying database.
* @return true if the corresponding transaction has been comitted, else false. * @return true if the corresponding transaction has been committed, else false.
*/ */
public boolean hasCommittedDBTransaction(); public boolean hasCommittedDBTransaction();

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -362,9 +362,6 @@ public class MemoryBlockDB implements MemoryBlock {
@Override @Override
public byte getByte(Address addr) throws MemoryAccessException { public byte getByte(Address addr) throws MemoryAccessException {
if (memMap.getLiveMemoryHandler() != null) {
return memMap.getByte(addr);
}
checkValid(); checkValid();
long offset = getBlockOffset(addr); long offset = getBlockOffset(addr);
return getByte(offset); return getByte(offset);
@ -378,10 +375,6 @@ public class MemoryBlockDB implements MemoryBlock {
@Override @Override
public int getBytes(Address addr, byte[] b, int off, int len) public int getBytes(Address addr, byte[] b, int off, int len)
throws IndexOutOfBoundsException, MemoryAccessException { throws IndexOutOfBoundsException, MemoryAccessException {
if (memMap.getLiveMemoryHandler() != null) {
return memMap.getBytes(addr, b, off, len);
}
checkValid(); checkValid();
long offset = getBlockOffset(addr); long offset = getBlockOffset(addr);
return getBytes(offset, b, off, len); return getBytes(offset, b, off, len);
@ -389,10 +382,6 @@ public class MemoryBlockDB implements MemoryBlock {
@Override @Override
public void putByte(Address addr, byte b) throws MemoryAccessException { public void putByte(Address addr, byte b) throws MemoryAccessException {
if (memMap.getLiveMemoryHandler() != null) {
memMap.setByte(addr, b);
return;
}
long offset = getBlockOffset(addr); long offset = getBlockOffset(addr);
memMap.lock.acquire(); memMap.lock.acquire();
try { try {
@ -414,10 +403,6 @@ public class MemoryBlockDB implements MemoryBlock {
@Override @Override
public int putBytes(Address addr, byte[] b, int off, int len) public int putBytes(Address addr, byte[] b, int off, int len)
throws IndexOutOfBoundsException, MemoryAccessException { throws IndexOutOfBoundsException, MemoryAccessException {
if (memMap.getLiveMemoryHandler() != null) {
memMap.setBytes(addr, b, off, len);
return len;
}
memMap.lock.acquire(); memMap.lock.acquire();
try { try {
checkValid(); checkValid();

View file

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

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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(); throw new UnsupportedOperationException();
} }
@Override
public void setLiveMemoryHandler(LiveMemoryHandler handler) {
throw new UnsupportedOperationException();
}
@Override
public LiveMemoryHandler getLiveMemoryHandler() {
throw new UnsupportedOperationException();
}
@Override @Override
public MemoryBlock createInitializedBlock(String name, Address start, InputStream is, public MemoryBlock createInitializedBlock(String name, Address start, InputStream is,
long length, TaskMonitor monitor, boolean overlay) { long length, TaskMonitor monitor, boolean overlay) {

View file

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

View file

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