From bef0660e6a88a8e58fe1b5412caccb567107a1a3 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Tue, 18 Feb 2025 18:23:30 +0000 Subject: [PATCH] GP-5314: Destroy LiveMemoryHandler --- .../app/services/DebuggerControlService.java | 10 +- .../debug/service/tracermi/OpenTrace.java | 50 +++- .../service/tracermi/TraceRmiHandler.java | 38 ++- .../AbstractTracePatchInstructionAction.java | 25 +- ...ntPlatformTracePatchInstructionAction.java | 9 +- .../DebuggerDisassemblerPlugin.java | 7 +- .../disassemble/TracePatchDataAction.java | 65 +++++ .../core/debug/gui/PasteIntoTargetMixin.java | 77 ++++++ .../gui/listing/DebuggerListingProvider.java | 23 ++ .../memory/DebuggerMemoryBytesProvider.java | 155 +++++++++--- .../control/DebuggerControlServicePlugin.java | 157 +----------- .../DebuggerDisassemblerPluginTestHelper.java | 7 +- .../DBTraceGuestPlatformMappedMemory.java | 14 +- .../AbstractDBTraceProgramViewMemory.java | 24 +- .../model/program/TraceProgramViewMemory.java | 10 - .../core/assembler/AssemblerPlugin.java | 63 +++-- .../core/assembler/PatchDataAction.java | 39 +-- .../main/java/ghidra/app/util/ByteCopier.java | 239 +++++++++--------- .../database/mem/MemoryManagerTest.java | 76 +----- .../plugin/core/checksums/MyTestMemory.java | 14 +- .../core/format/ByteBlockAccessException.java | 43 ++-- .../DB/src/main/java/db/Transaction.java | 45 ++-- .../data/DomainObjectTransactionManager.java | 21 +- .../framework/model/TransactionInfo.java | 8 +- .../program/database/mem/MemoryBlockDB.java | 19 +- .../program/database/mem/MemoryMapDB.java | 67 +---- .../program/model/mem/LiveMemoryHandler.java | 81 ------ .../program/model/mem/LiveMemoryListener.java | 23 -- .../java/ghidra/program/model/mem/Memory.java | 12 - .../ghidra/program/model/mem/StubMemory.java | 14 +- .../control/DebuggerControlPluginTest.java | 9 +- .../DebuggerMemoryBytesProviderTest.java | 6 +- 32 files changed, 667 insertions(+), 783 deletions(-) create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/TracePatchDataAction.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/PasteIntoTargetMixin.java delete mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/LiveMemoryHandler.java delete mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/LiveMemoryListener.java diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerControlService.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerControlService.java index 87cefac0ea..f592f55f90 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerControlService.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerControlService.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -24,7 +24,6 @@ import ghidra.pcode.utils.Utils; import ghidra.program.model.address.Address; import ghidra.program.model.lang.Register; import ghidra.program.model.lang.RegisterValue; -import ghidra.program.model.mem.LiveMemoryHandler; import ghidra.trace.model.Trace; import ghidra.trace.model.program.TraceProgramView; @@ -53,9 +52,6 @@ public interface DebuggerControlService { } } - interface StateEditingMemoryHandler extends StateEditor, LiveMemoryHandler { - } - interface ControlModeChangeListener { void modeChanged(Trace trace, ControlMode mode); } @@ -78,5 +74,5 @@ public interface DebuggerControlService { */ StateEditor createStateEditor(Trace trace); - StateEditingMemoryHandler createStateEditor(TraceProgramView view); + StateEditor createStateEditor(TraceProgramView view); } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/OpenTrace.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/OpenTrace.java index c4f0697308..412cbfd9e2 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/OpenTrace.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/OpenTrace.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,6 +17,9 @@ package ghidra.app.plugin.core.debug.service.tracermi; import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler.*; import ghidra.debug.api.tracermi.TraceRmiError; +import ghidra.framework.data.DomainObjectAdapterDB; +import ghidra.framework.model.TransactionInfo; +import ghidra.framework.model.TransactionListener; import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; import ghidra.rmi.trace.TraceRmi.*; @@ -29,12 +32,51 @@ class OpenTrace implements ValueDecoder { final DoId doId; final Trace trace; final TraceRmiTarget target; + final CurrentTxListener txListener; TraceSnapshot lastSnapshot; + class CurrentTxListener implements TransactionListener { + boolean undoable; + + public void markNotUndoable() { + undoable = false; + } + + @Override + public void transactionStarted(DomainObjectAdapterDB domainObj, TransactionInfo tx) { + undoable = true; + } + + @Override + public void transactionEnded(DomainObjectAdapterDB domainObj) { + if (!undoable) { + trace.clearUndo(); + } + } + + @Override + public void undoStackChanged(DomainObjectAdapterDB domainObj) { + // NOP + } + + @Override + public void undoRedoOccurred(DomainObjectAdapterDB domainObj) { + // NOP + } + } + OpenTrace(DoId doId, Trace trace, TraceRmiTarget target) { this.doId = doId; this.trace = trace; this.target = target; + this.txListener = new CurrentTxListener(); + + trace.addTransactionListener(txListener); + } + + public void dispose(TraceRmiHandler consumer) { + trace.removeTransactionListener(txListener); + trace.release(consumer); } public TraceSnapshot createSnapshot(Snap snap, String description) { @@ -55,7 +97,7 @@ class OpenTrace implements ValueDecoder { TraceObject object = trace.getObjectManager().getObjectByCanonicalPath(TraceRmiHandler.toKeyPath(path)); if (required && object == null) { - throw new InvalidObjPathError(); + throw new InvalidObjPathError(path.getPath()); } return object; } @@ -78,7 +120,7 @@ class OpenTrace implements ValueDecoder { public AddressSpace getSpace(String name, boolean required) { AddressSpace space = trace.getBaseAddressFactory().getAddressSpace(name); if (required && space == null) { - throw new NoSuchAddressSpaceError(); + throw new NoSuchAddressSpaceError(name); } return space; } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java index 8ffb81edee..8589e7efcc 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java @@ -90,9 +90,15 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection { } protected static class InvalidObjPathError extends TraceRmiError { + public InvalidObjPathError(String path) { + super(path); + } } protected static class NoSuchAddressSpaceError extends TraceRmiError { + public NoSuchAddressSpaceError(String name) { + super(name); + } } protected static class InvalidSchemaError extends TraceRmiError { @@ -292,8 +298,8 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection { synchronized (openTxes) { while (!openTxes.isEmpty()) { Tid nextKey = openTxes.keySet().iterator().next(); - OpenTx open = openTxes.remove(nextKey); - open.tx.close(); + OpenTx openTx = openTxes.remove(nextKey); + openTx.tx.close(); } } @@ -309,7 +315,7 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection { // OK. Move on } } - open.trace.release(this); + open.dispose(this); } closed.complete(null); plugin.listeners.invoke().disconnected(this); @@ -840,7 +846,7 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection { protected ReplyCloseTrace handleCloseTrace(RequestCloseTrace req) { OpenTrace open = requireOpenTrace(req.getOid()); openTraces.removeById(open.doId); - open.trace.release(this); + open.dispose(this); return ReplyCloseTrace.getDefaultInstance(); } @@ -968,13 +974,27 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection { Msg.error(this, "Back-end debugger aborted a transaction!"); tx.tx.abortOnClose(); } - tx.tx.close(); + OpenTrace open = requireOpenTrace(tx.txId.doId); if (!tx.undoable) { - open.trace.clearUndo(); + /** + * The listener is invoked via runLater, so we must do the same here, so that events are + * processed in the order emitted. + */ + Swing.runLater(() -> open.txListener.markNotUndoable()); + } + + tx.tx.close(); + + final boolean restoreEvents; + synchronized (openTxes) { + restoreEvents = openTxes.keySet() + .stream() + .noneMatch(id -> id.doId.domObjId == req.getOid().getId()); + } + if (restoreEvents) { + open.trace.setEventsEnabled(true); } - // TODO: Check for other transactions on the same trace? - open.trace.setEventsEnabled(true); return ReplyEndTx.getDefaultInstance(); } @@ -1278,7 +1298,7 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection { @Override public void forceCloseTrace(Trace trace) { OpenTrace open = openTraces.removeByTrace(trace); - open.trace.release(this); + open.dispose(this); } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/AbstractTracePatchInstructionAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/AbstractTracePatchInstructionAction.java index b9efb61aad..cfd3fd736d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/AbstractTracePatchInstructionAction.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/AbstractTracePatchInstructionAction.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,12 +15,16 @@ */ package ghidra.app.plugin.core.debug.disassemble; +import java.util.concurrent.*; + import docking.ActionContext; import ghidra.app.plugin.assembler.Assembler; import ghidra.app.plugin.assembler.Assemblers; import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock; import ghidra.app.plugin.core.assembler.AssemblyDualTextField; import ghidra.app.plugin.core.assembler.PatchInstructionAction; +import ghidra.app.services.DebuggerControlService; +import ghidra.app.services.DebuggerControlService.StateEditor; import ghidra.program.model.address.*; import ghidra.program.model.lang.*; import ghidra.program.model.listing.CodeUnit; @@ -118,12 +122,25 @@ public abstract class AbstractTracePatchInstructionAction extends PatchInstructi if (view == null) { return; } + DebuggerControlService controlService = tool.getService(DebuggerControlService.class); + if (controlService == null) { + return; + } + StateEditor editor = controlService.createStateEditor(view); Address address = getAddress(); - // Get code unit and dependencies before invalidating it, just in case. + + // Get code unit and dependencies before invalidating it. CodeUnit cu = getCodeUnit(); RegisterValue contextValue = getContextValue(cu); TracePlatform platform = getPlatform(cu); - view.getMemory().setBytes(address, data); // This invalidates cu + + try { + editor.setVariable(address, data).get(1, TimeUnit.SECONDS); + } + catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new MemoryAccessException("Couldn't patch", e); + } + AddressSetView set = new AddressSet(address, address.add(data.length - 1)); TraceDisassembleCommand dis = new TraceDisassembleCommand(platform, address, set); if (contextValue != null) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/CurrentPlatformTracePatchInstructionAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/CurrentPlatformTracePatchInstructionAction.java index 676fa0219d..b763222cb7 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/CurrentPlatformTracePatchInstructionAction.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/CurrentPlatformTracePatchInstructionAction.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -29,10 +29,7 @@ public class CurrentPlatformTracePatchInstructionAction @Override protected boolean isApplicableToUnit(CodeUnit cu) { - if (!super.isApplicableToUnit(cu)) { - return false; - } - return cu instanceof TraceInstruction; + return super.isApplicableToUnit(cu) && cu instanceof TraceInstruction; } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblerPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblerPlugin.java index 8d82536883..777e85b6f2 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblerPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblerPlugin.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -170,6 +170,7 @@ public class DebuggerDisassemblerPlugin extends Plugin implements PopupActionPro CurrentPlatformTraceDisassembleAction actionDisassemble; CurrentPlatformTracePatchInstructionAction actionPatchInstruction; + TracePatchDataAction actionPatchData; public DebuggerDisassemblerPlugin(PluginTool tool) { super(tool); @@ -185,9 +186,11 @@ public class DebuggerDisassemblerPlugin extends Plugin implements PopupActionPro protected void createActions() { actionDisassemble = new CurrentPlatformTraceDisassembleAction(this); actionPatchInstruction = new CurrentPlatformTracePatchInstructionAction(this); + actionPatchData = new TracePatchDataAction(this); tool.addAction(actionDisassemble); tool.addAction(actionPatchInstruction); + tool.addAction(actionPatchData); } /** diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/TracePatchDataAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/TracePatchDataAction.java new file mode 100644 index 0000000000..57d4508837 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/TracePatchDataAction.java @@ -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 + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/PasteIntoTargetMixin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/PasteIntoTargetMixin.java new file mode 100644 index 0000000000..2dbe69d906 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/PasteIntoTargetMixin.java @@ -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; + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java index c2c8e4a842..5acb99e7fb 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java @@ -291,6 +291,24 @@ public class DebuggerListingProvider extends CodeViewerProvider { } protected class ForListingClipboardProvider extends CodeBrowserClipboardProvider { + protected class PasteIntoTargetCommand extends PasteByteStringCommand + implements PasteIntoTargetMixin { + protected PasteIntoTargetCommand(String string) { + super(string); + } + + @Override + protected boolean hasEnoughSpace(Program program, Address address, int byteCount) { + return doHasEnoughSpace(program, address, byteCount); + } + + @Override + protected boolean pasteBytes(Program program, byte[] bytes) { + return doPasteBytes(tool, controlService, consoleService, current, currentLocation, + bytes); + } + } + protected ForListingClipboardProvider() { super(DebuggerListingProvider.this.tool, DebuggerListingProvider.this); } @@ -317,6 +335,11 @@ public class DebuggerListingProvider extends CodeViewerProvider { } return super.canPaste(availableFlavors); } + + @Override + protected boolean pasteByteString(String string) { + return tool.execute(new PasteIntoTargetCommand(string), currentProgram); + } } private final DebuggerListingPlugin plugin; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java index d8eae4b92b..34316f89b9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java @@ -21,8 +21,10 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.lang.invoke.MethodHandles; import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.*; -import java.util.concurrent.CompletableFuture; +import java.util.concurrent.*; import javax.swing.*; @@ -40,9 +42,9 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAc import ghidra.app.plugin.core.debug.gui.action.*; import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec; import ghidra.app.plugin.core.format.ByteBlock; -import ghidra.app.services.DebuggerControlService; +import ghidra.app.plugin.core.format.ByteBlockAccessException; +import ghidra.app.services.*; import ghidra.app.services.DebuggerControlService.ControlModeChangeListener; -import ghidra.app.services.DebuggerTraceManagerService; import ghidra.debug.api.action.GoToInput; import ghidra.debug.api.action.LocationTrackingSpec; import ghidra.debug.api.tracemgr.DebuggerCoordinates; @@ -165,6 +167,50 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi } } + protected class ForBytesClipboardProvider extends ByteViewerClipboardProvider { + protected class PasteIntoTargetCommand extends PasteByteStringCommand + implements PasteIntoTargetMixin { + protected PasteIntoTargetCommand(String string) { + super(string); + } + + @Override + protected boolean hasEnoughSpace(Program program, Address address, int byteCount) { + return doHasEnoughSpace(program, address, byteCount); + } + + @Override + protected boolean pasteBytes(Program program, byte[] bytes) { + return doPasteBytes(tool, controlService, consoleService, current, currentLocation, + bytes); + } + } + + protected ForBytesClipboardProvider() { + super(DebuggerMemoryBytesProvider.this, DebuggerMemoryBytesProvider.this.tool); + } + + @Override + public boolean canPaste(DataFlavor[] availableFlavors) { + if (controlService == null) { + return false; + } + Trace trace = current.getTrace(); + if (trace == null) { + return false; + } + if (!controlService.getCurrentMode(trace).canEdit(current)) { + return false; + } + return super.canPaste(availableFlavors); + } + + @Override + protected boolean pasteByteString(String string) { + return tool.execute(new PasteIntoTargetCommand(string), currentProgram); + } + } + private final AutoReadMemorySpec defaultReadMemorySpec = AutoReadMemorySpec.fromConfigName(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME); @@ -172,6 +218,8 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi @AutoServiceConsumed private DebuggerTraceManagerService traceManager; + @AutoServiceConsumed + private DebuggerConsoleService consoleService; //@AutoServiceConsumed via method private DebuggerControlService controlService; @SuppressWarnings("unused") @@ -272,23 +320,83 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi }; } + /** + * Override where edits are allowed and direct sets through the control service. + */ + class TargetByteBlock extends MemoryByteBlock { + protected TargetByteBlock(Program program, Memory memory, MemoryBlock block) { + super(program, memory, block); + } + + /** + * {@inheritDoc} + * + *

+ * Overridden to ignore existing instructions. Let them be clobbered! + */ + @Override + protected boolean editAllowed(Address addr, long length) { + return controlService != null; + } + + protected ByteBuffer alloc(int size) { + return ByteBuffer.allocate(size) + .order(isBigEndian() + ? ByteOrder.BIG_ENDIAN + : ByteOrder.LITTLE_ENDIAN); + } + + protected void doSet(Address address, ByteBuffer buffer) throws ByteBlockAccessException { + checkEditsAllowed(address, buffer.capacity()); + try { + controlService.createStateEditor(current) + .setVariable(address, buffer.array()) + .get(1, TimeUnit.SECONDS); + } + catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new ByteBlockAccessException("Could not set target memory", e); + } + } + + @Override + public void setByte(BigInteger index, byte value) throws ByteBlockAccessException { + doSet(getAddress(index), alloc(Byte.BYTES).put(value)); + } + + @Override + public void setShort(BigInteger index, short value) throws ByteBlockAccessException { + doSet(getAddress(index), alloc(Short.BYTES).putShort(value)); + } + + @Override + public void setInt(BigInteger index, int value) throws ByteBlockAccessException { + doSet(getAddress(index), alloc(Integer.BYTES).putInt(value)); + } + + @Override + public void setLong(BigInteger index, long value) throws ByteBlockAccessException { + doSet(getAddress(index), alloc(Long.BYTES).putLong(value)); + } + } + + class TargetByteBlockSet extends ProgramByteBlockSet { + protected TargetByteBlockSet(ByteBlockChangeManager changeManager) { + super(DebuggerMemoryBytesProvider.this, DebuggerMemoryBytesProvider.this.program, + changeManager); + } + + @Override + protected MemoryByteBlock newMemoryByteBlock(Memory memory, MemoryBlock memBlock) { + return new TargetByteBlock(program, memory, memBlock); + } + } + @Override protected ProgramByteBlockSet newByteBlockSet(ByteBlockChangeManager changeManager) { if (program == null) { return null; } - // A bit of work to get it to ignore existing instructions. Let them be clobbered! - return new ProgramByteBlockSet(this, program, changeManager) { - @Override - protected MemoryByteBlock newMemoryByteBlock(Memory memory, MemoryBlock memBlock) { - return new MemoryByteBlock(program, memory, memBlock) { - @Override - protected boolean editAllowed(Address addr, long length) { - return true; - } - }; - } - }; + return new TargetByteBlockSet(changeManager); } /** @@ -373,22 +481,7 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi @Override protected ByteViewerClipboardProvider newClipboardProvider() { - return new ByteViewerClipboardProvider(this, tool) { - @Override - public boolean canPaste(DataFlavor[] availableFlavors) { - if (controlService == null) { - return false; - } - Trace trace = current.getTrace(); - if (trace == null) { - return false; - } - if (!controlService.getCurrentMode(trace).canEdit(current)) { - return false; - } - return super.canPaste(availableFlavors); - } - }; + return new ForBytesClipboardProvider(); } @AutoServiceConsumed diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServicePlugin.java index c1f21cdc68..508300556c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServicePlugin.java @@ -15,14 +15,13 @@ */ package ghidra.app.plugin.core.debug.service.control; -import java.nio.ByteBuffer; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; -import ghidra.app.plugin.core.debug.event.*; +import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent; import ghidra.app.services.*; import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; import ghidra.debug.api.control.ControlMode; @@ -31,11 +30,8 @@ import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.address.Address; -import ghidra.program.model.mem.*; import ghidra.trace.model.Trace; -import ghidra.trace.model.Trace.TraceProgramViewListener; import ghidra.trace.model.program.TraceProgramView; -import ghidra.trace.model.program.TraceProgramViewMemory; import ghidra.util.datastruct.ListenerSet; @PluginInfo( @@ -45,9 +41,7 @@ import ghidra.util.datastruct.ListenerSet; packageName = DebuggerPluginPackage.NAME, status = PluginStatus.RELEASED, eventsConsumed = { - TraceOpenedPluginEvent.class, TraceActivatedPluginEvent.class, - TraceClosedPluginEvent.class, }, servicesRequired = { DebuggerTraceManagerService.class, @@ -115,8 +109,7 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin } } - public class FollowsViewStateEditor extends AbstractStateEditor - implements StateEditingMemoryHandler { + public class FollowsViewStateEditor extends AbstractStateEditor { private final TraceProgramView view; public FollowsViewStateEditor(TraceProgramView view) { @@ -132,81 +125,11 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin public DebuggerCoordinates getCoordinates() { return traceManager.resolveView(view); } - - @Override - public void clearCache() { - // Nothing to do - } - - @Override - public byte getByte(Address addr) throws MemoryAccessException { - ByteBuffer buf = ByteBuffer.allocate(1); - view.getTrace().getMemoryManager().getViewBytes(view.getSnap(), addr, buf); - return buf.get(0); - } - - @Override - public int getBytes(Address address, byte[] buffer, int startIndex, int size) - throws MemoryAccessException { - return view.getTrace() - .getMemoryManager() - .getViewBytes(view.getSnap(), address, - ByteBuffer.wrap(buffer, startIndex, size)); - } - - @Override - public void putByte(Address address, byte value) throws MemoryAccessException { - try { - setVariable(address, new byte[] { value }).get(1, TimeUnit.SECONDS); - } - catch (ExecutionException e) { - throw new MemoryAccessException("Failed to write " + address + ": " + e.getCause()); - } - catch (TimeoutException | InterruptedException e) { - throw new MemoryAccessException("Failed to write " + address + ": " + e); - } - } - - @Override - public int putBytes(Address address, byte[] source, int startIndex, int size) - throws MemoryAccessException { - try { - setVariable(address, Arrays.copyOfRange(source, startIndex, startIndex + size)) - .get(1, TimeUnit.SECONDS); - } - catch (ExecutionException e) { - throw new MemoryAccessException("Failed to write " + address + ": " + e.getCause()); - } - catch (TimeoutException | InterruptedException e) { - throw new MemoryAccessException("Failed to write " + address + ": " + e); - } - return size; - } - - @Override - public void addLiveMemoryListener(LiveMemoryListener listener) { - throw new UnsupportedOperationException(); - } - - @Override - public void removeLiveMemoryListener(LiveMemoryListener listener) { - throw new UnsupportedOperationException(); - } } - protected class ListenerForEditorInstallation implements TraceProgramViewListener { - @Override - public void viewCreated(TraceProgramView view) { - installMemoryEditor(view); - } - } - - //@AutoServiceConsumed // via method + @AutoServiceConsumed private DebuggerTraceManagerService traceManager; - protected final ListenerForEditorInstallation listenerForEditorInstallation = - new ListenerForEditorInstallation(); - private final Map currentModes = new HashMap<>(); private final ListenerSet listeners = @@ -219,7 +142,6 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin @Override protected void dispose() { super.dispose(); - uninstallAllMemoryEditors(); } @Override @@ -265,7 +187,7 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin } @Override - public StateEditingMemoryHandler createStateEditor(TraceProgramView view) { + public StateEditor createStateEditor(TraceProgramView view) { return new FollowsViewStateEditor(view); } @@ -292,78 +214,11 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin } } - protected void installMemoryEditor(TraceProgramView view) { - TraceProgramViewMemory memory = view.getMemory(); - if (memory.getLiveMemoryHandler() != null) { - return; - } - memory.setLiveMemoryHandler(createStateEditor(view)); - } - - protected void uninstallMemoryEditor(TraceProgramView view) { - TraceProgramViewMemory memory = view.getMemory(); - LiveMemoryHandler handler = memory.getLiveMemoryHandler(); - if (!(handler instanceof StateEditingMemoryHandler)) { - return; - } - StateEditingMemoryHandler editor = (StateEditingMemoryHandler) handler; - if (editor.getService() != this) { - return; - } - memory.setLiveMemoryHandler(null); - } - - protected void installAllMemoryEditors(Trace trace) { - trace.addProgramViewListener(listenerForEditorInstallation); - for (TraceProgramView view : trace.getAllProgramViews()) { - installMemoryEditor(view); - } - } - - protected void installAllMemoryEditors() { - if (traceManager == null) { - return; - } - - for (Trace trace : traceManager.getOpenTraces()) { - installAllMemoryEditors(trace); - } - } - - protected void uninstallAllMemoryEditors(Trace trace) { - trace.removeProgramViewListener(listenerForEditorInstallation); - for (TraceProgramView view : trace.getAllProgramViews()) { - uninstallMemoryEditor(view); - } - } - - protected void uninstallAllMemoryEditors() { - if (traceManager == null) { - return; - } - for (Trace trace : traceManager.getOpenTraces()) { - uninstallAllMemoryEditors(trace); - } - } - @Override public void processEvent(PluginEvent event) { super.processEvent(event); - if (event instanceof TraceOpenedPluginEvent ev) { - installAllMemoryEditors(ev.getTrace()); - } - else if (event instanceof TraceActivatedPluginEvent ev) { + if (event instanceof TraceActivatedPluginEvent ev) { coordinatesActivated(ev.getActiveCoordinates(), ev.getCause()); } - else if (event instanceof TraceClosedPluginEvent ev) { - uninstallAllMemoryEditors(ev.getTrace()); - } - } - - @AutoServiceConsumed - private void setTraceManager(DebuggerTraceManagerService traceManager) { - uninstallAllMemoryEditors(); - this.traceManager = traceManager; - installAllMemoryEditors(); } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblerPluginTestHelper.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblerPluginTestHelper.java index bf3a77d024..0ae0002059 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblerPluginTestHelper.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblerPluginTestHelper.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -22,6 +22,7 @@ import ghidra.program.model.listing.Program; public class DebuggerDisassemblerPluginTestHelper extends AssemblerPluginTestHelper { public DebuggerDisassemblerPluginTestHelper(DebuggerDisassemblerPlugin disassemblerPlugin, CodeViewerProvider provider, Program program) { - super(disassemblerPlugin.actionPatchInstruction, null, provider, program); + super(disassemblerPlugin.actionPatchInstruction, disassemblerPlugin.actionPatchData, + provider, program); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatformMappedMemory.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatformMappedMemory.java index a72ff23af2..713c95615b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatformMappedMemory.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatformMappedMemory.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -225,16 +225,6 @@ public class DBTraceGuestPlatformMappedMemory implements Memory { return guest.getLanguage().isBigEndian(); } - @Override - public void setLiveMemoryHandler(LiveMemoryHandler handler) { - throw new UnsupportedOperationException(); - } - - @Override - public LiveMemoryHandler getLiveMemoryHandler() { - return null; - } - @Override public MemoryBlock createInitializedBlock(String name, Address start, InputStream is, long length, TaskMonitor monitor, boolean overlay) diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewMemory.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewMemory.java index 847aef024c..0a1b27360b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewMemory.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewMemory.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -47,8 +47,6 @@ public abstract class AbstractDBTraceProgramViewMemory protected boolean forceFullView = false; protected long snap; - protected LiveMemoryHandler memoryWriteRedirect; - private static final int CACHE_PAGE_COUNT = 3; protected final ByteCache cache = new ByteCache(CACHE_PAGE_COUNT) { @Override @@ -162,16 +160,6 @@ public abstract class AbstractDBTraceProgramViewMemory return program.getLanguage().isBigEndian(); } - @Override - public void setLiveMemoryHandler(LiveMemoryHandler handler) { - this.memoryWriteRedirect = handler; - } - - @Override - public LiveMemoryHandler getLiveMemoryHandler() { - return memoryWriteRedirect; - } - @Override public MemoryBlock createInitializedBlock(String name, Address start, InputStream is, long length, TaskMonitor monitor, boolean overlay) @@ -339,10 +327,6 @@ public abstract class AbstractDBTraceProgramViewMemory @Override public void setByte(Address addr, byte value) throws MemoryAccessException { - if (memoryWriteRedirect != null) { - memoryWriteRedirect.putByte(addr, value); - return; - } DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true); if (space.putBytes(snap, addr, ByteBuffer.wrap(new byte[] { value })) != 1) { throw new MemoryAccessException(); @@ -352,10 +336,6 @@ public abstract class AbstractDBTraceProgramViewMemory @Override public void setBytes(Address addr, byte[] source, int sIndex, int size) throws MemoryAccessException { - if (memoryWriteRedirect != null) { - memoryWriteRedirect.putBytes(addr, source, sIndex, size); - return; - } DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true); if (space.putBytes(snap, addr, ByteBuffer.wrap(source, sIndex, size)) != size) { throw new MemoryAccessException(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/program/TraceProgramViewMemory.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/program/TraceProgramViewMemory.java index ca1929006b..446c3c6536 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/program/TraceProgramViewMemory.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/program/TraceProgramViewMemory.java @@ -15,7 +15,6 @@ */ package ghidra.trace.model.program; -import ghidra.program.model.mem.LiveMemoryHandler; import ghidra.program.model.mem.Memory; public interface TraceProgramViewMemory extends Memory, SnapSpecificTraceView { @@ -25,13 +24,4 @@ public interface TraceProgramViewMemory extends Memory, SnapSpecificTraceView { void setForceFullView(boolean forceFullView); boolean isForceFullView(); - - /** - * {@inheritDoc} - * - *

- * For trace views, this only redirects memory writes. - */ - @Override - void setLiveMemoryHandler(LiveMemoryHandler handler); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssemblerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssemblerPlugin.java index 8c196badf7..976403460f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssemblerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssemblerPlugin.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -39,13 +39,18 @@ import ghidra.program.model.mem.MemBuffer; * The API for instruction assembly is available from {@link Assemblers}. For data assembly, the API * is in {@link DataType#encodeRepresentation(String, MemBuffer, Settings, int)}. */ -@PluginInfo(status = PluginStatus.RELEASED, packageName = CorePluginPackage.NAME, category = "Patching", shortDescription = "Assembler", description = "This plugin provides functionality for assembly patching. " + - "The assembler supports most processor languages also supported by the " + - "disassembler. Depending on the particular processor, your mileage may vary. " + - "We are in the process of testing and improving support for all our processors. " + - "You can access the assembler by pressing Ctrl-Shift-G, and then modifying the " + - "instruction in place. As you type, a content assist will guide you and provide " + - "assembled bytes when you have a complete instruction.") +@PluginInfo( + status = PluginStatus.RELEASED, + packageName = CorePluginPackage.NAME, + category = "Patching", + shortDescription = "Assembler", + description = "This plugin provides functionality for assembly patching. " + + "The assembler supports most processor languages also supported by the " + + "disassembler. Depending on the particular processor, your mileage may vary. " + + "We are in the process of testing and improving support for all our processors. " + + "You can access the assembler by pressing Ctrl-Shift-G, and then modifying the " + + "instruction in place. As you type, a content assist will guide you and provide " + + "assembled bytes when you have a complete instruction.") public class AssemblerPlugin extends ProgramPlugin { public static final String ASSEMBLER_NAME = "Assembler"; @@ -58,35 +63,39 @@ public class AssemblerPlugin extends ProgramPlugin { } private void createActions() { - // Debugger provides its own "Patch Instruction" action + // Debugger provides its own "Patch" actions patchInstructionAction = new PatchInstructionAction(this) { @Override public boolean isEnabledForContext(ActionContext context) { - if (!super.isEnabledForContext(context)) { - return false; - } - if (!(context instanceof ListingActionContext)) { - return false; - } - ListingActionContext lac = (ListingActionContext) context; - return !lac.getNavigatable().isDynamic(); + return super.isEnabledForContext(context) && + context instanceof ListingActionContext lac && + !lac.getNavigatable().isDynamic(); } @Override public boolean isAddToPopup(ActionContext context) { - if (!super.isAddToPopup(context)) { - return false; - } - if (!(context instanceof ListingActionContext)) { - return false; - } - ListingActionContext lac = (ListingActionContext) context; - return !lac.getNavigatable().isDynamic(); + return super.isAddToPopup(context) && + context instanceof ListingActionContext lac && + !lac.getNavigatable().isDynamic(); } }; tool.addAction(patchInstructionAction); - patchDataAction = new PatchDataAction(this); + patchDataAction = new PatchDataAction(this) { + @Override + public boolean isEnabledForContext(ActionContext context) { + return super.isEnabledForContext(context) && + context instanceof ListingActionContext lac && + !lac.getNavigatable().isDynamic(); + } + + @Override + public boolean isAddToPopup(ActionContext context) { + return super.isAddToPopup(context) && + context instanceof ListingActionContext lac && + !lac.getNavigatable().isDynamic(); + } + }; tool.addAction(patchDataAction); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchDataAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchDataAction.java index 40b026d0c0..c884e2a1e1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchDataAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchDataAction.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -73,14 +73,7 @@ public class PatchDataAction extends AbstractPatchAction { @Override protected boolean isApplicableToUnit(CodeUnit cu) { - if (!(cu instanceof Data)) { - return false; - } - Data data = (Data) cu; - if (!data.getBaseDataType().isEncodable()) { - return false; - } - return true; + return cu instanceof Data data && data.getBaseDataType().isEncodable(); } protected Data getData() { @@ -113,6 +106,23 @@ public class PatchDataAction extends AbstractPatchAction { input.setCaretPosition(repr.length()); } + protected void applyPatch(AddressRange rng, byte[] encoded) + throws MemoryAccessException, CodeUnitInsertionException { + Program program = getProgram(); + Address address = getAddress(); + Data data = getData(); + DataType dt = data.getBaseDataType(); + + int oldLength = data.getLength(); + if (encoded.length != oldLength) { + program.getListing().clearCodeUnits(address, rng.getMaxAddress(), false); + } + program.getMemory().setBytes(address, encoded); + if (encoded.length != oldLength) { + program.getListing().createData(address, dt, encoded.length); + } + } + @Override public void accept() { Program program = getProgram(); @@ -135,14 +145,7 @@ public class PatchDataAction extends AbstractPatchAction { } try (Transaction tx = program.openTransaction("Patch Data @" + address + ": " + input.getText())) { - int oldLength = data.getLength(); - if (encoded.length != oldLength) { - program.getListing().clearCodeUnits(address, rng.getMaxAddress(), false); - } - program.getMemory().setBytes(address, encoded); - if (encoded.length != oldLength) { - program.getListing().createData(address, dt, encoded.length); - } + applyPatch(rng, encoded); hide(); } catch (MemoryAccessException e) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/ByteCopier.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/ByteCopier.java index 2054bd43d4..ee77db5759 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/ByteCopier.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/ByteCopier.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -25,7 +25,6 @@ import docking.dnd.GenericDataFlavor; import docking.dnd.StringTransferable; import docking.widgets.OptionDialog; import ghidra.framework.cmd.Command; -import ghidra.framework.model.DomainObject; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.*; import ghidra.program.model.listing.*; @@ -38,8 +37,7 @@ import ghidra.util.*; import ghidra.util.task.TaskMonitor; /** - * Base class that can copy bytes into a Transferable object, and paste - * bytes into a program. + * Base class that can copy bytes into a Transferable object, and paste bytes into a program. * */ public abstract class ByteCopier { @@ -381,125 +379,12 @@ public abstract class ByteCopier { } protected boolean pasteByteString(final String string) { - Command cmd = new Command() { - - private String status = "Pasting"; - - @Override - public boolean applyTo(DomainObject domainObject) { - if (!(domainObject instanceof Program)) { - return false; - } - - String validString = string; - if (!isOnlyAsciiBytes(string)) { - tool.setStatusInfo("Pasted string contained non-text ascii bytes. " + - "Only the ascii will be used.", true); - validString = keepOnlyAsciiBytes(string); - } - - byte[] bytes = getBytes(validString); - if (bytes == null) { - status = "Improper data format. Expected sequence of hex bytes"; - tool.beep(); - return false; - } - - // Ensure that we are not writing over instructions - Program program = (Program) domainObject; - Address address = currentLocation.getAddress(); - if (!hasEnoughSpace(program, address, bytes.length)) { - status = - "Not enough space to paste all bytes. Encountered data or instructions."; - tool.beep(); - return false; - } - - // Ask the user before pasting a string into the program. Since having a string in - // the clipboard is so common, this is to prevent an accidental paste. - if (!confirmPaste(validString)) { - return true; // the user cancelled; the command is successful - } - - boolean pastedAllBytes = pasteBytes(program, bytes); - if (!pastedAllBytes) { - tool.setStatusInfo("Not all bytes were pasted due to memory access issues", - true); - } - - return true; - } - - private boolean pasteBytes(Program program, byte[] bytes) { - - // note: loop one byte at a time here, since Memory will validate all addresses - // before pasting any bytes - boolean foundError = false; - Address address = currentLocation.getAddress(); - Memory memory = program.getMemory(); - for (byte element : bytes) { - try { - memory.setByte(address, element); - } - catch (MemoryAccessException e) { - // Keep trying the remaining bytes. Should we just stop in this case? - foundError = true; - } - address = address.next(); - } - return !foundError; - } - - private boolean confirmPaste(String validString) { - - // create a truncated version of the string to show in the dialog - String partialText = validString.length() < 40 ? validString - : validString.substring(0, 40) + " ..."; - int result = OptionDialog.showYesNoDialog(null, "Paste String Into Program?", - "Are you sure you want to paste the string \"" + partialText + - "\"\n into the program's memory?"); - - return result != OptionDialog.NO_OPTION; - } - - private boolean hasEnoughSpace(Program program, Address address, int byteCount) { - Listing listing = program.getListing(); - for (int i = 0; i < byteCount;) { - if (address == null) { - status = "Not enough addresses to paste bytes"; - tool.beep(); - return false; - } - CodeUnit codeUnit = listing.getCodeUnitContaining(address); - if (!(codeUnit instanceof Data) || ((Data) codeUnit).isDefined()) { - status = "Cannot paste on top of defined instructions/data"; - tool.beep(); - return false; - } - int length = codeUnit.getLength(); - i += length; - address = codeUnit.getMaxAddress().next(); - } - return true; - } - - @Override - public String getStatusMsg() { - return status; - } - - @Override - public String getName() { - return "Paste"; - } - - }; - - return tool.execute(cmd, currentProgram); + return tool.execute(new PasteByteStringCommand(string), currentProgram); } /** * Create a Transferable from the given text. + * * @param text text used to create a Transferable * @return a Transferable */ @@ -511,8 +396,120 @@ public abstract class ByteCopier { // Inner Classes //================================================================================================== + protected class PasteByteStringCommand implements Command { + protected final String string; + private String status = "Pasting"; + + protected PasteByteStringCommand(String string) { + this.string = string; + } + + @Override + public boolean applyTo(Program program) { + String validString = string; + if (!isOnlyAsciiBytes(string)) { + tool.setStatusInfo("Pasted string contained non-text ascii bytes. " + + "Only the ascii will be used.", true); + validString = keepOnlyAsciiBytes(string); + } + + byte[] bytes = getBytes(validString); + if (bytes == null) { + status = "Improper data format. Expected sequence of hex bytes"; + tool.beep(); + return false; + } + + // Ensure that we are not writing over instructions + Address address = currentLocation.getAddress(); + if (!hasEnoughSpace(program, address, bytes.length)) { + status = + "Not enough space to paste all bytes. Encountered data or instructions."; + tool.beep(); + return false; + } + + // Ask the user before pasting a string into the program. Since having a string in + // the clipboard is so common, this is to prevent an accidental paste. + if (!confirmPaste(validString)) { + return true; // the user cancelled; the command is successful + } + + boolean pastedAllBytes = pasteBytes(program, bytes); + if (!pastedAllBytes) { + tool.setStatusInfo("Not all bytes were pasted due to memory access issues", + true); + } + + return true; + } + + protected boolean pasteBytes(Program program, byte[] bytes) { + + // note: loop one byte at a time here, since Memory will validate all addresses + // before pasting any bytes + boolean foundError = false; + Address address = currentLocation.getAddress(); + Memory memory = program.getMemory(); + for (byte element : bytes) { + try { + memory.setByte(address, element); + } + catch (MemoryAccessException e) { + // Keep trying the remaining bytes. Should we just stop in this case? + foundError = true; + } + address = address.next(); + } + return !foundError; + } + + protected boolean confirmPaste(String validString) { + + // create a truncated version of the string to show in the dialog + String partialText = validString.length() < 40 ? validString + : validString.substring(0, 40) + " ..."; + int result = OptionDialog.showYesNoDialog(null, "Paste String Into Program?", + "Are you sure you want to paste the string \"" + partialText + + "\"\n into the program's memory?"); + + return result != OptionDialog.NO_OPTION; + } + + protected boolean hasEnoughSpace(Program program, Address address, int byteCount) { + Listing listing = program.getListing(); + for (int i = 0; i < byteCount;) { + if (address == null) { + status = "Not enough addresses to paste bytes"; + tool.beep(); + return false; + } + CodeUnit codeUnit = listing.getCodeUnitContaining(address); + if (!(codeUnit instanceof Data data) || data.isDefined()) { + status = "Cannot paste on top of defined instructions/data"; + tool.beep(); + return false; + } + int length = codeUnit.getLength(); + i += length; + address = codeUnit.getMaxAddress().next(); + } + return true; + } + + @Override + public String getStatusMsg() { + return status; + } + + @Override + public String getName() { + return "Paste"; + } + } + /** - * An iterator of bytes from memory. This class exists because the {@link MemoryByteIterator} + * An iterator of bytes from memory. This class exists because the {@link MemoryByteIterator} * throws an exception from its next() method, which will not work for us. */ private static class ByteIterator implements Iterator { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/mem/MemoryManagerTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/mem/MemoryManagerTest.java index e24a7b8f06..62aea1f787 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/mem/MemoryManagerTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/mem/MemoryManagerTest.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -486,78 +486,6 @@ public class MemoryManagerTest extends AbstractGhidraHeadedIntegrationTest { assertEquals(block.isWrite(), newBlock.isWrite()); } - @Test - public void testLiveMemory() throws Exception { - - mem.createInitializedBlock("Test", addr(0), 0x1000, (byte) 0x55, null, false); - - LiveMemoryHandler testHandler = new LiveMemoryHandler() { - @Override - public void clearCache() { - } - - @Override - public byte getByte(Address addr) throws MemoryAccessException { - return 0; - } - - @Override - public int getBytes(Address addr, byte[] dest, int dIndex, int size) - throws MemoryAccessException { - for (int i = 0; i < size; ++i) { - dest[dIndex + i] = (byte) i; - } - return size; - } - - @Override - public void putByte(Address addr, byte value) { - } - - @Override - public int putBytes(Address address, byte[] source, int sIndex, int size) - throws MemoryAccessException { - return 0; - } - - @Override - public void addLiveMemoryListener(LiveMemoryListener listener) { - // TODO Auto-generated method stub - - } - - @Override - public void removeLiveMemoryListener(LiveMemoryListener listener) { - // TODO Auto-generated method stub - - } - }; - - assertEquals((byte) 0x55, mem.getByte(addr(0x500))); - - mem.setLiveMemoryHandler(testHandler); - - byte[] bytes = new byte[5]; - mem.getBytes(addr(0x1000), bytes); - - for (int i = 0; i < bytes.length; ++i) { - assertEquals(i, bytes[i]); - } - - assertEquals((byte) 0, mem.getByte(addr(0x500))); - - mem.setLiveMemoryHandler(null); - - try { - mem.getBytes(addr(0x1000), bytes); - Assert.fail(); - } - catch (MemoryAccessException e) { - } - - assertEquals((byte) 0x55, mem.getByte(addr(0x500))); - } - @Test public void testSplitBlock() throws Exception { createBlock("Test", addr(0), 100); diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/checksums/MyTestMemory.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/checksums/MyTestMemory.java index b5901c34fe..8720c24068 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/checksums/MyTestMemory.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/checksums/MyTestMemory.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -67,16 +67,6 @@ public class MyTestMemory extends AddressSet implements Memory { return false; } - @Override - public void setLiveMemoryHandler(LiveMemoryHandler handler) { - throw new UnsupportedOperationException(); - } - - @Override - public LiveMemoryHandler getLiveMemoryHandler() { - throw new UnsupportedOperationException(); - } - @Override public MemoryBlock createInitializedBlock(String name, Address start, InputStream is, long length, TaskMonitor monitor, boolean overlay) diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/format/ByteBlockAccessException.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/format/ByteBlockAccessException.java index 4ff14b362c..4296b5ff27 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/format/ByteBlockAccessException.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/format/ByteBlockAccessException.java @@ -18,26 +18,33 @@ package ghidra.app.plugin.core.format; import ghidra.util.exception.UsrException; /** - *

An ByteBlockAccessException indicates that the attempted - * access is not permitted. (i.e. Readable/Writeable)

- * + * A {@code ByteBlockAccessException} indicates that the attempted access is not permitted. (i.e. + * Readable/Writeable) */ public class ByteBlockAccessException extends UsrException { - /** - *

Constructs an ByteBlockAccessException with no detail message. + /** + * Construct an exception with no details */ - public ByteBlockAccessException() { - super(); - } - - - /** - *

Constructs an ByteBlockAccessException with the specified - * detail message. + public ByteBlockAccessException() { + super(); + } + + /** + * Construct an exception with the specified message * - * @param message The message. + * @param message the message */ - public ByteBlockAccessException(String message) { - super(message); - } -} + public ByteBlockAccessException(String message) { + super(message); + } + + /** + * Construct an exception with the specified message and cause + * + * @param message the message + * @param cause the cause + */ + public ByteBlockAccessException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/Ghidra/Framework/DB/src/main/java/db/Transaction.java b/Ghidra/Framework/DB/src/main/java/db/Transaction.java index f1a48fc24c..714fd7a717 100644 --- a/Ghidra/Framework/DB/src/main/java/db/Transaction.java +++ b/Ghidra/Framework/DB/src/main/java/db/Transaction.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -49,19 +49,24 @@ public abstract class Transaction implements AutoCloseable { } /** - * End this transaction if currently active. - * @param commit true if changes should be commited, false if all changes in this transaction - * should be discarded (i.e., rollback). If this is a "sub-transaction" and commit is false, - * the larger transaction will rollback upon completion. - * @return true if changes have been commited or false if nothing to commit or commit parameter - * was specified as false. + * End this transaction if currently active. + * + * @param commit true if changes should be committed, false if all changes in this transaction + * should be discarded (i.e., rollback). If this is a "sub-transaction" and commit is + * false, the larger transaction will rollback upon completion. + * @return true if changes have been committed or false if nothing to commit or commit parameter + * was specified as false. */ abstract protected boolean endTransaction(@SuppressWarnings("hiding") boolean commit); /** - * Determine if this is a sub-transaction to a larger transaction. If true is returned the - * larger transaction will not complete until all sub-transactions have ended. The larger - * transaction will rollback upon completion if any of the sub-transactions do not commit. + * Determine if this is a sub-transaction to a larger transaction. + * + *

+ * If true is returned the larger transaction will not complete until all sub-transactions have + * ended. The larger transaction will rollback upon completion if any of the sub-transactions do + * not commit. + * * @return true if this is a sub-transaction, else false. */ public boolean isSubTransaction() { @@ -69,16 +74,21 @@ public abstract class Transaction implements AutoCloseable { } /** - * Mark transaction for rollback/non-commit upon closing. A subsequent invocation of - * {@link #commitOnClose()} will alter this state prior to closing. + * Mark transaction for rollback/non-commit upon closing. + * + *

+ * A subsequent invocation of {@link #commitOnClose()} will alter this state prior to closing. */ public void abortOnClose() { commit = false; } /** - * Mark transaction for commit upon closing. This state is assumed by default. A subsequent - * invocation of {@link #abortOnClose()} will alter this state prior to closing. + * Mark transaction for commit upon closing. + * + *

+ * This state is assumed by default. A subsequent invocation of {@link #abortOnClose()} will + * alter this state prior to closing. */ public void commitOnClose() { commit = true; @@ -106,7 +116,9 @@ public abstract class Transaction implements AutoCloseable { /** * End this transaction if active using the current commit state. - * See {@link #commitOnClose()}, {@link #abortOnClose()}. + * + * @see #commitOnClose() + * @see #abortOnClose() */ @Override public void close() { @@ -115,5 +127,4 @@ public abstract class Transaction implements AutoCloseable { endTransaction(commit); } } - } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectTransactionManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectTransactionManager.java index d9791b17d2..b4d98f16e7 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectTransactionManager.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectTransactionManager.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,8 +20,7 @@ import java.util.*; import ghidra.framework.model.*; import ghidra.framework.model.TransactionInfo.Status; -import ghidra.util.Msg; -import ghidra.util.SystemUtilities; +import ghidra.util.*; import ghidra.util.datastruct.WeakDataStructureFactory; import ghidra.util.datastruct.WeakSet; @@ -192,9 +191,11 @@ class DomainObjectTransactionManager extends AbstractTransactionManager { } /** - * Returns the undo stack depth. - * (The number of items on the undo stack) + * Returns the undo stack depth (The number of items on the undo stack). + * + *

* This method is for JUnits. + * * @return the undo stack depth */ @Override @@ -333,7 +334,7 @@ class DomainObjectTransactionManager extends AbstractTransactionManager { } void notifyStartTransaction(TransactionInfo tx) { - SystemUtilities.runSwingLater(() -> { + Swing.runLater(() -> { for (TransactionListener listener : transactionListeners) { listener.transactionStarted(domainObj, tx); listener.undoStackChanged(domainObj); @@ -342,7 +343,7 @@ class DomainObjectTransactionManager extends AbstractTransactionManager { } void notifyEndTransaction() { - SystemUtilities.runSwingLater(() -> { + Swing.runLater(() -> { for (TransactionListener listener : transactionListeners) { listener.transactionEnded(domainObj); listener.undoStackChanged(domainObj); @@ -351,7 +352,7 @@ class DomainObjectTransactionManager extends AbstractTransactionManager { } void notifyUndoStackChanged() { - SystemUtilities.runSwingLater(() -> { + Swing.runLater(() -> { for (TransactionListener listener : transactionListeners) { listener.undoStackChanged(domainObj); } @@ -359,7 +360,7 @@ class DomainObjectTransactionManager extends AbstractTransactionManager { } void notifyUndoRedo() { - SystemUtilities.runSwingLater(() -> { + Swing.runLater(() -> { for (TransactionListener listener : transactionListeners) { listener.undoRedoOccurred(domainObj); listener.undoStackChanged(domainObj); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/TransactionInfo.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/TransactionInfo.java index d2d5188716..5b12f0b781 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/TransactionInfo.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/TransactionInfo.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -46,8 +46,8 @@ public interface TransactionInfo { /** * Determine if the corresponding transaction, and all of its sub-transactions, has been - * comitted to the underlying database. - * @return true if the corresponding transaction has been comitted, else false. + * committed to the underlying database. + * @return true if the corresponding transaction has been committed, else false. */ public boolean hasCommittedDBTransaction(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/MemoryBlockDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/MemoryBlockDB.java index a77953eb56..b16b173f00 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/MemoryBlockDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/MemoryBlockDB.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -362,9 +362,6 @@ public class MemoryBlockDB implements MemoryBlock { @Override public byte getByte(Address addr) throws MemoryAccessException { - if (memMap.getLiveMemoryHandler() != null) { - return memMap.getByte(addr); - } checkValid(); long offset = getBlockOffset(addr); return getByte(offset); @@ -378,10 +375,6 @@ public class MemoryBlockDB implements MemoryBlock { @Override public int getBytes(Address addr, byte[] b, int off, int len) throws IndexOutOfBoundsException, MemoryAccessException { - if (memMap.getLiveMemoryHandler() != null) { - return memMap.getBytes(addr, b, off, len); - } - checkValid(); long offset = getBlockOffset(addr); return getBytes(offset, b, off, len); @@ -389,10 +382,6 @@ public class MemoryBlockDB implements MemoryBlock { @Override public void putByte(Address addr, byte b) throws MemoryAccessException { - if (memMap.getLiveMemoryHandler() != null) { - memMap.setByte(addr, b); - return; - } long offset = getBlockOffset(addr); memMap.lock.acquire(); try { @@ -414,10 +403,6 @@ public class MemoryBlockDB implements MemoryBlock { @Override public int putBytes(Address addr, byte[] b, int off, int len) throws IndexOutOfBoundsException, MemoryAccessException { - if (memMap.getLiveMemoryHandler() != null) { - memMap.setBytes(addr, b, off, len); - return len; - } memMap.lock.acquire(); try { checkValid(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/MemoryMapDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/MemoryMapDB.java index 65189abc37..6c262f3512 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/MemoryMapDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/MemoryMapDB.java @@ -40,7 +40,7 @@ import ghidra.util.task.TaskMonitor; /** * The database memory map manager. */ -public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { +public class MemoryMapDB implements Memory, ManagerDB { private ProgramDB program; private AddressMapDB addrMap; @@ -68,7 +68,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { } private MemoryBlock lastBlock;// the last accessed block - private LiveMemoryHandler liveMemory; // lazy hashmap of block names to blocks, must be reloaded if blocks are removed or added private HashMap nameBlockMap = new HashMap<>(); @@ -260,9 +259,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { initializeBlocks(); buildAddressSets(true); } - if (liveMemory != null) { - liveMemory.clearCache(); - } addrMap.memoryMapChanged(this); } @@ -359,10 +355,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { @Override public AddressSetView getLoadedAndInitializedAddressSet() { - if (liveMemory != null) { - return this; // all memory is initialized! - } - MemoryAddressSetViews localAddrSetViews = buildAddressSets(false); return new AddressSetViewAdapter(localAddrSetViews.initializedAndLoaded); } @@ -586,29 +578,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { return defaultEndian == BIG_ENDIAN; } - @Override - public void setLiveMemoryHandler(LiveMemoryHandler handler) { - lock.acquire(); - try { - if (liveMemory != null) { - liveMemory.removeLiveMemoryListener(this); - } - liveMemory = handler; - if (liveMemory != null) { - liveMemory.addLiveMemoryListener(this); - } - program.invalidate(); - } - finally { - lock.release(); - } - } - - @Override - public LiveMemoryHandler getLiveMemoryHandler() { - return liveMemory; - } - @Override public MemoryBlock createInitializedBlock(String name, Address start, long size, byte initialValue, TaskMonitor monitor, boolean overlay) throws LockException, @@ -976,10 +945,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { lock.acquire(); try { program.checkExclusiveAccess(); - if (liveMemory != null) { - throw new MemoryBlockException( - "Memory move operation not permitted while live memory is active"); - } checkBlock(block); MemoryBlockDB memBlock = (MemoryBlockDB) block; @@ -1026,10 +991,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { lock.acquire(); try { program.checkExclusiveAccess(); - if (liveMemory != null) { - throw new MemoryBlockException( - "Memory split operation not permitted while live memory is active"); - } checkBlock(block); MemoryBlockDB memBlock = (MemoryBlockDB) block; if (!memBlock.contains(addr)) { @@ -1115,10 +1076,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { throws MemoryBlockException, LockException { program.checkExclusiveAccess(); - if (liveMemory != null) { - throw new MemoryBlockException( - "Memory join operation not permitted while live memory is active"); - } checkBlockForJoining(block1); checkBlockForJoining(block2); @@ -1433,9 +1390,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { @Override public byte getByte(Address addr) throws MemoryAccessException { - if (liveMemory != null) { - return liveMemory.getByte(addr); - } MemoryBlock block = getBlockDB(addr); if (block == null) { throw new MemoryAccessException( @@ -1452,9 +1406,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { @Override public int getBytes(Address addr, byte[] dest, int dIndex, int size) throws MemoryAccessException { - if (liveMemory != null) { - return liveMemory.getBytes(addr, dest, dIndex, size); - } int numRead = 0; long lastRead = 0; while (numRead < size) { @@ -1685,11 +1636,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { @Override public void setByte(Address addr, byte value) throws MemoryAccessException { - if (liveMemory != null) { - liveMemory.putByte(addr, value); - fireBytesChanged(addr, 1); - return; - } lock.acquire(); try { MemoryBlock block = getBlock(addr); @@ -1714,12 +1660,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { @Override public void setBytes(Address address, byte[] source, int sIndex, int size) throws MemoryAccessException { - if (liveMemory != null) { - int cnt = liveMemory.putBytes(address, source, sIndex, size); - fireBytesChanged(address, cnt); - return; - } - lock.acquire(); try { Address addr = address; @@ -2149,11 +2089,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { return super.hashCode(); } - @Override - public void memoryChanged(Address addr, int size) { - fireBytesChanged(addr, size); - } - @Override public AddressRangeIterator getAddressRanges(Address start, boolean forward) { return allAddrSet.getAddressRanges(start, forward); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/LiveMemoryHandler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/LiveMemoryHandler.java deleted file mode 100644 index fb58c82467..0000000000 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/LiveMemoryHandler.java +++ /dev/null @@ -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); -} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/LiveMemoryListener.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/LiveMemoryListener.java deleted file mode 100644 index dd90ca5a53..0000000000 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/LiveMemoryListener.java +++ /dev/null @@ -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); -} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/Memory.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/Memory.java index a1fdf4cc6d..8e96e26eaf 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/Memory.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/Memory.java @@ -151,18 +151,6 @@ public interface Memory extends AddressSetView { return block != null && block.isExternalBlock(); } - /** - * Sets the live memory handler - * @param handler the live memory handler - */ - public void setLiveMemoryHandler(LiveMemoryHandler handler); - - /** - * Returns the live memory handler instance used by this memory. - * @return the live memory handler - */ - public LiveMemoryHandler getLiveMemoryHandler(); - /** * Create an initialized memory block based upon a data {@link InputStream} and add it to * this Memory. diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/StubMemory.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/StubMemory.java index 816b150108..ae14cf4aa0 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/StubMemory.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/StubMemory.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -175,16 +175,6 @@ public class StubMemory extends AddressSet implements Memory { throw new UnsupportedOperationException(); } - @Override - public void setLiveMemoryHandler(LiveMemoryHandler handler) { - throw new UnsupportedOperationException(); - } - - @Override - public LiveMemoryHandler getLiveMemoryHandler() { - throw new UnsupportedOperationException(); - } - @Override public MemoryBlock createInitializedBlock(String name, Address start, InputStream is, long length, TaskMonitor monitor, boolean overlay) { diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java index 7034132d41..d187414885 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java @@ -35,8 +35,6 @@ import docking.dnd.GClipboard; import docking.widgets.OptionDialog; import generic.Unique; import ghidra.app.plugin.assembler.*; -import ghidra.app.plugin.core.assembler.AssemblerPlugin; -import ghidra.app.plugin.core.assembler.AssemblerPluginTestHelper; import ghidra.app.plugin.core.clipboard.ClipboardPlugin; import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin; @@ -465,7 +463,8 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerInteg @Test public void testPatchDataActionInDynamicListingEmu() throws Throwable { - AssemblerPlugin assemblerPlugin = addPlugin(tool, AssemblerPlugin.class); + DebuggerDisassemblerPlugin disassemblerPlugin = + addPlugin(tool, DebuggerDisassemblerPlugin.class); assertFalse(controlPlugin.actionControlMode.isEnabled()); @@ -482,8 +481,8 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerInteg } CodeViewerProvider listingProvider = listingPlugin.getProvider(); - AssemblerPluginTestHelper helper = - new AssemblerPluginTestHelper(assemblerPlugin, listingProvider, view); + DebuggerDisassemblerPluginTestHelper helper = + new DebuggerDisassemblerPluginTestHelper(disassemblerPlugin, listingProvider, view); traceManager.activateTrace(tb.trace); waitForSwing(); diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java index c48e2d171a..e2c1ffa879 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java @@ -1178,11 +1178,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge performAction(actionPaste, memBytesProvider, false); OptionDialog confirm = waitForDialogComponent(OptionDialog.class); pressButtonByText(confirm, "Yes"); - // TODO: This shouldn't be separate calls per byte! - handleWriteMemInvocation(process, tb.addr(0x55550800), new Bytes(0x42)); - handleWriteMemInvocation(process, tb.addr(0x55550801), new Bytes(0x53)); - handleWriteMemInvocation(process, tb.addr(0x55550802), new Bytes(0x64)); - handleWriteMemInvocation(process, tb.addr(0x55550803), new Bytes(0x75)); + handleWriteMemInvocation(process, tb.addr(0x55550800), new Bytes(0x42, 0x53, 0x64, 0x75)); performAction(actionEdit);