diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgManagerImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgManagerImpl.java index a0cf23b1a8..31863795bb 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgManagerImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgManagerImpl.java @@ -359,6 +359,7 @@ public class DbgManagerImpl implements DbgManager { } } + @Override public DbgSessionImpl getSessionComputeIfAbsent(DebugSessionId id, boolean fire) { synchronized (sessions) { if (!sessions.containsKey(id) && id.value() >= 0) { @@ -1403,13 +1404,13 @@ public class DbgManagerImpl implements DbgManager { @Override public CompletableFuture addMemory(DbgModuleMemory region) { memory.put(region.getId(), region); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } @Override public CompletableFuture removeMemory(Long id) { memory.remove(id); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } @Override @@ -1622,7 +1623,7 @@ public class DbgManagerImpl implements DbgManager { //getEventListeners().fire.promptChanged(prompt); continuation.complete(command); setContinuation(null); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return execute( new DbgConsoleExecCommand(this, command, DbgConsoleExecCommand.Output.CONSOLE)) diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetActiveScope.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetActiveScope.java index 450ec57a2f..6aa31e22ea 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetActiveScope.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetActiveScope.java @@ -42,7 +42,7 @@ public interface DbgModelTargetActiveScope extends DbgModelTargetObject, TargetA public default CompletableFuture doRequestActivation(TargetObject obj) { if (getManager().isWaiting()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } getModel().assertMine(TargetObject.class, obj); if (!PathUtils.isAncestor(this.getPath(), obj.getPath())) { @@ -62,7 +62,7 @@ public interface DbgModelTargetActiveScope extends DbgModelTargetObject, TargetA } throw new AssertionError(); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetFocusScope.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetFocusScope.java index 60a5d54d1a..7fb117f50c 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetFocusScope.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetFocusScope.java @@ -48,11 +48,11 @@ public interface DbgModelTargetFocusScope extends DbgModelTargetObject, TargetFo public default CompletableFuture doRequestFocus(TargetObject obj) { if (getManager().isWaiting()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } getModel().assertMine(TargetObject.class, obj); if (obj.equals(getFocus())) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } if (!PathUtils.isAncestor(this.getPath(), obj.getPath())) { throw new DebuggerIllegalArgumentException("Can only focus a successor of the scope"); @@ -63,7 +63,7 @@ public interface DbgModelTargetFocusScope extends DbgModelTargetObject, TargetFo DbgModelSelectableObject sel = (DbgModelSelectableObject) cur; //System.err.println("requestFocus " + obj); setFocus(sel); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } if (cur instanceof DbgModelTargetObject) { DbgModelTargetObject def = (DbgModelTargetObject) cur; @@ -72,7 +72,7 @@ public interface DbgModelTargetFocusScope extends DbgModelTargetObject, TargetFo } throw new AssertionError(); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetResumable.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetResumable.java index 41c76cf8ad..84c50344dd 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetResumable.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetResumable.java @@ -35,7 +35,7 @@ public interface DbgModelTargetResumable extends DbgModelTargetObject, TargetRes public default CompletableFuture resume() { DbgProcess process = getManager().getCurrentProcess(); if (process == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return getModel().gateFuture(process.cont()); } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetObject.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetObject.java index 0776aeaa21..ec43e56596 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetObject.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetObject.java @@ -58,7 +58,7 @@ public interface DbgModelTargetObject extends SpiTargetObject { public CompletableFuture> requestNativeAttributes(); public default CompletableFuture requestAugmentedAttributes() { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } public CompletableFuture> requestNativeElements(); diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetStackFrame.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetStackFrame.java index 2a9c3bc222..e8379459db 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetStackFrame.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetStackFrame.java @@ -61,33 +61,33 @@ public interface DbgModelTargetStackFrame extends // AddressSpace space = getModel().getAddressSpace("ram"); return requestNativeAttributes().thenCompose(attrs -> { if (attrs == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } map.putAll(attrs); DbgModelTargetObject attributes = (DbgModelTargetObject) attrs.get("Attributes"); if (attributes == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return attributes.requestAugmentedAttributes().thenCompose(ax -> { if (!isValid()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } Map subattrs = attributes.getCachedAttributes(); DbgModelTargetObject frameNumber = (DbgModelTargetObject) subattrs.get("FrameNumber"); if (frameNumber == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return frameNumber.requestAugmentedAttributes().thenCompose(bx -> { if (!isValid()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } Object noval = frameNumber.getCachedAttribute(VALUE_ATTRIBUTE_NAME); String nostr = noval.toString(); DbgModelTargetObject instructionOffset = (DbgModelTargetObject) subattrs.get("InstructionOffset"); if (instructionOffset == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return instructionOffset.requestAugmentedAttributes().thenAccept(cx -> { if (!isValid()) { diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelImpl.java index 383eccc8a8..2dbdf3434f 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelImpl.java @@ -138,7 +138,7 @@ public class DbgModelImpl extends AbstractDbgModel implements DebuggerObjectMode } catch (RejectedExecutionException e) { reportError(this, "Model is already closing", e); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } catch (Throwable t) { return CompletableFuture.failedFuture(t); diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetAvailableContainerImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetAvailableContainerImpl.java index c1d8dbdafb..4ff77e5a35 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetAvailableContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetAvailableContainerImpl.java @@ -90,7 +90,7 @@ public class DbgModelTargetAvailableContainerImpl extends DbgModelTargetObjectIm } default: } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetEventContainerImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetEventContainerImpl.java index 894a0c44e7..0945f11092 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetEventContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetEventContainerImpl.java @@ -58,7 +58,7 @@ public class DbgModelTargetEventContainerImpl extends DbgModelTargetObjectImpl DbgProcessImpl currentProcess = getManager().getCurrentProcess(); if (!refresh.equals(RefreshBehavior.REFRESH_ALWAYS) || (currentProcess != null && !currentProcess.equals(targetProcess.getProcess()))) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return listEventFilters().thenAccept(byName -> { List eventObjs; diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetEventImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetEventImpl.java index b1054b8628..5b81bccf80 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetEventImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetEventImpl.java @@ -204,6 +204,6 @@ public class DbgModelTargetEventImpl extends DbgModelTargetObjectImpl throw new DebuggerIllegalArgumentException("Option should be numeric"); default: } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetExceptionContainerImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetExceptionContainerImpl.java index f3456353b6..c36a559096 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetExceptionContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetExceptionContainerImpl.java @@ -58,7 +58,7 @@ public class DbgModelTargetExceptionContainerImpl extends DbgModelTargetObjectIm DbgProcessImpl currentProcess = getManager().getCurrentProcess(); if (!refresh.equals(RefreshBehavior.REFRESH_ALWAYS) || (currentProcess != null && !currentProcess.equals(targetProcess.getProcess()))) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return listExceptionFilters().thenAccept(byName -> { List excObjs; diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetExceptionImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetExceptionImpl.java index 967a743a1f..ee63238ec0 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetExceptionImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetExceptionImpl.java @@ -171,6 +171,6 @@ public class DbgModelTargetExceptionImpl extends DbgModelTargetObjectImpl throw new DebuggerIllegalArgumentException("Option should be numeric"); default: } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetMemoryContainerImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetMemoryContainerImpl.java index 182ecd0fac..d3a965fc40 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetMemoryContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetMemoryContainerImpl.java @@ -94,7 +94,7 @@ public class DbgModelTargetMemoryContainerImpl extends DbgModelTargetObjectImpl DbgProcessImpl currentProcess = getManager().getCurrentProcess(); if (!refresh.equals(RefreshBehavior.REFRESH_ALWAYS) || (currentProcess != null && !currentProcess.equals(targetProcess.getProcess()))) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return listMemory().thenAccept(byName -> { List sections; diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetProcessContainerImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetProcessContainerImpl.java index c68a08b3de..b75e3c69f6 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetProcessContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetProcessContainerImpl.java @@ -211,7 +211,7 @@ public class DbgModelTargetProcessContainerImpl extends DbgModelTargetObjectImpl } default: } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } @TargetMethod.Export("Populate") diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRegisterContainerImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRegisterContainerImpl.java index 7825bff3c5..74831c977b 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRegisterContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRegisterContainerImpl.java @@ -112,7 +112,7 @@ public class DbgModelTargetRegisterContainerImpl extends DbgModelTargetObjectImp if (regs.size() != registersByName.size() || getCachedElements().isEmpty()) { return requestElements(RefreshBehavior.REFRESH_NEVER); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); }).thenCompose(__ -> { Set toRead = new LinkedHashSet<>(); for (String regname : names) { diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetThreadContainerImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetThreadContainerImpl.java index 251315f832..42fa8f2f59 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetThreadContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetThreadContainerImpl.java @@ -186,7 +186,7 @@ public class DbgModelTargetThreadContainerImpl extends DbgModelTargetObjectImpl } default: } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } @TargetMethod.Export("Populate") diff --git a/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2Impl.java b/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2Impl.java index 51ede57815..149c6d6175 100644 --- a/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2Impl.java +++ b/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2Impl.java @@ -149,7 +149,7 @@ public class DbgModel2Impl extends AbstractDbgModel } catch (RejectedExecutionException e) { reportError(this, "Model is already closing", e); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } catch (Throwable t) { return CompletableFuture.failedFuture(t); diff --git a/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2TargetObjectImpl.java b/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2TargetObjectImpl.java index 8e960d457c..db31a66407 100644 --- a/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2TargetObjectImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2TargetObjectImpl.java @@ -165,7 +165,7 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject { diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/manager/impl/FridaManagerImpl.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/manager/impl/FridaManagerImpl.java index 784fb53b23..42d4cd526c 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/manager/impl/FridaManagerImpl.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/manager/impl/FridaManagerImpl.java @@ -1198,7 +1198,7 @@ public class FridaManagerImpl implements FridaManager { getEventListeners().fire.promptChanged(prompt); continuation.complete(command); setContinuation(null); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return execute( new FridaConsoleExecCommand(this, command, FridaConsoleExecCommand.Output.CONSOLE)) diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/iface1/FridaModelTargetActiveScope.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/iface1/FridaModelTargetActiveScope.java index da19e63e0b..eb5ba3d9d7 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/iface1/FridaModelTargetActiveScope.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/iface1/FridaModelTargetActiveScope.java @@ -42,7 +42,7 @@ public interface FridaModelTargetActiveScope extends FridaModelTargetObject, Tar public default CompletableFuture doRequestActivation(TargetObject obj) { if (getManager().isWaiting()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } getModel().assertMine(TargetObject.class, obj); if (!PathUtils.isAncestor(this.getPath(), obj.getPath())) { @@ -62,7 +62,7 @@ public interface FridaModelTargetActiveScope extends FridaModelTargetObject, Tar } throw new AssertionError(); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/iface1/FridaModelTargetFocusScope.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/iface1/FridaModelTargetFocusScope.java index 2edb310b96..baa77fd388 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/iface1/FridaModelTargetFocusScope.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/iface1/FridaModelTargetFocusScope.java @@ -48,11 +48,11 @@ public interface FridaModelTargetFocusScope extends FridaModelTargetObject, Targ public default CompletableFuture doRequestFocus(TargetObject obj) { if (getManager().isWaiting()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } getModel().assertMine(TargetObject.class, obj); if (obj.equals(getFocus())) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } if (!PathUtils.isAncestor(this.getPath(), obj.getPath())) { throw new DebuggerIllegalArgumentException("Can only focus a successor of the scope"); @@ -63,7 +63,7 @@ public interface FridaModelTargetFocusScope extends FridaModelTargetObject, Targ FridaModelSelectableObject sel = (FridaModelSelectableObject) cur; //System.err.println("requestFocus " + obj); setFocus(sel); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } if (cur instanceof FridaModelTargetObject) { FridaModelTargetObject def = (FridaModelTargetObject) cur; @@ -72,7 +72,7 @@ public interface FridaModelTargetFocusScope extends FridaModelTargetObject, Targ } throw new AssertionError(); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/iface2/FridaModelTargetObject.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/iface2/FridaModelTargetObject.java index e1197e3bdd..d37e25e87d 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/iface2/FridaModelTargetObject.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/iface2/FridaModelTargetObject.java @@ -52,7 +52,7 @@ public interface FridaModelTargetObject extends SpiTargetObject { public CompletableFuture> requestNativeAttributes(); public default CompletableFuture requestAugmentedAttributes() { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } public CompletableFuture> requestNativeElements(); diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaDebuggerBot.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaDebuggerBot.java deleted file mode 100644 index a81b477f5a..0000000000 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaDebuggerBot.java +++ /dev/null @@ -1,202 +0,0 @@ -/* ### - * 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 agent.frida.model.impl; - -import java.lang.invoke.MethodHandles; -import java.util.*; - -import agent.frida.frida.FridaThreadInfo; -import agent.frida.manager.FridaProcess; -import agent.frida.manager.FridaThread; -import agent.frida.manager.evt.FridaStateChangedEvent; -import agent.frida.manager.evt.FridaThreadSelectedEvent; -import agent.frida.manager.impl.FridaManagerImpl; -import ghidra.app.services.DebuggerWorkflowFrontEndService; -import ghidra.dbg.*; -import ghidra.dbg.error.DebuggerMemoryAccessException; -import ghidra.dbg.target.*; -import ghidra.dbg.target.TargetConsole.Channel; -import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; -import ghidra.dbg.util.DebuggerCallbackReorderer; -import ghidra.debug.api.workflow.DebuggerBot; -import ghidra.debug.api.workflow.DebuggerBotInfo; -import ghidra.framework.options.annotation.HelpInfo; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressRange; -import ghidra.util.datastruct.PrivatelyQueuedListener; - -@DebuggerBotInfo( - description = "Link debugger to Frida", - details = "Listens for debuggers to add state to Frida.", - help = @HelpInfo(anchor = "link_frida"), - enabledByDefault = true) -public class FridaDebuggerBot implements DebuggerBot { - private DebuggerWorkflowFrontEndService service; - - private FridaObjectListener listener = new FridaObjectListener(); - private List models = new ArrayList<>(); - private FridaModelImpl primary; - private FridaManagerImpl manager; - - @Override - public void enable(DebuggerWorkflowFrontEndService service) { - this.service = service; - } - - @Override - public boolean isEnabled() { - return service != null; - } - - @Override - public void disable() { - service = null; - } - - @Override - public void modelAdded(DebuggerObjectModel model) { - models.add(model); - if (model instanceof FridaModelImpl) { - primary = (FridaModelImpl) model; - manager = primary.getManager(); - } - else { - model.addModelListener(getListener(), true); - } - } - - @Override - public void modelRemoved(DebuggerObjectModel model) { - models.remove(model); - if (model instanceof FridaModelImpl) { - primary = null; - manager = null; - } - else { - model.removeModelListener(getListener()); - } - } - - public DebuggerModelListener getListener() { - return listener.queue.in; - } - - private Object findMatchingObject(TargetObject object) { - if (object instanceof TargetProcess) { - String id = Long.toHexString(((TargetProcess) object).getPid()); - return manager.getProcess(manager.getCurrentSession(), id); - } - if (object instanceof TargetThread) { - TargetProcess tp = DebugModelConventions.ancestor(TargetProcess.class, object); - Object found = findMatchingObject(tp); - if (found != null) { - FridaProcess process = (FridaProcess) found; - String id = Long.toHexString(((TargetThread) object).getTid()); - return manager.getThread(process, id); - } - } - return null; - } - - class FridaObjectListener extends AnnotatedDebuggerAttributeListener { - protected final DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this); - protected final PrivatelyQueuedListener queue = - new PrivatelyQueuedListener<>(DebuggerModelListener.class, "ObjectsProvider-EventQueue", - reorderer); - - public FridaObjectListener() { - super(MethodHandles.lookup()); - } - - @AttributeCallback(TargetAccessConditioned.ACCESSIBLE_ATTRIBUTE_NAME) - public void accessibilityChanged(TargetObject object, boolean accessible) { - //System.err.println("accessibilityChanged: "+object+":"+accessible); - } - - @Override - public void consoleOutput(TargetObject console, Channel channel, String out) { - //System.err.println("consoleOutput: "+out); - } - - @AttributeCallback(TargetObject.DISPLAY_ATTRIBUTE_NAME) - public void displayChanged(TargetObject object, String display) { - //System.err.println("displayChanged: "+display); - } - - @AttributeCallback(TargetObject.MODIFIED_ATTRIBUTE_NAME) - public void modifiedChanged(TargetObject object, boolean modified) { - //System.err.println("modifiedChanged: "+object); - } - - @AttributeCallback(TargetExecutionStateful.STATE_ATTRIBUTE_NAME) - public void executionStateChanged(TargetObject object, TargetExecutionState state) { - if (primary != null) { - Object localObject = findMatchingObject(object); - if (localObject != null) { - manager.processEvent(new FridaStateChangedEvent(localObject, state)); - } - } - } - - @AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME) - public void focusChanged(TargetObject object, TargetObject focused) { - if (primary != null) { - if (focused instanceof TargetThread) { - Object localObject = findMatchingObject(focused); - if (localObject != null) { - FridaThreadInfo info = new FridaThreadInfo((FridaThread) localObject); - manager.processEvent(new FridaThreadSelectedEvent(info)); - } - } - } - } - - @Override - public void memoryUpdated(TargetObject memory, Address address, byte[] data) { - //System.err.println("memoryUpdated: "+address); - } - - @Override - public void memoryReadError(TargetObject memory, AddressRange range, - DebuggerMemoryAccessException e) { - //System.err.println("memoryReadError: "+range); - } - - @AttributeCallback(TargetInterpreter.PROMPT_ATTRIBUTE_NAME) - public void promptChanged(TargetObject interpreter, String prompt) { - //System.err.println("promptChanged: "+prompt); - } - - @Override - public void registersUpdated(TargetObject bank, Map updates) { - //System.err.println("registersUpdated: "+bank); - } - - @Override - public void elementsChanged(TargetObject parent, Collection removed, - Map added) { - //System.err.println("elementsChanged: "+parent); - } - - @Override - public void attributesChanged(TargetObject parent, Collection removed, - Map added) { - super.attributesChanged(parent, removed, added); - //System.err.println("attributesChanged: "+parent); - } - } - -} diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelImpl.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelImpl.java index 1923e68cde..769fd803b1 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelImpl.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelImpl.java @@ -126,7 +126,7 @@ public class FridaModelImpl extends AbstractFridaModel implements DebuggerObject } catch (RejectedExecutionException e) { reportError(this, "Model is already closing", e); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } catch (Throwable t) { return CompletableFuture.failedFuture(t); diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetAvailableDevicesContainerImpl.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetAvailableDevicesContainerImpl.java index 5c87a1242c..ec1b81408b 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetAvailableDevicesContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetAvailableDevicesContainerImpl.java @@ -96,7 +96,7 @@ public class FridaModelTargetAvailableDevicesContainerImpl extends FridaModelTar } default: } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetAvailableProcessesContainerImpl.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetAvailableProcessesContainerImpl.java index 7f424ada5c..d4ac36cef1 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetAvailableProcessesContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetAvailableProcessesContainerImpl.java @@ -96,7 +96,7 @@ public class FridaModelTargetAvailableProcessesContainerImpl extends FridaModelT } default: } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetProcessContainerImpl.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetProcessContainerImpl.java index a0fdcfa9e8..1a8a13247b 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetProcessContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetProcessContainerImpl.java @@ -144,7 +144,7 @@ public class FridaModelTargetProcessContainerImpl extends FridaModelTargetObject } default: } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetProcessImpl.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetProcessImpl.java index 3df4e9826d..67487c2db3 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetProcessImpl.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetProcessImpl.java @@ -154,7 +154,7 @@ public class FridaModelTargetProcessImpl extends FridaModelTargetObjectImpl public CompletableFuture launch(List args) { model.gateFuture(getManager().execute(new FridaLaunchProcessCommand(getManager(), getProcess().getName(), args))); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } @Override @@ -200,7 +200,7 @@ public class FridaModelTargetProcessImpl extends FridaModelTargetObjectImpl @Override public CompletableFuture delete() { - return AsyncUtils.NIL; + return AsyncUtils.nil(); //return model.gateFuture(process.remove()); } diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetRegisterContainerImpl.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetRegisterContainerImpl.java index 9637837a20..df899382fa 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetRegisterContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetRegisterContainerImpl.java @@ -147,7 +147,7 @@ public class FridaModelTargetRegisterContainerImpl reg.getRegister().setValue(val.toString()); } broadcast().registersUpdated(getProxy(), values); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetThreadContainerImpl.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetThreadContainerImpl.java index 6cc9aa6928..9a122ec508 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetThreadContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetThreadContainerImpl.java @@ -170,7 +170,7 @@ public class FridaModelTargetThreadContainerImpl extends FridaModelTargetObjectI } default: } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbManagerImpl.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbManagerImpl.java index 885d33e6ce..8078d28f1d 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbManagerImpl.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbManagerImpl.java @@ -759,7 +759,7 @@ public class GdbManagerImpl implements GdbManager { protected CompletableFuture rc() { if (cliThread != null) { // NB. confirm and pagination are already disabled here - return AsyncUtils.NIL; + return AsyncUtils.nil(); } // NB. Don't disable pagination here. MI2 is not paginated. return CompletableFuture.allOf( diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelImpl.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelImpl.java index ff7ac03a73..a792c5338a 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelImpl.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelImpl.java @@ -156,7 +156,7 @@ public class GdbModelImpl extends AbstractDebuggerObjectModel { } }).thenCompose(__ -> { if (closed) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return gdb.runRC(); }); diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetAvailableContainer.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetAvailableContainer.java index 865b2097c4..b0d3e59b26 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetAvailableContainer.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetAvailableContainer.java @@ -86,7 +86,7 @@ public class GdbModelTargetAvailableContainer } default: } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetBreakpointContainer.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetBreakpointContainer.java index 8c4fd2e74a..59a821313a 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetBreakpointContainer.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetBreakpointContainer.java @@ -189,6 +189,6 @@ public class GdbModelTargetBreakpointContainer public CompletableFuture stateChanged(GdbStateChangeRecord sco) { // NB. This container should be updated via GDB's events. - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetBreakpointLocation.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetBreakpointLocation.java index 864459db3c..320fabd56a 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetBreakpointLocation.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetBreakpointLocation.java @@ -114,7 +114,7 @@ public class GdbModelTargetBreakpointLocation range = makeRange(impl.space.getAddress(vals.get(0)), vals.get(1).intValue()); doChangeAttributes("Initialized"); - return AsyncUtils.NIL; + return AsyncUtils.nil(Void.class); }).exceptionally(ex -> { CompletableFuture secondTry = inf.inferior.evaluate(String.format("(long long)&(%s)", exp)); diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetBreakpointSpec.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetBreakpointSpec.java index 6fcc168a9f..79768f5d39 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetBreakpointSpec.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetBreakpointSpec.java @@ -203,7 +203,7 @@ public class GdbModelTargetBreakpointSpec extends List locs = setInfoAndComputeLocations(oldInfo, newInfo); updateAttributesFromInfo(reason); setElements(locs, reason); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } protected CompletableFuture updateWptInfo(GdbBreakpointInfo oldInfo, diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferiorContainer.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferiorContainer.java index d6bf7f2231..6967ef7d27 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferiorContainer.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferiorContainer.java @@ -135,7 +135,7 @@ public class GdbModelTargetInferiorContainer public CompletableFuture requestElements(RefreshBehavior refresh) { if (!refresh.equals(RefreshBehavior.REFRESH_ALWAYS)) { updateUsingInferiors(impl.gdb.getKnownInferiors()); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return impl.gdb.listInferiors().thenAccept(this::updateUsingInferiors); } @@ -186,7 +186,7 @@ public class GdbModelTargetInferiorContainer } default: } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetMemoryRegion.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetMemoryRegion.java index 2ad7b15220..068d0305d4 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetMemoryRegion.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetMemoryRegion.java @@ -149,6 +149,6 @@ public class GdbModelTargetMemoryRegion public CompletableFuture stateChanged(GdbStateChangeRecord sco) { // Nothing to do here - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetProcessMemory.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetProcessMemory.java index 41046ef0cb..697bd5ce18 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetProcessMemory.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetProcessMemory.java @@ -127,7 +127,7 @@ public class GdbModelTargetProcessMemory protected CompletableFuture doRefresh() { if (inferior.getPid() == null) { setElements(List.of(), "Refreshed (while no process)"); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return inferior.listMappings().exceptionally(ex -> { Msg.error(this, "Could not list regions. Using default."); diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetRegisterContainer.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetRegisterContainer.java index 356ca0042d..09c22f560b 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetRegisterContainer.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetRegisterContainer.java @@ -68,7 +68,7 @@ public class GdbModelTargetRegisterContainer protected CompletableFuture completeUsingThreads(Map byId) { if (byId.isEmpty()) { setElements(List.of(), "Refreshed (with no thread)"); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } GdbThread thread = byId.values().iterator().next(); return thread.listRegisters().thenAccept(regs -> { diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetSectionContainer.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetSectionContainer.java index 4a1a44920c..8b8d8c6e04 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetSectionContainer.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetSectionContainer.java @@ -64,7 +64,7 @@ public class GdbModelTargetSectionContainer @Override public CompletableFuture requestElements(RefreshBehavior refresh) { if (!refresh.isRefresh(elements.keySet())) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return module.module.listSections(true).thenAccept(this::updateUsingSections); } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetSession.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetSession.java index 2166c61ffc..55ea252f77 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetSession.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetSession.java @@ -245,7 +245,7 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot catch (IOException e) { Msg.error(this, "Could not interrupt", e); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } @Override @@ -278,7 +278,7 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot } cur = cur.getParent(); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } @@ -297,11 +297,11 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot if (cur instanceof GdbModelSelectableObject) { GdbModelSelectableObject sel = (GdbModelSelectableObject) cur; setFocus(sel); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } cur = cur.getParent(); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } protected void invalidateMemoryAndRegisterCaches() { diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetStack.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetStack.java index 9f481cc95c..f30ff2a153 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetStack.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetStack.java @@ -98,7 +98,7 @@ public class GdbModelTargetStack extends if (innermost != null) { return innermost.stateChanged(sco); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); }).exceptionally(e -> { impl.reportError(this, "Could not update stack " + this + " on STOPPED", e); return null; diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThread.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThread.java index 3b9ea51fa0..85ca55c132 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThread.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThread.java @@ -254,7 +254,7 @@ public class GdbModelTargetThread public CompletableFuture stateChanged(GdbStateChangeRecord sco) { GdbState gdbState = sco.getState(); - CompletableFuture result = AsyncUtils.NIL; + CompletableFuture result = AsyncUtils.nil(); if (gdbState == GdbState.STOPPED) { Msg.debug(this, "Updating stack for " + this); result = CompletableFuture.allOf(updateInfo(), stack.stateChanged(sco)); diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThreadContainer.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThreadContainer.java index 5d4f6b9671..d56737d475 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThreadContainer.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThreadContainer.java @@ -94,7 +94,7 @@ public class GdbModelTargetThreadContainer protected CompletableFuture requestElements(RefreshBehavior refresh) { if (!refresh.equals(RefreshBehavior.REFRESH_ALWAYS)) { updateUsingThreads(inferior.getKnownThreads()); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return doRefresh(); } @@ -178,7 +178,7 @@ public class GdbModelTargetThreadContainer } default: } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } protected CompletableFuture refreshInternal() { diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/manager/impl/LldbManagerImpl.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/manager/impl/LldbManagerImpl.java index 0f28d5f65a..9461bb5d56 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/manager/impl/LldbManagerImpl.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/manager/impl/LldbManagerImpl.java @@ -1641,7 +1641,7 @@ public class LldbManagerImpl implements LldbManager { getEventListeners().fire.promptChanged(prompt); continuation.complete(command); setContinuation(null); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return execute( new LldbConsoleExecCommand(this, command, LldbConsoleExecCommand.Output.CONSOLE)) diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/iface1/LldbModelTargetActiveScope.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/iface1/LldbModelTargetActiveScope.java index f63aa0f0a1..623708cbd7 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/iface1/LldbModelTargetActiveScope.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/iface1/LldbModelTargetActiveScope.java @@ -42,7 +42,7 @@ public interface LldbModelTargetActiveScope extends LldbModelTargetObject, Targe public default CompletableFuture doRequestActivation(TargetObject obj) { if (getManager().isWaiting()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } getModel().assertMine(TargetObject.class, obj); if (!PathUtils.isAncestor(this.getPath(), obj.getPath())) { @@ -62,7 +62,7 @@ public interface LldbModelTargetActiveScope extends LldbModelTargetObject, Targe } throw new AssertionError(); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/iface1/LldbModelTargetFocusScope.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/iface1/LldbModelTargetFocusScope.java index a74377971e..489c88c2ff 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/iface1/LldbModelTargetFocusScope.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/iface1/LldbModelTargetFocusScope.java @@ -48,11 +48,11 @@ public interface LldbModelTargetFocusScope extends LldbModelTargetObject, Target public default CompletableFuture doRequestFocus(TargetObject obj) { if (getManager().isWaiting()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } getModel().assertMine(TargetObject.class, obj); if (obj.equals(getFocus())) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } if (!PathUtils.isAncestor(this.getPath(), obj.getPath())) { throw new DebuggerIllegalArgumentException("Can only focus a successor of the scope"); @@ -63,7 +63,7 @@ public interface LldbModelTargetFocusScope extends LldbModelTargetObject, Target LldbModelSelectableObject sel = (LldbModelSelectableObject) cur; //System.err.println("requestFocus " + obj); setFocus(sel); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } if (cur instanceof LldbModelTargetObject) { LldbModelTargetObject def = (LldbModelTargetObject) cur; @@ -72,7 +72,7 @@ public interface LldbModelTargetFocusScope extends LldbModelTargetObject, Target } throw new AssertionError(); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/iface2/LldbModelTargetObject.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/iface2/LldbModelTargetObject.java index 35d8fd7fc6..10afbbc4d3 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/iface2/LldbModelTargetObject.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/iface2/LldbModelTargetObject.java @@ -52,7 +52,7 @@ public interface LldbModelTargetObject extends SpiTargetObject { public CompletableFuture> requestNativeAttributes(); public default CompletableFuture requestAugmentedAttributes() { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } public CompletableFuture> requestNativeElements(); diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelImpl.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelImpl.java index 1241ba0182..0845a6e2a8 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelImpl.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelImpl.java @@ -138,7 +138,7 @@ public class LldbModelImpl extends AbstractLldbModel implements DebuggerObjectMo } catch (RejectedExecutionException e) { reportError(this, "Model is already closing", e); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } catch (Throwable t) { return CompletableFuture.failedFuture(t); diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetAbstractXpointSpec.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetAbstractXpointSpec.java index f2c926076a..eaf3f8e05c 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetAbstractXpointSpec.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetAbstractXpointSpec.java @@ -83,7 +83,7 @@ public abstract class LldbModelTargetAbstractXpointSpec extends LldbModelTargetO protected CompletableFuture init() { Object info = getModelObject(); updateInfo(info, "Created"); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } @Override diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetAvailableContainerImpl.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetAvailableContainerImpl.java index 63706131f0..9b9dce0500 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetAvailableContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetAvailableContainerImpl.java @@ -95,7 +95,7 @@ public class LldbModelTargetAvailableContainerImpl extends LldbModelTargetObject } default: } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetProcessContainerImpl.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetProcessContainerImpl.java index c31f6d24d1..ead16c72b0 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetProcessContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetProcessContainerImpl.java @@ -186,7 +186,7 @@ public class LldbModelTargetProcessContainerImpl extends LldbModelTargetObjectIm } default: } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetProcessImpl.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetProcessImpl.java index 67abf73807..7b25f6a5e6 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetProcessImpl.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetProcessImpl.java @@ -164,7 +164,7 @@ public class LldbModelTargetProcessImpl extends LldbModelTargetObjectImpl public CompletableFuture launch(List args) { model.gateFuture(getManager().execute(new LldbLaunchProcessCommand(getManager(), getProcess().GetProcessInfo().GetName(), args))); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } @Override @@ -210,7 +210,7 @@ public class LldbModelTargetProcessImpl extends LldbModelTargetObjectImpl @Override public CompletableFuture delete() { - return AsyncUtils.NIL; + return AsyncUtils.nil(); //return model.gateFuture(process.remove()); } diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetStackFrameRegisterBankImpl.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetStackFrameRegisterBankImpl.java index 6cc5b7a5f3..710855b747 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetStackFrameRegisterBankImpl.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetStackFrameRegisterBankImpl.java @@ -153,7 +153,7 @@ public class LldbModelTargetStackFrameRegisterBankImpl reg.getRegister().SetValueFromCString(val.toString()); } broadcast().registersUpdated(getProxy(), values); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } public Object getContainer() { diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetThreadContainerImpl.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetThreadContainerImpl.java index d4fb06cfd6..7b128893cb 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetThreadContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetThreadContainerImpl.java @@ -173,7 +173,7 @@ public class LldbModelTargetThreadContainerImpl extends LldbModelTargetObjectImp } default: } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerEmulationService.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerEmulationService.java index 897cc38891..9b896db335 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerEmulationService.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerEmulationService.java @@ -15,12 +15,15 @@ */ package ghidra.app.services; +import java.io.IOException; import java.util.Collection; import java.util.concurrent.CompletableFuture; import ghidra.debug.api.emulation.DebuggerPcodeEmulatorFactory; import ghidra.debug.api.emulation.DebuggerPcodeMachine; import ghidra.framework.plugintool.ServiceInfo; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; import ghidra.trace.model.Trace; import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.time.schedule.Scheduler; @@ -151,6 +154,22 @@ public interface DebuggerEmulationService { */ DebuggerPcodeEmulatorFactory getEmulatorFactory(); + /** + * Load the given program into a trace suitable for emulation in the UI, starting at the given + * address + * + *

+ * Note that the program bytes are not actually loaded into the trace. Rather a static mapping + * is generated, allowing the emulator to load bytes from the target program lazily. The trace + * is automatically loaded into the UI (trace manager). + * + * @param program the target program + * @param address the initial program counter + * @return the resulting trace + * @throws IOException if the trace cannot be created + */ + Trace launchProgram(Program program, Address address) throws IOException; + /** * Perform emulation to realize the machine state of the given time coordinates * diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTargetService.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTargetService.java new file mode 100644 index 0000000000..a5ea37aedd --- /dev/null +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTargetService.java @@ -0,0 +1,74 @@ +/* ### + * 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.services; + +import java.util.List; + +import ghidra.debug.api.target.Target; +import ghidra.debug.api.target.TargetPublicationListener; +import ghidra.framework.plugintool.ServiceInfo; +import ghidra.trace.model.Trace; + +@ServiceInfo( + description = """ + A service for tracking a set of published targets. Services capable of creating targets + should publish them using this service. + """, + defaultProviderName = "ghidra.app.plugin.core.debug.service.target.DebuggerTargetServicePlugin") +public interface DebuggerTargetService { + /** + * Publish a target to the service and its listeners + * + * @param target the new target + */ + void publishTarget(Target target); + + /** + * Withdraw a target from the service and its listeners + * + * @param target the (presumably invalidated) target + */ + void withdrawTarget(Target target); + + /** + * Get a list of all published targets + * + * @return the list in no particular order + */ + List getPublishedTargets(); + + /** + * Get the target for the given trace + * + * @param trace the trace + * @return the target, or null if there is no such target + */ + Target getTarget(Trace trace); + + /** + * Add a listener for target publication and withdrawal events + * + * @param listener the listener + */ + void addTargetPublicationListener(TargetPublicationListener listener); + + /** + * Remove a listener for target publication and withdrawal events + * + * @param listener the listener + */ + void removeTargetPublicationListener(TargetPublicationListener listener); +} diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java index 079aa5ac38..450a7b1199 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java @@ -27,6 +27,7 @@ import ghidra.trace.model.Trace; import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObjectKeyPath; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.util.TriConsumer; @@ -281,11 +282,10 @@ public interface DebuggerTraceManagerService { * * @param coordinates the desired coordinates * @param cause the cause of the activation - * @param syncTarget true synchronize the current target to the same coordinates * @return a future which completes when emulation and navigation is complete */ CompletableFuture activateAndNotify(DebuggerCoordinates coordinates, - ActivationCause cause, boolean syncTarget); + ActivationCause cause); /** * Activate the given coordinates, caused by the user @@ -429,6 +429,24 @@ public interface DebuggerTraceManagerService { activate(resolveFrame(frameLevel)); } + /** + * Resolve coordinates for the given object path using the manager's "best judgment" + * + * @see #resolveTrace(Trace) + * @param path the path + * @return the best coordinates + */ + DebuggerCoordinates resolvePath(TraceObjectKeyPath path); + + /** + * Activate the given object path + * + * @param path the desired path + */ + default void activatePath(TraceObjectKeyPath path) { + activate(resolvePath(path)); + } + /** * Resolve coordinates for the given object using the manager's "best judgment" * diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/control/ControlMode.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/control/ControlMode.java index 3fceb0e6b3..88e1fc2c3f 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/control/ControlMode.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/control/ControlMode.java @@ -24,10 +24,11 @@ import javax.swing.Icon; import db.Transaction; import generic.theme.GIcon; -import ghidra.app.services.*; +import ghidra.app.services.DebuggerEmulationService; +import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; import ghidra.async.AsyncUtils; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; @@ -124,24 +125,24 @@ public enum ControlMode { if (!coordinates.isAliveAndPresent()) { return false; } - TraceRecorder recorder = coordinates.getRecorder(); - return recorder.isVariableOnTarget(coordinates.getPlatform(), + Target target = coordinates.getTarget(); + return target.isVariableExists(coordinates.getPlatform(), coordinates.getThread(), coordinates.getFrame(), address, length); } @Override public CompletableFuture setVariable(PluginTool tool, DebuggerCoordinates coordinates, Address address, byte[] data) { - TraceRecorder recorder = coordinates.getRecorder(); - if (recorder == null) { + Target target = coordinates.getTarget(); + if (target == null) { return CompletableFuture .failedFuture(new MemoryAccessException("Trace has no live target")); } - if (!coordinates.isPresent()) { + if (!coordinates.isAliveAndPresent()) { return CompletableFuture .failedFuture(new MemoryAccessException("View is not the present")); } - return recorder.writeVariable(coordinates.getPlatform(), coordinates.getThread(), + return target.writeVariableAsync(coordinates.getPlatform(), coordinates.getThread(), coordinates.getFrame(), address, data); } @@ -257,7 +258,7 @@ public enum ControlMode { return CompletableFuture.failedFuture(new MemoryAccessException()); } } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } @Override @@ -347,7 +348,7 @@ public enum ControlMode { throw new AssertionError(e); } } - return traceManager.activateAndNotify(withTime, ActivationCause.EMU_STATE_EDIT, false); + return traceManager.activateAndNotify(withTime, ActivationCause.EMU_STATE_EDIT); } @Override diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/DebuggerPcodeEmulatorFactory.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/DebuggerPcodeEmulatorFactory.java index 99f4c585dc..026acaccce 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/DebuggerPcodeEmulatorFactory.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/DebuggerPcodeEmulatorFactory.java @@ -15,7 +15,7 @@ */ package ghidra.debug.api.emulation; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.framework.plugintool.PluginTool; import ghidra.trace.model.guest.TracePlatform; import ghidra.util.classfinder.ExtensionPoint; @@ -43,11 +43,11 @@ public interface DebuggerPcodeEmulatorFactory extends ExtensionPoint { * @param tool the tool creating the emulator * @param platform the user's current trace platform from which the emulator should load state * @param snap the user's current snap from which the emulator should load state - * @param recorder if applicable, the recorder for the trace's live target + * @param target if applicable, the live target * @return the emulator */ DebuggerPcodeMachine create(PluginTool tool, TracePlatform platform, long snap, - TraceRecorder recorder); + Target target); /** * Create the emulator diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/TraceRecorder.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/TraceRecorder.java index 965dfedef4..3b6bccc9ee 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/TraceRecorder.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/TraceRecorder.java @@ -37,6 +37,7 @@ import ghidra.trace.model.modules.TraceModule; import ghidra.trace.model.modules.TraceSection; import ghidra.trace.model.stack.TraceStackFrame; import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObjectKeyPath; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.TraceTimeManager; @@ -59,7 +60,7 @@ import ghidra.util.task.TaskMonitor; *

  • The recorder may not have actually recorded the object yet, despite receiving notice. * Recording is asynchronous, and it may also be waiting for additional dependencies or attributes * before it can create the corresponding trace object.
  • - *
  • The target object may not longer exist for a given trace object.
  • + *
  • The target object may no longer exist for a given trace object.
  • * * *

    @@ -223,6 +224,14 @@ public interface TraceRecorder { */ TargetObject getTargetObject(TraceObject obj); + /** + * Get the target object corresponding to the given path + * + * @param obj the trace object + * @return the target object, or null + */ + TargetObject getTargetObject(TraceObjectKeyPath path); + /** * Get the trace object corresponding to the given target object * diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/ActionName.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/ActionName.java new file mode 100644 index 0000000000..93161f2647 --- /dev/null +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/ActionName.java @@ -0,0 +1,147 @@ +/* ### + * 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.debug.api.target; + +/** + * A name for a commonly-recognized target action. + * + *

    + * Many common commands/actions have varying names across different back-end debuggers. We'd like to + * present common idioms for these common actions, but allow them to keep the names used by the + * back-end, because those names are probably better known to users of that back-end than Ghidra's + * action names are known. The action hints will affect the icon and placement of the action in the + * UI, but the display name will still reflect the name given by the back-end. Note that the "stock" + * action names are not a fixed enumeration. These are just the ones that might get special + * treatment from Ghidra. All methods should appear somewhere (at least, e.g., in context menus for + * applicable objects), even if the action name is unspecified or does not match a stock name. This + * list may change over time, but that shouldn't matter much. Each back-end should make its best + * effort to match its methods to these stock actions where applicable, but ultimately, it is up to + * the UI to decide what is presented where. + */ +public record ActionName(String name) { + public static final ActionName REFRESH = new ActionName("refresh"); + /** + * Activate a given object and optionally a time + * + *

    + * Forms: (focus:Object), (focus:Object, snap:LONG), (focus:Object, time:STR) + */ + public static final ActionName ACTIVATE = new ActionName("activate"); + /** + * A weaker form of activate. + * + *

    + * The user has expressed interest in an object, but has not activated it yet. This is often + * used to communicate selection (i.e., highlight) of the object. Whereas, double-clicking or + * pressing enter would more likely invoke 'activate.' + */ + public static final ActionName FOCUS = new ActionName("focus"); + public static final ActionName TOGGLE = new ActionName("toggle"); + public static final ActionName DELETE = new ActionName("delete"); + + /** + * Execute a CLI command + * + *

    + * Forms: (cmd:STRING):STRING; Optional arguments: capture:BOOL + */ + public static final ActionName EXECUTE = new ActionName("execute"); + + /** + * Connect the back-end to a (usually remote) target + * + *

    + * Forms: (spec:STRING) + */ + public static final ActionName CONNECT = new ActionName("connect"); + + /** + * Forms: (target:Attachable), (pid:INT), (spec:STRING) + */ + public static final ActionName ATTACH = new ActionName("attach"); + public static final ActionName DETACH = new ActionName("detach"); + + /** + * Forms: (command_line:STRING), (file:STRING,args:STRING), (file:STRING,args:STRING_ARRAY), + * (ANY*) + */ + public static final ActionName LAUNCH = new ActionName("launch"); + public static final ActionName KILL = new ActionName("kill"); + + public static final ActionName RESUME = new ActionName("resume"); + public static final ActionName INTERRUPT = new ActionName("interrupt"); + + /** + * All of these will show in the "step" portion of the control toolbar, if present. The + * difference in each "step_x" is minor. The icon will indicate which form, and the positions + * will be shifted so they appear in a consistent order. The display name is determined by the + * method name, not the action name. For stepping actions that don't fit the standards, use + * {@link #STEP_EXT}. There should be at most one of each standard applicable for any given + * context. (Multiple will appear, but may confuse the user.) You can have as many extended step + * actions as you like. They will be ordered lexicographically by name. + */ + public static final ActionName STEP_INTO = new ActionName("step_into"); + public static final ActionName STEP_OVER = new ActionName("step_over"); + public static final ActionName STEP_OUT = new ActionName("step_out"); + /** + * Skip is not typically available, except in emulators. If the back-end debugger does not have + * a command for this action out-of-the-box, we do not recommend trying to implement it + * yourself. The purpose of these actions just to expose/map each command to the UI, not to + * invent new features for the back-end debugger. + */ + public static final ActionName STEP_SKIP = new ActionName("step_skip"); + /** + * Step back is not typically available, except in emulators and timeless (or time-travel) + * debuggers. + */ + public static final ActionName STEP_BACK = new ActionName("step_back"); + /** + * The action for steps that don't fit one of the common stepping actions. + */ + public static final ActionName STEP_EXT = new ActionName("step_ext"); + + /** + * Forms: (addr:ADDRESS), R/W(rng:RANGE), (expr:STRING) + * + *

    + * Optional arguments: condition:STRING, commands:STRING + * + *

    + * The client may pass either null or "" for condition and/or commands to indicate omissions of + * those arguments. + */ + public static final ActionName BREAK_SW_EXECUTE = new ActionName("break_sw_execute"); + public static final ActionName BREAK_HW_EXECUTE = new ActionName("break_hw_execute"); + public static final ActionName BREAK_READ = new ActionName("break_read"); + public static final ActionName BREAK_WRITE = new ActionName("break_write"); + public static final ActionName BREAK_ACCESS = new ActionName("break_access"); + public static final ActionName BREAK_EXT = new ActionName("break_ext"); + + /** + * Forms: (rng:RANGE) + */ + public static final ActionName READ_MEM = new ActionName("read_mem"); + /** + * Forms: (addr:ADDRESS,data:BYTES) + */ + public static final ActionName WRITE_MEM = new ActionName("write_mem"); + + // NOTE: no read_reg. Use refresh(RegContainer), refresh(RegGroup), refresh(Register) + /** + * Forms: (frame:Frame,name:STRING,value:BYTES), (register:Register,value:BYTES) + */ + public static final ActionName WRITE_REG = new ActionName("write_reg"); +} diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/Target.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/Target.java new file mode 100644 index 0000000000..108f43f64c --- /dev/null +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/Target.java @@ -0,0 +1,260 @@ +/* ### + * 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.debug.api.target; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.*; +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; + +import org.apache.commons.lang3.exception.ExceptionUtils; + +import docking.ActionContext; +import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; +import ghidra.debug.api.tracemgr.DebuggerCoordinates; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.RegisterValue; +import ghidra.trace.model.Trace; +import ghidra.trace.model.breakpoint.TraceBreakpoint; +import ghidra.trace.model.breakpoint.TraceBreakpointKind; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.stack.TraceStackFrame; +import ghidra.trace.model.target.TraceObjectKeyPath; +import ghidra.trace.model.thread.TraceThread; +import ghidra.util.Msg; +import ghidra.util.Swing; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public interface Target { + long TIMEOUT_MILLIS = 10000; + + record ActionEntry(String display, ActionName name, String details, boolean requiresPrompt, + BooleanSupplier enabled, Supplier> action) { + + public boolean isEnabled() { + return enabled.getAsBoolean(); + } + + public CompletableFuture invokeAsync(boolean prompt) { + return action.get().orTimeout(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + } + + public CompletableFuture invokeAsyncLogged(boolean prompt, PluginTool tool) { + return invokeAsync(prompt).exceptionally(ex -> { + if (tool != null) { + tool.setStatusInfo(display + " failed: " + ex, true); + } + Msg.error(this, display + " failed: " + ex, ex); + return ExceptionUtils.rethrow(ex); + }); + } + + public void run(boolean prompt) { + get(prompt); + } + + public Object get(boolean prompt) { + if (Swing.isSwingThread()) { + throw new AssertionError("Refusing to block the Swing thread. Use a Task."); + } + try { + return invokeAsync(prompt).get(); + } + catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + } + + boolean isValid(); + + Trace getTrace(); + + long getSnap(); + + Map collectActions(ActionName name, ActionContext context); + + TraceThread getThreadForSuccessor(TraceObjectKeyPath path); + + TargetExecutionState getThreadExecutionState(TraceThread thread); + + TraceStackFrame getStackFrameForSuccessor(TraceObjectKeyPath path); + + boolean isSupportsFocus(); + + TraceObjectKeyPath getFocus(); + + CompletableFuture activateAsync(DebuggerCoordinates prev, DebuggerCoordinates coords); + + void activate(DebuggerCoordinates prev, DebuggerCoordinates coords); + + /** + * Invalidate any caches on the target's back end or on the client side of the connection. + * + *

    + * In general, back ends should avoid doing any caching. Instead, the front-end will assume + * anything marked {@link TraceMemoryState#KNOWN} is up to date. I.e., the trace database acts + * as the client-side cache for a live target. + * + *

    + * NOTE: This method exists for invalidating model-based target caches. It may be + * deprecated and removed, unless it turns out we need this for Trace RMI, too. + */ + CompletableFuture invalidateMemoryCachesAsync(); + + /** + * See {@link #invalidateMemoryCachesAsync()} + */ + void invalidateMemoryCaches(); + + /** + * @see #readMemory(AddressSetView, TaskMonitor) + */ + CompletableFuture readMemoryAsync(AddressSetView set, TaskMonitor monitor); + + /** + * Read and capture several ranges of target memory + * + *

    + * The target may read more than the requested memory, usually because it will read all pages + * containing any portion of the requested set. + * + *

    + * This task is relatively error tolerant. If a range cannot be captured -- a common occurrence + * -- the error is logged without throwing an exception. + * + * @param set the addresses to capture + * @param monitor a monitor for displaying task steps + * @throws CancelledException if the operation is cancelled + */ + void readMemory(AddressSetView set, TaskMonitor monitor) throws CancelledException; + + CompletableFuture writeMemoryAsync(Address address, byte[] data); + + void writeMemory(Address address, byte[] data); + + CompletableFuture readRegistersAsync(TracePlatform platform, TraceThread thread, + int frame, Set registers); + + void readRegisters(TracePlatform platform, TraceThread thread, int frame, + Set registers); + + CompletableFuture readRegistersAsync(TracePlatform platform, TraceThread thread, + int frame, AddressSetView guestSet); + + void readRegisters(TracePlatform platform, TraceThread thread, int frame, + AddressSetView guestSet); + + CompletableFuture writeRegisterAsync(TracePlatform platform, TraceThread thread, + int frame, RegisterValue value); + + void writeRegister(TracePlatform platform, TraceThread thread, int frame, RegisterValue value); + + CompletableFuture writeRegisterAsync(TracePlatform platform, TraceThread thread, + int frame, Address address, byte[] data); + + void writeRegister(TracePlatform platform, TraceThread thread, int frame, Address address, + byte[] data); + + /** + * Check if a given variable (register or memory) exists on target + * + * @param platform the platform whose language defines the registers + * @param thread if a register, the thread whose registers to examine + * @param frameLevel the frame, usually 0. + * @param address the address of the variable + * @param size the size of the variable. Ignored for memory + * @return true if the variable can be mapped to the target + */ + boolean isVariableExists(TracePlatform platform, TraceThread thread, int frame, Address address, + int length); + + /** + * @see #writeVariable(TracePlatform, TraceThread, int, Address, byte[]) + */ + CompletableFuture writeVariableAsync(TracePlatform platform, TraceThread thread, + int frame, + Address address, byte[] data); + + /** + * Write a variable (memory or register) of the given thread or the process + * + *

    + * This is a convenience for writing target memory or registers, based on address. If the given + * address represents a register, this will attempt to map it to a register and write it in the + * given thread and frame. If the address is in memory, it will simply delegate to + * {@link #writeMemory(Address, byte[])}. + * + * @param thread the thread. Ignored (may be null) if address is in memory + * @param frameLevel the frame, usually 0. Ignored if address is in memory + * @param address the starting address + * @param data the value to write + */ + void writeVariable(TracePlatform platform, TraceThread thread, int frame, Address address, + byte[] data); + + Set getSupportedBreakpointKinds(); + + CompletableFuture placeBreakpointAsync(AddressRange range, + Set kinds, String condition, String commands); + + void placeBreakpoint(AddressRange range, Set kinds, String condition, + String commands); + + /** + * Check if the given breakpoint (location) is still valid on target + * + * @param breakpoint the breakpoint + * @return true if valid + */ + boolean isBreakpointValid(TraceBreakpoint breakpoint); + + CompletableFuture deleteBreakpointAsync(TraceBreakpoint breakpoint); + + void deleteBreakpoint(TraceBreakpoint breakpoint); + + CompletableFuture toggleBreakpointAsync(TraceBreakpoint breakpoint, boolean enabled); + + void toggleBreakpoint(TraceBreakpoint breakpoint, boolean enabled); + + /** + * Terminate the target and its connection + * + *

    + * WARNING: This terminates the connection, even if there are other live targets still + * using it. One example where this might happen is if the target process launches a child, and + * the debugger is configured to remain attached to both. Whether this is expected or acceptable + * behavior has not been decided. + * + * @see #disconnect() + */ + CompletableFuture disconnectAsync(); + + /** + * Terminate the target and its connection + * + *

    + * NOTE: This method cannot be invoked on the Swing thread, because it may block on I/O. + * + * @see #disconnectAsync() + */ + void disconnect(); +} diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/TargetPublicationListener.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/TargetPublicationListener.java new file mode 100644 index 0000000000..6a4a63787d --- /dev/null +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/TargetPublicationListener.java @@ -0,0 +1,22 @@ +/* ### + * 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.debug.api.target; + +public interface TargetPublicationListener { + void targetPublished(Target target); + + void targetWithdrawn(Target target); +} diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java index 76aa23adce..bb896cf43d 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java @@ -22,7 +22,7 @@ import org.jdom.Element; import ghidra.app.services.DebuggerTraceManagerService; import ghidra.dbg.target.TargetObject; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.framework.data.DefaultProjectData; import ghidra.framework.model.*; import ghidra.framework.options.SaveState; @@ -83,38 +83,38 @@ public class DebuggerCoordinates { private final Trace trace; private final TracePlatform platform; - private final TraceRecorder recorder; + private final Target target; private final TraceThread thread; private final TraceProgramView view; private final TraceSchedule time; private final Integer frame; - private final TraceObject object; + private final TraceObjectKeyPath path; private final int hash; private Long viewSnap; + private TraceObject object; private TraceObject registerContainer; - DebuggerCoordinates(Trace trace, TracePlatform platform, TraceRecorder recorder, - TraceThread thread, TraceProgramView view, TraceSchedule time, Integer frame, - TraceObject object) { + DebuggerCoordinates(Trace trace, TracePlatform platform, Target target, TraceThread thread, + TraceProgramView view, TraceSchedule time, Integer frame, TraceObjectKeyPath path) { this.trace = trace; this.platform = platform; - this.recorder = recorder; + this.target = target; this.thread = thread; this.view = view; this.time = time; this.frame = frame; - this.object = object; + this.path = path; - this.hash = Objects.hash(trace, recorder, thread, view, time, frame, object); + this.hash = Objects.hash(trace, target, thread, view, time, frame, path); } @Override public String toString() { return String.format( - "Coords(trace=%s,recorder=%s,thread=%s,view=%s,time=%s,frame=%d,object=%s)", - trace, recorder, thread, view, time, frame, object); + "Coords(trace=%s,target=%s,thread=%s,view=%s,time=%s,frame=%d,path=%s)", + trace, target, thread, view, time, frame, path); } @Override @@ -126,7 +126,7 @@ public class DebuggerCoordinates { if (!Objects.equals(this.trace, that.trace)) { return false; } - if (!Objects.equals(this.recorder, that.recorder)) { + if (!Objects.equals(this.target, that.target)) { return false; } if (!Objects.equals(this.thread, that.thread)) { @@ -142,7 +142,7 @@ public class DebuggerCoordinates { if (!Objects.equals(this.frame, that.frame)) { return false; } - if (!Objects.equals(this.object, that.object)) { + if (!Objects.equals(this.path, that.path)) { return false; } @@ -172,13 +172,13 @@ public class DebuggerCoordinates { return resolveThread(trace, TraceSchedule.ZERO); } - private static TraceObject resolveObject(Trace trace, TraceThread thread, Integer frame, + private static TraceObjectKeyPath resolvePath(Trace trace, TraceThread thread, Integer frame, TraceSchedule time) { - TraceObject object = resolveObject(thread, frame, time); - if (object != null) { - return object; + TraceObjectKeyPath path = resolvePath(thread, frame, time); + if (path != null) { + return path; } - return trace.getObjectManager().getRootObject(); + return TraceObjectKeyPath.of(); } private static TraceProgramView resolveView(Trace trace, TraceSchedule time) { @@ -204,26 +204,25 @@ public class DebuggerCoordinates { TraceProgramView newView = resolveView(newTrace); TraceSchedule newTime = null; // Allow later resolution Integer newFrame = resolveFrame(newThread, newTime); - TraceObject newObject = resolveObject(newTrace, newThread, newFrame, newTime); + TraceObjectKeyPath newPath = resolvePath(newTrace, newThread, newFrame, newTime); return new DebuggerCoordinates(newTrace, newPlatform, null, newThread, newView, newTime, - newFrame, newObject); + newFrame, newPath); } throw new IllegalArgumentException("Cannot change trace"); } - private static TraceThread resolveThread(TraceRecorder recorder, TraceSchedule time) { - if (recorder.getSnap() != time.getSnap() || !recorder.isSupportsFocus()) { - return resolveThread(recorder.getTrace(), time); + private static TraceThread resolveThread(Target target, TraceSchedule time) { + if (target.getSnap() != time.getSnap() || !target.isSupportsFocus()) { + return resolveThread(target.getTrace(), time); } - return resolveThread(recorder, recorder.getFocus()); + return resolveThread(target, target.getFocus()); } - private static TraceThread resolveThread(Trace trace, TraceRecorder recorder, - TraceSchedule time) { - if (recorder == null) { + private static TraceThread resolveThread(Trace trace, Target target, TraceSchedule time) { + if (target == null) { return resolveThread(trace, time); } - return resolveThread(recorder, time); + return resolveThread(target, time); } private static Integer resolveFrame(TraceThread thread, TraceSchedule time) { @@ -231,29 +230,20 @@ public class DebuggerCoordinates { return null; } - private static Integer resolveFrame(TraceRecorder recorder, TraceThread thread, - TraceSchedule time) { - if (recorder == null || recorder.getSnap() != time.getSnap() || - !recorder.isSupportsFocus()) { + private static Integer resolveFrame(Target target, TraceThread thread, TraceSchedule time) { + if (target == null || target.getSnap() != time.getSnap() || + !target.isSupportsFocus()) { return resolveFrame(thread, time); } - return resolveFrame(recorder, recorder.getFocus()); + return resolveFrame(target, target.getFocus()); } - private static TraceObject resolveObject(Trace trace, TargetObject object) { - if (object == null) { - return null; - } - return trace.getObjectManager() - .getObjectByCanonicalPath(TraceObjectKeyPath.of(object.getPath())); - } - - private static TraceObject resolveObject(TraceRecorder recorder, TraceThread thread, + private static TraceObjectKeyPath resolvePath(Target target, TraceThread thread, Integer frame, TraceSchedule time) { - if (recorder.getSnap() != time.getSnap() || !recorder.isSupportsFocus()) { - return resolveObject(recorder.getTrace(), thread, frame, time); + if (target.getSnap() != time.getSnap() || !target.isSupportsFocus()) { + return resolvePath(target.getTrace(), thread, frame, time); } - return resolveObject(recorder.getTrace(), recorder.getFocus()); + return target.getFocus(); } public DebuggerCoordinates platform(TracePlatform newPlatform) { @@ -264,8 +254,8 @@ public class DebuggerCoordinates { if (trace == null) { return NOWHERE; } - return new DebuggerCoordinates(trace, resolvePlatform(trace), recorder, thread, view, - time, frame, object); + return new DebuggerCoordinates(trace, resolvePlatform(trace), target, thread, view, + time, frame, path); } if (trace == null) { Trace newTrace = newPlatform.getTrace(); @@ -273,38 +263,37 @@ public class DebuggerCoordinates { TraceProgramView newView = resolveView(newTrace); TraceSchedule newTime = null; // Allow later resolution Integer newFrame = resolveFrame(newThread, newTime); - TraceObject newObject = resolveObject(newTrace, newThread, newFrame, newTime); + TraceObjectKeyPath newPath = resolvePath(newTrace, newThread, newFrame, newTime); return new DebuggerCoordinates(newTrace, newPlatform, null, newThread, newView, newTime, - newFrame, newObject); + newFrame, newPath); } if (trace != newPlatform.getTrace()) { throw new IllegalArgumentException("Cannot change trace"); } - return new DebuggerCoordinates(trace, newPlatform, recorder, thread, view, time, frame, - object); + return new DebuggerCoordinates(trace, newPlatform, target, thread, view, time, frame, path); } - public DebuggerCoordinates recorder(TraceRecorder newRecorder) { - if (recorder == newRecorder) { + public DebuggerCoordinates target(Target newTarget) { + if (target == newTarget) { return this; } - if (newRecorder == null) { - return new DebuggerCoordinates(trace, platform, newRecorder, thread, view, time, frame, - object); + if (newTarget == null) { + return new DebuggerCoordinates(trace, platform, newTarget, thread, view, time, frame, + path); } - if (newRecorder != null && trace != null && newRecorder.getTrace() != trace) { + if (newTarget != null && trace != null && newTarget.getTrace() != trace) { throw new IllegalArgumentException("Cannot change trace"); } - Trace newTrace = trace != null ? trace : newRecorder.getTrace(); + Trace newTrace = trace != null ? trace : newTarget.getTrace(); TracePlatform newPlatform = platform != null ? platform : resolvePlatform(newTrace); - TraceSchedule newTime = time != null ? time : TraceSchedule.snap(newRecorder.getSnap()); - TraceThread newThread = thread != null ? thread : resolveThread(newRecorder, newTime); + TraceSchedule newTime = time != null ? time : TraceSchedule.snap(newTarget.getSnap()); + TraceThread newThread = thread != null ? thread : resolveThread(newTarget, newTime); TraceProgramView newView = view != null ? view : resolveView(newTrace, newTime); - Integer newFrame = frame != null ? frame : resolveFrame(newRecorder, newThread, newTime); - TraceObject threadOrFrameObject = resolveObject(newRecorder, newThread, newFrame, newTime); - TraceObject newObject = choose(object, threadOrFrameObject); - return new DebuggerCoordinates(newTrace, newPlatform, newRecorder, newThread, newView, - newTime, newFrame, newObject); + Integer newFrame = frame != null ? frame : resolveFrame(newTarget, newThread, newTime); + TraceObjectKeyPath threadOrFramePath = resolvePath(newTarget, newThread, newFrame, newTime); + TraceObjectKeyPath newPath = choose(path, threadOrFramePath); + return new DebuggerCoordinates(newTrace, newPlatform, newTarget, newThread, newView, + newTime, newFrame, newPath); } public DebuggerCoordinates reFindThread() { @@ -314,49 +303,39 @@ public class DebuggerCoordinates { return thread(trace.getThreadManager().getThread(thread.getKey())); } - private static TraceObject resolveObject(TraceThread thread, Integer frameLevel, + private static TraceObjectKeyPath resolvePath(TraceThread thread, Integer frameLevel, TraceSchedule time) { if (thread instanceof TraceObjectThread tot) { TraceObject objThread = tot.getObject(); if (frameLevel == null) { - return objThread; + return objThread.getCanonicalPath(); } TraceStack stack = thread.getTrace().getStackManager().getStack(thread, time.getSnap(), false); if (stack == null) { - return objThread; + return objThread.getCanonicalPath(); } TraceStackFrame frame = stack.getFrame(frameLevel, false); if (frame == null) { - return objThread; + return objThread.getCanonicalPath(); } - return ((TraceObjectStackFrame) frame).getObject(); + return ((TraceObjectStackFrame) frame).getObject().getCanonicalPath(); } return null; } - /** - * Check if the object is a canonical ancestor - * - * @param ancestor the proposed ancestor - * @param successor the proposed successor - * @return true if ancestor is in fact an ancestor of successor at the given time - */ - private static boolean isAncestor(TraceObject ancestor, TraceObject successor) { - return ancestor.getCanonicalPath().isAncestor(successor.getCanonicalPath()); - } - - private static TraceObject choose(TraceObject curObj, TraceObject newObj) { - if (curObj == null) { - return newObj; + private static TraceObjectKeyPath choose(TraceObjectKeyPath curPath, + TraceObjectKeyPath newPath) { + if (curPath == null) { + return newPath; } - if (newObj == null) { - return curObj; + if (newPath == null) { + return curPath; } - if (isAncestor(newObj, curObj)) { - return curObj; + if (newPath.isAncestor(curPath)) { + return curPath; } - return newObj; + return newPath; } public DebuggerCoordinates thread(TraceThread newThread) { @@ -367,19 +346,19 @@ public class DebuggerCoordinates { throw new IllegalArgumentException("Cannot change trace"); } if (newThread == null) { - newThread = resolveThread(trace, recorder, getTime()); + newThread = resolveThread(trace, target, getTime()); } Trace newTrace = trace != null ? trace : newThread.getTrace(); TracePlatform newPlatform = platform != null ? platform : resolvePlatform(newTrace); TraceSchedule newTime = time != null ? time : resolveTime(view); TraceProgramView newView = view != null ? view : resolveView(newTrace, newTime); // Yes, override frame with 0 on thread changes, unless target says otherwise - Integer newFrame = resolveFrame(recorder, newThread, newTime); + Integer newFrame = resolveFrame(target, newThread, newTime); // Yes, forced frame change may also force object change - TraceObject threadOrFrameObject = resolveObject(newThread, newFrame, newTime); - TraceObject newObject = choose(object, threadOrFrameObject); - return new DebuggerCoordinates(newTrace, newPlatform, recorder, newThread, newView, newTime, - newFrame, newObject); + TraceObjectKeyPath threadOrFramePath = resolvePath(newThread, newFrame, newTime); + TraceObjectKeyPath newPath = choose(path, threadOrFramePath); + return new DebuggerCoordinates(newTrace, newPlatform, target, newThread, newView, newTime, + newFrame, newPath); } /** @@ -404,8 +383,7 @@ public class DebuggerCoordinates { return this; } TraceSchedule newTime = TraceSchedule.snap(snap); - return new DebuggerCoordinates(trace, platform, recorder, thread, view, newTime, frame, - object); + return new DebuggerCoordinates(trace, platform, target, thread, view, newTime, frame, path); } public DebuggerCoordinates time(TraceSchedule newTime) { @@ -414,13 +392,13 @@ public class DebuggerCoordinates { } long snap = newTime.getSnap(); TraceThread newThread = thread != null && thread.getLifespan().contains(snap) ? thread - : resolveThread(trace, recorder, newTime); + : resolveThread(trace, target, newTime); // This will cause the frame to reset to 0 on every snap change. That's fair.... Integer newFrame = resolveFrame(newThread, newTime); - TraceObject threadOrFrameObject = resolveObject(newThread, newFrame, newTime); - TraceObject newObject = choose(object, threadOrFrameObject); - return new DebuggerCoordinates(trace, platform, recorder, newThread, view, newTime, - newFrame, newObject); + TraceObjectKeyPath threadOrFramePath = resolvePath(newThread, newFrame, newTime); + TraceObjectKeyPath newPath = choose(path, threadOrFramePath); + return new DebuggerCoordinates(trace, platform, target, newThread, view, newTime, + newFrame, newPath); } public DebuggerCoordinates frame(int newFrame) { @@ -430,10 +408,10 @@ public class DebuggerCoordinates { if (Objects.equals(frame, newFrame)) { return this; } - TraceObject threadOrFrameObject = resolveObject(thread, newFrame, getTime()); - TraceObject newObject = choose(object, threadOrFrameObject); - return new DebuggerCoordinates(trace, platform, recorder, thread, view, time, newFrame, - newObject); + TraceObjectKeyPath threadOrFramePath = resolvePath(thread, newFrame, getTime()); + TraceObjectKeyPath newPath = choose(path, threadOrFramePath); + return new DebuggerCoordinates(trace, platform, target, thread, view, time, newFrame, + newPath); } public DebuggerCoordinates frame(Integer newFrame) { @@ -444,8 +422,8 @@ public class DebuggerCoordinates { } private DebuggerCoordinates replaceView(TraceProgramView newView) { - return new DebuggerCoordinates(trace, platform, recorder, thread, newView, time, frame, - object); + return new DebuggerCoordinates(trace, platform, target, thread, newView, time, frame, + path); } private static TraceSchedule resolveTime(TraceProgramView view) { @@ -485,13 +463,21 @@ public class DebuggerCoordinates { return time(resolveTime(newView)).replaceView(newView); } - private static TraceThread resolveThread(TraceObject object) { + private static TraceThread resolveThread(Trace trace, TraceObjectKeyPath path) { + TraceObject object = trace.getObjectManager().getObjectByCanonicalPath(path); + if (object == null) { + return null; + } return object.queryCanonicalAncestorsInterface(TraceObjectThread.class) .findFirst() .orElse(null); } - private static Integer resolveFrame(TraceObject object) { + private static Integer resolveFrame(Trace trace, TraceObjectKeyPath path) { + TraceObject object = trace.getObjectManager().getObjectByCanonicalPath(path); + if (object == null) { + return null; + } TraceObjectStackFrame frame = object.queryCanonicalAncestorsInterface(TraceObjectStackFrame.class) .findFirst() @@ -499,57 +485,48 @@ public class DebuggerCoordinates { return frame == null ? null : frame.getLevel(); } - public DebuggerCoordinates object(TraceObject newObject) { - Trace newTrace; - if (trace == null) { - if (newObject == null) { - return NOWHERE; - } - newTrace = newObject.getTrace(); + public DebuggerCoordinates path(TraceObjectKeyPath newPath) { + if (trace == null && newPath == null) { + return NOWHERE; + } + else if (trace == null) { + throw new IllegalArgumentException("No trace"); } else { - if (newObject == null) { - return new DebuggerCoordinates(trace, platform, recorder, thread, view, time, frame, - newObject); + if (newPath == null) { + return new DebuggerCoordinates(trace, platform, target, thread, view, time, frame, + newPath); } - if (newObject.getTrace() != trace) { - throw new IllegalArgumentException("Cannot change trace"); - } - newTrace = trace; } - TracePlatform newPlatform = platform != null ? platform : resolvePlatform(newTrace); - TraceThread newThread = resolveThread(newObject); - Integer newFrame = resolveFrame(newObject); + TraceThread newThread = target != null + ? resolveThread(target, newPath) + : resolveThread(trace, newPath); + Integer newFrame = target != null + ? resolveFrame(target, newPath) + : resolveFrame(trace, newPath); - return new DebuggerCoordinates(newTrace, newPlatform, recorder, newThread, view, time, - newFrame, newObject); + return new DebuggerCoordinates(trace, platform, target, newThread, view, time, + newFrame, newPath); } - protected static TraceThread resolveThread(TraceRecorder recorder, TargetObject targetObject) { - return recorder.getTraceThreadForSuccessor(targetObject); + protected static TraceThread resolveThread(Target target, TraceObjectKeyPath objectPath) { + return target.getThreadForSuccessor(objectPath); } - protected static Integer resolveFrame(TraceRecorder recorder, TargetObject targetObject) { - TraceStackFrame frame = recorder.getTraceStackFrameForSuccessor(targetObject); + protected static Integer resolveFrame(Target target, TraceObjectKeyPath objectPath) { + TraceStackFrame frame = target.getStackFrameForSuccessor(objectPath); return frame == null ? null : frame.getLevel(); } - protected DebuggerCoordinates object(TraceObject traceObject, TargetObject targetObject) { - if (traceObject != null) { - return object(traceObject); - } - if (recorder == null) { - throw new IllegalArgumentException("No recorder"); - } - TraceThread newThread = resolveThread(recorder, targetObject); - Integer newFrame = resolveFrame(recorder, targetObject); - return new DebuggerCoordinates(trace, platform, recorder, - newThread == null ? thread : newThread, view, time, newFrame == null ? frame : newFrame, - null); + public DebuggerCoordinates object(TargetObject targetObject) { + return path(TraceObjectKeyPath.of(targetObject.getPath())); } - public DebuggerCoordinates object(TargetObject newObject) { - return object(resolveObject(trace, newObject), newObject); + public DebuggerCoordinates object(TraceObject newObject) { + if (newObject == null) { + return path(null); + } + return trace(newObject.getTrace()).path(newObject.getCanonicalPath()); } public Trace getTrace() { @@ -560,8 +537,8 @@ public class DebuggerCoordinates { return platform; } - public TraceRecorder getRecorder() { - return recorder; + public Target getTarget() { + return target; } public TraceThread getThread() { @@ -587,7 +564,21 @@ public class DebuggerCoordinates { return frame == null ? 0 : frame; } - public TraceObject getObject() { + public TraceObjectKeyPath getPath() { + return path; + } + + private TraceObject doGetObject() { + if (trace == null) { + return null; + } + return trace.getObjectManager().getObjectByCanonicalPath(path); + } + + public synchronized TraceObject getObject() { + if (object == null) { + object = doGetObject(); + } return object; } @@ -595,7 +586,7 @@ public class DebuggerCoordinates { if (registerContainer != null) { return registerContainer; } - return registerContainer = object.queryRegisterContainer(getFrame()); + return registerContainer = getObject().queryRegisterContainer(getFrame()); } public synchronized long getViewSnap() { @@ -750,16 +741,16 @@ public class DebuggerCoordinates { } public boolean isAlive() { - return recorder != null && recorder.isRecording(); + return target != null && target.isValid(); } - public boolean isPresent() { + protected boolean isPresent() { TraceSchedule defaultedTime = getTime(); - return recorder.getSnap() == defaultedTime.getSnap() && defaultedTime.isSnapOnly(); + return target.getSnap() == defaultedTime.getSnap() && defaultedTime.isSnapOnly(); } - public boolean isReadsPresent() { - return recorder.getSnap() == getTime().getSnap(); + protected boolean isReadsPresent() { + return target.getSnap() == getTime().getSnap(); } public boolean isAliveAndPresent() { diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteAsyncResult.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteAsyncResult.java index 1ba9a26cc2..adc716a795 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteAsyncResult.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteAsyncResult.java @@ -34,6 +34,7 @@ import ghidra.trace.model.target.TraceObject; * return a {@link TraceObject}, so that a client can quickly associate the trace changes with the * method. Otherwise, please return null/void/None for all methods. * + *

    * NOTE: To avoid the mistake of blocking the Swing thread on an asynchronous result, the * {@link #get()} methods have been overridden to check for the Swing thread. If invoked on the * Swing thread with a timeout greater than 1 second, an assertion error will be thrown. Please use diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteMethod.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteMethod.java index a0204dfc56..44cac0d15b 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteMethod.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteMethod.java @@ -24,6 +24,7 @@ import ghidra.async.AsyncUtils; import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.schema.*; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.debug.api.target.ActionName; import ghidra.trace.model.Trace; import ghidra.trace.model.target.TraceObject; @@ -41,123 +42,6 @@ import ghidra.trace.model.target.TraceObject; */ public interface RemoteMethod { - /** - * A "hint" for how to map the method to a common action. - * - *

    - * Many common commands/actions have varying names across different back-end debuggers. We'd - * like to present common idioms for these common actions, but allow them to keep the names used - * by the back-end, because those names are probably better known to users of that back-end than - * Ghidra's action names are known. The action hints will affect the icon and placement of the - * action in the UI, but the display name will still reflect the name given by the back-end. - * Note that the "stock" action names are not a fixed enumeration. These are just the ones that - * might get special treatment from Ghidra. All methods should appear somewhere (at least, e.g., - * in context menus for applicable objects), even if the action name is unspecified or does not - * match a stock name. This list may change over time, but that shouldn't matter much. Each - * back-end should make its best effort to match its methods to these stock actions where - * applicable, but ultimately, it is up to the UI to decide what is presented where. - */ - public record Action(String name) { - public static final Action REFRESH = new Action("refresh"); - public static final Action ACTIVATE = new Action("activate"); - /** - * A weaker form of activate. - * - *

    - * The user has expressed interest in an object, but has not activated it yet. This is often - * used to communicate selection (i.e., highlight) of the object. Whereas, double-clicking - * or pressing enter would more likely invoke 'activate.' - */ - public static final Action FOCUS = new Action("focus"); - public static final Action TOGGLE = new Action("toggle"); - public static final Action DELETE = new Action("delete"); - - /** - * Forms: (cmd:STRING):STRING - * - * Optional arguments: capture:BOOL - */ - public static final Action EXECUTE = new Action("execute"); - - /** - * Forms: (spec:STRING) - */ - public static final Action CONNECT = new Action("connect"); - - /** - * Forms: (target:Attachable), (pid:INT), (spec:STRING) - */ - public static final Action ATTACH = new Action("attach"); - public static final Action DETACH = new Action("detach"); - - /** - * Forms: (command_line:STRING), (file:STRING,args:STRING), (file:STRING,args:STRING_ARRAY), - * (ANY*) - */ - public static final Action LAUNCH = new Action("launch"); - public static final Action KILL = new Action("kill"); - - public static final Action RESUME = new Action("resume"); - public static final Action INTERRUPT = new Action("interrupt"); - - /** - * All of these will show in the "step" portion of the control toolbar, if present. The - * difference in each "step_x" is minor. The icon will indicate which form, and the - * positions will be shifted so they appear in a consistent order. The display name is - * determined by the method name, not the action name. For stepping actions that don't fit - * the standards, use {@link #STEP_EXT}. There should be at most one of each standard - * applicable for any given context. (Multiple will appear, but may confuse the user.) You - * can have as many extended step actions as you like. They will be ordered - * lexicographically by name. - */ - public static final Action STEP_INTO = new Action("step_into"); - public static final Action STEP_OVER = new Action("step_over"); - public static final Action STEP_OUT = new Action("step_out"); - /** - * Skip is not typically available, except in emulators. If the back-end debugger does not - * have a command for this action out-of-the-box, we do not recommend trying to implement it - * yourself. The purpose of these actions just to expose/map each command to the UI, not to - * invent new features for the back-end debugger. - */ - public static final Action STEP_SKIP = new Action("step_skip"); - /** - * Step back is not typically available, except in emulators and timeless (or time-travel) - * debuggers. - */ - public static final Action STEP_BACK = new Action("step_back"); - /** - * The action for steps that don't fit one of the common stepping actions. - */ - public static final Action STEP_EXT = new Action("step_ext"); - - /** - * Forms: (addr:ADDRESS), R/W(rng:RANGE), set(expr:STRING) - * - * Optional arguments: condition:STRING, commands:STRING - */ - public static final Action BREAK_SW_EXECUTE = new Action("break_sw_execute"); - public static final Action BREAK_HW_EXECUTE = new Action("break_hw_execute"); - public static final Action BREAK_READ = new Action("break_read"); - public static final Action BREAK_WRITE = new Action("break_write"); - public static final Action BREAK_ACCESS = new Action("break_access"); - public static final Action BREAK_EXT = new Action("break_ext"); - - /** - * Forms: (rng:RANGE) - */ - public static final Action READ_MEM = new Action("read_mem"); - /** - * Forms: (addr:ADDRESS,data:BYTES) - */ - public static final Action WRITE_MEM = new Action("write_mem"); - - // NOTE: no read_reg. Use refresh(RegContainer), refresh(RegGroup), refresh(Register) - /** - * Forms: (frame:Frame,name:STRING,value:BYTES), (register:Register,value:BYTES) - */ - public static final Action WRITE_REG = new Action("write_reg"); - } - /** * The name of the method. * @@ -170,7 +54,7 @@ public interface RemoteMethod { * * @return the action */ - Action action(); + ActionName action(); /** * A description of the method. diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteMethodRegistry.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteMethodRegistry.java index ba0a51ff2d..b98a58847c 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteMethodRegistry.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteMethodRegistry.java @@ -18,12 +18,12 @@ package ghidra.debug.api.tracermi; import java.util.Map; import java.util.Set; -import ghidra.debug.api.tracermi.RemoteMethod.Action; +import ghidra.debug.api.target.ActionName; public interface RemoteMethodRegistry { Map all(); RemoteMethod get(String name); - Set getByAction(Action action); + Set getByAction(ActionName action); } diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteParameter.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteParameter.java index c64dfad8bf..fcee4d4b3d 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteParameter.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteParameter.java @@ -18,9 +18,15 @@ package ghidra.debug.api.tracermi; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; public interface RemoteParameter { + String name(); + SchemaName type(); boolean required(); Object getDefaultValue(); + + String display(); + + String description(); } diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/workflow/DebuggerBot.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/workflow/DebuggerBot.java index f0ebcc030e..b4ee473e6d 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/workflow/DebuggerBot.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/workflow/DebuggerBot.java @@ -15,9 +15,7 @@ */ package ghidra.debug.api.workflow; -import ghidra.app.services.DebuggerModelService; import ghidra.app.services.DebuggerWorkflowFrontEndService; -import ghidra.dbg.DebuggerObjectModel; import ghidra.framework.options.AutoOptions; import ghidra.framework.plugintool.PluginTool; import ghidra.lifecycle.Internal; @@ -26,7 +24,6 @@ import ghidra.trace.model.Trace; import ghidra.util.HelpLocation; import ghidra.util.Msg; import ghidra.util.classfinder.ExtensionPoint; -import ghidra.util.datastruct.CollectionChangeListener; /** * A bot (or analyzer) that aids the user in the debugging workflow @@ -186,24 +183,6 @@ public interface DebuggerBot extends ExtensionPoint { */ void disable(); - /** - * A model has been added to the model service - * - * @see DebuggerModelService#addModelsChangedListener(CollectionChangeListener) - * @param model the new model - */ - default void modelAdded(DebuggerObjectModel model) { - } - - /** - * A model has been removed from the model service - * - * @see DebuggerModelService#addModelsChangedListener(CollectionChangeListener) - * @param model the removed model - */ - default void modelRemoved(DebuggerObjectModel model) { - } - /** * A program has been opened in a tool * diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java similarity index 99% rename from Ghidra/Debug/Debugger/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java rename to Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java index d7f391a322..7920288bbf 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java @@ -23,7 +23,6 @@ import java.util.*; import java.util.concurrent.*; import java.util.stream.Collectors; -import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils; import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraState; import ghidra.app.services.*; @@ -38,8 +37,9 @@ import ghidra.dbg.util.PathUtils; import ghidra.debug.api.breakpoint.LogicalBreakpoint; import ghidra.debug.api.control.ControlMode; import ghidra.debug.api.model.DebuggerProgramLaunchOffer; -import ghidra.debug.api.model.TraceRecorder; import ghidra.debug.api.model.DebuggerProgramLaunchOffer.*; +import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.pcode.exec.trace.TraceSleighUtils; import ghidra.program.flatapi.FlatProgramAPI; @@ -611,20 +611,7 @@ public interface FlatDebuggerAPI { * @throws IOException if the trace cannot be created */ default Trace emulateLaunch(Program program, Address address) throws IOException { - Trace trace = null; - try { - trace = ProgramEmulationUtils.launchEmulationTrace(program, address, this); - DebuggerTraceManagerService traceManager = getTraceManager(); - traceManager.openTrace(trace); - traceManager.activateTrace(trace); - Swing.allowSwingToProcessEvents(); - } - finally { - if (trace != null) { - trace.release(this); - } - } - return trace; + return getEmulationService().launchProgram(program, address); } /** @@ -1433,15 +1420,15 @@ public interface FlatDebuggerAPI { } /** - * Get the recorder for the current target + * Get the current target * *

    * If the current trace is not live, this returns null. * - * @return the recorder, or null + * @return the target, or null */ - default TraceRecorder getCurrentRecorder() { - return getTraceManager().getCurrent().getRecorder(); + default Target getCurrentTarget() { + return getTraceManager().getCurrent().getTarget(); } /** @@ -1459,6 +1446,7 @@ public interface FlatDebuggerAPI { * @param program the program * @return the offers */ + @Deprecated default List getLaunchOffers(Program program) { return getModelService().getProgramLaunchOffers(program).collect(Collectors.toList()); } diff --git a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpClient.java b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpClient.java index b1beed640c..b1ebce65de 100644 --- a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpClient.java +++ b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpClient.java @@ -504,7 +504,7 @@ public class GadpClient extends AbstractDebuggerObjectModel } catch (RejectedExecutionException e) { reportError(this, "Client already closed", e); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } catch (IOException e) { return CompletableFuture.failedFuture(e); diff --git a/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/GadpClientServerTest.java b/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/GadpClientServerTest.java index 952d7eb494..62e2d2f4cd 100644 --- a/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/GadpClientServerTest.java +++ b/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/GadpClientServerTest.java @@ -439,7 +439,7 @@ public class GadpClientServerTest implements AsyncTestUtils { TestGadpTargetProcess process = new TestGadpTargetProcess(this, pid, base); changeElements(List.of(), List.of(process), Map.of(), "Launched"); parent.setFocus(process); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } @Override @@ -459,7 +459,7 @@ public class GadpClientServerTest implements AsyncTestUtils { else { throw new DebuggerIllegalArgumentException("unrecognized option: '" + key + "'"); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } public void setBase(int base) { diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetRoot.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetRoot.java index b2d13853bf..675f4b0689 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetRoot.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetRoot.java @@ -244,7 +244,7 @@ public class JdiModelTargetRoot extends DefaultTargetModelRoot implements // catch (IOException e) { Msg.error(this, "Could not interrupt", e); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } protected void invalidateMemoryAndRegisterCaches() { diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetVMContainer.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetVMContainer.java index 9de043c78d..729ef20084 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetVMContainer.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetVMContainer.java @@ -155,7 +155,7 @@ public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl public CompletableFuture requestElements(RefreshBehavior refresh) { if (!refresh.equals(RefreshBehavior.REFRESH_ALWAYS)) { updateUsingVMs(impl.getManager().getKnownVMs()); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return impl.getManager().listVMs().thenAccept(this::updateUsingVMs); } diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/iface1/JdiModelTargetFocusScope.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/iface1/JdiModelTargetFocusScope.java index 67ed6a9b93..55e377f545 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/iface1/JdiModelTargetFocusScope.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/iface1/JdiModelTargetFocusScope.java @@ -59,6 +59,6 @@ public interface JdiModelTargetFocusScope extends JdiModelTargetObject, TargetFo } cur = cur.getParent(); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java index fb15ebff74..9d8d73c8e4 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java @@ -156,7 +156,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer Trace trace) { Address probeAddress = getMappingProbeAddress(); if (probeAddress == null) { - return AsyncUtils.NIL; // No need to wait on mapping of nothing + return AsyncUtils.nil(); // No need to wait on mapping of nothing } ProgramLocation probe = new ProgramLocation(program, probeAddress); var result = new CompletableFuture() { diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/DefaultRemoteMethodRegistry.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/DefaultRemoteMethodRegistry.java index 5c8aafeea2..0fa90b77c8 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/DefaultRemoteMethodRegistry.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/DefaultRemoteMethodRegistry.java @@ -17,13 +17,12 @@ package ghidra.app.plugin.core.debug.service.rmi.trace; import java.util.*; -import ghidra.debug.api.tracermi.RemoteMethod; -import ghidra.debug.api.tracermi.RemoteMethodRegistry; -import ghidra.debug.api.tracermi.RemoteMethod.Action; +import ghidra.debug.api.target.ActionName; +import ghidra.debug.api.tracermi.*; public class DefaultRemoteMethodRegistry implements RemoteMethodRegistry { private final Map map = new HashMap<>(); - private final Map> byAction = new HashMap<>(); + private final Map> byAction = new HashMap<>(); protected void add(RemoteMethod method) { synchronized (map) { @@ -47,7 +46,7 @@ public class DefaultRemoteMethodRegistry implements RemoteMethodRegistry { } @Override - public Set getByAction(Action action) { + public Set getByAction(ActionName action) { synchronized (map) { return byAction.getOrDefault(action, Set.of()); } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/OpenTrace.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/OpenTrace.java index 2bff60bc60..9e0f0a38e9 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/OpenTrace.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/OpenTrace.java @@ -27,11 +27,13 @@ import ghidra.trace.model.time.TraceSnapshot; class OpenTrace implements ValueDecoder { final DoId doId; final Trace trace; + final TraceRmiTarget target; TraceSnapshot lastSnapshot; - OpenTrace(DoId doId, Trace trace) { + OpenTrace(DoId doId, Trace trace, TraceRmiTarget target) { this.doId = doId; this.trace = trace; + this.target = target; } public TraceSnapshot createSnapshot(Snap snap, String description) { diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/RecordRemoteMethod.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/RecordRemoteMethod.java index 8168874d6b..90adf56e8c 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/RecordRemoteMethod.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/RecordRemoteMethod.java @@ -18,12 +18,11 @@ package ghidra.app.plugin.core.debug.service.rmi.trace; import java.util.Map; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; -import ghidra.debug.api.tracermi.RemoteMethod; -import ghidra.debug.api.tracermi.RemoteParameter; -import ghidra.debug.api.tracermi.RemoteMethod.Action; +import ghidra.debug.api.target.ActionName; +import ghidra.debug.api.tracermi.*; import ghidra.trace.model.Trace; -public record RecordRemoteMethod(TraceRmiHandler handler, String name, Action action, +public record RecordRemoteMethod(TraceRmiHandler handler, String name, ActionName action, String description, Map parameters, SchemaName retType) implements RemoteMethod { @Override diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiHandler.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiHandler.java index 0d74bd134f..05405dfb04 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiHandler.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiHandler.java @@ -41,9 +41,9 @@ import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; import ghidra.dbg.target.schema.XmlSchemaContext; import ghidra.dbg.util.PathPattern; import ghidra.dbg.util.PathUtils; +import ghidra.debug.api.target.ActionName; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.debug.api.tracermi.*; -import ghidra.debug.api.tracermi.RemoteMethod.Action; import ghidra.framework.model.*; import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.AutoService.Wiring; @@ -135,7 +135,7 @@ public class TraceRmiHandler implements TraceRmiConnection { protected record OpenTx(Tid txId, Transaction tx, boolean undoable) { } - protected static class OpenTraceMap { + protected class OpenTraceMap { private final Map byId = new HashMap<>(); private final Map byTrace = new HashMap<>(); private final CompletableFuture first = new CompletableFuture<>(); @@ -154,6 +154,7 @@ public class TraceRmiHandler implements TraceRmiConnection { return null; } byTrace.remove(removed.trace); + plugin.withdrawTarget(removed.target); return removed; } @@ -169,6 +170,8 @@ public class TraceRmiHandler implements TraceRmiConnection { byId.put(openTrace.doId, openTrace); byTrace.put(openTrace.trace, openTrace); first.complete(openTrace); + + plugin.publishTarget(openTrace.target); } public CompletableFuture getFirstAsync() { @@ -812,9 +815,9 @@ public class TraceRmiHandler implements TraceRmiConnection { DomainFolder folder = createFolders(traces, path.getParent()); CompilerSpec cs = requireCompilerSpec(req.getLanguage(), req.getCompiler()); DBTrace trace = new DBTrace(path.getFileName().toString(), cs, this); - + TraceRmiTarget target = new TraceRmiTarget(plugin.getTool(), this, trace); DoId doId = requireAvailableDoId(req.getOid()); - openTraces.put(new OpenTrace(doId, trace)); + openTraces.put(new OpenTrace(doId, trace, target)); createDeconflictedFile(folder, trace); return ReplyCreateTrace.getDefaultInstance(); } @@ -961,7 +964,8 @@ public class TraceRmiHandler implements TraceRmiConnection { throw error; } for (Method m : req.getMethodsList()) { - RemoteMethod rm = new RecordRemoteMethod(this, m.getName(), new Action(m.getAction()), + RemoteMethod rm = new RecordRemoteMethod(this, m.getName(), + new ActionName(m.getAction()), m.getDescription(), m.getParametersList() .stream() .collect(Collectors.toMap(MethodParameter::getName, this::makeParameter)), diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiPlugin.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiPlugin.java index 2f6cb6c516..089a148579 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiPlugin.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiPlugin.java @@ -23,10 +23,14 @@ import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent; import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; +import ghidra.app.services.DebuggerTargetService; import ghidra.app.services.TraceRmiService; import ghidra.debug.api.tracermi.TraceRmiConnection; import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.AutoService.Wiring; +import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.util.Swing; import ghidra.util.task.ConsoleTaskMonitor; import ghidra.util.task.TaskMonitor; @@ -43,11 +47,20 @@ import ghidra.util.task.TaskMonitor; TraceActivatedPluginEvent.class, TraceClosedPluginEvent.class, }, + servicesRequired = { + DebuggerTargetService.class, + }, servicesProvided = { TraceRmiService.class, }) public class TraceRmiPlugin extends Plugin implements TraceRmiService { private static final int DEFAULT_PORT = 15432; + + @AutoServiceConsumed + private DebuggerTargetService targetService; + @SuppressWarnings("unused") + private final Wiring autoServiceWiring; + private final TaskMonitor monitor = new ConsoleTaskMonitor(); private SocketAddress serverAddress = new InetSocketAddress("0.0.0.0", DEFAULT_PORT); @@ -57,6 +70,7 @@ public class TraceRmiPlugin extends Plugin implements TraceRmiService { public TraceRmiPlugin(PluginTool tool) { super(tool); + autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this); } public TaskMonitor getTaskMonitor() { @@ -132,4 +146,16 @@ public class TraceRmiPlugin extends Plugin implements TraceRmiService { public Collection getAllConnections() { return List.copyOf(handlers); } + + void publishTarget(TraceRmiTarget target) { + Swing.runIfSwingOrRunLater(() -> { + targetService.publishTarget(target); + }); + } + + void withdrawTarget(TraceRmiTarget target) { + Swing.runIfSwingOrRunLater(() -> { + targetService.withdrawTarget(target); + }); + } } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiTarget.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiTarget.java new file mode 100644 index 0000000000..28ea7039fc --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiTarget.java @@ -0,0 +1,1080 @@ +/* ### + * 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.service.rmi.trace; + +import java.io.IOException; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.function.BooleanSupplier; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.exception.ExceptionUtils; + +import docking.ActionContext; +import ghidra.app.context.ProgramLocationActionContext; +import ghidra.app.plugin.core.debug.gui.model.DebuggerObjectActionContext; +import ghidra.app.plugin.core.debug.service.target.AbstractTarget; +import ghidra.app.services.DebuggerTraceManagerService; +import ghidra.async.*; +import ghidra.dbg.target.*; +import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; +import ghidra.dbg.target.schema.*; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.dbg.util.PathMatcher; +import ghidra.dbg.util.PathPredicates; +import ghidra.dbg.util.PathPredicates.Align; +import ghidra.debug.api.target.ActionName; +import ghidra.debug.api.tracemgr.DebuggerCoordinates; +import ghidra.debug.api.tracermi.*; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.utils.Utils; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.RegisterValue; +import ghidra.trace.model.Lifespan; +import ghidra.trace.model.Trace; +import ghidra.trace.model.breakpoint.*; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.stack.*; +import ghidra.trace.model.target.*; +import ghidra.trace.model.thread.TraceObjectThread; +import ghidra.trace.model.thread.TraceThread; +import ghidra.util.Msg; +import ghidra.util.task.TaskMonitor; + +public class TraceRmiTarget extends AbstractTarget { + private static final String BREAK_HW_EXEC = "breakHwExec"; + private static final String BREAK_SW_EXEC = "breakSwExec"; + private static final String BREAK_READ = "breakRead"; + private static final String BREAK_WRITE = "breakWrite"; + private static final String BREAK_ACCESS = "breakAccess"; + private final TraceRmiConnection connection; + private final Trace trace; + + private final Matches matches = new Matches(); + private final Set supportedBreakpointKinds; + + public TraceRmiTarget(PluginTool tool, TraceRmiConnection connection, Trace trace) { + super(tool); + this.connection = connection; + this.trace = trace; + this.supportedBreakpointKinds = computeSupportedBreakpointKinds(); + } + + @Override + public boolean isValid() { + return !connection.isClosed(); + } + + @Override + public Trace getTrace() { + return trace; + } + + @Override + public long getSnap() { + return connection.getLastSnapshot(trace); + } + + @Override + public TargetExecutionState getThreadExecutionState(TraceThread thread) { + if (!(thread instanceof TraceObjectThread tot)) { + Msg.error(this, "Non-object thread with Trace RMI!"); + return TargetExecutionState.ALIVE; + } + return tot.getObject().getExecutionState(getSnap()); + } + + @Override + public TraceThread getThreadForSuccessor(TraceObjectKeyPath path) { + TraceObject object = trace.getObjectManager().getObjectByCanonicalPath(path); + if (object == null) { + return null; + } + return object.queryCanonicalAncestorsInterface(TraceObjectThread.class) + .findFirst() + .orElse(null); + } + + @Override + public TraceStackFrame getStackFrameForSuccessor(TraceObjectKeyPath path) { + TraceObject object = trace.getObjectManager().getObjectByCanonicalPath(path); + if (object == null) { + return null; + } + return object.queryCanonicalAncestorsInterface(TraceObjectStackFrame.class) + .findFirst() + .orElse(null); + } + + protected TraceObject findObject(ActionContext context) { + if (context instanceof DebuggerObjectActionContext ctx) { + List values = ctx.getObjectValues(); + if (values.size() == 1) { + TraceObjectValue ov = values.get(0); + if (ov.isObject()) { + return ov.getChild(); + } + } + } + DebuggerTraceManagerService traceManager = + tool.getService(DebuggerTraceManagerService.class); + if (traceManager != null) { + return traceManager.getCurrentObject(); + } + return null; + } + + protected Object findArgumentForSchema(ActionContext context, TargetObjectSchema schema) { + if (schema instanceof EnumerableTargetObjectSchema prim) { + return switch (prim) { + case OBJECT -> findObject(context); + case ADDRESS -> findAddress(context); + case RANGE -> findRange(context); + default -> null; + }; + } + TraceObject object = findObject(context); + if (object == null) { + return null; + } + return object.querySuitableSchema(schema); + } + + private enum Missing { + MISSING; // The argument requires a prompt + } + + protected Object findArgument(RemoteParameter parameter, ActionContext context) { + SchemaName type = parameter.type(); + TargetObjectSchema schema = getSchemaContext().getSchema(type); + if (schema == null) { + Msg.error(this, "Schema " + type + " not in trace! " + trace); + return null; + } + Object arg = findArgumentForSchema(context, schema); + if (arg != null) { + return arg; + } + if (!parameter.required()) { + return parameter.getDefaultValue(); + } + return Missing.MISSING; + } + + protected Map collectArguments(RemoteMethod method, ActionContext context) { + return method.parameters() + .entrySet() + .stream() + .collect( + Collectors.toMap(Entry::getKey, e -> findArgument(e.getValue(), context))); + } + + private TargetExecutionState getStateOf(TraceObject object) { + return object.getExecutionState(getSnap()); + } + + private boolean stateOrNull(TraceObject object, + Predicate predicate) { + TargetExecutionState state = getStateOf(object); + return state == null || predicate.test(state); + } + + protected BooleanSupplier chooseEnabler(RemoteMethod method, Map args) { + ActionName name = method.action(); + SchemaContext ctx = getSchemaContext(); + RemoteParameter firstParam = method.parameters() + .values() + .stream() + .filter(p -> TargetObject.class.isAssignableFrom(ctx.getSchema(p.type()).getType())) + .findFirst() + .orElse(null); + if (firstParam == null) { + return () -> true; + } + TraceObject firstArg = (TraceObject) args.get(firstParam.name()); + if (ActionName.RESUME.equals(name) || + ActionName.STEP_BACK.equals(name) || + ActionName.STEP_EXT.equals(name) || + ActionName.STEP_INTO.equals(name) || + ActionName.STEP_OUT.equals(name) || + ActionName.STEP_OVER.equals(name) || + ActionName.STEP_SKIP.equals(name)) { + return () -> stateOrNull(firstArg, TargetExecutionState::isStopped); + } + else if (ActionName.INTERRUPT.equals(name)) { + return () -> stateOrNull(firstArg, TargetExecutionState::isRunning); + } + else if (ActionName.KILL.equals(name)) { + return () -> stateOrNull(firstArg, TargetExecutionState::isAlive); + } + return () -> true; + } + + protected ActionEntry createEntry(RemoteMethod method, ActionContext context) { + Map args = collectArguments(method, context); + boolean requiresPrompt = args.values().contains(Missing.MISSING); + return new ActionEntry(method.name(), method.action(), method.description(), requiresPrompt, + chooseEnabler(method, args), () -> method.invokeAsync(args).toCompletableFuture()); + } + + protected Map collectFromMethods(Collection methods, + ActionContext context) { + Map result = new HashMap<>(); + for (RemoteMethod m : methods) { + result.put(m.name(), createEntry(m, context)); + } + return result; + } + + protected boolean isAddressMethod(RemoteMethod method, SchemaContext ctx) { + return method.parameters() + .values() + .stream() + .filter(p -> ctx.getSchema(p.type()).getType() == Address.class) + .count() == 1; + } + + @Override + protected Map collectAddressActions(ProgramLocationActionContext context) { + SchemaContext ctx = getSchemaContext(); + Map result = new HashMap<>(); + for (RemoteMethod m : connection.getMethods().all().values()) { + if (!isAddressMethod(m, ctx)) { + continue; + } + result.put(m.name(), createEntry(m, context)); + } + return result; + } + + @Override + protected Map collectAllActions(ActionContext context) { + return collectFromMethods(connection.getMethods().all().values(), context); + } + + protected Map collectByName(ActionName name, ActionContext context) { + return collectFromMethods(connection.getMethods().getByAction(name), context); + } + + @Override + protected Map collectResumeActions(ActionContext context) { + return collectByName(ActionName.RESUME, context); + } + + @Override + protected Map collectInterruptActions(ActionContext context) { + return collectByName(ActionName.INTERRUPT, context); + } + + @Override + protected Map collectKillActions(ActionContext context) { + return collectByName(ActionName.KILL, context); + } + + @Override + protected Map collectStepIntoActions(ActionContext context) { + return collectByName(ActionName.STEP_INTO, context); + } + + @Override + protected Map collectStepOverActions(ActionContext context) { + return collectByName(ActionName.STEP_OVER, context); + } + + @Override + protected Map collectStepOutActions(ActionContext context) { + return collectByName(ActionName.STEP_OUT, context); + } + + @Override + protected Map collectStepExtActions(ActionContext context) { + return collectByName(ActionName.STEP_EXT, context); + } + + @Override + public boolean isSupportsFocus() { + return trace.getObjectManager() + .getRootSchema() + .getInterfaces() + .contains(TargetFocusScope.class) && + !connection.getMethods().getByAction(ActionName.ACTIVATE).isEmpty(); + } + + @Override + public TraceObjectKeyPath getFocus() { + TraceObjectValue focusVal = trace.getObjectManager() + .getRootObject() + .getAttribute(getSnap(), TargetFocusScope.FOCUS_ATTRIBUTE_NAME); + if (focusVal == null || !focusVal.isObject()) { + return null; + } + return focusVal.getChild().getCanonicalPath(); + } + + interface MethodMatcher { + default MatchedMethod match(RemoteMethod method, SchemaContext ctx) { + List spec = spec(); + if (spec.size() != method.parameters().size()) { + return null; + } + Map found = new HashMap<>(); + for (ParamSpec ps : spec) { + RemoteParameter param = ps.find(method, ctx); + if (param == null) { + return null; + } + found.put(ps.name(), param); + } + return new MatchedMethod(method, Map.copyOf(found), score()); + } + + List spec(); + + int score(); + + static MatchedMethod matchPreferredForm(RemoteMethod method, SchemaContext ctx, + List preferred) { + return preferred.stream() + .map(m -> m.match(method, ctx)) + .filter(m -> m != null) + .findFirst() + .orElse(null); + } + } + + record MatchedMethod(RemoteMethod method, Map params, int score) + implements Comparable { + @Override + public int compareTo(MatchedMethod that) { + return Integer.compare(this.score, that.score); + } + } + + protected static boolean typeMatches(RemoteParameter param, SchemaContext ctx, Class type) { + TargetObjectSchema sch = ctx.getSchema(param.type()); + if (type == TargetObject.class) { + return sch.getType() == type; + } + else if (TargetObject.class.isAssignableFrom(type)) { + return sch.getInterfaces().contains(type); + } + else { + return sch.getType() == type; + } + } + + interface ParamSpec { + String name(); + + RemoteParameter find(RemoteMethod method, SchemaContext ctx); + } + + record TypeParamSpec(String name, Class type) implements ParamSpec { + @Override + public RemoteParameter find(RemoteMethod method, SchemaContext ctx) { + List withType = method.parameters() + .values() + .stream() + .filter(p -> typeMatches(p, ctx, type)) + .toList(); + if (withType.size() != 1) { + return null; + } + return withType.get(0); + } + } + + record NameParamSpec(String name, Class type) implements ParamSpec { + @Override + public RemoteParameter find(RemoteMethod method, SchemaContext ctx) { + RemoteParameter param = method.parameters().get(name); + if (typeMatches(param, ctx, type)) { + return param; + } + return null; + } + } + + @SafeVarargs + protected static List matchers(T... list) { + List result = List.of(list); + result.sort(Comparator.comparing(MethodMatcher::score).reversed()); + if (result.isEmpty()) { + throw new AssertionError("empty matchers list?"); + } + int prevScore = result.get(0).score(); + for (int i = 1; i < result.size(); i++) { + int curScore = result.get(i).score(); + if (prevScore <= curScore) { + throw new AssertionError("duplicate scores: " + curScore); + } + } + return result; + } + + record ActivateMatcher(int score, List spec) implements MethodMatcher { + static final ActivateMatcher HAS_FOCUS_TIME = new ActivateMatcher(3, List.of( + new TypeParamSpec("focus", TargetObject.class), + new TypeParamSpec("time", String.class))); + static final ActivateMatcher HAS_FOCUS_SNAP = new ActivateMatcher(2, List.of( + new TypeParamSpec("focus", TargetObject.class), + new TypeParamSpec("snap", Long.class))); + static final ActivateMatcher HAS_FOCUS = new ActivateMatcher(1, List.of( + new TypeParamSpec("focus", TargetObject.class))); + static final List ALL = + matchers(HAS_FOCUS_TIME, HAS_FOCUS_SNAP, HAS_FOCUS); + } + + record ReadMemMatcher(int score, List spec) implements MethodMatcher { + static final ReadMemMatcher HAS_RANGE = new ReadMemMatcher(1, List.of( + new TypeParamSpec("range", AddressRange.class))); + static final List ALL = matchers(HAS_RANGE); + } + + record WriteMemMatcher(int score, List spec) implements MethodMatcher { + static final WriteMemMatcher HAS_RANGE = new WriteMemMatcher(1, List.of( + new TypeParamSpec("start", Address.class), + new TypeParamSpec("data", byte[].class))); + static final List ALL = matchers(HAS_RANGE); + } + + record ReadRegsMatcher(int score, List spec) implements MethodMatcher { + static final ReadRegsMatcher HAS_CONTAINER = new ReadRegsMatcher(3, List.of( + new TypeParamSpec("container", TargetRegisterContainer.class))); + static final ReadRegsMatcher HAS_BANK = new ReadRegsMatcher(2, List.of( + new TypeParamSpec("bank", TargetRegisterBank.class))); + static final ReadRegsMatcher HAS_REGISTER = new ReadRegsMatcher(1, List.of( + new TypeParamSpec("register", TargetRegister.class))); + static final List ALL = matchers(HAS_CONTAINER, HAS_BANK, HAS_REGISTER); + } + + record WriteRegMatcher(int score, List spec) implements MethodMatcher { + static final WriteRegMatcher HAS_FRAME_NAME_VALUE = new WriteRegMatcher(2, List.of( + new TypeParamSpec("frame", TargetStackFrame.class), + new TypeParamSpec("name", String.class), + new TypeParamSpec("value", byte[].class))); + static final WriteRegMatcher HAS_REG_VALUE = new WriteRegMatcher(1, List.of( + new TypeParamSpec("register", TargetRegister.class), + new TypeParamSpec("value", byte[].class))); + static final List ALL = matchers(HAS_FRAME_NAME_VALUE, HAS_REG_VALUE); + } + + record BreakExecMatcher(int score, List spec) implements MethodMatcher { + static final BreakExecMatcher HAS_ADDR_COND_CMDS = new BreakExecMatcher(4, List.of( + new TypeParamSpec("address", Address.class), + new NameParamSpec("condition", String.class), + new NameParamSpec("commands", String.class))); + static final BreakExecMatcher HAS_ADDR_COND = new BreakExecMatcher(3, List.of( + new TypeParamSpec("address", Address.class), + new NameParamSpec("condition", String.class))); + static final BreakExecMatcher HAS_ADDR_CMDS = new BreakExecMatcher(2, List.of( + new TypeParamSpec("address", Address.class), + new NameParamSpec("commands", String.class))); + static final BreakExecMatcher HAS_ADDR = new BreakExecMatcher(1, List.of( + new TypeParamSpec("address", Address.class))); + static final List ALL = + matchers(HAS_ADDR_COND_CMDS, HAS_ADDR_COND, HAS_ADDR_CMDS, HAS_ADDR); + } + + record BreakAccMatcher(int score, List spec) implements MethodMatcher { + static final BreakAccMatcher HAS_RNG_COND_CMDS = new BreakAccMatcher(4, List.of( + new TypeParamSpec("range", AddressRange.class), + new NameParamSpec("condition", String.class), + new NameParamSpec("commands", String.class))); + static final BreakAccMatcher HAS_RNG_COND = new BreakAccMatcher(3, List.of( + new TypeParamSpec("range", AddressRange.class), + new NameParamSpec("condition", String.class))); + static final BreakAccMatcher HAS_RNG_CMDS = new BreakAccMatcher(2, List.of( + new TypeParamSpec("range", AddressRange.class), + new NameParamSpec("commands", String.class))); + static final BreakAccMatcher HAS_RNG = new BreakAccMatcher(1, List.of( + new TypeParamSpec("range", AddressRange.class))); + static final List ALL = + matchers(HAS_RNG_COND_CMDS, HAS_RNG_COND, HAS_RNG_CMDS, HAS_RNG); + } + + record DelBreakMatcher(int score, List spec) implements MethodMatcher { + static final DelBreakMatcher HAS_LOC = new DelBreakMatcher(2, List.of( + new TypeParamSpec("location", TargetBreakpointLocation.class))); + static final DelBreakMatcher HAS_SPEC = new DelBreakMatcher(1, List.of( + new TypeParamSpec("specification", TargetBreakpointSpec.class))); + static final List ALL = matchers(HAS_LOC, HAS_SPEC); + static final List SPEC = matchers(HAS_SPEC); + } + + record ToggleBreakMatcher(int score, List spec) implements MethodMatcher { + static final ToggleBreakMatcher HAS_LOC = new ToggleBreakMatcher(2, List.of( + new TypeParamSpec("location", TargetBreakpointLocation.class), + new TypeParamSpec("enabled", Boolean.class))); + static final ToggleBreakMatcher HAS_SPEC = new ToggleBreakMatcher(1, List.of( + new TypeParamSpec("specification", TargetBreakpointSpec.class), + new TypeParamSpec("enabled", Boolean.class))); + static final List ALL = matchers(HAS_LOC, HAS_SPEC); + static final List SPEC = matchers(HAS_SPEC); + } + + protected class Matches { + private final Map map = new HashMap<>(); + + public MatchedMethod getBest(String name, ActionName action, + List preferred) { + return map.computeIfAbsent(name, n -> chooseBest(action, preferred)); + } + + private MatchedMethod chooseBest(ActionName name, List preferred) { + if (preferred.isEmpty()) { + return null; + } + SchemaContext ctx = getSchemaContext(); + MatchedMethod best = connection.getMethods() + .getByAction(name) + .stream() + .map(m -> MethodMatcher.matchPreferredForm(m, ctx, preferred)) + .filter(f -> f != null) + .max(MatchedMethod::compareTo) + .orElse(null); + if (best == null) { + Msg.error(this, "No suitable " + name + " method"); + } + return best; + } + } + + @Override + public CompletableFuture activateAsync(DebuggerCoordinates prev, + DebuggerCoordinates coords) { + MatchedMethod activate = + matches.getBest("activate", ActionName.ACTIVATE, ActivateMatcher.ALL); + if (activate == null) { + return AsyncUtils.nil(); + } + + Map args = new HashMap<>(); + RemoteParameter paramFocus = activate.params.get("focus"); + args.put(paramFocus.name(), coords.getObject()); + RemoteParameter paramTime = activate.params.get("time"); + if (paramTime != null) { + args.put(paramTime.name(), coords.getTime().toString()); + } + RemoteParameter paramSnap = activate.params.get("snap"); + if (paramSnap != null) { + args.put(paramSnap.name(), coords.getSnap()); + } + return activate.method.invokeAsync(args).toCompletableFuture().thenApply(__ -> null); + } + + @Override + public CompletableFuture invalidateMemoryCachesAsync() { + return AsyncUtils.nil(); + } + + protected static final int BLOCK_BITS = 12; + protected static final int BLOCK_SIZE = 1 << BLOCK_BITS; + protected static final long BLOCK_MASK = -1L << BLOCK_BITS; + + protected static AddressSetView quantize(AddressSetView set) { + AddressSet result = new AddressSet(); + // Not terribly efficient, but this is one range most of the time + for (AddressRange range : set) { + AddressSpace space = range.getAddressSpace(); + Address min = space.getAddress(range.getMinAddress().getOffset() & BLOCK_MASK); + Address max = space.getAddress(range.getMaxAddress().getOffset() | ~BLOCK_MASK); + result.add(new AddressRangeImpl(min, max)); + } + return result; + } + + protected SchemaContext getSchemaContext() { + return trace.getObjectManager().getRootSchema().getContext(); + } + + @Override + public CompletableFuture readMemoryAsync(AddressSetView set, TaskMonitor monitor) { + // I still separate into blocks, because I want user to be able to cancel + // NOTE: I don't intend to warn about the number of requests. + // They're delivered in serial, and there's a cancel button that works + + MatchedMethod readMem = matches.getBest("readMem", ActionName.READ_MEM, ReadMemMatcher.ALL); + if (readMem == null) { + return AsyncUtils.nil(); + } + RemoteParameter paramRange = readMem.params.get("range"); + + int total = 0; + AddressSetView quantized = quantize(set); + for (AddressRange r : quantized) { + total += Long.divideUnsigned(r.getLength() + BLOCK_SIZE - 1, BLOCK_SIZE); + } + monitor.initialize(total); + monitor.setMessage("Reading memory"); + // NOTE: Don't read in parallel, lest we overload the connection + return AsyncUtils.each(TypeSpec.VOID, quantized.iterator(), (r, loop) -> { + AddressRangeChunker blocks = new AddressRangeChunker(r, BLOCK_SIZE); + AsyncUtils.each(TypeSpec.VOID, blocks.iterator(), (blk, inner) -> { + monitor.incrementProgress(1); + RemoteAsyncResult future = + readMem.method.invokeAsync(Map.of(paramRange.name(), blk)); + future.exceptionally(e -> { + Msg.error(this, "Could not read " + blk + ": " + e); + return null; // Continue looping on errors + }).thenApply(__ -> !monitor.isCancelled()).handle(inner::repeatWhile); + }).thenApply(v -> !monitor.isCancelled()).handle(loop::repeatWhile); + }); + } + + @Override + public CompletableFuture writeMemoryAsync(Address address, byte[] data) { + MatchedMethod writeMem = + matches.getBest("writeMem", ActionName.WRITE_MEM, WriteMemMatcher.ALL); + if (writeMem == null) { + return AsyncUtils.nil(); + } + Map args = new HashMap<>(); + args.put(writeMem.params.get("start").name(), address); + args.put(writeMem.params.get("data").name(), data); + return writeMem.method.invokeAsync(args).toCompletableFuture().thenApply(__ -> null); + } + + @Override + public CompletableFuture readRegistersAsync(TracePlatform platform, TraceThread thread, + int frame, Set registers) { + MatchedMethod readRegs = + matches.getBest("readRegs", ActionName.REFRESH, ReadRegsMatcher.ALL); + if (readRegs == null) { + return AsyncUtils.nil(); + } + if (!(thread instanceof TraceObjectThread tot)) { + Msg.error(this, "Non-object trace with TraceRmi!"); + return AsyncUtils.nil(); + } + TraceObject container = tot.getObject().queryRegisterContainer(frame); + RemoteParameter paramContainer = readRegs.params.get("container"); + if (paramContainer != null) { + return readRegs.method.invokeAsync(Map.of(paramContainer.name(), container)) + .toCompletableFuture() + .thenApply(__ -> null); + } + Set keys = new HashSet<>(); + for (Register r : registers) { + String lower = r.getName().toLowerCase(); + keys.add(lower); + keys.add("[" + lower + "]"); + } + Set regs = container + .querySuccessorsTargetInterface(Lifespan.at(getSnap()), TargetRegister.class, + true) + .filter(p -> keys.contains(p.getLastEntry().getEntryKey().toLowerCase())) + .map(r -> r.getDestination(null)) + .collect(Collectors.toSet()); + RemoteParameter paramBank = readRegs.params.get("bank"); + if (paramBank != null) { + Set banks = regs.stream() + .flatMap(r -> r.queryCanonicalAncestorsTargetInterface(TargetRegisterBank.class) + .findFirst() + .stream()) + .collect(Collectors.toSet()); + AsyncFence fence = new AsyncFence(); + banks.stream().forEach(b -> { + fence.include( + readRegs.method.invokeAsync(Map.of(paramBank.name(), b)).toCompletableFuture()); + }); + return fence.ready(); + } + RemoteParameter paramRegister = readRegs.params.get("register"); + if (paramRegister != null) { + AsyncFence fence = new AsyncFence(); + regs.stream().forEach(r -> { + fence.include(readRegs.method.invokeAsync(Map.of(paramRegister.name(), r)) + .toCompletableFuture()); + }); + return fence.ready(); + } + throw new AssertionError(); + } + + protected TraceObject findRegisterObject(TraceObjectThread thread, int frame, String name) { + TraceObject container = thread.getObject().queryRegisterContainer(frame); + if (container == null) { + Msg.error(this, "No register container for thread=" + thread + ",frame=" + frame); + return null; + } + PathMatcher matcher = container.getTargetSchema().searchFor(TargetRegister.class, true); + PathPredicates pred = matcher.applyKeys(Align.RIGHT, name) + .or(matcher.applyKeys(Align.RIGHT, name.toLowerCase())) + .or(matcher.applyKeys(Align.RIGHT, name.toUpperCase())); + TraceObjectValPath regValPath = + container.getCanonicalSuccessors(pred).findFirst().orElse(null); + if (regValPath == null) { + Msg.error(this, "Cannot find register object for " + name + " in " + container); + return null; + } + return regValPath.getDestination(container); + } + + @Override + public CompletableFuture writeRegisterAsync(TracePlatform platform, TraceThread thread, + int frame, RegisterValue value) { + MatchedMethod writeReg = + matches.getBest("writeReg", ActionName.WRITE_REG, WriteRegMatcher.ALL); + if (writeReg == null) { + return AsyncUtils.nil(); + } + if (!(thread instanceof TraceObjectThread tot)) { + Msg.error(this, "Non-object trace with TraceRmi!"); + return AsyncUtils.nil(); + } + Register register = value.getRegister(); + String regName = register.getName(); + byte[] data = + Utils.bigIntegerToBytes(value.getUnsignedValue(), register.getMinimumByteSize(), true); + + RemoteParameter paramFrame = writeReg.params.get("frame"); + if (paramFrame != null) { + TraceStack stack = trace.getStackManager().getLatestStack(thread, getSnap()); + TraceStackFrame frameObj = stack.getFrame(frame, false); + return writeReg.method.invokeAsync(Map.ofEntries( + Map.entry(paramFrame.name(), frameObj), + Map.entry(writeReg.params.get("name").name(), regName), + Map.entry(writeReg.params.get("data").name(), data))) + .toCompletableFuture() + .thenApply(__ -> null); + } + TraceObject regObj = findRegisterObject(tot, frame, regName); + if (regObj == null) { + return AsyncUtils.nil(); + } + return writeReg.method.invokeAsync(Map.ofEntries( + Map.entry(writeReg.params.get("frame").name(), regObj), + Map.entry(writeReg.params.get("data").name(), data))) + .toCompletableFuture() + .thenApply(__ -> null); + } + + protected boolean isMemorySpaceValid(AddressSpace space) { + return trace.getBaseAddressFactory().getAddressSpace(space.getSpaceID()) == space; + } + + protected boolean isRegisterValid(TracePlatform platform, TraceThread thread, int frame, + Address address, int length) { + if (!isMemorySpaceValid(address.getAddressSpace())) { + return false; + } + Register register = + platform.getLanguage().getRegister(address.getPhysicalAddress(), length); + if (register == null) { + return false; + } + if (!(thread instanceof TraceObjectThread tot)) { + return false; + } + TraceObject regObj = findRegisterObject(tot, frame, register.getName()); + if (regObj == null) { + return false; + } + return true; + } + + @Override + public boolean isVariableExists(TracePlatform platform, TraceThread thread, int frame, + Address address, int length) { + if (address.isMemoryAddress()) { + return isMemorySpaceValid(address.getAddressSpace()); + } + if (address.isRegisterAddress()) { + return isRegisterValid(platform, thread, frame, address, length); + } + return false; + } + + @Override + public CompletableFuture writeVariableAsync(TracePlatform platform, TraceThread thread, + int frame, Address address, byte[] data) { + if (address.isMemoryAddress()) { + return writeMemoryAsync(address, data); + } + if (address.isRegisterAddress()) { + return writeRegisterAsync(platform, thread, frame, address, data); + } + Msg.error(this, "Address is neither memory nor register: " + address); + return AsyncUtils.nil(); + } + + protected Address expectSingleAddr(AddressRange range, TraceBreakpointKind kind) { + Address address = range.getMinAddress(); + if (range.getLength() != 1) { + Msg.warn(this, "Expected single address for " + kind + " breakpoint. Got " + range + + ". Using " + address); + } + return address; + } + + protected void putOptionalBreakArgs(Map args, MatchedMethod brk, + String condition, String commands) { + if (condition != null && !condition.isBlank()) { + RemoteParameter paramCond = brk.params.get("condition"); + if (paramCond == null) { + Msg.error(this, "No condition parameter on " + brk.method); + } + else { + args.put(paramCond.name(), condition); + } + } + if (commands != null && !commands.isBlank()) { + RemoteParameter paramCmds = brk.params.get("commands"); + if (paramCmds == null) { + Msg.error(this, "No commands parameter on " + brk.method); + } + else { + args.put(paramCmds.name(), commands); + } + } + } + + protected CompletableFuture doPlaceExecBreakAsync(MatchedMethod breakExec, + Address address, String condition, String commands) { + Map args = new HashMap<>(); + args.put(breakExec.params.get("address").name(), address); + putOptionalBreakArgs(args, breakExec, condition, commands); + return breakExec.method.invokeAsync(args).toCompletableFuture().thenApply(__ -> null); + } + + protected CompletableFuture placeHwExecBreakAsync(Address address, String condition, + String commands) { + MatchedMethod breakHwExec = + matches.getBest(BREAK_HW_EXEC, ActionName.BREAK_HW_EXECUTE, BreakExecMatcher.ALL); + if (breakHwExec == null) { + return AsyncUtils.nil(); + } + return doPlaceExecBreakAsync(breakHwExec, address, condition, commands); + } + + protected CompletableFuture placeSwExecBreakAsync(Address address, String condition, + String commands) { + MatchedMethod breakSwExec = + matches.getBest(BREAK_SW_EXEC, ActionName.BREAK_SW_EXECUTE, BreakExecMatcher.ALL); + if (breakSwExec == null) { + return AsyncUtils.nil(); + } + return doPlaceExecBreakAsync(breakSwExec, address, condition, commands); + } + + protected CompletableFuture doPlaceAccBreakAsync(MatchedMethod breakAcc, + AddressRange range, String condition, String commands) { + Map args = new HashMap<>(); + args.put(breakAcc.params.get("range").name(), range); + putOptionalBreakArgs(args, breakAcc, condition, commands); + return breakAcc.method.invokeAsync(args).toCompletableFuture().thenApply(__ -> null); + } + + protected CompletableFuture placeReadBreakAsync(AddressRange range, String condition, + String commands) { + MatchedMethod breakRead = + matches.getBest(BREAK_READ, ActionName.BREAK_READ, BreakAccMatcher.ALL); + if (breakRead == null) { + return AsyncUtils.nil(); + } + return doPlaceAccBreakAsync(breakRead, range, condition, commands); + } + + protected CompletableFuture placeWriteBreakAsync(AddressRange range, String condition, + String commands) { + MatchedMethod breakWrite = + matches.getBest(BREAK_WRITE, ActionName.BREAK_WRITE, BreakAccMatcher.ALL); + if (breakWrite == null) { + return AsyncUtils.nil(); + } + return doPlaceAccBreakAsync(breakWrite, range, condition, commands); + } + + protected CompletableFuture placeAccessBreakAsync(AddressRange range, String condition, + String commands) { + MatchedMethod breakAccess = + matches.getBest(BREAK_ACCESS, ActionName.BREAK_ACCESS, BreakAccMatcher.ALL); + if (breakAccess == null) { + return AsyncUtils.nil(); + } + return doPlaceAccBreakAsync(breakAccess, range, condition, commands); + } + + @Override + public CompletableFuture placeBreakpointAsync(AddressRange range, + Set kinds, String condition, String commands) { + Set copyKinds = Set.copyOf(kinds); + if (copyKinds.equals(Set.of(TraceBreakpointKind.HW_EXECUTE))) { + return placeHwExecBreakAsync(expectSingleAddr(range, TraceBreakpointKind.HW_EXECUTE), + condition, commands); + } + if (copyKinds.equals(Set.of(TraceBreakpointKind.SW_EXECUTE))) { + return placeSwExecBreakAsync(expectSingleAddr(range, TraceBreakpointKind.SW_EXECUTE), + condition, commands); + } + if (copyKinds.equals(Set.of(TraceBreakpointKind.READ))) { + return placeReadBreakAsync(range, condition, commands); + } + if (copyKinds.equals(Set.of(TraceBreakpointKind.WRITE))) { + return placeWriteBreakAsync(range, condition, commands); + } + if (copyKinds.equals(Set.of(TraceBreakpointKind.READ, TraceBreakpointKind.WRITE))) { + return placeAccessBreakAsync(range, condition, commands); + } + Msg.error(this, "Invalid kinds in combination: " + kinds); + return AsyncUtils.nil(); + } + + protected Set computeSupportedBreakpointKinds() { + MatchedMethod breakHwExec = matches.getBest(BREAK_HW_EXEC, null, null); + MatchedMethod breakSwExec = matches.getBest(BREAK_SW_EXEC, null, null); + MatchedMethod breakRead = matches.getBest(BREAK_READ, null, null); + MatchedMethod breakWrite = matches.getBest(BREAK_WRITE, null, null); + MatchedMethod breakAccess = matches.getBest(BREAK_ACCESS, null, null); + + Set result = new HashSet<>(); + if (breakHwExec != null) { + result.add(TraceBreakpointKind.HW_EXECUTE); + } + if (breakSwExec != null) { + result.add(TraceBreakpointKind.SW_EXECUTE); + } + if (breakRead != null) { + result.add(TraceBreakpointKind.READ); + } + if (breakWrite != null) { + result.add(TraceBreakpointKind.WRITE); + } + if (breakAccess != null) { + result.add(TraceBreakpointKind.READ); + result.add(TraceBreakpointKind.WRITE); + } + return Set.copyOf(result); + } + + @Override + public Set getSupportedBreakpointKinds() { + return supportedBreakpointKinds; + } + + @Override + public boolean isBreakpointValid(TraceBreakpoint breakpoint) { + if (breakpoint.getName().endsWith("emu-" + breakpoint.getMinAddress())) { + return false; + } + if (!breakpoint.getLifespan().contains(getSnap())) { + return false; + } + return true; + } + + protected CompletableFuture deleteBreakpointSpecAsync(TraceObjectBreakpointSpec spec) { + MatchedMethod delBreak = + matches.getBest("delBreakSpec", ActionName.DELETE, DelBreakMatcher.SPEC); + if (delBreak == null) { + return AsyncUtils.nil(); + } + return delBreak.method + .invokeAsync(Map.of(delBreak.params.get("specification").name(), spec)) + .toCompletableFuture() + .thenApply(__ -> null); + } + + protected CompletableFuture deleteBreakpointLocAsync(TraceObjectBreakpointLocation loc) { + MatchedMethod delBreak = + matches.getBest("delBreakLoc", ActionName.DELETE, DelBreakMatcher.ALL); + if (delBreak == null) { + return AsyncUtils.nil(); + } + RemoteParameter paramLocation = delBreak.params.get("location"); + if (paramLocation != null) { + return delBreak.method.invokeAsync(Map.of(paramLocation.name(), loc)) + .toCompletableFuture() + .thenApply(__ -> null); + } + return deleteBreakpointSpecAsync(loc.getSpecification()); + } + + @Override + public CompletableFuture deleteBreakpointAsync(TraceBreakpoint breakpoint) { + if (breakpoint instanceof TraceObjectBreakpointLocation loc) { + return deleteBreakpointLocAsync(loc); + } + if (breakpoint instanceof TraceObjectBreakpointSpec spec) { + return deleteBreakpointSpecAsync(spec); + } + Msg.error(this, "Unrecognized TraceBreakpoint: " + breakpoint); + return AsyncUtils.nil(); + } + + protected CompletableFuture toggleBreakpointSpecAsync(TraceObjectBreakpointSpec spec, + boolean enabled) { + MatchedMethod delBreak = + matches.getBest("toggleBreakSpec", ActionName.TOGGLE, ToggleBreakMatcher.SPEC); + if (delBreak == null) { + return AsyncUtils.nil(); + } + return delBreak.method + .invokeAsync(Map.ofEntries( + Map.entry(delBreak.params.get("specification").name(), spec), + Map.entry(delBreak.params.get("enabled").name(), enabled))) + .toCompletableFuture() + .thenApply(__ -> null); + } + + protected CompletableFuture toggleBreakpointLocAsync(TraceObjectBreakpointLocation loc, + boolean enabled) { + MatchedMethod delBreak = + matches.getBest("toggleBreakLoc", ActionName.TOGGLE, ToggleBreakMatcher.ALL); + if (delBreak == null) { + return AsyncUtils.nil(); + } + RemoteParameter paramLocation = delBreak.params.get("location"); + if (paramLocation != null) { + return delBreak.method + .invokeAsync(Map.ofEntries( + Map.entry(paramLocation.name(), loc), + Map.entry(delBreak.params.get("enabled").name(), enabled))) + .toCompletableFuture() + .thenApply(__ -> null); + } + return deleteBreakpointSpecAsync(loc.getSpecification()); + } + + @Override + public CompletableFuture toggleBreakpointAsync(TraceBreakpoint breakpoint, + boolean enabled) { + if (breakpoint instanceof TraceObjectBreakpointLocation loc) { + return toggleBreakpointLocAsync(loc, enabled); + } + if (breakpoint instanceof TraceObjectBreakpointSpec spec) { + return toggleBreakpointSpecAsync(spec, enabled); + } + Msg.error(this, "Unrecognized TraceBreakpoint: " + breakpoint); + return AsyncUtils.nil(); + } + + @Override + public CompletableFuture disconnectAsync() { + return CompletableFuture.runAsync(() -> { + try { + connection.close(); + } + catch (IOException e) { + ExceptionUtils.rethrow(e); + } + }); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/event/ModelObjectFocusedPluginEvent.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/event/ModelObjectFocusedPluginEvent.java deleted file mode 100644 index 95a6de0e93..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/event/ModelObjectFocusedPluginEvent.java +++ /dev/null @@ -1,48 +0,0 @@ -/* ### - * 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.event; - -import ghidra.dbg.target.TargetObject; -import ghidra.framework.plugintool.PluginEvent; - -/** - * Plugin event class for notification of objects being focused in a connected debugger. - */ -public class ModelObjectFocusedPluginEvent extends PluginEvent { - static final String NAME = "Object Focused"; - - private final TargetObject focus; - - /** - * Construct a new plugin event. - * - * @param source name of the plugin that created this event - * @param focusRef the object (ref) associated with this event - */ - public ModelObjectFocusedPluginEvent(String source, TargetObject focus) { - super(source, NAME); - this.focus = focus; - } - - /** - * Return the new focused object ref. Should never be null. - * - * @return the focused object ref - */ - public TargetObject getFocus() { - return focus; - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/event/TraceRecorderAdvancedPluginEvent.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/event/TraceRecorderAdvancedPluginEvent.java deleted file mode 100644 index 2e58d83c3f..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/event/TraceRecorderAdvancedPluginEvent.java +++ /dev/null @@ -1,57 +0,0 @@ -/* ### - * 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.event; - -import ghidra.debug.api.model.TraceRecorder; -import ghidra.framework.plugintool.PluginEvent; - -public class TraceRecorderAdvancedPluginEvent extends PluginEvent { - static final String NAME = "Recorder Advanced"; - - private final TraceRecorder recorder; - private final long snap; - - /** - * Construct a new plugin event. - * - * @param source name of the plugin that created this event - * @param recorder the recorder that has advanced to its next snap - * @param snap the snap to which the recorder advanced - */ - public TraceRecorderAdvancedPluginEvent(String source, TraceRecorder recorder, long snap) { - super(source, NAME); - this.recorder = recorder; - this.snap = snap; - } - - /** - * Get the recorder that has advanced to its next snap - * - * @return the recorder - */ - public TraceRecorder getRecorder() { - return recorder; - } - - /** - * Get the snap to which the recorder advanced - * - * @return the snap - */ - public long getSnap() { - return snap; - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java index fea61370b3..4e092eacc4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java @@ -1339,30 +1339,6 @@ public interface DebuggerResources { } } - abstract class AbstractCaptureTypesAction extends DockingAction { - public static final String NAME = "Capture Data Types"; - public static final Icon ICON = ICON_DATA_TYPES; - public static final String HELP_ANCHOR = "capture_types"; - - public AbstractCaptureTypesAction(Plugin owner) { - super(NAME, owner.getName()); - setDescription("Capture data types from selected modules"); - setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR)); - } - } - - abstract class AbstractCaptureSymbolsAction extends DockingAction { - public static final String NAME = "Capture Symbols"; - public static final Icon ICON = ICON_CAPTURE_SYMBOLS; - public static final String HELP_ANCHOR = "capture_symbols"; - - public AbstractCaptureSymbolsAction(Plugin owner) { - super(NAME, owner.getName()); - setDescription("Capture symbols from selected modules"); - setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR)); - } - } - abstract class AbstractImportFromFileSystemAction extends DockingAction { public static final String NAME = "Import From File System"; public static final String HELP_ANCHOR = "import_from_fs"; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java index aca113f5e0..12c43b0a19 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java @@ -16,9 +16,7 @@ package ghidra.app.plugin.core.debug.gui.action; import java.lang.invoke.MethodHandles; -import java.util.Collection; import java.util.Objects; -import java.util.concurrent.CompletableFuture; import docking.ActionContext; import docking.ComponentProvider; @@ -30,15 +28,11 @@ import docking.widgets.EventTrigger; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractRefreshSelectedMemoryAction; import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec; -import ghidra.app.plugin.core.debug.utils.BackgroundUtils; import ghidra.app.util.viewer.listingpanel.AddressSetDisplayListener; -import ghidra.dbg.DebuggerObjectModel; -import ghidra.dbg.target.TargetMemory; -import ghidra.dbg.target.TargetObject; -import ghidra.dbg.util.PathMatcher; -import ghidra.debug.api.model.TraceRecorder; -import ghidra.debug.api.model.TraceRecorderListener; +import ghidra.debug.api.target.Target; import ghidra.debug.api.tracemgr.DebuggerCoordinates; +import ghidra.framework.cmd.BackgroundCommand; +import ghidra.framework.model.DomainObject; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoConfigStateField; @@ -51,7 +45,8 @@ import ghidra.trace.model.memory.TraceMemoryState; import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.time.TraceSnapshot; import ghidra.util.Msg; -import ghidra.util.Swing; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; public abstract class DebuggerReadsMemoryTrait { protected static final AutoConfigState.ClassHandler CONFIG_STATE_HANDLER = @@ -77,37 +72,25 @@ public abstract class DebuggerReadsMemoryTrait { } final AddressSetView sel = selection; Trace trace = current.getTrace(); - TraceRecorder recorder = current.getRecorder(); - BackgroundUtils.async(tool, trace, NAME, true, true, false, (_t, monitor) -> { - TargetObject target = recorder.getTarget(); - DebuggerObjectModel model = target.getModel(); - model.invalidateAllLocalCaches(); - PathMatcher memMatcher = target.getSchema().searchFor(TargetMemory.class, true); - Collection memories = memMatcher.getCachedSuccessors(target).values(); - CompletableFuture[] requests = memories.stream() - .map(TargetObject::invalidateCaches) - .toArray(CompletableFuture[]::new); - return CompletableFuture.allOf(requests).thenCompose(_r -> { - return recorder.readMemoryBlocks(sel, monitor); - }); - }); + Target target = current.getTarget(); + tool.executeBackgroundCommand(new BackgroundCommand(NAME, true, true, false) { + @Override + public boolean applyTo(DomainObject obj, TaskMonitor monitor) { + target.invalidateMemoryCaches(); + try { + target.readMemory(sel, monitor); + } + catch (CancelledException e) { + return false; + } + return true; + } + }, trace); } @Override public boolean isEnabledForContext(ActionContext context) { - if (!current.isAliveAndReadsPresent()) { - return false; - } - AddressSetView selection = getSelection(); - if (selection == null || selection.isEmpty()) { - selection = visible; - } - TraceRecorder recorder = current.getRecorder(); - // TODO: Either allow partial, or provide action to intersect with accessible - if (!recorder.getAccessibleMemory().contains(selection)) { - return false; - } - return true; + return current.isAliveAndReadsPresent(); } public void updateEnabled(ActionContext context) { @@ -142,15 +125,6 @@ public abstract class DebuggerReadsMemoryTrait { } } - protected class ForAccessRecorderListener implements TraceRecorderListener { - @Override - public void processMemoryAccessibilityChanged(TraceRecorder recorder) { - Swing.runIfSwingOrRunLater(() -> { - actionRefreshSelected.updateEnabled(null); - }); - } - } - protected class ForVisibilityListener implements AddressSetDisplayListener { @Override public void visibleAddressesChanged(AddressSetView visibleAddresses) { @@ -177,7 +151,6 @@ public abstract class DebuggerReadsMemoryTrait { protected final ForReadsTraceListener traceListener = new ForReadsTraceListener(); - protected final ForAccessRecorderListener recorderListener = new ForAccessRecorderListener(); protected final ForVisibilityListener displayListener = new ForVisibilityListener(); protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; @@ -196,7 +169,7 @@ public abstract class DebuggerReadsMemoryTrait { if (!Objects.equals(a.getTime(), b.getTime())) { return false; } - if (!Objects.equals(a.getRecorder(), b.getRecorder())) { + if (!Objects.equals(a.getTarget(), b.getTarget())) { return false; } return true; @@ -214,38 +187,19 @@ public abstract class DebuggerReadsMemoryTrait { } } - protected void addNewRecorderListener() { - if (current.getRecorder() != null) { - current.getRecorder().addListener(recorderListener); - } - } - - protected void removeOldRecorderListener() { - if (current.getRecorder() != null) { - current.getRecorder().removeListener(recorderListener); - } - } - public void goToCoordinates(DebuggerCoordinates coordinates) { if (sameCoordinates(current, coordinates)) { current = coordinates; return; } boolean doTraceListener = !Objects.equals(current.getTrace(), coordinates.getTrace()); - boolean doRecListener = !Objects.equals(current.getRecorder(), coordinates.getRecorder()); if (doTraceListener) { removeOldTraceListener(); } - if (doRecListener) { - removeOldRecorderListener(); - } current = coordinates; if (doTraceListener) { addNewTraceListener(); } - if (doRecListener) { - addNewRecorderListener(); - } doAutoRead(); // NB. provider should call contextChanged, updating actions diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java index ff91cb64ac..f007e9d3e9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java @@ -178,11 +178,24 @@ public class DebuggerTrackLocationTrait { return true; } + protected boolean hasSpec(LocationTrackingSpec spec) { + for (ActionState state : action.getAllActionStates()) { + if (spec.equals(state.getUserData())) { + return true; + } + } + return false; + } + public void setSpec(LocationTrackingSpec spec) { if (action == null) { // It might if the client doesn't need a new button, e.g., TraceDiff doSetSpec(spec); } + else if (!hasSpec(spec)) { + Msg.warn(this, "No action state for given tracking spec: " + spec); + doSetSpec(spec); + } else { action.setCurrentActionStateByUserData(spec); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/LoadEmulatorAutoReadMemorySpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/LoadEmulatorAutoReadMemorySpec.java index b87fc30f89..8bac873914 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/LoadEmulatorAutoReadMemorySpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/LoadEmulatorAutoReadMemorySpec.java @@ -60,13 +60,13 @@ public class LoadEmulatorAutoReadMemorySpec implements AutoReadMemorySpec { DebuggerStaticMappingService mappingService = tool.getService(DebuggerStaticMappingService.class); if (mappingService == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } Trace trace = coordinates.getTrace(); if (trace == null || coordinates.isAlive() || !ProgramEmulationUtils.isEmulatedProgram(trace)) { // Never interfere with a live target - return AsyncUtils.NIL; + return AsyncUtils.nil(); } TraceMemoryManager mm = trace.getMemoryManager(); AddressSet toRead = new AddressSet(RecorderUtils.INSTANCE.quantize(12, visible)); @@ -80,7 +80,7 @@ public class LoadEmulatorAutoReadMemorySpec implements AutoReadMemorySpec { } if (toRead.isEmpty()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } long snap = coordinates.getSnap(); @@ -94,7 +94,7 @@ public class LoadEmulatorAutoReadMemorySpec implements AutoReadMemorySpec { mm.putBytes(snap, hostAddr, buf); } }.visit(trace, snap, toRead); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } catch (MemoryAccessException e) { throw new AssertionError(e); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/NoneAutoReadMemorySpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/NoneAutoReadMemorySpec.java index 4989dbdb6c..de4780d444 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/NoneAutoReadMemorySpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/NoneAutoReadMemorySpec.java @@ -46,6 +46,6 @@ public class NoneAutoReadMemorySpec implements AutoReadMemorySpec { @Override public CompletableFuture readMemory(PluginTool tool, DebuggerCoordinates coordinates, AddressSetView visible) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleAutoReadMemorySpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleAutoReadMemorySpec.java index 64a73b53c6..492977f8db 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleAutoReadMemorySpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleAutoReadMemorySpec.java @@ -21,7 +21,7 @@ import javax.swing.Icon; import ghidra.app.plugin.core.debug.gui.DebuggerResources.AutoReadMemoryAction; import ghidra.async.AsyncUtils; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.AddressSet; @@ -52,22 +52,18 @@ public class VisibleAutoReadMemorySpec implements AutoReadMemorySpec { public CompletableFuture readMemory(PluginTool tool, DebuggerCoordinates coordinates, AddressSetView visible) { if (!coordinates.isAliveAndReadsPresent()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } - TraceRecorder recorder = coordinates.getRecorder(); - boolean ffv = coordinates.getView().getMemory().isForceFullView(); - AddressSetView visibleAccessible = - ffv ? visible : recorder.getAccessibleMemory().intersect(visible); + Target target = coordinates.getTarget(); TraceMemoryManager mm = coordinates.getTrace().getMemoryManager(); - AddressSetView alreadyKnown = - mm.getAddressesWithState(coordinates.getSnap(), visibleAccessible, - s -> s == TraceMemoryState.KNOWN); - AddressSet toRead = visibleAccessible.subtract(alreadyKnown); + AddressSetView alreadyKnown = mm.getAddressesWithState(coordinates.getSnap(), visible, + s -> s == TraceMemoryState.KNOWN); + AddressSet toRead = visible.subtract(alreadyKnown); if (toRead.isEmpty()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } - return recorder.readMemoryBlocks(toRead, TaskMonitor.DUMMY); + return target.readMemoryAsync(toRead, TaskMonitor.DUMMY); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleROOnceAutoReadMemorySpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleROOnceAutoReadMemorySpec.java index af5cc3b042..af3828a1e2 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleROOnceAutoReadMemorySpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleROOnceAutoReadMemorySpec.java @@ -22,7 +22,7 @@ import javax.swing.Icon; import ghidra.app.plugin.core.debug.gui.DebuggerResources.AutoReadMemoryAction; import ghidra.async.AsyncUtils; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.*; @@ -53,20 +53,16 @@ public class VisibleROOnceAutoReadMemorySpec implements AutoReadMemorySpec { public CompletableFuture readMemory(PluginTool tool, DebuggerCoordinates coordinates, AddressSetView visible) { if (!coordinates.isAliveAndReadsPresent()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } - TraceRecorder recorder = coordinates.getRecorder(); - boolean ffv = coordinates.getView().getMemory().isForceFullView(); - AddressSetView visibleAccessible = - ffv ? visible : recorder.getAccessibleMemory().intersect(visible); + Target target = coordinates.getTarget(); TraceMemoryManager mm = coordinates.getTrace().getMemoryManager(); - AddressSetView alreadyKnown = - mm.getAddressesWithState(coordinates.getSnap(), visibleAccessible, - s -> s == TraceMemoryState.KNOWN); - AddressSet toRead = visibleAccessible.subtract(alreadyKnown); + AddressSetView alreadyKnown = mm.getAddressesWithState(coordinates.getSnap(), visible, + s -> s == TraceMemoryState.KNOWN); + AddressSet toRead = visible.subtract(alreadyKnown); if (toRead.isEmpty()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } AddressSet everKnown = new AddressSet(); @@ -89,9 +85,9 @@ public class VisibleROOnceAutoReadMemorySpec implements AutoReadMemorySpec { toRead.delete(everKnown.intersect(readOnly)); if (toRead.isEmpty()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } - return recorder.readMemoryBlocks(toRead, TaskMonitor.DUMMY); + return target.readMemoryAsync(toRead, TaskMonitor.DUMMY); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java index 5629dc5fa3..edea70f2e4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java @@ -46,10 +46,10 @@ import ghidra.app.util.viewer.listingpanel.MarkerClickedListener; import ghidra.async.AsyncDebouncer; import ghidra.async.AsyncTimer; import ghidra.debug.api.breakpoint.LogicalBreakpoint; -import ghidra.debug.api.breakpoint.LogicalBreakpointsChangeListener; import ghidra.debug.api.breakpoint.LogicalBreakpoint.State; +import ghidra.debug.api.breakpoint.LogicalBreakpointsChangeListener; import ghidra.debug.api.control.ControlMode; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.framework.options.AutoOptions; import ghidra.framework.options.annotation.*; import ghidra.framework.plugintool.*; @@ -724,7 +724,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin // @AutoServiceConsumed via method DebuggerLogicalBreakpointService breakpointService; @AutoServiceConsumed - private DebuggerModelService modelService; + private DebuggerTargetService targetService; @AutoServiceConsumed private DebuggerStaticMappingService mappingService; @AutoServiceConsumed @@ -739,29 +739,29 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin private final AutoService.Wiring autoServiceWiring; @AutoOptionDefined( - name = DebuggerResources.OPTION_NAME_COLORS_ENABLED_BREAKPOINT_COLORING_BACKGROUND, // - description = "Whether or not to color background for memory at an enabled breakpoint", // + name = DebuggerResources.OPTION_NAME_COLORS_ENABLED_BREAKPOINT_COLORING_BACKGROUND, + description = "Whether or not to color background for memory at an enabled breakpoint", help = @HelpInfo(anchor = "colors")) private boolean breakpointEnabledColoringBackground = DebuggerResources.DEFAULT_COLOR_ENABLED_BREAKPOINT_COLORING_BACKGROUND; @AutoOptionDefined( - name = DebuggerResources.OPTION_NAME_COLORS_DISABLED_BREAKPOINT_COLORING_BACKGROUND, // - description = "Whether or not to color background for memory at a disabled breakpoint", // + name = DebuggerResources.OPTION_NAME_COLORS_DISABLED_BREAKPOINT_COLORING_BACKGROUND, + description = "Whether or not to color background for memory at a disabled breakpoint", help = @HelpInfo(anchor = "colors")) private boolean breakpointDisabledColoringBackground = DebuggerResources.DEFAULT_COLOR_DISABLED_BREAKPOINT_COLORING_BACKGROUND; @AutoOptionDefined( - name = DebuggerResources.OPTION_NAME_COLORS_INEFF_EN_BREAKPOINT_COLORING_BACKGROUND, // - description = "Whether or not to color background for memory at an enabled, but ineffective, breakpoint", // + name = DebuggerResources.OPTION_NAME_COLORS_INEFF_EN_BREAKPOINT_COLORING_BACKGROUND, + description = "Whether or not to color background for memory at an enabled, but ineffective, breakpoint", help = @HelpInfo(anchor = "colors")) private boolean breakpointIneffEnColoringBackground = DebuggerResources.DEFAULT_COLOR_INEFF_EN_BREAKPOINT_COLORING_BACKGROUND; @AutoOptionDefined( - name = DebuggerResources.OPTION_NAME_COLORS_INEFF_DIS_BREAKPOINT_COLORING_BACKGROUND, // - description = "Whether or not to color background for memory at an disabled, but ineffective, breakpoint", // + name = DebuggerResources.OPTION_NAME_COLORS_INEFF_DIS_BREAKPOINT_COLORING_BACKGROUND, + description = "Whether or not to color background for memory at an disabled, but ineffective, breakpoint", help = @HelpInfo(anchor = "colors")) private boolean breakpointIneffDisColoringBackground = DebuggerResources.DEFAULT_COLOR_INEFF_DIS_BREAKPOINT_COLORING_BACKGROUND; @@ -880,14 +880,14 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin if (mode.useEmulatedBreakpoints()) { return EnumSet.allOf(TraceBreakpointKind.class); } - if (modelService == null) { + if (targetService == null) { return Set.of(); } - TraceRecorder recorder = modelService.getRecorder(trace); - if (recorder == null) { + Target target = targetService.getTarget(trace); + if (target == null) { return Set.of(); } - return recorder.getSupportedBreakpointKinds(); + return target.getSupportedBreakpointKinds(); } protected Set getSupportedKindsFromContext(ActionContext context) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java index 736c51d505..476470bae1 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java @@ -38,10 +38,10 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import ghidra.app.services.*; import ghidra.app.services.DebuggerControlService.ControlModeChangeListener; import ghidra.debug.api.breakpoint.LogicalBreakpoint; -import ghidra.debug.api.breakpoint.LogicalBreakpointsChangeListener; import ghidra.debug.api.breakpoint.LogicalBreakpoint.State; +import ghidra.debug.api.breakpoint.LogicalBreakpointsChangeListener; import ghidra.debug.api.control.ControlMode; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.model.DomainObject; import ghidra.framework.plugintool.*; @@ -868,8 +868,8 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter ? ControlMode.DEFAULT : controlService.getCurrentMode(trace); DebuggerCoordinates currentFor = traceManager.getCurrentFor(trace); - TraceRecorder recorder = currentFor.getRecorder(); - if (!mode.useEmulatedBreakpoints() && recorder == null) { + Target target = currentFor.getTarget(); + if (!mode.useEmulatedBreakpoints() && target == null) { return; } Lifespan span = Lifespan.at(currentFor.getSnap()); @@ -882,7 +882,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter } else { for (TraceBreakpoint l : breaks) { - if (recorder.getTargetBreakpoint(l) != null) { + if (target.isBreakpointValid(l)) { visible.add(l); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java index a23408fbb8..0e288e3418 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java @@ -17,9 +17,7 @@ package ghidra.app.plugin.core.debug.gui.control; import java.awt.event.KeyEvent; import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.function.BiPredicate; -import java.util.function.Function; +import java.util.function.BiFunction; import java.util.stream.Collectors; import javax.swing.Icon; @@ -28,38 +26,40 @@ import javax.swing.KeyStroke; import docking.ActionContext; import docking.DockingContextListener; import docking.action.*; -import docking.action.builder.AbstractActionBuilder; import docking.action.builder.ActionBuilder; import docking.menu.ActionState; import docking.menu.MultiStateDockingAction; import docking.widgets.EventTrigger; import ghidra.app.plugin.PluginCategoryNames; -import ghidra.app.plugin.core.debug.*; -import ghidra.app.plugin.core.debug.event.*; +import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin; +import ghidra.app.plugin.core.debug.DebuggerPluginPackage; +import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent; +import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.plugin.core.debug.service.model.TraceRecorderTarget; import ghidra.app.services.*; import ghidra.app.services.DebuggerControlService.ControlModeChangeListener; import ghidra.app.services.DebuggerEmulationService.CachedEmulator; import ghidra.app.services.DebuggerEmulationService.EmulatorStateListener; import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; import ghidra.async.AsyncUtils; -import ghidra.dbg.DebuggerObjectModel; -import ghidra.dbg.target.*; -import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; -import ghidra.dbg.target.TargetSteppable.TargetStepKind; import ghidra.debug.api.control.ControlMode; import ghidra.debug.api.emulation.DebuggerPcodeMachine; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.ActionName; +import ghidra.debug.api.target.Target; +import ghidra.debug.api.target.Target.ActionEntry; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.trace.model.*; import ghidra.trace.model.Trace.TraceObjectChangeType; -import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObjectValue; import ghidra.trace.model.time.schedule.Scheduler; import ghidra.util.*; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.Task; +import ghidra.util.task.TaskMonitor; @PluginInfo( shortDescription = "Debugger global controls", @@ -70,7 +70,6 @@ import ghidra.util.*; eventsConsumed = { TraceActivatedPluginEvent.class, TraceClosedPluginEvent.class, - ModelObjectFocusedPluginEvent.class, }, servicesRequired = { DebuggerControlService.class, @@ -83,131 +82,6 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin return String.format("%02d", subGroup); } - abstract class TargetAction extends DockingAction { - TraceObject object; - - public TargetAction(String name) { - super(name, DebuggerControlPlugin.this.getName()); - } - - abstract Class getTargetInterface(); - - TraceObject findTraceObject() { - TraceObject focus = current.getObject(); - if (focus == null) { - return null; - } - return focus.querySuitableTargetInterface(getTargetInterface()); - } - - T getTargetObject(TraceObject object) { - TraceRecorder recorder = current.getRecorder(); - if (recorder == null || !recorder.isRecording()) { - return null; - } - Class iface = getTargetInterface(); - if (object != null) { - return iface.cast(recorder.getTargetObject(object)); - } - TargetObject focus = recorder.getFocus(); - if (focus == null) { - return null; - } - return focus.getCachedSuitable(iface); - } - - abstract boolean isEnabledForObject(T t); - - abstract void actionPerformed(T t); - - @Override - public boolean isEnabledForContext(ActionContext context) { - object = findTraceObject(); - T t = getTargetObject(object); - if (t == null || !t.getModel().isAlive()) { - return false; - } - return isEnabledForObject(t); - } - - @Override - public void actionPerformed(ActionContext context) { - T t = getTargetObject(object); - if (t == null) { - return; - } - actionPerformed(t); - } - } - - static class TargetActionBuilder - extends AbstractActionBuilder, ActionContext, TargetActionBuilder> { - final DebuggerControlPlugin owner; - - @SuppressWarnings("unchecked") - Class iface = (Class) TargetObject.class; - Function> actionCallback; - BiPredicate enabledPredicate; - - public TargetActionBuilder(String name, DebuggerControlPlugin owner) { - super(name, owner.getName()); - this.owner = owner; - } - - @SuppressWarnings("unchecked") - public TargetActionBuilder withInterface(Class iface) { - this.iface = (Class) iface; - return (TargetActionBuilder) self(); - } - - public TargetActionBuilder enabledWhenTarget( - BiPredicate enabledPredicate) { - this.enabledPredicate = enabledPredicate; - return self(); - } - - public TargetActionBuilder onTargetAction( - Function> actionCallback) { - this.actionCallback = actionCallback; - return self(); - } - - @Override - protected TargetActionBuilder self() { - return this; - } - - @Override - public TargetAction build() { - Objects.requireNonNull(iface, "Must specify withInterface"); - Objects.requireNonNull(enabledPredicate, "Must specify enabledWhenTarget"); - Objects.requireNonNull(actionCallback, "Must specify onTargetAction"); - - TargetAction action = owner.new TargetAction<>(name) { - @Override - Class getTargetInterface() { - return iface; - } - - @Override - boolean isEnabledForObject(T t) { - return enabledPredicate.test(object, t); - } - - @Override - void actionPerformed(T t) { - actionCallback.apply(t).exceptionally(ex -> { - owner.tool.setStatusInfo(name + " failed: " + ex.getMessage(), true); - Msg.error(this, name + " failed", ex); - return null; - }); - } - }; - decorateAction(action); - return action; - } - } - interface ControlAction { String GROUP = DebuggerResources.GROUP_CONTROL; } @@ -253,19 +127,15 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin } interface TargetResumeAction extends ResumeAction { - String NAME = "Resume Target"; - String DESCRIPTION = "Resume, i.e., go or continue execution of the target"; String HELP_ANCHOR = "target_resume"; - static TargetActionBuilder builder(DebuggerControlPlugin owner) { + static ActionBuilder builder(String name, DebuggerControlPlugin owner) { String ownerName = owner.getName(); - return new TargetActionBuilder<>(NAME, owner) - .description(DESCRIPTION) + return new ActionBuilder(name, ownerName) .toolBarIcon(ICON) .toolBarGroup(GROUP, intSubGroup(SUB_GROUP)) .keyBinding(KEY_BINDING) - .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)) - .withInterface(TargetResumable.class); + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } @@ -292,19 +162,15 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin } interface TargetInterruptAction extends InterruptAction { - String NAME = "Interrupt Target"; - String DESCRIPTION = "Interrupt, i.e., suspend, the target"; String HELP_ANCHOR = "target_interrupt"; - static TargetActionBuilder builder(DebuggerControlPlugin owner) { + static ActionBuilder builder(String name, DebuggerControlPlugin owner) { String ownerName = owner.getName(); - return new TargetActionBuilder<>(NAME, owner) - .description(DESCRIPTION) + return new ActionBuilder(name, ownerName) .toolBarIcon(ICON) .toolBarGroup(GROUP, intSubGroup(SUB_GROUP)) .keyBinding(KEY_BINDING) - .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)) - .withInterface(TargetInterruptible.class); + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } @@ -328,20 +194,16 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin Icon ICON = DebuggerResources.ICON_KILL; String HELP_ANCHOR = "target_kill"; int SUB_GROUP = 2; - String NAME = "Kill Target"; - String DESCRIPTION = "Kill, i.e., forcibly terminate the target"; KeyStroke KEY_BINDING = KeyStroke.getKeyStroke(KeyEvent.VK_K, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); - static TargetActionBuilder builder(DebuggerControlPlugin owner) { + static ActionBuilder builder(String name, DebuggerControlPlugin owner) { String ownerName = owner.getName(); - return new TargetActionBuilder<>(NAME, owner) - .description(DESCRIPTION) + return new ActionBuilder(name, ownerName) .toolBarIcon(ICON) .toolBarGroup(GROUP, intSubGroup(SUB_GROUP)) .keyBinding(KEY_BINDING) - .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)) - .withInterface(TargetKillable.class); + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } @@ -391,19 +253,15 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin } interface TargetStepIntoAction extends StepIntoAction { - String NAME = "Step Target Into"; - String DESCRIPTION = "Step the target a single instruction, descending into calls"; String HELP_ANCHOR = "target_step_into"; - static TargetActionBuilder builder(DebuggerControlPlugin owner) { + static ActionBuilder builder(String name, DebuggerControlPlugin owner) { String ownerName = owner.getName(); - return new TargetActionBuilder<>(NAME, owner) - .description(DESCRIPTION) + return new ActionBuilder(name, ownerName) .toolBarIcon(ICON) .toolBarGroup(GROUP, intSubGroup(SUB_GROUP)) .keyBinding(KEY_BINDING) - .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)) - .withInterface(TargetSteppable.class); + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } @@ -425,22 +283,18 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin } interface TargetStepOverAction extends ControlAction { - String NAME = "Step Target Over"; - String DESCRIPTION = "Step the target a single instruction, without following calls"; Icon ICON = DebuggerResources.ICON_STEP_OVER; String HELP_ANCHOR = "target_step_over"; int SUB_GROUP = 6; KeyStroke KEY_BINDING = KeyStroke.getKeyStroke(KeyEvent.VK_F10, 0); - static TargetActionBuilder builder(DebuggerControlPlugin owner) { + static ActionBuilder builder(String name, DebuggerControlPlugin owner) { String ownerName = owner.getName(); - return new TargetActionBuilder<>(NAME, owner) - .description(DESCRIPTION) + return new ActionBuilder(name, ownerName) .toolBarIcon(ICON) .toolBarGroup(GROUP, intSubGroup(SUB_GROUP)) .keyBinding(KEY_BINDING) - .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)) - .withInterface(TargetSteppable.class); + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } @@ -464,43 +318,35 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin } } - interface TargetStepFinishAction extends ControlAction { - String NAME = "Step Target Finish"; - String DESCRIPTION = "Step the target until it completes the current frame"; + interface TargetStepOutAction extends ControlAction { Icon ICON = DebuggerResources.ICON_STEP_FINISH; - String HELP_ANCHOR = "target_step_finish"; + String HELP_ANCHOR = "target_step_out"; int SUB_GROUP = 8; KeyStroke KEY_BINDING = KeyStroke.getKeyStroke(KeyEvent.VK_F12, 0); - static TargetActionBuilder builder(DebuggerControlPlugin owner) { + static ActionBuilder builder(String name, DebuggerControlPlugin owner) { String ownerName = owner.getName(); - return new TargetActionBuilder<>(NAME, owner) - .description(DESCRIPTION) + return new ActionBuilder(name, ownerName) .toolBarIcon(ICON) .toolBarGroup(GROUP, intSubGroup(SUB_GROUP)) .keyBinding(KEY_BINDING) - .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)) - .withInterface(TargetSteppable.class); + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } - interface TargetStepLastAction extends ControlAction { - String NAME = "Step Target Repeat Last"; - String DESCRIPTION = "Step the target in a target-defined way"; + interface TargetStepExtAction extends ControlAction { Icon ICON = DebuggerResources.ICON_STEP_LAST; - String HELP_ANCHOR = "target_step_last"; + String HELP_ANCHOR = "target_step_ext"; int SUB_GROUP = 9; KeyStroke KEY_BINDING = KeyStroke.getKeyStroke(KeyEvent.VK_F8, KeyEvent.CTRL_DOWN_MASK); - static TargetActionBuilder builder(DebuggerControlPlugin owner) { + static ActionBuilder builder(String name, DebuggerControlPlugin owner) { String ownerName = owner.getName(); - return new TargetActionBuilder<>(NAME, owner) - .description(DESCRIPTION) + return new ActionBuilder(name, ownerName) .toolBarIcon(ICON) - .toolBarGroup(GROUP, intSubGroup(SUB_GROUP)) + .toolBarGroup(GROUP, intSubGroup(SUB_GROUP) + name) .keyBinding(KEY_BINDING) - .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)) - .withInterface(TargetSteppable.class); + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } @@ -580,15 +426,8 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin protected MultiStateDockingAction actionControlMode; - DockingAction actionTargetResume; - DockingAction actionTargetInterrupt; - DockingAction actionTargetKill; DockingAction actionTargetDisconnect; - DockingAction actionTargetStepInto; - DockingAction actionTargetStepOver; - DockingAction actionTargetStepFinish; - DockingAction actionTargetStepLast; - Set actionsTarget; + final Set actionsTarget = new HashSet<>(); DockingAction actionEmulateResume; DockingAction actionEmulateInterrupt; @@ -658,41 +497,11 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin actionControlMode = new ControlModeAction(); tool.addAction(actionControlMode); - actionTargetResume = TargetResumeAction.builder(this) - .enabledWhenTarget(this::isActionTargetResumeEnabled) - .onTargetAction(this::activatedTargetResume) - .build(); - actionTargetInterrupt = TargetInterruptAction.builder(this) - .enabledWhenTarget(this::isActionTargetInterruptEnabled) - .onTargetAction(this::activatedTargetInterrupt) - .build(); - actionTargetKill = TargetKillAction.builder(this) - .enabledWhenTarget(this::isActionTargetKillEnabled) - .onTargetAction(this::activatedTargetKill) - .build(); actionTargetDisconnect = DisconnectAction.builder(this) .enabledWhen(this::isActionTargetDisconnectEnabled) .onAction(this::activatedTargetDisconnect) .build(); - actionTargetStepInto = TargetStepIntoAction.builder(this) - .enabledWhenTarget(this::isActionTargetStepEnabled) - .onTargetAction(this::activatedTargetStepInto) - .build(); - actionTargetStepOver = TargetStepOverAction.builder(this) - .enabledWhenTarget(this::isActionTargetStepEnabled) - .onTargetAction(this::activatedTargetStepOver) - .build(); - actionTargetStepFinish = TargetStepFinishAction.builder(this) - .enabledWhenTarget(this::isActionTargetStepEnabled) - .onTargetAction(this::activatedTargetStepFinish) - .build(); - actionTargetStepLast = TargetStepLastAction.builder(this) - .enabledWhenTarget(this::isActionTargetStepEnabled) - .onTargetAction(this::activatedTargetStepLast) - .build(); - actionsTarget = Set.of(actionTargetResume, actionTargetInterrupt, actionTargetKill, - actionTargetDisconnect, actionTargetStepInto, actionTargetStepOver, - actionTargetStepFinish, actionTargetStepLast); + updateTargetActions(); actionEmulateResume = EmulateResumeAction.builder(this) .enabledWhen(this::isActionEmulateResumeEnabled) @@ -732,6 +541,72 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin updateActions(); } + protected interface TargetActionBuilderFactory + extends BiFunction { + } + + protected DockingAction buildTargetAction(TargetActionBuilderFactory factory, + ActionEntry entry) { + return factory.apply(entry.display(), this) + .description(entry.details()) + .enabledWhen(ctx -> entry.isEnabled()) + .onAction(ctx -> runTask(entry)) + .build(); + } + + protected void runTask(ActionEntry entry) { + tool.execute(new TargetActionTask(entry)); + } + + protected void addTargetActionsForName(Target target, ActionName name, + TargetActionBuilderFactory factory) { + for (ActionEntry entry : target.collectActions(name, context).values()) { + if (entry.requiresPrompt()) { + continue; + } + actionsTarget.add(buildTargetAction(factory, entry)); + } + } + + /** + * This is for testing purposes only. Fetch an action from "targetActions" whose name matches + * that given. + * + *

    + * Since the tests are still assuming {@link TraceRecorderTarget}s, actions can be reliably + * retrieved by name. + * + * @param name the action name + * @return the action, or null + */ + /* testing */ DockingAction getTargetAction(String name) { + for (DockingAction action : actionsTarget) { + if (name.equals(action.getName())) { + return action; + } + } + return null; + } + + protected void updateTargetActions() { + hideActions(actionsTarget); + actionsTarget.clear(); + actionsTarget.add(actionTargetDisconnect); + + Target target = current.getTarget(); + if (target == null || !target.isValid()) { + return; + } + + addTargetActionsForName(target, ActionName.RESUME, TargetResumeAction::builder); + addTargetActionsForName(target, ActionName.INTERRUPT, TargetInterruptAction::builder); + addTargetActionsForName(target, ActionName.KILL, TargetKillAction::builder); + addTargetActionsForName(target, ActionName.STEP_INTO, TargetStepIntoAction::builder); + addTargetActionsForName(target, ActionName.STEP_OVER, TargetStepOverAction::builder); + addTargetActionsForName(target, ActionName.STEP_OUT, TargetStepOutAction::builder); + addTargetActionsForName(target, ActionName.STEP_EXT, TargetStepExtAction::builder); + } + protected void activateControlMode(ActionState state, EventTrigger trigger) { if (current.getTrace() == null) { return; @@ -752,85 +627,29 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin }); } - private TargetExecutionState getStateOf(TraceObject traceObject, TargetObject targetObject) { - if (traceObject != null) { - return traceObject.getExecutionState(current.getSnap()); - } - TargetExecutionStateful stateful = - targetObject.getCachedSuitable(TargetExecutionStateful.class); - return stateful == null ? null : stateful.getExecutionState(); - } - - private boolean isActionTargetResumeEnabled(TraceObject object, TargetResumable resumable) { - TargetExecutionState state = getStateOf(object, resumable); - // If the object isn't stateful, always allow this action. Such models should be corrected - return state == null || state.isStopped(); - } - - private CompletableFuture activatedTargetResume(TargetResumable resumable) { - return resumable.resume(); - } - - private boolean isActionTargetInterruptEnabled(TraceObject object, - TargetInterruptible interruptible) { - TargetExecutionState state = getStateOf(object, interruptible); - // If the object isn't stateful, always allow this action. - return state == null || state.isRunning(); - } - - private CompletableFuture activatedTargetInterrupt(TargetInterruptible interruptible) { - return interruptible.interrupt(); - } - - private boolean isActionTargetKillEnabled(TraceObject object, TargetKillable killable) { - TargetExecutionState state = getStateOf(object, killable); - // If the object isn't stateful, always allow this action. Such models should be corrected - return state == null || state.isAlive(); - } - - private CompletableFuture activatedTargetKill(TargetKillable killable) { - return killable.kill(); - } - private boolean isActionTargetDisconnectEnabled(ActionContext context) { return current.isAlive(); } private void activatedTargetDisconnect(ActionContext context) { - TraceRecorder recorder = current.getRecorder(); - if (recorder == null) { + Target target = current.getTarget(); + if (target == null) { return; } - DebuggerObjectModel model = recorder.getTarget().getModel(); - model.close().exceptionally(ex -> { - tool.setStatusInfo("Disconnect failed: " + ex.getMessage(), true); - Msg.error(this, "Disconnect failed", ex); - return null; + tool.execute(new Task("Disconnect", false, false, false) { + @Override + public void run(TaskMonitor monitor) throws CancelledException { + try { + target.disconnect(); + } + catch (Exception e) { + tool.setStatusInfo("Disconnect failed: " + e, true); + Msg.error(this, "Disconnect failed: " + e, e); + } + } }); } - private boolean isActionTargetStepEnabled(TraceObject object, TargetSteppable steppable) { - TargetExecutionState state = getStateOf(object, steppable); - // If the object isn't stateful, always allow this action. Such models should be corrected - return state == null || state.isStopped(); - } - - private CompletableFuture activatedTargetStepInto(TargetSteppable steppable) { - return steppable.step(TargetStepKind.INTO); - } - - private CompletableFuture activatedTargetStepOver(TargetSteppable steppable) { - return steppable.step(TargetStepKind.OVER); - } - - private CompletableFuture activatedTargetStepFinish(TargetSteppable steppable) { - return steppable.step(TargetStepKind.FINISH); - } - - private CompletableFuture activatedTargetStepLast(TargetSteppable steppable) { - return steppable.step(TargetStepKind.EXTENDED); - } - private boolean haveEmuAndTrace() { if (emulationService == null) { return false; @@ -980,6 +799,7 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin } } current = coords; + updateTargetActions(); updateActions(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPlugin.java index 09db27c0bc..e4f5ef7eeb 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPlugin.java @@ -15,34 +15,27 @@ */ package ghidra.app.plugin.core.debug.gui.control; -import java.util.*; +import java.util.ArrayList; +import java.util.List; import docking.ActionContext; import docking.Tool; import docking.action.*; import docking.actions.PopupActionProvider; -import ghidra.app.context.ProgramLocationActionContext; +import ghidra.app.context.ProgramActionContext; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.services.*; -import ghidra.dbg.target.TargetMethod; -import ghidra.dbg.target.TargetMethod.ParameterDescription; -import ghidra.dbg.target.TargetMethod.TargetParameterMap; -import ghidra.dbg.target.TargetObject; -import ghidra.dbg.util.PathPredicates; import ghidra.debug.api.control.ControlMode; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; +import ghidra.debug.api.target.Target.ActionEntry; import ghidra.debug.api.tracemgr.DebuggerCoordinates; 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.util.MarkerLocation; -import ghidra.program.util.ProgramLocation; +import ghidra.program.model.listing.Program; import ghidra.trace.model.Trace; import ghidra.trace.model.program.TraceProgramView; -import ghidra.trace.model.target.TraceObject; -import ghidra.util.Msg; @PluginInfo( shortDescription = "Debugger model method actions", @@ -58,40 +51,18 @@ import ghidra.util.Msg; public class DebuggerMethodActionsPlugin extends Plugin implements PopupActionProvider { public static final String GROUP_METHODS = "Debugger Methods"; - private static String getDisplay(TargetMethod method) { - String display = method.getDisplay(); - if (display != null) { - return display; - } - return method.getName(); - } + class InvokeActionEntryAction extends DockingAction { + private final ActionEntry entry; - class InvokeMethodAction extends DockingAction { - private final TargetMethod method; - - public InvokeMethodAction(TargetMethod method) { - super(getDisplay(method), DebuggerMethodActionsPlugin.this.getName()); - this.method = method; + public InvokeActionEntryAction(ActionEntry entry) { + super(entry.display(), DebuggerMethodActionsPlugin.this.getName()); + this.entry = entry; setPopupMenuData(new MenuData(new String[] { getName() }, GROUP_METHODS)); } @Override public void actionPerformed(ActionContext context) { - Map arguments = collectArguments(method.getParameters(), context); - if (arguments == null) { - // Context changed out from under me? - return; - } - method.invoke(arguments).thenAccept(result -> { - if (consoleService != null && method.getReturnType() != Void.class) { - consoleService.log(null, getDisplay(method) + " returned " + result); - } - }).exceptionally(ex -> { - tool.setStatusInfo( - "Invocation of " + getDisplay(method) + " failed: " + ex.getMessage(), true); - Msg.error(this, "Invocation of " + method.getPath() + " failed", ex); - return null; - }); + tool.execute(new TargetActionTask(entry)); } } @@ -129,91 +100,30 @@ public class DebuggerMethodActionsPlugin extends Plugin implements PopupActionPr if (!isControlTarget()) { return List.of(); } - TargetObject curObj = getCurrentTargetObject(); - if (curObj == null) { + Target target = getTarget(context); + if (target == null) { return List.of(); } + List result = new ArrayList<>(); - PathPredicates matcher = curObj.getModel() - .getRootSchema() - .matcherForSuitable(TargetMethod.class, curObj.getPath()); - for (TargetObject obj : matcher.getCachedSuccessors(curObj.getModel().getModelRoot()) - .values()) { - if (!(obj instanceof TargetMethod method)) { - continue; - } - Map arguments = collectArguments(method.getParameters(), context); - if (arguments == null) { - continue; - } - result.add(new InvokeMethodAction(method)); + for (ActionEntry entry : target.collectActions(null, context).values()) { + result.add(new InvokeActionEntryAction(entry)); } return result; } - private TargetObject getCurrentTargetObject() { + private Target getTarget(ActionContext context) { if (traceManager == null) { return null; } + if (context instanceof ProgramActionContext ctx) { + Program program = ctx.getProgram(); + if (program instanceof TraceProgramView view) { + DebuggerCoordinates coords = traceManager.getCurrentFor(view.getTrace()); + return coords == null ? null : coords.getTarget(); + } + } DebuggerCoordinates current = traceManager.getCurrent(); - TraceRecorder recorder = current.getRecorder(); - if (recorder == null) { - return null; - } - TraceObject object = current.getObject(); - if (object != null) { - return recorder.getTargetObject(object); - } - return recorder.getFocus(); - } - - private Address dynamicAddress(ProgramLocation loc) { - if (loc.getProgram() instanceof TraceProgramView) { - return loc.getAddress(); - } - if (traceManager == null) { - return null; - } - ProgramLocation dloc = - mappingService.getDynamicLocationFromStatic(traceManager.getCurrentView(), loc); - if (dloc == null) { - return null; - } - return dloc.getByteAddress(); - } - - private Map collectArguments(TargetParameterMap params, ActionContext context) { - // The only required non-defaulted argument allowed must be an Address - // There must be an Address parameter - ParameterDescription addrParam = null; - for (ParameterDescription p : params.values()) { - if (p.type == Address.class) { - if (addrParam != null) { - return null; - } - addrParam = p; - } - else if (p.required && p.defaultValue == null) { - return null; - } - } - if (addrParam == null) { - return null; - } - if (context instanceof ProgramLocationActionContext ctx) { - Address address = dynamicAddress(ctx.getLocation()); - if (address == null) { - return null; - } - return Map.of(addrParam.name, address); - } - if (context.getContextObject() instanceof MarkerLocation ml) { - Address address = dynamicAddress(new ProgramLocation(ml.getProgram(), ml.getAddr())); - if (address == null) { - return null; - } - return Map.of(addrParam.name, address); - } - return null; + return current == null ? null : current.getTarget(); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetActionTask.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetActionTask.java new file mode 100644 index 0000000000..5839abb4c6 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetActionTask.java @@ -0,0 +1,35 @@ +/* ### + * 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.control; + +import ghidra.debug.api.target.Target.ActionEntry; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.Task; +import ghidra.util.task.TaskMonitor; + +class TargetActionTask extends Task { + private ActionEntry entry; + + public TargetActionTask(ActionEntry entry) { + super(entry.display(), false, false, false); + this.entry = entry; + } + + @Override + public void run(TaskMonitor monitor) throws CancelledException { + entry.run(false); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPlugin.java index 1cc6a9350d..633b47a77d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPlugin.java @@ -69,7 +69,7 @@ public class DebuggerCopyActionsPlugin extends AbstractDebuggerPlugin { @AutoServiceConsumed private DebuggerStaticMappingService mappingService; @AutoServiceConsumed - private DebuggerModelService modelService; + private DebuggerTargetService targetService; public DebuggerCopyActionsPlugin(PluginTool tool) { super(tool); @@ -129,7 +129,7 @@ public class DebuggerCopyActionsPlugin extends AbstractDebuggerPlugin { copyDialog.setSource(context.getProgram(), context.getSelection()); copyDialog.setProgramManager(programManager); copyDialog.setStaticMappingService(mappingService); - copyDialog.setModelService(modelService); + copyDialog.setTargetService(targetService); copyDialog.setDestination(programManager.getCurrentProgram()); copyDialog.reset(); copyDialog.setStatusText(""); @@ -143,7 +143,7 @@ public class DebuggerCopyActionsPlugin extends AbstractDebuggerPlugin { copyDialog.setSource(context.getProgram(), context.getSelection()); copyDialog.setProgramManager(programManager); copyDialog.setStaticMappingService(mappingService); - copyDialog.setModelService(modelService); + copyDialog.setTargetService(targetService); copyDialog.setDestination(copyDialog.NEW_PROGRAM); copyDialog.reset(); copyDialog.setStatusText(""); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java index ced2af66f9..b08a143fd4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java @@ -35,7 +35,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.copying.DebuggerCopyPlan.Copier; import ghidra.app.services.*; import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.PluginTool; import ghidra.program.database.ProgramDB; @@ -282,7 +282,7 @@ public class DebuggerCopyIntoProgramDialog extends ReusableDialogComponentProvid } } - protected DebuggerModelService modelService; + protected DebuggerTargetService targetService; protected ProgramManager programManager; protected DebuggerStaticMappingService staticMappingService; @@ -290,7 +290,6 @@ public class DebuggerCopyIntoProgramDialog extends ReusableDialogComponentProvid protected AddressSetView set; protected CompletableFuture lastTask; - protected CompletableFuture captureTask; protected final DefaultComboBoxModel comboDestinationModel = new DefaultComboBoxModel<>(); @@ -438,16 +437,6 @@ public class DebuggerCopyIntoProgramDialog extends ReusableDialogComponentProvid addButton(resetButton); } - @Override - protected void cancelCallback() { - synchronized (this) { - if (captureTask != null) { - captureTask.cancel(false); - } - } - super.cancelCallback(); - } - @Override protected void okCallback() { super.okCallback(); @@ -491,28 +480,28 @@ public class DebuggerCopyIntoProgramDialog extends ReusableDialogComponentProvid tableModel.delete(entry); } - protected TraceRecorder getRecorderIfReadsPresent() { - if (modelService == null) { + protected Target getTargetIfReadsPresent() { + if (targetService == null) { return null; } - TraceRecorder recorder = modelService.getRecorder(source.getTrace()); - if (recorder == null) { + Target target = targetService.getTarget(source.getTrace()); + if (target == null) { return null; } - if (!DebuggerCoordinates.NOWHERE.view(source).recorder(recorder).isAliveAndReadsPresent()) { + if (!DebuggerCoordinates.NOWHERE.view(source).target(target).isAliveAndReadsPresent()) { return null; } - return recorder; + return target; } protected void checkCbCaptureEnabled() { - boolean en = getRecorderIfReadsPresent() != null; + boolean en = getTargetIfReadsPresent() != null; cbCapture.setEnabled(en); cbCapture.setSelected(en); } - public void setModelService(DebuggerModelService modelService) { - this.modelService = modelService; + public void setTargetService(DebuggerTargetService targetService) { + this.targetService = targetService; checkCbCaptureEnabled(); } @@ -562,7 +551,7 @@ public class DebuggerCopyIntoProgramDialog extends ReusableDialogComponentProvid } public void setCapture(boolean capture) { - if (capture && getRecorderIfReadsPresent() == null) { + if (capture && getTargetIfReadsPresent() == null) { throw new IllegalStateException( "Cannot enable capture unless live and reading the present"); } @@ -570,7 +559,7 @@ public class DebuggerCopyIntoProgramDialog extends ReusableDialogComponentProvid } public boolean isCapture() { - return (cbCapture.isSelected() && getRecorderIfReadsPresent() != null); + return (cbCapture.isSelected() && getTargetIfReadsPresent() != null); } public void setRelocate(boolean relocate) { @@ -788,55 +777,40 @@ public class DebuggerCopyIntoProgramDialog extends ReusableDialogComponentProvid return block; } - protected void executeEntry(RangeEntry entry, Program dest, TraceRecorder recorder, - TaskMonitor monitor) throws Exception { + protected void executeEntry(RangeEntry entry, Program dest, Target target, TaskMonitor monitor) + throws Exception { MemoryBlock block = executeEntryBlock(entry, dest, monitor); Address dstMin = entry.getDstRange().getMinAddress(); if (block.isOverlay()) { dstMin = block.getStart().getAddressSpace().getAddress(dstMin.getOffset()); } - if (recorder != null) { - executeCapture(entry.getSrcRange(), recorder, monitor); + if (target != null) { + executeCapture(entry.getSrcRange(), target, monitor); } plan.execute(source, entry.getSrcRange(), dest, dstMin, monitor); } - protected TraceRecorder getRecorderIfEnabledAndReadsPresent() { + protected Target getTargetIfEnabledAndReadsPresent() { if (!cbCapture.isSelected()) { return null; } - return getRecorderIfReadsPresent(); + return getTargetIfReadsPresent(); } - protected void executeCapture(AddressRange range, TraceRecorder recorder, TaskMonitor monitor) + protected void executeCapture(AddressRange range, Target target, TaskMonitor monitor) throws Exception { - synchronized (this) { - monitor.checkCancelled(); - CompletableFuture recCapture = - recorder.readMemoryBlocks(new AddressSet(range), monitor); - this.captureTask = recCapture.thenCompose(__ -> { - return recorder.getTarget().getModel().flushEvents(); - }).thenCompose(__ -> { - return recorder.flushTransactions(); - }); - } - try { - captureTask.get(); // Not a fan, but whatever. - } - finally { - captureTask = null; - } + target.readMemory(new AddressSet(range), monitor); } protected void executePlan(TaskMonitor monitor) throws Exception { Program dest = getDestination().getOrCreateProgram(source, this); boolean doRelease = !Arrays.asList(programManager.getAllOpenPrograms()).contains(dest); - TraceRecorder recorder = getRecorderIfEnabledAndReadsPresent(); + Target target = getTargetIfEnabledAndReadsPresent(); try (Transaction tx = dest.openTransaction("Copy From Trace")) { monitor.initialize(tableModel.getRowCount()); for (RangeEntry entry : tableModel.getModelData()) { monitor.setMessage("Copying into " + entry.getDstRange()); - executeEntry(entry, dest, recorder, monitor); + executeEntry(entry, dest, target, monitor); monitor.incrementProgress(1); } programManager.openProgram(dest); 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 39e63dfb94..0ea8961f39 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 @@ -99,7 +99,7 @@ public class DebuggerListingProvider extends CodeViewerProvider { if (!Objects.equals(a.getView(), b.getView())) { return false; // Subsumes trace } - if (!Objects.equals(a.getRecorder(), b.getRecorder())) { + if (!Objects.equals(a.getTarget(), b.getTarget())) { return false; // For capture memory action } if (!Objects.equals(a.getTime(), b.getTime())) { 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 8c81d18925..d1ecc34dfb 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 @@ -71,7 +71,7 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi if (!Objects.equals(a.getView(), b.getView())) { return false; // Subsumes trace } - if (!Objects.equals(a.getRecorder(), b.getRecorder())) { + if (!Objects.equals(a.getTarget(), b.getTarget())) { return false; // For capture memory action } if (!Objects.equals(a.getTime(), b.getTime())) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewTraceListener.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewTraceListener.java index 68be49f1fe..9e5dfab453 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewTraceListener.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewTraceListener.java @@ -19,7 +19,6 @@ import java.util.*; import ghidra.async.AsyncDebouncer; import ghidra.async.AsyncTimer; -import ghidra.debug.api.model.TraceRecorder; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.program.model.address.*; import ghidra.trace.model.*; @@ -38,7 +37,6 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener { protected MemviewProvider provider; DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; Trace currentTrace; - TraceRecorder currentRecorder; private boolean trackTrace = false; private boolean trackThreads = true; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java index bcd52e020c..46f98cee07 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java @@ -36,12 +36,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import ghidra.app.plugin.core.debug.gui.model.DebuggerObjectActionContext; import ghidra.app.plugin.core.debug.service.modules.MapModulesBackgroundCommand; import ghidra.app.plugin.core.debug.service.modules.MapSectionsBackgroundCommand; -import ghidra.app.plugin.core.debug.utils.BackgroundUtils; import ghidra.app.services.*; -import ghidra.async.AsyncUtils; -import ghidra.async.TypeSpec; -import ghidra.dbg.target.TargetModule; -import ghidra.debug.api.model.TraceRecorder; import ghidra.debug.api.modules.*; import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry; import ghidra.debug.api.modules.SectionMapProposal.SectionMapEntry; @@ -61,7 +56,6 @@ import ghidra.trace.model.*; import ghidra.trace.model.modules.*; import ghidra.util.HelpLocation; import ghidra.util.Msg; -import ghidra.util.datastruct.CollectionChangeListener; public class DebuggerModulesProvider extends ComponentProviderAdapter { @@ -224,23 +218,6 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { return null; } - protected class RecordersChangedListener implements CollectionChangeListener { - @Override - public void elementAdded(TraceRecorder element) { - contextChanged(); - } - - @Override - public void elementRemoved(TraceRecorder element) { - contextChanged(); - } - - @Override - public void elementModified(TraceRecorder element) { - contextChanged(); - } - } - protected class SelectAddressesAction extends AbstractSelectAddressesAction { public static final String GROUP = DebuggerResources.GROUP_GENERAL; @@ -275,81 +252,6 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { } } - protected class CaptureTypesAction extends AbstractCaptureTypesAction { - public static final String GROUP = DebuggerResources.GROUP_GENERAL; - - public CaptureTypesAction() { - super(plugin); - setToolBarData(new ToolBarData(ICON, GROUP)); - setPopupMenuData(new MenuData(new String[] { NAME }, GROUP)); - // TODO: Until we support this in an agent, hide it - //addLocalAction(this); - setEnabled(false); - } - - @Override - public void actionPerformed(ActionContext context) { - Set modules = getSelectedModules(context); - if (modules == null) { - return; - } - TraceRecorder recorder = modelService.getRecorder(current.getTrace()); - BackgroundUtils.async(tool, current.getTrace(), "Capture Types", true, true, false, - (__, monitor) -> AsyncUtils.each(TypeSpec.VOID, modules.iterator(), (m, loop) -> { - if (recorder.getTargetModule(m) == null) { - loop.repeatWhile(!monitor.isCancelled()); - } - else { - recorder.captureDataTypes(m, monitor) - .thenApply(v -> !monitor.isCancelled()) - .handle(loop::repeatWhile); - } - })); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - return isCaptureApplicable(context); - } - } - - protected class CaptureSymbolsAction extends AbstractCaptureSymbolsAction { - public static final String GROUP = DebuggerResources.GROUP_MAINTENANCE; - - public CaptureSymbolsAction() { - super(plugin); - setToolBarData(new ToolBarData(ICON, GROUP)); - setPopupMenuData(new MenuData(new String[] { NAME }, GROUP)); - addLocalAction(this); - setEnabled(false); - } - - @Override - public void actionPerformed(ActionContext context) { - Set modules = getSelectedModules(context); - if (modules == null) { - return; - } - TraceRecorder recorder = modelService.getRecorder(current.getTrace()); - BackgroundUtils.async(tool, current.getTrace(), NAME, true, true, false, - (__, monitor) -> AsyncUtils.each(TypeSpec.VOID, modules.iterator(), (m, loop) -> { - if (recorder.getTargetModule(m) == null) { - loop.repeatWhile(!monitor.isCancelled()); - } - else { - recorder.captureSymbols(m, monitor) - .thenApply(v -> !monitor.isCancelled()) - .handle(loop::repeatWhile); - } - })); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - return isCaptureApplicable(context); - } - } - protected class ImportFromFileSystemAction extends AbstractImportFromFileSystemAction { public static final String GROUP = DebuggerResources.GROUP_GENERAL; @@ -387,8 +289,6 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { final DebuggerModulesPlugin plugin; - // @AutoServiceConsumed via method - private DebuggerModelService modelService; @AutoServiceConsumed private DebuggerStaticMappingService staticMappingService; @AutoServiceConsumed @@ -406,9 +306,6 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { @SuppressWarnings("unused") private final AutoService.Wiring autoServiceWiring; - private final RecordersChangedListener recordersChangedListener = - new RecordersChangedListener(); - private final JSplitPane mainPanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT); DebuggerModulesPanel modulesPanel; @@ -439,8 +336,6 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { DockingAction actionMapMissingModule; SelectAddressesAction actionSelectAddresses; - CaptureTypesAction actionCaptureTypes; - CaptureSymbolsAction actionCaptureSymbols; ImportFromFileSystemAction actionImportFromFileSystem; // TODO: Save the state of this toggle? Not really compelled. ToggleDockingAction actionFilterSectionsByModules; @@ -480,18 +375,6 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { importerService.importFile(root, file); } - @AutoServiceConsumed - private void setModelService(DebuggerModelService modelService) { - if (this.modelService != null) { - this.modelService.removeTraceRecordersChangedListener(recordersChangedListener); - } - this.modelService = modelService; - if (this.modelService != null) { - this.modelService.addTraceRecordersChangedListener(recordersChangedListener); - } - contextChanged(); - } - @AutoServiceConsumed private void setConsoleService(DebuggerConsoleService consoleService) { if (consoleService != null) { @@ -619,8 +502,6 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { .build(); actionSelectAddresses = new SelectAddressesAction(); - actionCaptureTypes = new CaptureTypesAction(); - actionCaptureSymbols = new CaptureSymbolsAction(); actionImportFromFileSystem = new ImportFromFileSystemAction(); actionFilterSectionsByModules = FilterAction.builder(plugin) .description("Filter sections to those in selected modules") @@ -813,35 +694,6 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { } } - private boolean isCaptureApplicable(ActionContext context) { - if (modelService == null) { - return false; - } - if (current.getTrace() == null) { - return false; - } - TraceRecorder recorder = modelService.getRecorder(current.getTrace()); - if (recorder == null) { - return false; - } - if (context instanceof DebuggerModuleActionContext ctx) { - if (!ctx.getSelectedModules().isEmpty()) { - return true; - } - } - if (context instanceof DebuggerObjectActionContext ctx) { - if (!ctx.getObjectValues().isEmpty()) { - return ctx.getObjectValues() - .get(0) - .getChild() - .getTargetSchema() - .getInterfaces() - .contains(TargetModule.class); - } - } - return false; - } - protected void promptModuleProposal(Collection proposal) { if (proposal.isEmpty()) { Msg.showInfo(this, getComponent(), "Map Modules", diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java index af64681694..b34d2d2003 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java @@ -1392,7 +1392,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter } attachDialog.fetchAndDisplayAttachable(); tool.showDialog(attachDialog); - return AsyncUtils.NIL; + return AsyncUtils.nil(); }, "Couldn't attach"); } @@ -1548,7 +1548,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter if (valid != null) { startRecording(valid, true); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); }, "Couldn't record"); } @@ -1591,7 +1591,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter performAction(context, false, TargetBreakpointSpecContainer.class, container -> { breakpointDialog.setContainer(container); tool.showDialog(breakpointDialog); - return AsyncUtils.NIL; + return AsyncUtils.nil(); }, "Couldn't set breakpoint"); } @@ -1621,12 +1621,12 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter Map> configParameters = configurable.getConfigurableOptions(); if (configParameters.isEmpty()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } Map args = configDialog.promptArguments(configParameters); if (args == null) { // User cancelled - return AsyncUtils.NIL; + return AsyncUtils.nil(); } AsyncFence fence = new AsyncFence(); for (Entry entry : args.entrySet()) { @@ -1642,14 +1642,14 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter if (t != null) { navigateToSelectedObject(t, null); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); }, "Couldn't navigate"); } public void initiateConsole(ActionContext context) { performAction(context, false, TargetInterpreter.class, interpreter -> { getPlugin().showConsole(interpreter); - return AsyncUtils.NIL; + return AsyncUtils.nil(); }, "Couldn't show interpreter"); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/DummyTargetObject.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/DummyTargetObject.java index a5cdfddd2f..2dd4e7c31f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/DummyTargetObject.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/DummyTargetObject.java @@ -159,7 +159,7 @@ public class DummyTargetObject implements TargetObject { @Override public CompletableFuture resync(RefreshBehavior attributes, RefreshBehavior elements) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java index a886b31567..0f523e1204 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java @@ -23,7 +23,6 @@ import java.util.List; import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; import java.util.function.*; -import java.util.stream.Collectors; import javax.swing.*; import javax.swing.table.TableColumn; @@ -50,9 +49,7 @@ import ghidra.async.AsyncLazyValue; import ghidra.async.AsyncUtils; import ghidra.base.widgets.table.DataTypeTableCellEditor; import ghidra.dbg.error.DebuggerModelAccessException; -import ghidra.dbg.target.TargetRegisterBank; -import ghidra.dbg.target.TargetThread; -import ghidra.debug.api.model.*; +import ghidra.debug.api.target.Target; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.docking.settings.Settings; import ghidra.framework.model.DomainObject; @@ -76,7 +73,8 @@ import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceAddressSpace; import ghidra.trace.util.TraceRegisterUtils; -import ghidra.util.*; +import ghidra.util.HelpLocation; +import ghidra.util.Msg; import ghidra.util.classfinder.ClassSearcher; import ghidra.util.data.DataTypeParser.AllowedDataTypes; import ghidra.util.exception.CancelledException; @@ -236,7 +234,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter if (!Objects.equals(a.getPlatform(), b.getPlatform())) { return false; // subsumes trace } - if (!Objects.equals(a.getRecorder(), b.getRecorder())) { + if (!Objects.equals(a.getTarget(), b.getTarget())) { return false; // For live read/writes } if (!Objects.equals(a.getThread(), b.getThread())) { @@ -389,18 +387,6 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter } } - class RegAccessListener implements TraceRecorderListener { - @Override - public void registerBankMapped(TraceRecorder recorder) { - Swing.runIfSwingOrRunLater(() -> loadValues()); - } - - @Override - public void registerAccessibilityChanged(TraceRecorder recorder) { - Swing.runIfSwingOrRunLater(() -> loadValues()); - } - } - class RegisterValueCellRenderer extends HexBigIntegerTableCellRenderer { @Override public final Component getTableCellRendererComponent(GTableCellRenderingData data) { @@ -466,7 +452,6 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter private AsyncLazyValue readTheseCoords = new AsyncLazyValue<>(this::readRegistersIfLiveAndAccessible); /* "read" past tense */ private Trace currentTrace; // Copy for transition - private TraceRecorder currentRecorder; // Copy for transition @AutoServiceConsumed private DebuggerTraceManagerService traceManager; @@ -483,7 +468,6 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter private final AutoOptions.Wiring autoOptionsWiring; private final TraceChangeListener traceChangeListener = new TraceChangeListener(); - private final RegAccessListener regAccessListener = new RegAccessListener(); private JPanel mainPanel = new JPanel(new BorderLayout()); @@ -787,29 +771,6 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter addNewTraceListener(); } - private void removeOldRecorderListener() { - if (currentRecorder == null) { - return; - } - currentRecorder.removeListener(regAccessListener); - } - - private void addNewRecorderListener() { - if (currentRecorder == null) { - return; - } - currentRecorder.addListener(regAccessListener); - } - - private void doSetRecorder(TraceRecorder recorder) { - if (currentRecorder == recorder) { - return; - } - removeOldRecorderListener(); - this.currentRecorder = recorder; - addNewRecorderListener(); - } - public void coordinatesActivated(DebuggerCoordinates coordinates) { if (sameCoordinates(current, coordinates)) { current = coordinates; @@ -819,16 +780,14 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter previous = current; current = coordinates; - readTheseCoords = new AsyncLazyValue<>(this::readRegistersIfLiveAndAccessible); + readTheseCoords.forget(); doSetTrace(current.getTrace()); - doSetRecorder(current.getRecorder()); updateSubTitle(); prepareRegisterSpace(); recomputeViewKnown(); loadRegistersAndValues(); contextChanged(); - //checkEditsEnabled(); } protected void traceClosed(Trace trace) { @@ -1289,7 +1248,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter if (current.getThread() == null) { regsTableModel.clear(); regMap.clear(); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } Set selected = getSelectionFor(current.getPlatform()); displaySelectedRegisters(selected); @@ -1299,70 +1258,21 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter protected CompletableFuture loadValues() { TraceThread curThread = current.getThread(); if (curThread == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } regsTableModel.fireTableDataChanged(); - //return AsyncUtils.NIL; - // In case we need to read a non-zero frame return readTheseCoords.request(); } - private Set baseRegisters(Set regs) { - return regs.stream().filter(Register::isBaseRegister).collect(Collectors.toSet()); - } - - protected CompletableFuture readRegistersLegacy(TraceRecorder recorder, - TraceThread traceThread, Set toRead) { - DebuggerRegisterMapper regMapper = recorder.getRegisterMapper(traceThread); - if (regMapper == null) { - Msg.error(this, "Target is live, but we haven't got a register mapper, yet"); - return AsyncUtils.NIL; - } - toRead.retainAll(regMapper.getRegistersOnTarget()); - Set banks = - recorder.getTargetRegisterBanks(traceThread, current.getFrame()); - if (banks == null || banks.isEmpty()) { - Msg.error(this, "Current frame's bank does not exist"); - return AsyncUtils.NIL; - } - // TODO: Should probably always be the host platform. I suspect it's ignored anyway. - return recorder.captureThreadRegisters(current.getPlatform(), traceThread, - current.getFrame(), toRead); - } - - protected CompletableFuture readRegistersObjectMode(TraceRecorder recorder, - TraceThread traceThread, Set toRead) { - return recorder.captureThreadRegisters(current.getPlatform(), traceThread, - current.getFrame(), toRead); - } - protected CompletableFuture readRegistersIfLiveAndAccessible() { - TraceRecorder recorder = current.getRecorder(); - if (recorder == null) { - return AsyncUtils.NIL; - } - if (recorder.getSnap() != current.getSnap()) { - return AsyncUtils.NIL; - } -// if (current.getFrame() == 0) { -// // Should have been pushed by model. non-zero frames are poll-only -// return AsyncUtils.NIL; -// } - TraceThread traceThread = current.getThread(); - TargetThread targetThread = recorder.getTargetThread(traceThread); - if (targetThread == null) { - return AsyncUtils.NIL; + Target target = current.getTarget(); + if (!current.isAliveAndReadsPresent()) { + return AsyncUtils.nil(); } - Set toRead = new HashSet<>(baseRegisters(getSelectionFor(current.getPlatform()))); - - CompletableFuture future; - if (current.getTrace().getObjectManager().getRootSchema() == null) { - future = readRegistersLegacy(recorder, traceThread, toRead); - } - else { - future = readRegistersObjectMode(recorder, traceThread, toRead); - } + Set registers = getSelectionFor(current.getPlatform()); + CompletableFuture future = target.readRegistersAsync(current.getPlatform(), + current.getThread(), current.getFrame(), registers); return future.exceptionally(ex -> { ex = AsyncUtils.unwrapThrowable(ex); String msg = "Could not read target registers for selected thread: " + ex.getMessage(); @@ -1372,50 +1282,6 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter }).thenApply(__ -> null); } - protected String formatAddressInfo(Address address) { - return address.toString(); // TODO; - // TODO: Examine static mapped programs, too - /*Memory mem = program.getMemoryManager(); - MemoryBlock addrBlock = mem.getBlock(address); - if (addrBlock == null) { - return ""; - } - Function function = program.getFunctionManager().getFunctionContaining(address); - if (function != null) { - Address entry = function.getEntryPoint(); - long diff = address.subtract(entry); - if (diff < 0) { - return function.getName() + "-" + (-diff); - } - if (diff > 0) { - return function.getName() + "+" + diff; - } - return function.getName(); - } - Data defData = program.getListing().getDefinedDataContaining(address); - if (defData != null) { - // Use existing mechanism - return SymbolUtilities.getDynamicName(program, address); - } - // It is either undefined or an instruction outside a function - SymbolTable table = program.getSymbolTable(); - Symbol primary = table.getPrimarySymbol(address); - if (primary != null) { - return primary.getName(); - } - Symbol before = table.getSymbolIterator(address, false).next(); - if (before != null) { - MemoryBlock symBlock = mem.getBlock(before.getAddress()); - if (addrBlock == symBlock) { - long diff = address.subtract(before.getAddress()); - return before.getName() + "+" + diff; - } - } - // TODO: Making an assumption about block name here. Generally true, but user can fuddle. - String moduleName = addrBlock.getName().split(":")[0]; - return address.toString(moduleName + ":");*/ - } - public void writeDataState(SaveState saveState) { if (isClone) { current.writeDataState(tool, saveState, KEY_DEBUGGER_COORDINATES); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerLegacyThreadsPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerLegacyThreadsPanel.java index dbce409919..819712443f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerLegacyThreadsPanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerLegacyThreadsPanel.java @@ -120,7 +120,7 @@ public class DebuggerLegacyThreadsPanel extends JPanel { public ThreadTableModel(DebuggerThreadsProvider provider) { super(provider.getTool(), "Threads", ThreadTableColumns.class, - TraceThread::getObjectKey, t -> new ThreadRow(provider.modelService, t), + TraceThread::getObjectKey, t -> new ThreadRow(provider, t), ThreadRow::getThread); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java index e06f0d44bb..151e41cde3 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java @@ -49,7 +49,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { if (!Objects.equals(a.getTrace(), b.getTrace())) { return false; } - if (!Objects.equals(a.getRecorder(), b.getRecorder())) { + if (!Objects.equals(a.getTarget(), b.getTarget())) { return false; // For live read/writes } if (!Objects.equals(a.getThread(), b.getThread())) { @@ -99,8 +99,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; Trace currentTrace; // Copy for transition - // @AutoServiceConsumed by method - DebuggerModelService modelService; + @AutoServiceConsumed + DebuggerTargetService targetService; // @AutoServiceConsumed by method private DebuggerTraceManagerService traceManager; @SuppressWarnings("unused") @@ -119,6 +119,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { DebuggerLegacyThreadsPanel legacyPanel; DockingAction actionSaveTrace; + // TODO: This should probably be moved to ModelProvider ToggleDockingAction actionSyncTarget; ActionContext myActionContext; @@ -149,7 +150,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { @AutoServiceConsumed public void setTraceManager(DebuggerTraceManagerService traceManager) { if (this.traceManager != null) { - this.traceManager.removeSynchronizeActiveChangeListener(synchronizeTargetChangeListener); + this.traceManager + .removeSynchronizeActiveChangeListener(synchronizeTargetChangeListener); } this.traceManager = traceManager; if (traceManager != null) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerTraceTabPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerTraceTabPanel.java index 0478108b12..5e2debd326 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerTraceTabPanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerTraceTabPanel.java @@ -29,35 +29,33 @@ import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; -import ghidra.app.services.*; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.app.services.DebuggerTargetService; +import ghidra.app.services.DebuggerTraceManagerService; +import ghidra.debug.api.target.Target; +import ghidra.debug.api.target.TargetPublicationListener; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginEventListener; import ghidra.trace.model.Trace; import ghidra.util.Swing; -import ghidra.util.datastruct.CollectionChangeListener; import utilities.util.SuppressableCallback; import utilities.util.SuppressableCallback.Suppression; public class DebuggerTraceTabPanel extends HorizontalTabPanel implements PluginEventListener { - private class RecordersChangeListener implements CollectionChangeListener { + private class TargetsChangeListener implements TargetPublicationListener { @Override - public void elementAdded(TraceRecorder element) { + public void targetPublished(Target target) { Swing.runIfSwingOrRunLater(() -> repaint()); + } @Override - public void elementModified(TraceRecorder element) { + public void targetWithdrawn(Target target) { Swing.runIfSwingOrRunLater(() -> repaint()); - } - @Override - public void elementRemoved(TraceRecorder element) { - Swing.runIfSwingOrRunLater(() -> repaint()); } } @@ -65,14 +63,13 @@ public class DebuggerTraceTabPanel extends HorizontalTabPanel private final DebuggerThreadsProvider provider; // @AutoServiceConsumed by method - DebuggerModelService modelService; + DebuggerTargetService targetService; @AutoServiceConsumed private DebuggerTraceManagerService traceManager; @SuppressWarnings("unused") private final AutoService.Wiring autoServiceWiring; - private final CollectionChangeListener recordersListener = - new RecordersChangeListener(); + private final TargetsChangeListener targetsListener = new TargetsChangeListener(); DockingAction actionCloseTrace; DockingAction actionCloseOtherTraces; @@ -99,11 +96,11 @@ public class DebuggerTraceTabPanel extends HorizontalTabPanel } protected Icon getIcon(Trace value) { - if (modelService == null) { + if (targetService == null) { return super.getIcon(value); } - TraceRecorder recorder = modelService.getRecorder(value); - if (recorder == null || !recorder.isRecording()) { + Target target = targetService.getTarget(value); + if (target == null || !target.isValid()) { return super.getIcon(value); } return DebuggerResources.ICON_RECORD; @@ -140,7 +137,7 @@ public class DebuggerTraceTabPanel extends HorizontalTabPanel .buildAndInstallLocal(provider); actionCloseDeadTraces = CloseDeadTracesAction.builderPopup(plugin) .withContext(DebuggerTraceFileActionContext.class) - .popupWhen(c -> !traceManager.getOpenTraces().isEmpty() && modelService != null) + .popupWhen(c -> !traceManager.getOpenTraces().isEmpty() && targetService != null) .onAction(c -> traceManager.closeDeadTraces()) .buildAndInstallLocal(provider); } @@ -179,13 +176,13 @@ public class DebuggerTraceTabPanel extends HorizontalTabPanel } @AutoServiceConsumed - public void setModelService(DebuggerModelService modelService) { - if (this.modelService != null) { - this.modelService.removeTraceRecordersChangedListener(recordersListener); + public void setTargetService(DebuggerTargetService targetService) { + if (this.targetService != null) { + this.targetService.removeTargetPublicationListener(targetsListener); } - this.modelService = modelService; - if (this.modelService != null) { - this.modelService.addTraceRecordersChangedListener(recordersListener); + this.targetService = targetService; + if (this.targetService != null) { + this.targetService.addTargetPublicationListener(targetsListener); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/ThreadRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/ThreadRow.java index 0c16d6c92b..c98cc7f61c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/ThreadRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/ThreadRow.java @@ -16,20 +16,19 @@ package ghidra.app.plugin.core.debug.gui.thread; import db.Transaction; -import ghidra.app.services.DebuggerModelService; import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.trace.model.Lifespan; import ghidra.trace.model.Trace; import ghidra.trace.model.thread.TraceThread; import ghidra.util.Msg; public class ThreadRow { - private final DebuggerModelService service; + private final DebuggerThreadsProvider provider; private final TraceThread thread; - public ThreadRow(DebuggerModelService service, TraceThread thread) { - this.service = service; + public ThreadRow(DebuggerThreadsProvider provider, TraceThread thread) { + this.provider = provider; this.thread = thread; } @@ -76,21 +75,22 @@ public class ThreadRow { } public ThreadState getState() { + // TODO: Once transition to TraceRmi is complete, this is all in TraceObjectManager if (!thread.isAlive()) { return ThreadState.TERMINATED; } - if (service == null) { + if (provider.targetService == null) { return ThreadState.ALIVE; } - TraceRecorder recorder = service.getRecorder(thread.getTrace()); - if (recorder == null) { + Target target = provider.targetService.getTarget(thread.getTrace()); + if (target == null) { return ThreadState.ALIVE; } - TargetExecutionState targetState = recorder.getTargetThreadState(thread); - if (targetState == null) { + TargetExecutionState state = target.getThreadExecutionState(thread); + if (state == null) { return ThreadState.UNKNOWN; } - switch (targetState) { + switch (state) { case ALIVE: return ThreadState.ALIVE; case INACTIVE: diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java index 7fc4af896f..446fbd7716 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java @@ -230,7 +230,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter if (!Objects.equals(a.getPlatform(), b.getPlatform())) { return false; } - if (!Objects.equals(a.getRecorder(), b.getRecorder())) { + if (!Objects.equals(a.getTrace(), b.getTrace())) { return false; // May need to read target } if (!Objects.equals(a.getTime(), b.getTime())) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/arm/ArmDisassemblyInject.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/arm/ArmDisassemblyInject.java index 8f6ff74710..d84e84e393 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/arm/ArmDisassemblyInject.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/arm/ArmDisassemblyInject.java @@ -20,6 +20,7 @@ import java.math.BigInteger; import ghidra.app.plugin.core.debug.disassemble.TraceDisassembleCommand; import ghidra.app.plugin.core.debug.workflow.DisassemblyInject; import ghidra.app.plugin.core.debug.workflow.DisassemblyInjectInfo; +import ghidra.app.plugin.core.debug.workflow.DisassemblyInjectInfo.CompilerInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.lang.*; @@ -30,27 +31,27 @@ import ghidra.trace.model.thread.TraceThread; import ghidra.util.Msg; @DisassemblyInjectInfo( - langIDs = { - "ARM:LE:32:v8", - "ARM:LE:32:v8T", - "ARM:LEBE:32:v8LEInstruction", - "ARM:BE:32:v8", - "ARM:BE:32:v8T", - "ARM:LE:32:v7", - "ARM:LEBE:32:v7LEInstruction", - "ARM:BE:32:v7", - "ARM:LE:32:Cortex", - "ARM:BE:32:Cortex", - "ARM:LE:32:v6", - "ARM:BE:32:v6", - "ARM:LE:32:v5t", - "ARM:BE:32:v5t", - "ARM:LE:32:v5", - "ARM:BE:32:v5", - "ARM:LE:32:v4t", - "ARM:BE:32:v4t", - "ARM:LE:32:v4", - "ARM:BE:32:v4", + compilers = { + @CompilerInfo(langID = "ARM:LE:32:v8"), + @CompilerInfo(langID = "ARM:LE:32:v8T"), + @CompilerInfo(langID = "ARM:LEBE:32:v8LEInstruction"), + @CompilerInfo(langID = "ARM:BE:32:v8"), + @CompilerInfo(langID = "ARM:BE:32:v8T"), + @CompilerInfo(langID = "ARM:LE:32:v7"), + @CompilerInfo(langID = "ARM:LEBE:32:v7LEInstruction"), + @CompilerInfo(langID = "ARM:BE:32:v7"), + @CompilerInfo(langID = "ARM:LE:32:Cortex"), + @CompilerInfo(langID = "ARM:BE:32:Cortex"), + @CompilerInfo(langID = "ARM:LE:32:v6"), + @CompilerInfo(langID = "ARM:BE:32:v6"), + @CompilerInfo(langID = "ARM:LE:32:v5t"), + @CompilerInfo(langID = "ARM:BE:32:v5t"), + @CompilerInfo(langID = "ARM:LE:32:v5"), + @CompilerInfo(langID = "ARM:BE:32:v5"), + @CompilerInfo(langID = "ARM:LE:32:v4t"), + @CompilerInfo(langID = "ARM:BE:32:v4t"), + @CompilerInfo(langID = "ARM:LE:32:v4"), + @CompilerInfo(langID = "ARM:BE:32:v4"), }) public class ArmDisassemblyInject implements DisassemblyInject { protected static final long THUMB_BIT = 0x20; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengX64DisassemblyInject.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengX64DisassemblyInject.java index 6fee5eb1e8..0e4ed34502 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengX64DisassemblyInject.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengX64DisassemblyInject.java @@ -19,18 +19,18 @@ import java.io.IOException; import java.math.BigInteger; import java.util.Collection; import java.util.Set; -import java.util.concurrent.*; import java.util.stream.Collectors; import ghidra.app.plugin.core.debug.disassemble.TraceDisassembleCommand; import ghidra.app.plugin.core.debug.workflow.DisassemblyInject; import ghidra.app.plugin.core.debug.workflow.DisassemblyInjectInfo; -import ghidra.app.services.DebuggerModelService; +import ghidra.app.plugin.core.debug.workflow.DisassemblyInjectInfo.CompilerInfo; +import ghidra.app.services.DebuggerTargetService; import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.MemBufferByteProvider; import ghidra.app.util.bin.format.pe.*; import ghidra.app.util.bin.format.pe.PortableExecutable.SectionLayout; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.*; import ghidra.program.model.lang.*; @@ -40,10 +40,14 @@ import ghidra.trace.model.Trace; import ghidra.trace.model.modules.TraceModule; import ghidra.trace.model.thread.TraceThread; import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; -@DisassemblyInjectInfo(langIDs = { "x86:LE:64:default" }) -// TODO: Filter / selector on debugger. This is running for GDB, too.... +@DisassemblyInjectInfo( + compilers = { + @CompilerInfo(langID = "x86:LE:64:default", compilerID = "windows"), + @CompilerInfo(langID = "x86:LE:64:default", compilerID = "clangwindows"), + }) public class DbgengX64DisassemblyInject implements DisassemblyInject { enum Mode { @@ -58,14 +62,14 @@ public class DbgengX64DisassemblyInject implements DisassemblyInject { if (first == null) { return; } - DebuggerModelService modelService = tool.getService(DebuggerModelService.class); - TraceRecorder recorder = modelService == null ? null : modelService.getRecorder(trace); + DebuggerTargetService targetService = tool.getService(DebuggerTargetService.class); + Target target = targetService == null ? null : targetService.getTarget(trace); Collection modules = trace.getModuleManager().getModulesAt(snap, first.getMinAddress()); Msg.debug(this, "Disassembling in modules: " + modules.stream().map(TraceModule::getName).collect(Collectors.joining(","))); Set modes = modules.stream() - .map(m -> modeForModule(recorder, trace, snap, m)) + .map(m -> modeForModule(target, trace, snap, m)) .filter(m -> m != Mode.UNK) .collect(Collectors.toSet()); Msg.debug(this, "Disassembling in mode(s): " + modes); @@ -79,46 +83,34 @@ public class DbgengX64DisassemblyInject implements DisassemblyInject { ProgramContextImpl context = new ProgramContextImpl(language); language.applyContextSettings(context); RegisterValue ctxVal = context.getDisassemblyContext(first.getMinAddress()); - if (mode == Mode.X64) { - command.setInitialContext(ctxVal + command.setInitialContext(switch (mode) { + case X64 -> ctxVal .assign(longModeReg, BigInteger.ONE) .assign(addrsizeReg, BigInteger.TWO) - .assign(opsizeReg, BigInteger.ONE)); - } - else if (mode == Mode.X86) { - command.setInitialContext(ctxVal + .assign(opsizeReg, BigInteger.ONE); + case X86 -> ctxVal .assign(longModeReg, BigInteger.ZERO) .assign(addrsizeReg, BigInteger.ONE) - .assign(opsizeReg, BigInteger.ONE)); - } - // Shouldn't ever get anything else. + .assign(opsizeReg, BigInteger.ONE); + default -> throw new AssertionError(); + }); } - private T waitOn(CompletableFuture future) - throws InterruptedException, ExecutionException, TimeoutException { - // Just don't hang the Ghidra task thread indefinitely. - return future.get(1000, TimeUnit.MILLISECONDS); - } - - protected Mode modeForModule(TraceRecorder recorder, Trace trace, long snap, + protected Mode modeForModule(Target target, Trace trace, long snap, TraceModule module) { - if (recorder != null && recorder.getSnap() == snap) { + if (target != null && target.getSnap() == snap) { AddressSet set = new AddressSet(); set.add(module.getBase(), module.getBase()); // Recorder should read page try { - waitOn(recorder.readMemoryBlocks(set, TaskMonitor.DUMMY)); - waitOn(recorder.getTarget().getModel().flushEvents()); - waitOn(recorder.flushTransactions()); + target.readMemory(set, TaskMonitor.DUMMY); trace.flushEvents(); } - catch (InterruptedException | ExecutionException | TimeoutException e) { - Msg.error(this, "Could not read module header from target", e); - // Try to parse whatever's there. If 0s, it'll come UNK. + catch (CancelledException e) { + throw new AssertionError(e); } } MemBuffer bufferAt = trace.getMemoryManager().getBufferAt(snap, module.getBase()); - ByteProvider bp = new MemBufferByteProvider(bufferAt); - try { + try (ByteProvider bp = new MemBufferByteProvider(bufferAt)) { PortableExecutable pe = new PortableExecutable(bp, SectionLayout.MEMORY, false, false); NTHeader ntHeader = pe.getNTHeader(); if (ntHeader == null) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/BreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/BreakpointActionItem.java index 59cdd8a5ac..7aec00b20b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/BreakpointActionItem.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/BreakpointActionItem.java @@ -33,7 +33,7 @@ public interface BreakpointActionItem { * @param length the length * @return the range */ - default AddressRange range(Address address, long length) { + static AddressRange range(Address address, long length) { try { return new AddressRangeImpl(address, length); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/BreakpointActionSet.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/BreakpointActionSet.java index 7eae6d3a28..75d8bc41e2 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/BreakpointActionSet.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/BreakpointActionSet.java @@ -19,7 +19,7 @@ import java.util.LinkedHashSet; import java.util.concurrent.CompletableFuture; import ghidra.async.AsyncFence; -import ghidra.dbg.target.*; +import ghidra.debug.api.target.Target; import ghidra.trace.model.breakpoint.TraceBreakpoint; /** @@ -39,20 +39,10 @@ public class BreakpointActionSet extends LinkedHashSet { * @param loc the target breakpoint * @return the added item */ - public EnableTargetBreakpointActionItem planEnableTarget(TargetBreakpointLocation loc) { - if (loc instanceof TargetTogglable) { - EnableTargetBreakpointActionItem action = - new EnableTargetBreakpointActionItem((TargetTogglable) loc); - add(action); - return action; - } - TargetBreakpointSpec spec = loc.getSpecification(); - if (spec instanceof TargetTogglable) { - EnableTargetBreakpointActionItem action = new EnableTargetBreakpointActionItem(spec); - add(action); - return action; - } - return null; + public EnableTargetBreakpointActionItem planEnableTarget(Target target, TraceBreakpoint bpt) { + EnableTargetBreakpointActionItem action = new EnableTargetBreakpointActionItem(target, bpt); + add(action); + return action; } /** @@ -73,20 +63,11 @@ public class BreakpointActionSet extends LinkedHashSet { * @param loc the target breakpoint * @return the added item */ - public DisableTargetBreakpointActionItem planDisableTarget(TargetBreakpointLocation loc) { - if (loc instanceof TargetTogglable) { - DisableTargetBreakpointActionItem action = - new DisableTargetBreakpointActionItem((TargetTogglable) loc); - add(action); - return action; - } - TargetBreakpointSpec spec = loc.getSpecification(); - if (spec instanceof TargetTogglable) { - DisableTargetBreakpointActionItem action = new DisableTargetBreakpointActionItem(spec); - add(action); - return action; - } - return null; + public DisableTargetBreakpointActionItem planDisableTarget(Target target, TraceBreakpoint bpt) { + DisableTargetBreakpointActionItem action = + new DisableTargetBreakpointActionItem(target, bpt); + add(action); + return action; } /** @@ -107,21 +88,10 @@ public class BreakpointActionSet extends LinkedHashSet { * @param loc the target breakpoint * @return the added item */ - public DeleteTargetBreakpointActionItem planDeleteTarget(TargetBreakpointLocation loc) { - if (loc instanceof TargetDeletable) { - DeleteTargetBreakpointActionItem action = - new DeleteTargetBreakpointActionItem((TargetDeletable) loc); - add(action); - return action; - } - TargetBreakpointSpec spec = loc.getSpecification(); - if (spec instanceof TargetTogglable) { - DeleteTargetBreakpointActionItem action = - new DeleteTargetBreakpointActionItem((TargetDeletable) spec); - add(action); - return action; - } - return null; + public DeleteTargetBreakpointActionItem planDeleteTarget(Target target, TraceBreakpoint bpt) { + DeleteTargetBreakpointActionItem action = new DeleteTargetBreakpointActionItem(target, bpt); + add(action); + return action; } /** diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java index bb686870ed..821c9b33d9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java @@ -32,15 +32,13 @@ import ghidra.app.plugin.core.debug.event.*; import ghidra.app.services.*; import ghidra.app.services.DebuggerControlService.ControlModeChangeListener; import ghidra.async.SwingExecutorService; -import ghidra.dbg.target.TargetBreakpointLocation; -import ghidra.dbg.target.TargetObject; -import ghidra.dbg.util.PathUtils; import ghidra.debug.api.breakpoint.LogicalBreakpoint; import ghidra.debug.api.breakpoint.LogicalBreakpoint.State; import ghidra.debug.api.breakpoint.LogicalBreakpointsChangeListener; import ghidra.debug.api.control.ControlMode; -import ghidra.debug.api.model.TraceRecorder; import ghidra.debug.api.modules.DebuggerStaticMappingChangeListener; +import ghidra.debug.api.target.Target; +import ghidra.debug.api.target.TargetPublicationListener; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.model.*; import ghidra.framework.plugintool.*; @@ -57,7 +55,6 @@ import ghidra.trace.model.breakpoint.TraceBreakpointKind; import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.util.TraceAddressSpace; import ghidra.util.Msg; -import ghidra.util.datastruct.CollectionChangeListener; import ghidra.util.datastruct.ListenerSet; @PluginInfo( @@ -172,15 +169,15 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin } } - protected class TrackRecordersListener implements CollectionChangeListener { + protected class TrackRecordersListener implements TargetPublicationListener { @Override - public void elementAdded(TraceRecorder element) { - processChange(c -> evtTraceRecordingStarted(c, element), "recordingStarted"); + public void targetPublished(Target target) { + processChange(c -> evtTraceTargetPublished(c, target), "targetPublished"); } @Override - public void elementRemoved(TraceRecorder element) { - processChange(c -> evtTraceRecordingStopped(c, element), "recordingStopped"); + public void targetWithdrawn(Target target) { + processChange(c -> evtTraceTargetWithdrawn(c, target), "targetWithdrawn"); } } @@ -458,7 +455,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin final Trace trace; final TraceBreakpointsListener breakpointListener; - TraceRecorder recorder; + Target target; long snap = -1; public InfoPerTrace(Trace trace) { @@ -468,11 +465,11 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin trace.addListener(breakpointListener); } - protected void setRecorderAndSnap(TraceRecorder recorder, long snap, ChangeCollector c) { - if (this.recorder == recorder && this.snap == snap) { + protected void setTargetAndSnap(Target target, long snap, ChangeCollector c) { + if (this.target == target && this.snap == snap) { return; } - this.recorder = recorder; + this.target = target; this.snap = snap; for (InfoPerProgram info : programInfos.values()) { @@ -488,7 +485,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin Collection kinds) { LoneLogicalBreakpoint lb = new LoneLogicalBreakpoint(tool, trace, address, length, kinds); - lb.setRecorder(trace, recorder); + lb.setTarget(trace, target); return lb; } @@ -527,7 +524,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin for (LogicalBreakpointInternal lb : Set.copyOf(set)) { for (TraceBreakpoint tb : Set.copyOf(lb.getTraceBreakpoints(trace))) { if (!mode.useEmulatedBreakpoints() && - (recorder == null || recorder.getTargetBreakpoint(tb) == null)) { + (target == null || !target.isBreakpointValid(tb))) { forgetTraceBreakpoint(r, tb); continue; } @@ -553,7 +550,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin protected void trackTraceBreakpoints(AddCollector a) { ControlMode mode = getMode(trace); - if (!mode.useEmulatedBreakpoints() && recorder == null) { + if (!mode.useEmulatedBreakpoints() && target == null) { return; } Collection visible = new ArrayList<>(); @@ -599,7 +596,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin ControlMode mode, boolean forceUpdate) throws TrackedTooSoonException { if (!mode.useEmulatedBreakpoints() && - (recorder == null || recorder.getTargetBreakpoint(tb) == null)) { + (target == null || !target.isBreakpointValid(tb))) { return; } Address traceAddr = tb.getMinAddress(); @@ -654,7 +651,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin continue; } lb.setTraceAddress(ti.trace, loc.getAddress()); - lb.setRecorder(ti.trace, ti.recorder); + lb.setTarget(ti.trace, ti.target); ti.breakpointsByAddress.computeIfAbsent(loc.getAddress(), __ -> new HashSet<>()) .add(lb); } @@ -806,7 +803,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin } // @AutoServiceConsumed via method - private DebuggerModelService modelService; + private DebuggerTargetService targetService; @AutoServiceConsumed private DebuggerTraceManagerService traceManager; // @AutoServiceConsumed via method @@ -820,7 +817,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin private final ListenerSet changeListeners = new ListenerSet<>(LogicalBreakpointsChangeListener.class); - private final TrackRecordersListener recorderListener = new TrackRecordersListener(); + private final TrackRecordersListener targetsListener = new TrackRecordersListener(); private final TrackMappingsListener mappingListener = new TrackMappingsListener(); private final TrackModesListener modeListener = new TrackModesListener(); @@ -908,13 +905,13 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin } @AutoServiceConsumed - private void setModelService(DebuggerModelService modelService) { - if (this.modelService != null) { - this.modelService.removeTraceRecordersChangedListener(recorderListener); + private void setModelService(DebuggerTargetService targetService) { + if (this.targetService != null) { + this.targetService.removeTargetPublicationListener(targetsListener); } - this.modelService = modelService; - if (this.modelService != null) { - this.modelService.addTraceRecordersChangedListener(recorderListener); + this.targetService = targetService; + if (this.targetService != null) { + this.targetService.addTargetPublicationListener(targetsListener); } } @@ -968,13 +965,13 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin // The mapping removals, if any, will clean up related traces } - private void doTrackTrace(ChangeCollector c, Trace trace, TraceRecorder recorder, long snap) { + private void doTrackTrace(ChangeCollector c, Trace trace, Target target, long snap) { InfoPerTrace info = traceInfos.get(trace); if (info == null) { info = new InfoPerTrace(trace); traceInfos.put(trace, info); } - info.setRecorderAndSnap(recorder, snap, c); + info.setTargetAndSnap(target, snap, c); } private void doUntrackTrace(ChangeCollector c, Trace trace) { @@ -995,17 +992,17 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin } } - private void evtTraceRecordingStarted(ChangeCollector c, TraceRecorder recorder) { - Trace trace = recorder.getTrace(); + private void evtTraceTargetPublished(ChangeCollector c, Target target) { + Trace trace = target.getTrace(); if (!traceManager.getOpenTraces().contains(trace)) { return; } long snap = traceManager.getCurrentFor(trace).getSnap(); - doTrackTrace(c, trace, recorder, snap); + doTrackTrace(c, trace, target, snap); } - private void evtTraceRecordingStopped(ChangeCollector c, TraceRecorder recorder) { - Trace trace = recorder.getTrace(); + private void evtTraceTargetWithdrawn(ChangeCollector c, Target target) { + Trace trace = target.getTrace(); if (!traceManager.getOpenTraces().contains(trace)) { return; } @@ -1015,9 +1012,9 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin private void traceOpened(Trace trace) { processChange(c -> { - TraceRecorder recorder = modelService == null ? null : modelService.getRecorder(trace); + Target target = targetService == null ? null : targetService.getTarget(trace); long snap = traceManager.getCurrentFor(trace).getSnap(); - doTrackTrace(c, trace, recorder, snap); + doTrackTrace(c, trace, target, snap); }, "traceOpened"); } @@ -1025,7 +1022,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin if (coordinates.getTrace() == null) { return; } - processChange(c -> doTrackTrace(c, coordinates.getTrace(), coordinates.getRecorder(), + processChange(c -> doTrackTrace(c, coordinates.getTrace(), coordinates.getTarget(), coordinates.getSnap()), "coordinatesActivated"); } @@ -1163,7 +1160,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin continue; } lb.setTraceAddress(ti.trace, loc.getAddress()); - lb.setRecorder(ti.trace, ti.recorder); + lb.setTarget(ti.trace, ti.target); } } return lb; @@ -1180,21 +1177,21 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin public CompletableFuture placeBreakpointAt(Trace trace, Address address, long length, Collection kinds, String name) { long snap = traceManager.getCurrentFor(trace).getSnap(); - TraceRecorder recorder = modelService == null ? null : modelService.getRecorder(trace); + Target target = targetService == null ? null : targetService.getTarget(trace); ProgramLocation staticLocation = mappingService.getOpenMappedLocation( new DefaultTraceLocation(trace, null, Lifespan.at(snap), address)); if (staticLocation == null) { LoneLogicalBreakpoint lb = new LoneLogicalBreakpoint(tool, trace, address, length, kinds); - lb.setRecorder(trace, recorder); + lb.setTarget(trace, target); return lb.enableForTrace(trace); } MappedLogicalBreakpoint lb = new MappedLogicalBreakpoint(tool, staticLocation.getProgram(), staticLocation.getByteAddress(), length, kinds); lb.setTraceAddress(trace, address); - lb.setRecorder(trace, recorder); + lb.setTarget(trace, target); lb.enableForProgramWithName(name); return lb.enableForTrace(trace); } @@ -1263,33 +1260,29 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin : controlService.getCurrentMode(trace); } + interface TargetBreakpointConsumer { + void accept(BreakpointActionSet actions, Target target, TraceBreakpoint tb); + } + private void planActOnLoc(BreakpointActionSet actions, TraceBreakpoint tb, - BiConsumer targetLocConsumer, + TargetBreakpointConsumer targetBptConsumer, BiConsumer emuLocConsumer) { ControlMode mode = getMode(tb.getTrace()); if (mode.useEmulatedBreakpoints()) { planActOnLocEmu(actions, tb, emuLocConsumer); } else { - planActOnLocTarget(actions, tb, targetLocConsumer); + planActOnLocTarget(actions, tb, targetBptConsumer); } } private void planActOnLocTarget(BreakpointActionSet actions, TraceBreakpoint tb, - BiConsumer targetLocConsumer) { - TraceRecorder recorder = - modelService == null ? null : modelService.getRecorder(tb.getTrace()); - if (recorder == null) { + TargetBreakpointConsumer targetBptConsumer) { + Target target = targetService == null ? null : targetService.getTarget(tb.getTrace()); + if (target == null) { return; } - List path = PathUtils.parse(tb.getPath()); - TargetObject object = recorder.getTarget().getModel().getModelObject(path); - if (!(object instanceof TargetBreakpointLocation)) { - Msg.error(this, tb.getPath() + " is not a target breakpoint location"); - return; - } - TargetBreakpointLocation loc = (TargetBreakpointLocation) object; - targetLocConsumer.accept(actions, loc); + targetBptConsumer.accept(actions, target, tb); } private void planActOnLocEmu(BreakpointActionSet actions, TraceBreakpoint tb, @@ -1298,7 +1291,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin } protected CompletableFuture actOnLocs(Collection col, - BiConsumer targetLocConsumer, + TargetBreakpointConsumer targetBptConsumer, BiConsumer emuLocConsumer, Consumer progConsumer) { BreakpointActionSet actions = new BreakpointActionSet(); @@ -1307,7 +1300,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin if (col.containsAll(lb.getTraceBreakpoints())) { progConsumer.accept(lb); } - planActOnLoc(actions, tb, targetLocConsumer, emuLocConsumer); + planActOnLoc(actions, tb, targetBptConsumer, emuLocConsumer); } return actions.execute(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteEmuBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteEmuBreakpointActionItem.java index 85af3d90dc..7df3edb371 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteEmuBreakpointActionItem.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteEmuBreakpointActionItem.java @@ -34,6 +34,6 @@ public record DeleteEmuBreakpointActionItem(TraceBreakpoint bpt) implements Brea bpt.setEmuEnabled(false); } } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteTargetBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteTargetBreakpointActionItem.java index 1a8e6ae2a9..4c863bce0e 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteTargetBreakpointActionItem.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteTargetBreakpointActionItem.java @@ -17,12 +17,13 @@ package ghidra.app.plugin.core.debug.service.breakpoint; import java.util.concurrent.CompletableFuture; -import ghidra.dbg.target.TargetDeletable; +import ghidra.debug.api.target.Target; +import ghidra.trace.model.breakpoint.TraceBreakpoint; -public record DeleteTargetBreakpointActionItem(TargetDeletable deletable) +public record DeleteTargetBreakpointActionItem(Target target, TraceBreakpoint bpt) implements BreakpointActionItem { @Override public CompletableFuture execute() { - return deletable.delete(); + return target.deleteBreakpointAsync(bpt); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableEmuBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableEmuBreakpointActionItem.java index 5beb65cd54..88313b55c4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableEmuBreakpointActionItem.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableEmuBreakpointActionItem.java @@ -29,6 +29,6 @@ public record DisableEmuBreakpointActionItem(TraceBreakpoint bpt) bpt.getTrace().openTransaction("Disable Emulated Breakpoint")) { bpt.setEmuEnabled(false); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableTargetBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableTargetBreakpointActionItem.java index 69d2ca4363..6ae89b6400 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableTargetBreakpointActionItem.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableTargetBreakpointActionItem.java @@ -17,12 +17,13 @@ package ghidra.app.plugin.core.debug.service.breakpoint; import java.util.concurrent.CompletableFuture; -import ghidra.dbg.target.TargetTogglable; +import ghidra.debug.api.target.Target; +import ghidra.trace.model.breakpoint.TraceBreakpoint; -public record DisableTargetBreakpointActionItem(TargetTogglable togglable) +public record DisableTargetBreakpointActionItem(Target target, TraceBreakpoint bpt) implements BreakpointActionItem { @Override public CompletableFuture execute() { - return togglable.disable(); + return target.toggleBreakpointAsync(bpt, false); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableEmuBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableEmuBreakpointActionItem.java index c6883f7211..8a90a5271c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableEmuBreakpointActionItem.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableEmuBreakpointActionItem.java @@ -28,6 +28,6 @@ public record EnableEmuBreakpointActionItem(TraceBreakpoint bpt) implements Brea bpt.getTrace().openTransaction("Enable Emulated Breakpoint")) { bpt.setEmuEnabled(true); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableTargetBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableTargetBreakpointActionItem.java index 8138a885da..556d049c96 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableTargetBreakpointActionItem.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableTargetBreakpointActionItem.java @@ -17,12 +17,13 @@ package ghidra.app.plugin.core.debug.service.breakpoint; import java.util.concurrent.CompletableFuture; -import ghidra.dbg.target.TargetTogglable; +import ghidra.debug.api.target.Target; +import ghidra.trace.model.breakpoint.TraceBreakpoint; -public record EnableTargetBreakpointActionItem(TargetTogglable togglable) +public record EnableTargetBreakpointActionItem(Target target, TraceBreakpoint bpt) implements BreakpointActionItem { @Override public CompletableFuture execute() { - return togglable.enable(); + return target.toggleBreakpointAsync(bpt, true); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LogicalBreakpointInternal.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LogicalBreakpointInternal.java index a54dc239b2..87e0ae2f2f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LogicalBreakpointInternal.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LogicalBreakpointInternal.java @@ -16,9 +16,10 @@ package ghidra.app.plugin.core.debug.service.breakpoint; import ghidra.debug.api.breakpoint.LogicalBreakpoint; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.program.model.address.Address; -import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Bookmark; +import ghidra.program.model.listing.Program; import ghidra.trace.model.Trace; import ghidra.trace.model.breakpoint.TraceBreakpoint; @@ -31,7 +32,7 @@ public interface LogicalBreakpointInternal extends LogicalBreakpoint { */ void setTraceAddress(Trace trace, Address address); - void setRecorder(Trace trace, TraceRecorder recorder); + void setTarget(Trace trace, Target target); /** * Remove the given trace from this set diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LoneLogicalBreakpoint.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LoneLogicalBreakpoint.java index e25b456609..89303e50db 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LoneLogicalBreakpoint.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LoneLogicalBreakpoint.java @@ -19,7 +19,7 @@ import java.util.*; import java.util.concurrent.CompletableFuture; import ghidra.async.AsyncUtils; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.framework.model.DomainObject; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; @@ -101,8 +101,8 @@ public class LoneLogicalBreakpoint implements LogicalBreakpointInternal { } @Override - public void setRecorder(Trace trace, TraceRecorder recorder) { - breaks.setRecorder(recorder); + public void setTarget(Trace trace, Target target) { + breaks.setTarget(target); } @Override @@ -196,7 +196,7 @@ public class LoneLogicalBreakpoint implements LogicalBreakpointInternal { public CompletableFuture enableForTrace(Trace trace) { if (trace != breaks.getTrace()) { // Ignore silently - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return enable(); } @@ -205,7 +205,7 @@ public class LoneLogicalBreakpoint implements LogicalBreakpointInternal { public CompletableFuture disableForTrace(Trace trace) { if (trace != breaks.getTrace()) { // Ignore silently - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return disable(); } @@ -214,7 +214,7 @@ public class LoneLogicalBreakpoint implements LogicalBreakpointInternal { public CompletableFuture deleteForTrace(Trace trace) { if (trace != breaks.getTrace()) { // Ignore silently - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return delete(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/MappedLogicalBreakpoint.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/MappedLogicalBreakpoint.java index 3ad7a1cd93..439dd6a853 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/MappedLogicalBreakpoint.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/MappedLogicalBreakpoint.java @@ -18,9 +18,8 @@ package ghidra.app.plugin.core.debug.service.breakpoint; import java.util.*; import java.util.concurrent.CompletableFuture; -import ghidra.app.services.DebuggerModelService; import ghidra.async.AsyncUtils; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.framework.model.DomainObject; import ghidra.framework.plugintool.PluginTool; import ghidra.pcode.exec.SleighUtils; @@ -81,14 +80,6 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal { return true; } - protected TraceRecorder requireRecorder(DebuggerModelService modelService, Trace trace) { - TraceRecorder recorder = modelService.getRecorder(trace); - if (recorder == null) { - throw new AssertionError("This trace is not live"); - } - return recorder; - } - @Override public void enableForProgram() { progBreak.enable(); @@ -125,7 +116,7 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal { breaks = traceBreaks.get(trace); } if (breaks == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } breaks.planEnable(actions, length, kinds); return actions.execute(); @@ -139,7 +130,7 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal { breaks = traceBreaks.get(trace); } if (breaks == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } breaks.planDisable(actions, length, kinds); return actions.execute(); @@ -153,7 +144,7 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal { breaks = traceBreaks.get(trace); } if (breaks == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } breaks.planDelete(actions, length, kinds); return actions.execute(); @@ -304,9 +295,9 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal { } @Override - public void setRecorder(Trace trace, TraceRecorder recorder) { + public void setTarget(Trace trace, Target target) { synchronized (traceBreaks) { - traceBreaks.get(trace).setRecorder(recorder); + traceBreaks.get(trace).setTarget(target); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceEmuBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceEmuBreakpointActionItem.java index 3ef326021e..441f91e879 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceEmuBreakpointActionItem.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceEmuBreakpointActionItem.java @@ -100,11 +100,11 @@ public record PlaceEmuBreakpointActionItem(Trace trace, long snap, Address addre try (Transaction tx = trace.openTransaction("Place Emulated Breakpoint")) { // Defaults with emuEnable=true TraceBreakpoint bpt = trace.getBreakpointManager() - .addBreakpoint(computePath(), Lifespan.at(snap), range(address, length), - Set.of(), kinds, false, null); + .addBreakpoint(computePath(), Lifespan.at(snap), + BreakpointActionItem.range(address, length), Set.of(), kinds, false, null); bpt.setName(createName(address)); bpt.setEmuSleigh(emuSleigh); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } catch (DuplicateNameException e) { throw new AssertionError(e); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceTargetBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceTargetBreakpointActionItem.java index d17406f773..dce7642a30 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceTargetBreakpointActionItem.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceTargetBreakpointActionItem.java @@ -15,27 +15,24 @@ */ package ghidra.app.plugin.core.debug.service.breakpoint; -import java.util.*; +import java.util.Collection; +import java.util.Set; import java.util.concurrent.CompletableFuture; -import ghidra.dbg.target.TargetBreakpointSpecContainer; -import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind; -import ghidra.program.model.address.*; +import ghidra.debug.api.target.Target; +import ghidra.program.model.address.AddressRange; +import ghidra.trace.model.breakpoint.TraceBreakpointKind; -public record PlaceTargetBreakpointActionItem(TargetBreakpointSpecContainer container, - Address address, long length, Set kinds) - implements BreakpointActionItem { +public record PlaceTargetBreakpointActionItem(Target target, AddressRange range, + Set kinds) implements BreakpointActionItem { - public PlaceTargetBreakpointActionItem(TargetBreakpointSpecContainer container, Address address, - long length, Set kinds) { - this.container = container; - this.address = address; - this.length = length; - this.kinds = Set.copyOf(kinds); + public PlaceTargetBreakpointActionItem(Target target, AddressRange range, + Collection kinds) { + this(target, range, Set.copyOf(kinds)); } @Override public CompletableFuture execute() { - return container.placeBreakpoint(range(address, length), kinds); + return target.placeBreakpointAsync(range, kinds, null, null); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java index 6717e23027..c8651b9a17 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java @@ -19,16 +19,14 @@ import java.util.*; import java.util.stream.Collectors; import db.Transaction; -import ghidra.app.services.*; -import ghidra.dbg.target.*; -import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind; +import ghidra.app.services.DebuggerControlService; +import ghidra.app.services.DebuggerTraceManagerService; import ghidra.debug.api.breakpoint.LogicalBreakpoint.TraceMode; import ghidra.debug.api.control.ControlMode; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.framework.plugintool.PluginTool; import ghidra.pcode.exec.SleighUtils; import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressRange; import ghidra.trace.model.Trace; import ghidra.trace.model.breakpoint.TraceBreakpoint; import ghidra.trace.model.breakpoint.TraceBreakpointKind; @@ -49,7 +47,7 @@ class TraceBreakpointSet { private final Set> breakpoints = new HashSet<>(); - private TraceRecorder recorder; + private Target target; private String emuSleigh; /** @@ -71,12 +69,12 @@ class TraceBreakpointSet { } /** - * Set the recorder when the trace is associated to a live target + * Set the target when the trace is associated to a live target * - * @param recorder the recorder + * @param target the target */ - public void setRecorder(TraceRecorder recorder) { - this.recorder = recorder; + public void setTarget(Target target) { + this.target = target; } private ControlMode getControlMode() { @@ -115,18 +113,6 @@ class TraceBreakpointSet { return address; } - /** - * If there is a live target, get the dynamic address in the target's space - * - * @return the dynamic address on target - */ - public Address computeTargetAddress() { - if (recorder == null) { - throw new AssertionError(); - } - return recorder.getMemoryMapper().traceToTarget(address); - } - /** * Compute the mode (enablement) of this set * @@ -318,7 +304,7 @@ class TraceBreakpointSet { Collection kinds) { long snap = getSnap(); if (breakpoints.isEmpty()) { - if (recorder == null || getControlMode().useEmulatedBreakpoints()) { + if (target == null || getControlMode().useEmulatedBreakpoints()) { planPlaceEmu(actions, snap, length, kinds); } else { @@ -326,7 +312,7 @@ class TraceBreakpointSet { } } else { - if (recorder == null || getControlMode().useEmulatedBreakpoints()) { + if (target == null || getControlMode().useEmulatedBreakpoints()) { planEnableEmu(actions); } else { @@ -337,19 +323,12 @@ class TraceBreakpointSet { private void planPlaceTarget(BreakpointActionSet actions, long snap, long length, Collection kinds) { - if (snap != recorder.getSnap()) { + if (snap != target.getSnap()) { throw new AssertionError("Target breakpoints must be requested at present snap"); } - Set tKinds = - TraceRecorder.traceToTargetBreakpointKinds(kinds); - for (TargetBreakpointSpecContainer cont : recorder - .collectBreakpointContainers(null)) { - LinkedHashSet supKinds = new LinkedHashSet<>(tKinds); - supKinds.retainAll(cont.getSupportedBreakpointKinds()); - actions.add(new PlaceTargetBreakpointActionItem(cont, computeTargetAddress(), - length, supKinds)); - } + actions.add(new PlaceTargetBreakpointActionItem(target, + BreakpointActionItem.range(address, length), kinds)); } private void planPlaceEmu(BreakpointActionSet actions, long snap, long length, @@ -361,11 +340,7 @@ class TraceBreakpointSet { private void planEnableTarget(BreakpointActionSet actions) { for (IDHashed bpt : breakpoints) { - TargetBreakpointLocation loc = recorder.getTargetBreakpoint(bpt.obj); - if (loc == null) { - continue; - } - actions.planEnableTarget(loc); + actions.planEnableTarget(target, bpt.obj); } } @@ -394,21 +369,8 @@ class TraceBreakpointSet { private void planDisableTarget(BreakpointActionSet actions, long length, Collection kinds) { - Set tKinds = TraceRecorder.traceToTargetBreakpointKinds(kinds); - Address targetAddr = computeTargetAddress(); - for (TargetBreakpointLocation loc : recorder.collectBreakpoints(null)) { - AddressRange range = loc.getRange(); - if (!targetAddr.equals(range.getMinAddress())) { - continue; - } - if (length != range.getLength()) { - continue; - } - TargetBreakpointSpec spec = loc.getSpecification(); - if (!Objects.equals(spec.getKinds(), tKinds)) { - continue; - } - actions.planDisableTarget(loc); + for (IDHashed bpt : breakpoints) { + actions.planDisableTarget(target, bpt.obj); } } @@ -437,21 +399,8 @@ class TraceBreakpointSet { private void planDeleteTarget(BreakpointActionSet actions, long length, Set kinds) { - Set tKinds = TraceRecorder.traceToTargetBreakpointKinds(kinds); - Address targetAddr = computeTargetAddress(); - for (TargetBreakpointLocation loc : recorder.collectBreakpoints(null)) { - AddressRange range = loc.getRange(); - if (!targetAddr.equals(range.getMinAddress())) { - continue; - } - if (length != range.getLength()) { - continue; - } - TargetBreakpointSpec spec = loc.getSpecification(); - if (!Objects.equals(spec.getKinds(), tKinds)) { - continue; - } - actions.planDeleteTarget(loc); + for (IDHashed bpt : breakpoints) { + actions.planDeleteTarget(target, bpt.obj); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractDebuggerPcodeEmulatorFactory.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractDebuggerPcodeEmulatorFactory.java index d76bfcfc19..577cb66c48 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractDebuggerPcodeEmulatorFactory.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractDebuggerPcodeEmulatorFactory.java @@ -18,14 +18,14 @@ package ghidra.app.plugin.core.debug.service.emulation; import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess; import ghidra.debug.api.emulation.DebuggerPcodeEmulatorFactory; import ghidra.debug.api.emulation.DebuggerPcodeMachine; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.framework.plugintool.PluginTool; import ghidra.trace.model.guest.TracePlatform; public abstract class AbstractDebuggerPcodeEmulatorFactory implements DebuggerPcodeEmulatorFactory { @Override public DebuggerPcodeMachine create(PluginTool tool, TracePlatform platform, long snap, - TraceRecorder recorder) { - return create(new DefaultPcodeDebuggerAccess(tool, recorder, platform, snap)); + Target target) { + return create(new DefaultPcodeDebuggerAccess(tool, target, platform, snap)); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulator.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulator.java index ca5c6681fb..782b9341b2 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulator.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulator.java @@ -15,7 +15,6 @@ */ package ghidra.app.plugin.core.debug.service.emulation; -import ghidra.app.plugin.core.debug.service.emulation.data.*; import ghidra.debug.api.emulation.DebuggerPcodeMachine; import ghidra.debug.api.emulation.PcodeDebuggerAccess; import ghidra.pcode.emu.PcodeEmulator; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java index e7da93cf71..1d51f6f86e 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java @@ -43,8 +43,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.services.*; import ghidra.async.AsyncLazyMap; import ghidra.debug.api.control.ControlMode; -import ghidra.debug.api.emulation.DebuggerPcodeEmulatorFactory; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; +import ghidra.debug.api.emulation.*; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; @@ -63,8 +62,7 @@ import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.schedule.*; import ghidra.trace.model.time.schedule.Scheduler.RunResult; -import ghidra.util.HelpLocation; -import ghidra.util.Msg; +import ghidra.util.*; import ghidra.util.classfinder.ClassSearcher; import ghidra.util.datastruct.ListenerSet; import ghidra.util.exception.CancelledException; @@ -346,7 +344,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm @AutoServiceConsumed private DebuggerTraceManagerService traceManager; @AutoServiceConsumed - private DebuggerModelService modelService; + private DebuggerTargetService targetService; @AutoServiceConsumed private DebuggerPlatformService platformService; @AutoServiceConsumed @@ -727,7 +725,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } } DebuggerPcodeMachine emu = emulatorFactory.create(tool, platform, time.getSnap(), - modelService == null ? null : modelService.getRecorder(trace)); + targetService == null ? null : targetService.getTarget(trace)); try (BusyEmu be = new BusyEmu(new CachedEmulator(key.trace, emu))) { installBreakpoints(key.trace, key.time.getSnap(), be.ce.emulator()); monitor.initialize(time.totalTickCount()); @@ -813,6 +811,23 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } } + @Override + public Trace launchProgram(Program program, Address address) throws IOException { + Trace trace = null; + try { + trace = ProgramEmulationUtils.launchEmulationTrace(program, address, this); + traceManager.openTrace(trace); + traceManager.activateTrace(trace); + Swing.allowSwingToProcessEvents(); + } + finally { + if (trace != null) { + trace.release(this); + } + } + return trace; + } + @Override public long emulate(TracePlatform platform, TraceSchedule time, TaskMonitor monitor) throws CancelledException { @@ -861,7 +876,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } @AutoServiceConsumed - private void setModelService(DebuggerModelService modelService) { + private void setModelService(DebuggerTargetService targetService) { cache.clear(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/AbstractPcodeDebuggerAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/AbstractPcodeDebuggerAccess.java index 8729a6e7fa..f7419800c3 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/AbstractPcodeDebuggerAccess.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/AbstractPcodeDebuggerAccess.java @@ -16,7 +16,7 @@ package ghidra.app.plugin.core.debug.service.emulation.data; import ghidra.debug.api.emulation.*; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.framework.plugintool.PluginTool; import ghidra.pcode.exec.trace.data.AbstractPcodeTraceAccess; import ghidra.trace.model.guest.TracePlatform; @@ -32,7 +32,7 @@ public abstract class AbstractPcodeDebuggerAccess recorder.getTarget().getModel().flushEvents()) - .thenCompose(__ -> recorder.flushTransactions()) - .thenAccept(__ -> platform.getTrace().flushEvents()) - .thenApply(__ -> true); + return target.readMemoryAsync(hostView, TaskMonitor.DUMMY).thenApply(__ -> true); } @Override @@ -94,11 +90,7 @@ public class DefaultPcodeDebuggerMemoryAccess extends DefaultPcodeTraceMemoryAcc if (!isLive()) { return CompletableFuture.completedFuture(false); } - return recorder.writeMemory(address, data) - .thenCompose(__ -> recorder.getTarget().getModel().flushEvents()) - .thenCompose(__ -> recorder.flushTransactions()) - .thenAccept(__ -> platform.getTrace().flushEvents()) - .thenApply(__ -> true); + return target.writeMemoryAsync(address, data).thenApply(__ -> true); } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerRegistersAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerRegistersAccess.java index 0b33d367d0..c68e03e2e4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerRegistersAccess.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerRegistersAccess.java @@ -15,21 +15,17 @@ */ package ghidra.app.plugin.core.debug.service.emulation.data; -import java.util.HashSet; -import java.util.Set; import java.util.concurrent.CompletableFuture; import ghidra.debug.api.emulation.PcodeDebuggerRegistersAccess; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.framework.plugintool.PluginTool; import ghidra.pcode.exec.trace.data.DefaultPcodeTraceRegistersAccess; -import ghidra.program.model.address.*; -import ghidra.program.model.lang.Language; -import ghidra.program.model.lang.Register; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; import ghidra.trace.model.TraceTimeViewport; import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.thread.TraceThread; -import ghidra.util.Msg; /** * The default data-and-debugger access shim for session registers @@ -38,25 +34,25 @@ public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegist implements PcodeDebuggerRegistersAccess, InternalPcodeDebuggerDataAccess { protected final PluginTool tool; - protected final TraceRecorder recorder; + protected final Target target; /** * Construct a shim * * @param tool the tool controlling the session - * @param recorder the target's recorder + * @param target the target * @param platform the associated platform, having the same trace as the recorder * @param snap the associated snap * @param thread the associated thread whose registers to access * @param frame the associated frame, or 0 if not applicable * @param viewport the viewport, set to the same snapshot */ - protected DefaultPcodeDebuggerRegistersAccess(PluginTool tool, TraceRecorder recorder, + protected DefaultPcodeDebuggerRegistersAccess(PluginTool tool, Target target, TracePlatform platform, long snap, TraceThread thread, int frame, TraceTimeViewport viewport) { super(platform, snap, thread, frame, viewport); this.tool = tool; - this.recorder = recorder; + this.target = target; } @Override @@ -70,38 +66,16 @@ public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegist } @Override - public TraceRecorder getRecorder() { - return recorder; + public Target getTarget() { + return target; } @Override public CompletableFuture readFromTargetRegisters(AddressSetView guestView) { - if (guestView.isEmpty() || !isLive()) { + if (!isLive()) { return CompletableFuture.completedFuture(false); } - Set toRead = new HashSet<>(); - Language language = platform.getLanguage(); - for (AddressRange guestRng : guestView) { - Register register = - language.getRegister(guestRng.getMinAddress().getPhysicalAddress(), - (int) guestRng.getLength()); - if (register == null) { - Msg.error(this, "Could not figure register for " + guestRng); - } - else if (!recorder.getRegisterMapper(thread) - .getRegistersOnTarget() - .contains(register)) { - Msg.warn(this, "Register not recognized by target: " + register); - } - else { - toRead.add(register); - } - } - return recorder.captureThreadRegisters(platform, thread, 0, toRead) - .thenCompose(__ -> recorder.getTarget().getModel().flushEvents()) - .thenCompose(__ -> recorder.flushTransactions()) - .thenAccept(__ -> platform.getTrace().flushEvents()) - .thenApply(__ -> true); + return target.readRegistersAsync(platform, thread, frame, guestView).thenApply(__ -> true); } @Override @@ -109,10 +83,7 @@ public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegist if (!isLive()) { return CompletableFuture.completedFuture(false); } - return recorder.writeRegister(platform, thread, frame, address.getPhysicalAddress(), data) - .thenCompose(__ -> recorder.getTarget().getModel().flushEvents()) - .thenCompose(__ -> recorder.flushTransactions()) - .thenAccept(__ -> platform.getTrace().flushEvents()) + return target.writeRegisterAsync(platform, thread, frame, address, data) .thenApply(__ -> true); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/InternalPcodeDebuggerDataAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/InternalPcodeDebuggerDataAccess.java index 877698bd94..1fca7979a7 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/InternalPcodeDebuggerDataAccess.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/InternalPcodeDebuggerDataAccess.java @@ -15,7 +15,7 @@ */ package ghidra.app.plugin.core.debug.service.emulation.data; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.framework.plugintool.PluginTool; import ghidra.lifecycle.Internal; import ghidra.pcode.exec.trace.data.InternalPcodeTraceDataAccess; @@ -25,16 +25,16 @@ import ghidra.trace.model.TraceTimeViewport; public interface InternalPcodeDebuggerDataAccess extends InternalPcodeTraceDataAccess { PluginTool getTool(); - TraceRecorder getRecorder(); + Target getTarget(); default boolean isLive() { - TraceRecorder recorder = getRecorder(); - if (recorder == null || !recorder.isRecording()) { + Target target = getTarget(); + if (target == null || !target.isValid()) { return false; } TraceTimeViewport viewport = getViewport(); for (long s : viewport.getReversedSnaps()) { - if (recorder.getSnap() == s) { + if (target.getSnap() == s) { return true; } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceInternal.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceInternal.java index cd84597602..7c546e1c40 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceInternal.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceInternal.java @@ -18,8 +18,9 @@ package ghidra.app.plugin.core.debug.service.model; import java.io.IOException; import java.util.Collection; -import ghidra.app.plugin.core.debug.event.*; -import ghidra.app.services.*; +import ghidra.app.plugin.core.debug.event.ModelActivatedPluginEvent; +import ghidra.app.services.DebuggerModelService; +import ghidra.app.services.DebuggerTraceManagerService; import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.target.TargetObject; @@ -27,7 +28,6 @@ import ghidra.debug.api.model.DebuggerTargetTraceMapper; import ghidra.debug.api.model.TraceRecorder; import ghidra.framework.plugintool.PluginEvent; import ghidra.lifecycle.Internal; -import ghidra.util.Swing; /** * Specifies additional methods on the model service which are available for internal testing @@ -97,10 +97,7 @@ public interface DebuggerModelServiceInternal extends DebuggerModelService { * * @param focused the focused object */ - default void fireFocusEvent(TargetObject focused) { - Swing.runIfSwingOrRunLater( - () -> firePluginEvent(new ModelObjectFocusedPluginEvent(getName(), focused))); - } + void fireFocusEvent(TargetObject focused); /** * Fire a recorder-advanced event @@ -108,12 +105,7 @@ public interface DebuggerModelServiceInternal extends DebuggerModelService { * @param recorder the recorder that advanced * @param snap the snap to which it advanced */ - default void fireSnapEvent(TraceRecorder recorder, long snap) { - // firePluginEvent uses Swing.runNow, and I can't wait here. - // Especially since the swing thread gets real busy during a step - Swing.runIfSwingOrRunLater( - () -> firePluginEvent(new TraceRecorderAdvancedPluginEvent(getName(), recorder, snap))); - } + void fireSnapEvent(TraceRecorder recorder, long snap); // Impl should inherit from Plugin String getName(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java index 1a9adcaa41..578588a6f9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java @@ -280,6 +280,16 @@ public class DebuggerModelServicePlugin extends Plugin return true; } + @Override + public void fireFocusEvent(TargetObject focused) { + // Nothing to do + } + + @Override + public void fireSnapEvent(TraceRecorder recorder, long snap) { + // Nothing to do + } + @Override public TraceRecorder recordTarget(TargetObject target, DebuggerTargetTraceMapper mapper, ActionSource source) throws IOException { @@ -670,6 +680,5 @@ public class DebuggerModelServicePlugin extends Plugin public CompletableFuture showConnectDialog(DebuggerModelFactory factory) { return doShowConnectDialog(tool, factory, null); } - - + } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java index faadc3dae9..191515f70c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java @@ -43,6 +43,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.DebugProgramAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.DisconnectAllAction; import ghidra.app.plugin.core.debug.utils.BackgroundUtils; import ghidra.app.services.*; +import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; import ghidra.async.AsyncUtils; import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerObjectModel; @@ -51,17 +52,21 @@ import ghidra.dbg.target.TargetThread; import ghidra.debug.api.action.ActionSource; import ghidra.debug.api.model.*; import ghidra.debug.api.model.DebuggerProgramLaunchOffer.PromptMode; +import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.main.AppInfo; import ghidra.framework.main.FrontEndTool; import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.*; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; import ghidra.program.model.listing.ProgramUserData; import ghidra.program.model.util.StringPropertyMap; import ghidra.trace.model.Trace; +import ghidra.trace.model.target.TraceObjectKeyPath; import ghidra.trace.model.thread.TraceThread; import ghidra.util.Msg; +import ghidra.util.Swing; import ghidra.util.datastruct.CollectionChangeListener; import ghidra.util.datastruct.ListenerSet; import ghidra.util.exception.CancelledException; @@ -78,6 +83,7 @@ import ghidra.util.task.TaskMonitor; ProgramClosedPluginEvent.class, }, servicesRequired = { + DebuggerTargetService.class, DebuggerTraceManagerService.class, }, servicesProvided = { @@ -195,11 +201,19 @@ public class DebuggerModelServiceProxyPlugin extends Plugin @Override public void elementAdded(TraceRecorder element) { recorderListeners.fire.elementAdded(element); + Swing.runIfSwingOrRunLater(() -> { + TraceRecorderTarget target = new TraceRecorderTarget(tool, element); + targets.put(element, target); + targetService.publishTarget(target); + }); } @Override public void elementRemoved(TraceRecorder element) { recorderListeners.fire.elementRemoved(element); + Swing.runIfSwingOrRunLater(() -> { + targetService.withdrawTarget(Objects.requireNonNull(targets.get(element))); + }); } @Override @@ -210,10 +224,12 @@ public class DebuggerModelServiceProxyPlugin extends Plugin protected DebuggerModelServicePlugin delegate; - /*@AutoServiceConsumed - private ProgramManager programManager; - @SuppressWarnings("unused") // need strong obj - private AutoService.Wiring autoServiceWiring;*/ + @AutoServiceConsumed + private DebuggerTraceManagerService traceManager; + @AutoServiceConsumed + private DebuggerTargetService targetService; + @SuppressWarnings("unused") + private final AutoService.Wiring autoServiceWiring; // This is not delegated. Each tool can have its own active model. protected DebuggerObjectModel currentModel; @@ -239,8 +255,11 @@ public class DebuggerModelServiceProxyPlugin extends Plugin protected final ListenerSet> recorderListeners = new ListenerSet<>(CollectionChangeListener.of(TraceRecorder.class)); + protected final Map targets = new HashMap<>(); + public DebuggerModelServiceProxyPlugin(PluginTool tool) { super(tool); + autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this); } @Override @@ -471,6 +490,39 @@ public class DebuggerModelServiceProxyPlugin extends Plugin } } + @Override + public void fireFocusEvent(TargetObject focused) { + if (focused == null) { + return; + } + TraceRecorder recorder = getRecorderForSuccessor(focused); + if (recorder == null) { + return; + } + Trace trace = recorder.getTrace(); + if (trace == null) { + return; + } + Trace curTrace = traceManager.getCurrentTrace(); + if (curTrace != null && curTrace != trace) { + return; + } + DebuggerCoordinates focusedCoords = + traceManager.getCurrent().trace(trace).path(TraceObjectKeyPath.of(focused.getPath())); + traceManager.activate(focusedCoords, ActivationCause.SYNC_MODEL); + } + + @Override + public void fireSnapEvent(TraceRecorder recorder, long snap) { + Trace trace = recorder.getTrace(); + DebuggerCoordinates current = traceManager.getCurrentFor(trace); + if (current.getTrace() == null) { + return; + } + DebuggerCoordinates snapCoords = current.snapNoResolve(snap); + traceManager.activate(snapCoords, ActivationCause.FOLLOW_PRESENT); + } + @Override public void processEvent(PluginEvent event) { super.processEvent(event); @@ -594,16 +646,14 @@ public class DebuggerModelServiceProxyPlugin extends Plugin @Override public TraceRecorder recordTargetAndActivateTrace(TargetObject target, - DebuggerTargetTraceMapper mapper, DebuggerTraceManagerService traceManager) + DebuggerTargetTraceMapper mapper, DebuggerTraceManagerService altTraceManager) throws IOException { - return delegate.recordTargetAndActivateTrace(target, mapper, traceManager); + return delegate.recordTargetAndActivateTrace(target, mapper, altTraceManager); } @Override public TraceRecorder recordTargetAndActivateTrace(TargetObject target, DebuggerTargetTraceMapper mapper) throws IOException { - DebuggerTraceManagerService traceManager = - tool.getService(DebuggerTraceManagerService.class); return delegate.recordTargetAndActivateTrace(target, mapper, traceManager); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java index e63fc58a99..4f379c53ba 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java @@ -151,7 +151,7 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder { TargetRegisterContainer descs = bank.getDescriptions(); if (descs == null) { Msg.error(this, "Cannot create mapper, yet: Descriptions is null."); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return initRegMapper(descs).thenAccept(__ -> { recorder.getListeners().fire.registerBankMapped(recorder); @@ -379,7 +379,7 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder { "All given registers must be recognized by the target"); } if (values.isEmpty()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } Map tVals = values.entrySet().stream().map(ent -> { if (ent.getKey() != ent.getValue().getRegister()) { @@ -409,18 +409,18 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder { protected CompletableFuture readAlignedConditionally(String name, Address targetAddress) { if (targetAddress == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } Address traceAddress = objectManager.getMemoryMapper().targetToTrace(targetAddress); if (traceAddress == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } if (!checkReadCondition(traceAddress)) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } AddressRange targetRange = threadMemory.alignAndLimitToFloor(targetAddress, 1); if (targetRange == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } TimedMsg.debug(this, " Reading memory at " + name + " (" + targetAddress + " -> " + targetRange + ")"); @@ -535,7 +535,7 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder { Set toRead = new LinkedHashSet<>(); synchronized (recorder) { if (regMapper == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } bank = regs.get(0); pc = pcReg; @@ -545,18 +545,18 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder { toRead.add(pc); } if (bank == null || pc == null || sp == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } System.err.println("URM:" + getTargetThread()); TimedMsg.info(this, "Reading " + toRead + " of " + getTargetThread()); return bank.readRegisters(toRead).thenCompose(vals -> { synchronized (recorder) { if (memoryManager == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } if (threadMemory == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } AsyncFence fence = new AsyncFence(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java index cf2ccd2d37..b5d3e69c41 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java @@ -47,6 +47,7 @@ import ghidra.trace.model.modules.TraceModule; import ghidra.trace.model.modules.TraceSection; import ghidra.trace.model.stack.TraceStackFrame; import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObjectKeyPath; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.util.Msg; @@ -110,6 +111,11 @@ public class DefaultTraceRecorder implements TraceRecorder { return null; } + @Override + public TargetObject getTargetObject(TraceObjectKeyPath path) { + return target.getModel().getModelObject(path.getKeyList()); + } + @Override public TraceObject getTraceObject(TargetObject obj) { return null; @@ -279,7 +285,7 @@ public class DefaultTraceRecorder implements TraceRecorder { @Override public CompletableFuture readMemoryBlocks(AddressSetView set, TaskMonitor monitor) { if (set.isEmpty()) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return memoryRecorder.captureProcessMemory(set, monitor); } @@ -288,7 +294,7 @@ public class DefaultTraceRecorder implements TraceRecorder { public CompletableFuture captureDataTypes(TargetDataTypeNamespace namespace, TaskMonitor monitor) { if (!valid) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return datatypeRecorder.captureDataTypes(namespace, monitor); } @@ -298,7 +304,7 @@ public class DefaultTraceRecorder implements TraceRecorder { TargetModule targetModule = getTargetModule(module); if (targetModule == null) { Msg.error(this, "Module " + module + " is not loaded"); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return datatypeRecorder.captureDataTypes(targetModule, monitor); } @@ -307,7 +313,7 @@ public class DefaultTraceRecorder implements TraceRecorder { public CompletableFuture captureSymbols(TargetSymbolNamespace namespace, TaskMonitor monitor) { if (!valid) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return symbolRecorder.captureSymbols(namespace, monitor); } @@ -317,7 +323,7 @@ public class DefaultTraceRecorder implements TraceRecorder { TargetModule targetModule = getTargetModule(module); if (targetModule == null) { Msg.error(this, "Module " + module + " is not loaded"); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return symbolRecorder.captureSymbols(targetModule, monitor); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceEventListener.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceEventListener.java index 26ccfc8ac5..dc6ff48b12 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceEventListener.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceEventListener.java @@ -66,7 +66,7 @@ public class TraceEventListener extends AnnotatedDebuggerAttributeListener { public CompletableFuture init() { DebuggerObjectModel model = target.getModel(); model.addModelListener(queue.in, true); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } private boolean successor(TargetObject ref) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java new file mode 100644 index 0000000000..5f82570935 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java @@ -0,0 +1,551 @@ +/* ### + * 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.service.model; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.function.Predicate; + +import docking.ActionContext; +import ghidra.app.context.ProgramLocationActionContext; +import ghidra.app.plugin.core.debug.gui.model.DebuggerObjectActionContext; +import ghidra.app.plugin.core.debug.service.target.AbstractTarget; +import ghidra.app.services.DebuggerConsoleService; +import ghidra.app.services.DebuggerTraceManagerService; +import ghidra.async.AsyncFence; +import ghidra.async.AsyncUtils; +import ghidra.dbg.DebuggerObjectModel; +import ghidra.dbg.target.*; +import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind; +import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; +import ghidra.dbg.target.TargetMethod.ParameterDescription; +import ghidra.dbg.target.TargetMethod.TargetParameterMap; +import ghidra.dbg.target.TargetSteppable.TargetStepKind; +import ghidra.dbg.util.PathMatcher; +import ghidra.dbg.util.PathPredicates; +import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.ActionName; +import ghidra.debug.api.tracemgr.DebuggerCoordinates; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.RegisterValue; +import ghidra.trace.model.Trace; +import ghidra.trace.model.breakpoint.TraceBreakpoint; +import ghidra.trace.model.breakpoint.TraceBreakpointKind; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.stack.TraceStackFrame; +import ghidra.trace.model.target.*; +import ghidra.trace.model.thread.TraceThread; +import ghidra.util.Msg; +import ghidra.util.task.TaskMonitor; + +public class TraceRecorderTarget extends AbstractTarget { + private final TraceRecorder recorder; + + protected static boolean isSameFocus(DebuggerCoordinates prev, DebuggerCoordinates resolved) { + if (!Objects.equals(prev.getObject(), resolved.getObject())) { + return false; + } + if (!Objects.equals(prev.getFrame(), resolved.getFrame())) { + return false; + } + if (!Objects.equals(prev.getThread(), resolved.getThread())) { + return false; + } + if (!Objects.equals(prev.getTrace(), resolved.getTrace())) { + return false; + } + return true; + } + + protected static boolean checkTargetActivation(DebuggerCoordinates prev, + DebuggerCoordinates resolved) { + if (!resolved.isAlive()) { + return false; + } + if (isSameFocus(prev, resolved)) { + return false; + } + return true; + } + + public TraceRecorderTarget(PluginTool tool, TraceRecorder recorder) { + super(tool); + this.recorder = recorder; + } + + @Override + public boolean isValid() { + return recorder.isRecording(); + } + + protected T findObjectInContext(ActionContext context, + Class iface) { + if (context instanceof DebuggerObjectActionContext ctx) { + List values = ctx.getObjectValues(); + if (values.size() != 1) { + return null; + } + TraceObjectValue single = values.get(0); + if (!single.isObject()) { + return null; + } + TraceObject suitable = single.getChild().querySuitableTargetInterface(iface); + if (suitable == null) { + return null; + } + return iface.cast(recorder.getTargetObject(suitable)); + } + return null; + } + + protected T findObjectInTrace(ActionContext context, Class iface) { + DebuggerTraceManagerService traceManager = + tool.getService(DebuggerTraceManagerService.class); + TraceObject focus = traceManager.getCurrentObject(); + if (focus == null) { + return null; + } + TraceObject suitable = focus.querySuitableTargetInterface(iface); + if (suitable == null) { + return null; + } + return iface.cast(recorder.getTargetObject(suitable)); + } + + protected T findObjectInRecorder(ActionContext context, + Class iface) { + if (!isValid()) { + return null; + } + TargetObject focus = recorder.getFocus(); + if (focus == null) { + return null; + } + return focus.getCachedSuitable(iface); + } + + protected T findObject(ActionContext context, Class iface) { + T object = findObjectInContext(context, iface); + if (object != null) { + return object; + } + object = findObjectInTrace(context, iface); + if (object != null) { + return object; + } + return findObjectInRecorder(context, iface); + } + + private Map collectArgumentsReqAddr(TargetParameterMap params, + Address address) { + // The only required non-defaulted argument allowed must be an Address + // There must be an Address parameter + ParameterDescription addrParam = null; + for (ParameterDescription p : params.values()) { + if (p.type == Address.class) { + if (addrParam != null) { + return null; + } + addrParam = p; + } + else if (p.required && p.defaultValue == null) { + return null; + } + } + if (addrParam == null) { + return null; + } + return Map.of(addrParam.name, address); + } + + private record MethodWithArgs(TargetMethod method, Map arguments) { + } + + private List findAddressMethods(ProgramLocationActionContext context) { + Address address = findAddress(context); + if (address == null) { + return List.of(); + } + TargetObject object = findObject(context, TargetObject.class); + if (object == null) { + return List.of(); + } + + List result = new ArrayList<>(); + PathPredicates matcher = object.getModel() + .getRootSchema() + .matcherForSuitable(TargetMethod.class, object.getPath()); + for (TargetObject obj : matcher.getCachedSuccessors(object.getModel().getModelRoot()) + .values()) { + if (!(obj instanceof TargetMethod method)) { + continue; + } + Map arguments = + collectArgumentsReqAddr(method.getParameters(), address); + if (arguments == null) { + continue; + } + result.add(new MethodWithArgs(method, arguments)); + } + return result; + } + + private static String getDisplay(TargetMethod method) { + String display = method.getDisplay(); + if (display != null) { + return display; + } + return method.getName(); + } + + private ActionEntry makeEntry(TargetMethod method, Map arguments) { + return new ActionEntry(method.getDisplay(), null, null, false, () -> true, () -> { + return method.invoke(arguments).thenAccept(result -> { + DebuggerConsoleService consoleService = + tool.getService(DebuggerConsoleService.class); + if (consoleService != null && method.getReturnType() != Void.class) { + consoleService.log(null, getDisplay(method) + " returned " + result); + } + }); + }); + } + + @Override + public Map collectAddressActions(ProgramLocationActionContext context) { + Map result = new HashMap<>(); + for (MethodWithArgs mwa : findAddressMethods(context)) { + result.put(mwa.method.getJoinedPath("."), makeEntry(mwa.method, mwa.arguments)); + } + return result; + } + + protected Map collectIfaceActions( + ActionContext context, Class iface, String display, ActionName name, + String description, Predicate enabled, Function> action) { + T object = findObject(context, iface); + if (object == null) { + return Map.of(); + } + return Map.of(display, new ActionEntry(display, name, description, false, + () -> enabled.test(object), () -> action.apply(object))); + } + + private TargetExecutionState getStateOf(TargetObject object) { + TargetExecutionStateful stateful = object.getCachedSuitable(TargetExecutionStateful.class); + return stateful == null ? null : stateful.getExecutionState(); + } + + private Predicate stateNullOr( + Predicate predicate) { + return object -> { + TargetExecutionState state = getStateOf(object); + return state == null || predicate.test(state); + }; + } + + @Override + protected Map collectResumeActions(ActionContext context) { + return collectIfaceActions(context, TargetResumable.class, "Resume", + ActionName.RESUME, "Resume, i.e., go or continue execution of the target", + stateNullOr(TargetExecutionState::isStopped), TargetResumable::resume); + } + + @Override + protected Map collectInterruptActions(ActionContext context) { + return collectIfaceActions(context, TargetInterruptible.class, "Interrupt", + ActionName.INTERRUPT, "Interrupt, i.e., suspend, the target", + stateNullOr(TargetExecutionState::isRunning), TargetInterruptible::interrupt); + } + + @Override + protected Map collectKillActions(ActionContext context) { + return collectIfaceActions(context, TargetKillable.class, "Kill", + ActionName.KILL, "Kill, i.e., forcibly terminate the target", + stateNullOr(TargetExecutionState::isAlive), TargetKillable::kill); + } + + @Override + protected Map collectStepIntoActions(ActionContext context) { + return collectIfaceActions(context, TargetSteppable.class, "Step Into", + ActionName.STEP_INTO, "Step the target a single instruction, descending into calls", + stateNullOr(TargetExecutionState::isStopped), + steppable -> steppable.step(TargetStepKind.INTO)); + } + + @Override + protected Map collectStepOverActions(ActionContext context) { + return collectIfaceActions(context, TargetSteppable.class, "Step Over", + ActionName.STEP_OVER, "Step the target a single instruction, without following calls", + stateNullOr(TargetExecutionState::isStopped), + steppable -> steppable.step(TargetStepKind.OVER)); + } + + @Override + protected Map collectStepOutActions(ActionContext context) { + return collectIfaceActions(context, TargetSteppable.class, "Step Out", + ActionName.STEP_OUT, "Step the target until it completes the current frame", + stateNullOr(TargetExecutionState::isStopped), + steppable -> steppable.step(TargetStepKind.FINISH)); + } + + @Override + protected Map collectStepExtActions(ActionContext context) { + return collectIfaceActions(context, TargetSteppable.class, "Step Last", + ActionName.STEP_EXT, "Step the target in a target-defined way", + stateNullOr(TargetExecutionState::isStopped), + steppable -> steppable.step(TargetStepKind.EXTENDED)); + } + + @Override + public Trace getTrace() { + return recorder.getTrace(); + } + + @Override + public long getSnap() { + return recorder.getSnap(); + } + + @Override + public TargetExecutionState getThreadExecutionState(TraceThread thread) { + return recorder.getTargetThreadState(thread); + } + + @Override + public boolean isSupportsFocus() { + return recorder.isSupportsFocus(); + } + + @Override + public TraceObjectKeyPath getFocus() { + TargetObject object = recorder.getFocus(); + if (object == null) { + return null; + } + return TraceObjectKeyPath.of(object.getPath()); + } + + protected TargetObject toTargetObject(DebuggerCoordinates coords) { + TraceObject obj = coords.getObject(); + if (obj != null) { + TargetObject object = + recorder.getTarget().getSuccessor(obj.getCanonicalPath().getKeyList()); + if (object != null) { + return object; + } + } + TargetStackFrame frame = + recorder.getTargetStackFrame(coords.getThread(), coords.getFrame()); + if (frame != null) { + return frame; + } + TargetThread thread = recorder.getTargetThread(coords.getThread()); + if (thread != null) { + return thread; + } + return recorder.getTarget(); + } + + @Override + public CompletableFuture activateAsync(DebuggerCoordinates prev, + DebuggerCoordinates coords) { + if (!checkTargetActivation(prev, coords)) { + return AsyncUtils.nil(); + } + + if (!recorder.isRecording() || recorder.getSnap() != coords.getSnap() || + !coords.getTime().isSnapOnly()) { + return AsyncUtils.nil(); + } + TargetObject obj = toTargetObject(coords); + if (obj == null) { + return AsyncUtils.nil(); + } + return recorder.requestActivation(obj).thenApply(__ -> null); + } + + @Override + public TraceThread getThreadForSuccessor(TraceObjectKeyPath path) { + if (path == null) { + return null; + } + TargetObject object = recorder.getTargetObject(path); + if (object == null) { + return null; + } + return recorder.getTraceThreadForSuccessor(object); + } + + @Override + public TraceStackFrame getStackFrameForSuccessor(TraceObjectKeyPath path) { + if (path == null) { + return null; + } + TargetObject object = recorder.getTargetObject(path); + if (object == null) { + return null; + } + return recorder.getTraceStackFrameForSuccessor(object); + } + + @Override + public CompletableFuture invalidateMemoryCachesAsync() { + TargetObject target = recorder.getTarget(); + DebuggerObjectModel model = target.getModel(); + model.invalidateAllLocalCaches(); + PathMatcher memMatcher = target.getSchema().searchFor(TargetMemory.class, true); + Collection memories = memMatcher.getCachedSuccessors(target).values(); + CompletableFuture[] requests = memories.stream() + .map(TargetObject::invalidateCaches) + .toArray(CompletableFuture[]::new); + return CompletableFuture.allOf(requests); + } + + @Override + public CompletableFuture readMemoryAsync(AddressSetView set, TaskMonitor monitor) { + return recorder.readMemoryBlocks(set, monitor) + .thenCompose(__ -> recorder.getTarget().getModel().flushEvents()) + .thenCompose(__ -> recorder.flushTransactions()) + .thenAccept(__ -> recorder.getTrace().flushEvents()); + } + + @Override + public CompletableFuture readRegistersAsync(TracePlatform platform, TraceThread thread, + int frame, Set registers) { + if (registers.isEmpty()) { + return AsyncUtils.nil(); + } + return recorder.captureThreadRegisters(platform, thread, frame, registers) + .thenCompose(__ -> recorder.getTarget().getModel().flushEvents()) + .thenCompose(__ -> recorder.flushTransactions()) + .thenAccept(__ -> platform.getTrace().flushEvents()); + } + + @Override + public boolean isVariableExists(TracePlatform platform, TraceThread thread, int frame, + Address address, int length) { + return recorder.isVariableOnTarget(platform, thread, frame, address, length); + } + + @Override + public CompletableFuture writeMemoryAsync(Address address, byte[] data) { + return recorder.writeMemory(address, data) + .thenCompose(__ -> recorder.getTarget().getModel().flushEvents()) + .thenCompose(__ -> recorder.flushTransactions()) + .thenAccept(__ -> recorder.getTrace().flushEvents()); + } + + @Override + public CompletableFuture writeRegisterAsync(TracePlatform platform, TraceThread thread, + int frame, RegisterValue value) { + return recorder.writeThreadRegisters(platform, thread, frame, + Map.of(value.getRegister(), value)) + .thenCompose(__ -> recorder.getTarget().getModel().flushEvents()) + .thenCompose(__ -> recorder.flushTransactions()) + .thenAccept(__ -> recorder.getTrace().flushEvents()); + } + + @Override + public CompletableFuture writeRegisterAsync(TracePlatform platform, TraceThread thread, + int frame, Address address, byte[] data) { + return recorder.writeRegister(platform, thread, frame, address, data) + .thenCompose(__ -> recorder.getTarget().getModel().flushEvents()) + .thenCompose(__ -> recorder.flushTransactions()) + .thenAccept(__ -> recorder.getTrace().flushEvents()); + } + + @Override + public CompletableFuture writeVariableAsync(TracePlatform platform, TraceThread thread, + int frame, Address address, byte[] data) { + return recorder.writeVariable(platform, thread, frame, address, data); + } + + @Override + public CompletableFuture placeBreakpointAsync(AddressRange range, + Set kinds, String condition, String commands) { + if (condition != null && !condition.isBlank()) { + Msg.warn(this, "breakpoint condition not supported by recorder-based targets"); + } + if (commands != null && !commands.isBlank()) { + Msg.warn(this, "breakpoint commands not supported by recorder-based targets"); + } + Set tKinds = + TraceRecorder.traceToTargetBreakpointKinds(kinds); + AddressRange targetRange = recorder.getMemoryMapper().traceToTarget(range); + AsyncFence fence = new AsyncFence(); + for (TargetBreakpointSpecContainer cont : recorder.collectBreakpointContainers(null)) { + Set stKinds = new LinkedHashSet<>(tKinds); + stKinds.retainAll(cont.getSupportedBreakpointKinds()); + fence.include(cont.placeBreakpoint(targetRange, stKinds)); + } + return fence.ready(); + } + + @Override + public Set getSupportedBreakpointKinds() { + return recorder.getSupportedBreakpointKinds(); + } + + @Override + public boolean isBreakpointValid(TraceBreakpoint breakpoint) { + return recorder.getTargetBreakpoint(breakpoint) != null; + } + + @Override + public CompletableFuture deleteBreakpointAsync(TraceBreakpoint breakpoint) { + TargetBreakpointLocation loc = recorder.getTargetBreakpoint(breakpoint); + if (loc == null) { + Msg.warn(this, "Breakpoint not valid on target: " + loc); + return AsyncUtils.nil(); + } + if (loc instanceof TargetDeletable del) { + return del.delete(); + } + TargetBreakpointSpec spec = loc.getSpecification(); + if (spec instanceof TargetDeletable del) { + return del.delete(); + } + Msg.warn(this, "Neither location nor specification for breakpoint is deletable: " + loc); + return AsyncUtils.nil(); + } + + @Override + public CompletableFuture toggleBreakpointAsync(TraceBreakpoint breakpoint, + boolean enabled) { + TargetBreakpointLocation loc = recorder.getTargetBreakpoint(breakpoint); + if (loc == null) { + Msg.warn(this, "Breakpoint not valid on target: " + loc); + return AsyncUtils.nil(); + } + if (loc instanceof TargetTogglable tog) { + return tog.toggle(enabled); + } + TargetBreakpointSpec spec = loc.getSpecification(); + return spec.toggle(enabled); + } + + @Override + public CompletableFuture disconnectAsync() { + return recorder.getTarget() + .getModel() + .close() + .orTimeout(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java index e470d129ac..c5f9788b76 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java @@ -52,6 +52,7 @@ import ghidra.trace.model.modules.*; import ghidra.trace.model.stack.TraceObjectStackFrame; import ghidra.trace.model.stack.TraceStackFrame; import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObjectKeyPath; import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; @@ -294,7 +295,7 @@ public class ObjectBasedTraceRecorder implements TraceRecorder { // TODO: Make this method synchronous? timeRecorder.createSnapshot("Started recording " + target.getModel(), null, null); target.getModel().addModelListener(listenerForRecord, true); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } @Override @@ -359,6 +360,11 @@ public class ObjectBasedTraceRecorder implements TraceRecorder { return objectRecorder.toTrace(obj); } + @Override + public TargetObject getTargetObject(TraceObjectKeyPath path) { + return target.getModel().getModelObject(path.getKeyList()); + } + @Override public TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt) { return objectRecorder.getTargetInterface(bpt, TraceObjectBreakpointLocation.class, @@ -532,7 +538,7 @@ public class ObjectBasedTraceRecorder implements TraceRecorder { * what communicates that convention? */ if (regContainer == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return regContainer.resync(); } @@ -592,7 +598,7 @@ public class ObjectBasedTraceRecorder implements TraceRecorder { int frameLevel, Map values) { TargetRegisterContainer regContainer = getTargetRegisterContainer(thread, frameLevel); if (regContainer == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } Map> writesByBank = new HashMap<>(); for (RegisterValue rv : values.values()) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/AbstractTarget.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/AbstractTarget.java new file mode 100644 index 0000000000..cda72ab07c --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/AbstractTarget.java @@ -0,0 +1,393 @@ +/* ### + * 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.service.target; + +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.*; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import docking.ActionContext; +import ghidra.app.context.NavigatableActionContext; +import ghidra.app.context.ProgramLocationActionContext; +import ghidra.app.nav.Navigatable; +import ghidra.app.services.*; +import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; +import ghidra.async.AsyncUtils; +import ghidra.debug.api.target.ActionName; +import ghidra.debug.api.target.Target; +import ghidra.debug.api.tracemgr.DebuggerCoordinates; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.utils.Utils; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.RegisterValue; +import ghidra.program.util.MarkerLocation; +import ghidra.program.util.ProgramLocation; +import ghidra.trace.model.TraceSpan; +import ghidra.trace.model.breakpoint.TraceBreakpoint; +import ghidra.trace.model.breakpoint.TraceBreakpointKind; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.program.TraceProgramView; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.util.TraceRegisterUtils; +import ghidra.util.Msg; +import ghidra.util.Swing; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public abstract class AbstractTarget implements Target { + protected final PluginTool tool; + + public AbstractTarget(PluginTool tool) { + this.tool = tool; + } + + private Address staticToDynamicAddress(ProgramLocation location) { + DebuggerStaticMappingService mappingService = + tool.getService(DebuggerStaticMappingService.class); + if (mappingService == null) { + return null; + } + ProgramLocation dynLoc = mappingService + .getDynamicLocationFromStatic(getTrace().getProgramView(), location); + if (dynLoc == null) { + return null; + } + return dynLoc.getByteAddress(); + } + + protected Address findAddress(Navigatable nav) { + if (nav.isDynamic()) { + return nav.getLocation().getAddress(); + } + return staticToDynamicAddress(nav.getLocation()); + } + + protected Address findAddress(MarkerLocation location) { + if (location.getProgram() instanceof TraceProgramView) { + return location.getAddr(); + } + return staticToDynamicAddress( + new ProgramLocation(location.getProgram(), location.getAddr())); + } + + protected Address findAddress(ActionContext context) { + if (context instanceof NavigatableActionContext ctx) { + Address address = findAddress(ctx.getNavigatable()); + if (address != null) { + return address; + } + } + if (context.getContextObject() instanceof MarkerLocation ml) { + Address address = findAddress(ml); + if (address != null) { + return address; + } + } + DebuggerListingService listingService = tool.getService(DebuggerListingService.class); + if (listingService != null) { + Address address = findAddress(listingService.getNavigatable()); + if (address != null) { + return address; + } + } + CodeViewerService codeViewerService = tool.getService(CodeViewerService.class); + if (codeViewerService != null) { + Address address = findAddress(codeViewerService.getNavigatable()); + if (address != null) { + return address; + } + } + return null; + } + + protected AddressRange singleRange(AddressSetView set) { + if (set.getNumAddressRanges() != 1) { + return null; + } + return set.getFirstRange(); + } + + protected AddressRange findRange(Navigatable nav) { + if (nav.isDynamic()) { + return singleRange(nav.getSelection()); + } + DebuggerStaticMappingService mappingService = + tool.getService(DebuggerStaticMappingService.class); + if (mappingService == null) { + return null; + } + + long snap = getSnap(); + AddressSet result = new AddressSet(); + Map> mapped = + mappingService.getOpenMappedViews(nav.getProgram(), nav.getSelection()); + for (Entry> ent : mapped.entrySet()) { + TraceSpan span = ent.getKey(); + if (span.getTrace() != getTrace() || span.getSpan().contains(snap)) { + continue; + } + for (MappedAddressRange mar : ent.getValue()) { + result.add(mar.getDestinationAddressRange()); + } + } + if (result.isEmpty()) { + return null; + } + + return singleRange(result); + } + + protected AddressRange findRange(ActionContext context) { + if (context instanceof NavigatableActionContext ctx) { + AddressRange range = findRange(ctx.getNavigatable()); + if (range != null) { + return range; + } + } + DebuggerListingService listingService = tool.getService(DebuggerListingService.class); + if (listingService != null) { + AddressRange range = findRange(listingService.getNavigatable()); + if (range != null) { + return range; + } + } + CodeViewerService codeViewerService = tool.getService(CodeViewerService.class); + if (codeViewerService != null) { + AddressRange range = findRange(codeViewerService.getNavigatable()); + if (range != null) { + return range; + } + } + return null; + } + + protected abstract Map collectAddressActions( + ProgramLocationActionContext context); + + protected Map collectAllActions(ActionContext context) { + return Stream.of( + collectResumeActions(context), + collectInterruptActions(context), + collectKillActions(context), + collectStepIntoActions(context), + collectStepOverActions(context), + collectStepOutActions(context), + collectStepExtActions(context)) + .flatMap(m -> m.entrySet().stream()) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + } + + protected abstract Map collectResumeActions(ActionContext context); + + protected abstract Map collectInterruptActions(ActionContext context); + + protected abstract Map collectKillActions(ActionContext context); + + protected abstract Map collectStepIntoActions(ActionContext context); + + protected abstract Map collectStepOverActions(ActionContext context); + + protected abstract Map collectStepOutActions(ActionContext context); + + protected abstract Map collectStepExtActions(ActionContext context); + + @Override + public Map collectActions(ActionName name, ActionContext context) { + if (name == null) { + if (context instanceof ProgramLocationActionContext ctx) { + return collectAddressActions(ctx); + } + return collectAllActions(context); + } + else if (ActionName.RESUME.equals(name)) { + return collectResumeActions(context); + } + else if (ActionName.INTERRUPT.equals(name)) { + return collectInterruptActions(context); + } + else if (ActionName.KILL.equals(name)) { + return collectKillActions(context); + } + else if (ActionName.STEP_INTO.equals(name)) { + return collectStepIntoActions(context); + } + else if (ActionName.STEP_OVER.equals(name)) { + return collectStepOverActions(context); + } + else if (ActionName.STEP_OUT.equals(name)) { + return collectStepOutActions(context); + } + else if (ActionName.STEP_EXT.equals(name)) { + return collectStepExtActions(context); + } + Msg.warn(this, "Unrecognized action name: " + name); + return Map.of(); + } + + protected static T doSync(String name, Supplier> supplier) + throws InterruptedException, ExecutionException { + if (Swing.isSwingThread()) { + throw new AssertionError("Cannot " + name + " using Swing thread. Use a Task."); + } + CompletableFuture future = supplier.get(); + return future.orTimeout(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS).get(); + } + + protected static T getSync(String name, Supplier> supplier) { + try { + return doSync(name, supplier); + } + catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + protected static void runSync(String name, Supplier> supplier) { + getSync(name, supplier); + } + + protected static T doSyncMonitored(TaskMonitor monitor, String name, + Supplier> supplier) + throws CancelledException, InterruptedException, ExecutionException { + monitor.checkCancelled(); + try { + return doSync(name, supplier); + } + catch (ExecutionException e) { + if (e.getCause() instanceof CancelledException ce) { + throw ce; + } + throw e; + } + } + + protected static T getSyncMonitored(TaskMonitor monitor, String name, + Supplier> supplier) throws CancelledException { + try { + return doSyncMonitored(monitor, name, supplier); + } + catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + protected static void runSyncMonitored(TaskMonitor monitor, String name, + Supplier> supplier) throws CancelledException { + getSyncMonitored(monitor, name, supplier); + } + + @Override + public void activate(DebuggerCoordinates prev, DebuggerCoordinates coords) { + runSync("activate", () -> activateAsync(prev, coords)); + } + + @Override + public void invalidateMemoryCaches() { + runSync("invalidate memory caches", () -> invalidateMemoryCachesAsync()); + } + + @Override + public void readMemory(AddressSetView set, TaskMonitor monitor) throws CancelledException { + runSyncMonitored(monitor, "read memory", () -> readMemoryAsync(set, monitor)); + } + + @Override + public void writeMemory(Address address, byte[] data) { + runSync("write memory", () -> writeMemoryAsync(address, data)); + } + + @Override + public void readRegisters(TracePlatform platform, TraceThread thread, + int frame, Set registers) { + runSync("read registers", + () -> readRegistersAsync(platform, thread, frame, registers)); + } + + @Override + public CompletableFuture readRegistersAsync(TracePlatform platform, TraceThread thread, + int frame, AddressSetView guestSet) { + if (guestSet.isEmpty()) { + return AsyncUtils.nil(); + } + Set registers = + TraceRegisterUtils.registersIntersecting(platform.getLanguage(), guestSet); + return readRegistersAsync(platform, thread, frame, registers); + } + + @Override + public void readRegisters(TracePlatform platform, TraceThread thread, + int frame, AddressSetView guestSet) { + runSync("read registers", + () -> readRegistersAsync(platform, thread, frame, guestSet)); + } + + @Override + public void writeRegister(TracePlatform platform, TraceThread thread, int frame, + RegisterValue value) { + runSync("write register", () -> writeRegisterAsync(platform, thread, frame, value)); + } + + @Override + public CompletableFuture writeRegisterAsync(TracePlatform platform, TraceThread thread, + int frame, Address address, byte[] data) { + Register register = platform.getLanguage().getRegister(address, data.length); + if (register == null) { + throw new IllegalArgumentException( + "Cannot identify the (single) register to write: " + address); + } + RegisterValue value = new RegisterValue(register, + Utils.bytesToBigInteger(data, data.length, register.isBigEndian(), false)); + return writeRegisterAsync(platform, thread, frame, value); + } + + @Override + public void writeRegister(TracePlatform platform, TraceThread thread, int frame, + Address address, byte[] data) { + runSync("write register", () -> writeRegisterAsync(platform, thread, frame, address, data)); + } + + @Override + public void writeVariable(TracePlatform platform, TraceThread thread, int frame, + Address address, byte[] data) { + runSync("write variable", () -> writeVariableAsync(platform, thread, frame, address, data)); + } + + @Override + public void placeBreakpoint(AddressRange range, Set kinds, + String condition, String commands) { + runSync("place breakpoint", () -> placeBreakpointAsync(range, kinds, condition, commands)); + } + + @Override + public void deleteBreakpoint(TraceBreakpoint breakpoint) { + runSync("delete breakpoint", () -> deleteBreakpointAsync(breakpoint)); + } + + @Override + public void toggleBreakpoint(TraceBreakpoint breakpoint, boolean enabled) { + String msg = enabled ? "enable breakpoint" : "disable breakpoint"; + runSync(msg, () -> toggleBreakpointAsync(breakpoint, enabled)); + } + + @Override + public void disconnect() { + runSync("disconnect", this::disconnectAsync); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/DebuggerTargetServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/DebuggerTargetServicePlugin.java new file mode 100644 index 0000000000..bca06c9c99 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/DebuggerTargetServicePlugin.java @@ -0,0 +1,94 @@ +/* ### + * 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.service.target; + +import java.util.*; + +import ghidra.app.plugin.PluginCategoryNames; +import ghidra.app.plugin.core.debug.DebuggerPluginPackage; +import ghidra.app.services.DebuggerTargetService; +import ghidra.debug.api.target.Target; +import ghidra.debug.api.target.TargetPublicationListener; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.trace.model.Trace; +import ghidra.util.datastruct.ListenerSet; + +@PluginInfo( + shortDescription = "Debugger targets manager service", + description = "Maintains a collection of published targets and notifies listeners of changes.", + category = PluginCategoryNames.DEBUGGER, + packageName = DebuggerPluginPackage.NAME, + status = PluginStatus.RELEASED, + servicesProvided = { + DebuggerTargetService.class, + }) +public class DebuggerTargetServicePlugin extends Plugin implements DebuggerTargetService { + + public DebuggerTargetServicePlugin(PluginTool tool) { + super(tool); + } + + private final Map targets = new HashMap<>(); + private final ListenerSet listeners = + new ListenerSet<>(TargetPublicationListener.class); + + @Override + public void publishTarget(Target target) { + boolean notify; + synchronized (targets) { + notify = targets.put(target.getTrace(), target) != target; + } + if (notify) { + listeners.fire.targetPublished(target); + } + } + + @Override + public void withdrawTarget(Target target) { + boolean notify; + synchronized (targets) { + notify = targets.remove(target.getTrace()) == target; + } + if (notify) { + listeners.fire.targetWithdrawn(target); + } + } + + @Override + public List getPublishedTargets() { + synchronized (targets) { + return List.copyOf(targets.values()); + } + } + + @Override + public Target getTarget(Trace trace) { + synchronized (targets) { + return targets.get(trace); + } + } + + @Override + public void addTargetPublicationListener(TargetPublicationListener listener) { + listeners.add(listener); + } + + @Override + public void removeTargetPublicationListener(TargetPublicationListener listener) { + listeners.remove(listener); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java index 3e31c7d659..c618a4f9b8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java @@ -34,10 +34,11 @@ import ghidra.app.services.*; import ghidra.app.services.DebuggerControlService.ControlModeChangeListener; import ghidra.async.*; import ghidra.async.AsyncConfigFieldCodec.BooleanAsyncConfigFieldCodec; -import ghidra.dbg.target.*; +import ghidra.dbg.target.TargetObject; import ghidra.debug.api.control.ControlMode; -import ghidra.debug.api.model.TraceRecorder; import ghidra.debug.api.platform.DebuggerPlatformMapper; +import ghidra.debug.api.target.Target; +import ghidra.debug.api.target.TargetPublicationListener; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.client.ClientUtil; import ghidra.framework.client.NotConnectedException; @@ -56,7 +57,6 @@ import ghidra.trace.model.Trace.TraceThreadChangeType; import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.program.TraceVariableSnapProgramView; -import ghidra.trace.model.stack.TraceStackFrame; import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObjectKeyPath; import ghidra.trace.model.thread.TraceThread; @@ -64,7 +64,6 @@ import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.util.*; import ghidra.util.database.DomainObjectLockHold; -import ghidra.util.datastruct.CollectionChangeListener; import ghidra.util.exception.*; import ghidra.util.task.*; @@ -81,8 +80,6 @@ import ghidra.util.task.*; eventsConsumed = { TraceActivatedPluginEvent.class, TraceClosedPluginEvent.class, - ModelObjectFocusedPluginEvent.class, - TraceRecorderAdvancedPluginEvent.class, DebuggerPlatformPluginEvent.class, }, servicesRequired = {}, @@ -113,10 +110,14 @@ public class DebuggerTraceManagerServicePlugin extends Plugin } private void threadAdded(TraceThread thread) { - TraceRecorder recorder = current.getRecorder(); - if (supportsFocus(recorder)) { + Target target = current.getTarget(); + if (supportsFocus(target)) { // TODO: Same for stack frame? I can't imagine it's as common as this.... - if (thread == recorder.getTraceThreadForSuccessor(recorder.getFocus())) { + TraceObjectKeyPath focus = target.getFocus(); + if (focus == null) { + return; + } + if (thread == target.getThreadForSuccessor(focus)) { activate(current.thread(thread), ActivationCause.SYNC_MODEL); } return; @@ -143,8 +144,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin } private void objectCreated(TraceObject object) { - TraceRecorder recorder = current.getRecorder(); - if (supportsFocus(recorder)) { + Target target = current.getTarget(); + if (supportsFocus(target)) { return; } if (current.getTrace() != trace) { @@ -194,36 +195,36 @@ public class DebuggerTraceManagerServicePlugin extends Plugin } // TODO: This is a bit out of this manager's bounds, but acceptable for now. - class ForRecordersListener implements CollectionChangeListener { + class ForTargetsListener implements TargetPublicationListener { @Override - public void elementAdded(TraceRecorder recorder) { - Swing.runLater(() -> updateCurrentRecorder()); + public void targetPublished(Target target) { + Swing.runLater(() -> updateCurrentTarget()); } - public CompletableFuture waitUnlockedDebounced(TraceRecorder recorder) { - Trace trace = recorder.getTrace(); + public CompletableFuture waitUnlockedDebounced(Target target) { + Trace trace = target.getTrace(); return new TransactionEndFuture(trace) .thenCompose(__ -> AsyncTimer.DEFAULT_TIMER.mark().after(100)) .thenComposeAsync(__ -> { if (trace.isLocked()) { - return waitUnlockedDebounced(recorder); + return waitUnlockedDebounced(target); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); }); } @Override - public void elementRemoved(TraceRecorder recorder) { + public void targetWithdrawn(Target target) { boolean save = isSaveTracesByDefault(); CompletableFuture flush = save - ? waitUnlockedDebounced(recorder) - : AsyncUtils.NIL; + ? waitUnlockedDebounced(target) + : AsyncUtils.nil(); flush.thenRunAsync(() -> { - updateCurrentRecorder(); + updateCurrentTarget(); if (!isAutoCloseOnTerminate()) { return; } - Trace trace = recorder.getTrace(); + Trace trace = target.getTrace(); synchronized (listenersByTrace) { if (!listenersByTrace.containsKey(trace)) { return; @@ -244,17 +245,17 @@ public class DebuggerTraceManagerServicePlugin extends Plugin if (trace != current.getTrace() || !mode.followsPresent()) { return; } - TraceRecorder curRecorder = current.getRecorder(); - if (curRecorder == null) { + Target curTarget = current.getTarget(); + if (curTarget == null) { return; } DebuggerCoordinates coords = current; - TargetObject focus = curRecorder.getFocus(); + TraceObjectKeyPath focus = curTarget.getFocus(); if (focus != null && synchronizeActive.get()) { - coords = coords.object(focus); + coords = coords.path(focus); } - coords = coords.snap(curRecorder.getSnap()); - activateAndNotify(coords, ActivationCause.FOLLOW_PRESENT, false); + coords = coords.snap(curTarget.getSnap()); + activateAndNotify(coords, ActivationCause.FOLLOW_PRESENT); } } @@ -262,7 +263,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin protected final Map listenersByTrace = new WeakHashMap<>(); protected final Set tracesView = Collections.unmodifiableSet(listenersByTrace.keySet()); - private final ForRecordersListener forRecordersListener = new ForRecordersListener(); + private final ForTargetsListener forTargetsListener = new ForTargetsListener(); private final ForFollowPresentListener forFollowPresentListener = new ForFollowPresentListener(); @@ -276,7 +277,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin protected final AsyncReference autoCloseOnTerminate = new AsyncReference<>(true); // @AutoServiceConsumed via method - private DebuggerModelService modelService; + private DebuggerTargetService targetService; @AutoServiceConsumed private DebuggerEmulationService emulationService; @AutoServiceConsumed @@ -335,7 +336,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin .onAction(this::activatedCloseOtherTraces) .buildAndInstall(tool); actionCloseDeadTraces = CloseDeadTracesAction.builder(this) - .enabledWhen(ctx -> !tracesView.isEmpty() && modelService != null) + .enabledWhen(ctx -> !tracesView.isEmpty() && targetService != null) .onAction(this::activatedCloseDeadTraces) .buildAndInstall(tool); @@ -449,12 +450,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin @Override public void closeDeadTraces() { Swing.runIfSwingOrRunLater(() -> { - if (modelService == null) { + if (targetService == null) { return; } for (Trace trace : getOpenTraces()) { - TraceRecorder recorder = modelService.getRecorder(trace); - if (recorder == null) { + Target target = targetService.getTarget(trace); + if (target == null) { closeTrace(trace); } } @@ -462,13 +463,13 @@ public class DebuggerTraceManagerServicePlugin extends Plugin } @AutoServiceConsumed - private void setModelService(DebuggerModelService modelService) { - if (this.modelService != null) { - this.modelService.removeTraceRecordersChangedListener(forRecordersListener); + private void setModelService(DebuggerTargetService targetService) { + if (this.targetService != null) { + this.targetService.removeTargetPublicationListener(forTargetsListener); } - this.modelService = modelService; - if (this.modelService != null) { - this.modelService.addTraceRecordersChangedListener(forRecordersListener); + this.targetService = targetService; + if (this.targetService != null) { + this.targetService.addTargetPublicationListener(forTargetsListener); } } @@ -506,37 +507,22 @@ public class DebuggerTraceManagerServicePlugin extends Plugin return false; } - protected TraceThread threadFromTargetFocus(TraceRecorder recorder, TargetObject focus) { - return focus == null ? null : recorder.getTraceThreadForSuccessor(focus); + protected boolean supportsFocus(Target target) { + return target != null && target.isSupportsFocus(); } - protected TraceObject objectFromTargetFocus(TraceRecorder recorder, TargetObject focus) { - return focus == null ? null - : recorder.getTrace() - .getObjectManager() - .getObjectByCanonicalPath(TraceObjectKeyPath.of(focus.getPath())); - } - - protected TraceStackFrame frameFromTargetFocus(TraceRecorder recorder, TargetObject focus) { - return focus == null ? null : recorder.getTraceStackFrameForSuccessor(focus); - } - - protected boolean supportsFocus(TraceRecorder recorder) { - return recorder != null && recorder.isSupportsFocus(); - } - - protected DebuggerCoordinates fillInRecorder(Trace trace, DebuggerCoordinates coordinates) { + protected DebuggerCoordinates fillInTarget(Trace trace, DebuggerCoordinates coordinates) { if (trace == null) { return DebuggerCoordinates.NOWHERE; } - if (coordinates.getRecorder() != null) { + if (coordinates.getTarget() != null) { return coordinates; } - TraceRecorder recorder = computeRecorder(trace); - if (recorder == null) { + Target target = computeTarget(trace); + if (target == null) { return coordinates; } - return coordinates.recorder(recorder); + return coordinates.target(target); } protected DebuggerCoordinates fillInPlatform(DebuggerCoordinates coordinates) { @@ -557,12 +543,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin protected DebuggerCoordinates doSetCurrent(DebuggerCoordinates newCurrent, ActivationCause cause) { newCurrent = newCurrent == null ? DebuggerCoordinates.NOWHERE : newCurrent; - newCurrent = fillInRecorder(newCurrent.getTrace(), newCurrent); + newCurrent = fillInTarget(newCurrent.getTrace(), newCurrent); newCurrent = fillInPlatform(newCurrent); if (cause == ActivationCause.START_RECORDING || cause == ActivationCause.FOLLOW_PRESENT) { - TraceRecorder recorder = newCurrent.getRecorder(); - if (recorder != null) { - newCurrent = newCurrent.snap(recorder.getSnap()); + Target target = newCurrent.getTarget(); + if (target != null) { + newCurrent = newCurrent.snap(target.getSnap()); } } synchronized (listenersByTrace) { @@ -586,39 +572,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin tool.contextChanged(null); } - protected boolean doModelObjectFocused(TargetObject obj, boolean requirePresent) { - curObj = obj; - if (!synchronizeActive.get()) { - return false; - } - if (requirePresent && !current.isDeadOrPresent()) { - return false; - } - if (modelService == null) { - // Bad timing could allow this - return false; - } - /** - * TODO: Only switch if current trace belongs to the same model? There are many - * considerations to avoid surprise here. If the user is focused on the CLI, then we should - * always switch. It's harder if that CLI is outside of Ghidra.... For now, let's always - * switch. - */ - - TraceRecorder recorder = modelService.getRecorderForSuccessor(obj); - if (recorder == null) { - return false; - } - Trace trace = recorder.getTrace(); - synchronized (listenersByTrace) { - if (!listenersByTrace.containsKey(trace)) { - return false; - } - } - activateAndNotify(getCurrentFor(trace).object(obj), ActivationCause.SYNC_MODEL, false); - return true; - } - private boolean isFollowsPresent(Trace trace) { ControlMode mode = controlService == null ? ControlMode.DEFAULT @@ -626,35 +579,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin return mode.followsPresent(); } - protected void doTraceRecorderAdvanced(TraceRecorder recorder, long snap) { - Trace trace = recorder.getTrace(); - if (!isFollowsPresent(trace)) { - return; - } - if (trace != current.getTrace()) { - /** - * The snap needs to match upon re-activating this trace, lest it look like the user - * intentionally navigated to the past, causing the mode to switch away from target. - */ - DebuggerCoordinates inactive = null; - synchronized (listenersByTrace) { - DebuggerCoordinates curForTrace = getCurrentFor(trace); - inactive = curForTrace.snapNoResolve(snap); - lastCoordsByTrace.put(trace, inactive); - } - try { - trace.getProgramView().setSnap(snap); - } - catch (TraceClosedException e) { - // Whatever. Presumably, a closed event is already queued.... - Msg.warn(this, "Ignoring snapshot advance for closed trace: " + e); - } - firePluginEvent(new TraceInactiveCoordinatesPluginEvent(getName(), inactive)); - return; - } - activate(resolveSnap(snap), ActivationCause.FOLLOW_PRESENT); - } - protected TracePlatform getPlatformForMapper(Trace trace, TraceObject object, DebuggerPlatformMapper mapper) { return trace.getPlatformManager().getPlatform(mapper.getCompilerSpec(object)); @@ -677,24 +601,24 @@ public class DebuggerTraceManagerServicePlugin extends Plugin } } - protected TraceRecorder computeRecorder(Trace trace) { - if (modelService == null) { + protected Target computeTarget(Trace trace) { + if (targetService == null) { return null; } if (trace == null) { return null; } - return modelService.getRecorder(trace); + return targetService.getTarget(trace); } - protected void updateCurrentRecorder() { - TraceRecorder recorder = computeRecorder(current.getTrace()); - if (recorder == null) { + protected void updateCurrentTarget() { + Target target = computeTarget(current.getTrace()); + if (target == null) { return; } - DebuggerCoordinates toActivate = current.recorder(recorder); + DebuggerCoordinates toActivate = current.target(target); if (isFollowsPresent(current.getTrace())) { - toActivate = toActivate.snap(recorder.getSnap()); + toActivate = toActivate.snap(target.getSnap()); } activate(toActivate, ActivationCause.FOLLOW_PRESENT); } @@ -708,13 +632,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin else if (event instanceof TraceClosedPluginEvent ev) { doTraceClosed(ev.getTrace()); } - else if (event instanceof ModelObjectFocusedPluginEvent ev) { - doModelObjectFocused(ev.getFocus(), true); - } - else if (event instanceof TraceRecorderAdvancedPluginEvent ev) { - // TimedMsg.debug(this, "Processing trace-advanced event"); - doTraceRecorderAdvanced(ev.getRecorder(), ev.getSnap()); - } else if (event instanceof DebuggerPlatformPluginEvent ev) { doPlatformMapperSelected(ev.getTrace(), ev.getMapper()); } @@ -733,8 +650,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin @Override public DebuggerCoordinates getCurrentFor(Trace trace) { synchronized (listenersByTrace) { - // If known, fill in recorder ASAP, so it determines the time - return fillInRecorder(trace, + // If known, fill in target ASAP, so it determines the time + return fillInTarget(trace, lastCoordsByTrace.getOrDefault(trace, DebuggerCoordinates.NOWHERE)); } } @@ -809,7 +726,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView(); if (varView == null) { // Should only happen with NOWHERE fireLocationEvent(coordinates, cause); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return materialize(coordinates).thenAcceptAsync(snap -> { if (!coordinates.equals(current)) { @@ -1031,7 +948,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin public CompletableFuture saveTrace(Trace trace, boolean force) { if (isDisposed()) { Msg.error(this, "Cannot save trace after manager disposal! Data may have been lost."); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return saveTrace(tool, trace, force); } @@ -1096,91 +1013,70 @@ public class DebuggerTraceManagerServicePlugin extends Plugin return elem.toString(); } - protected static boolean isSameFocus(DebuggerCoordinates prev, DebuggerCoordinates resolved) { - if (!Objects.equals(prev.getObject(), resolved.getObject())) { - return false; - } - if (!Objects.equals(prev.getFrame(), resolved.getFrame())) { - return false; - } - if (!Objects.equals(prev.getThread(), resolved.getThread())) { - return false; - } - if (!Objects.equals(prev.getTrace(), resolved.getTrace())) { - return false; - } - return true; - } - - protected static TargetObject translateToTarget(DebuggerCoordinates prev, - DebuggerCoordinates resolved) { - if (!resolved.isAliveAndPresent()) { - return null; - } - if (isSameFocus(prev, resolved)) { - return null; - } - TraceRecorder recorder = resolved.getRecorder(); - TraceObject obj = resolved.getObject(); - if (obj != null) { - TargetObject object = - recorder.getTarget().getSuccessor(obj.getCanonicalPath().getKeyList()); - if (object != null) { - return object; - } - } - TargetStackFrame frame = - recorder.getTargetStackFrame(resolved.getThread(), resolved.getFrame()); - if (frame != null) { - return frame; - } - TargetThread thread = recorder.getTargetThread(resolved.getThread()); - if (thread != null) { - return thread; - } - return recorder.getTarget(); - } - @Override public CompletableFuture activateAndNotify(DebuggerCoordinates coordinates, - ActivationCause cause, boolean syncTarget) { - DebuggerCoordinates prev; - DebuggerCoordinates resolved; - + ActivationCause cause) { Trace newTrace = coordinates.getTrace(); synchronized (listenersByTrace) { if (newTrace != null && !listenersByTrace.containsKey(newTrace)) { + if (cause == ActivationCause.FOLLOW_PRESENT) { + Msg.error(this, + "Ignoring activation because FOLLOW_PRESENT for non-opened trace"); + return AsyncUtils.nil(); + } throw new IllegalStateException( "Trace must be opened before activated: " + newTrace); } } + + if (!synchronizeActive.get() && cause == ActivationCause.SYNC_MODEL) { + return AsyncUtils.nil(); + } + if (cause == ActivationCause.FOLLOW_PRESENT) { + if (!isFollowsPresent(newTrace)) { + return AsyncUtils.nil(); + } + if (current.getTrace() != newTrace) { + /** + * The snap needs to match upon re-activating this trace, lset it look like the user + * intentionally navigated to the past. That may cause the control mode to switch + * off of "Target." + */ + try { + newTrace.getProgramView().setSnap(coordinates.getViewSnap()); + } + catch (TraceClosedException e) { + // Presumably, a closed event is queued + Msg.warn(this, "Ignoring time activation for closed trace: " + e); + } + firePluginEvent(new TraceInactiveCoordinatesPluginEvent(getName(), coordinates)); + return AsyncUtils.nil(); + } + } + DebuggerCoordinates prev; + DebuggerCoordinates resolved; + prev = current; resolved = doSetCurrent(coordinates, cause); if (resolved == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } CompletableFuture future = prepareViewAndFireEvent(resolved, cause); - if (!syncTarget) { + + if (!synchronizeActive.get() || cause != ActivationCause.USER) { return future; } - if (!synchronizeActive.get()) { + Target target = resolved.getTarget(); + if (target == null) { return future; } - TraceRecorder recorder = resolved.getRecorder(); - if (recorder == null) { - return future; - } - TargetObject activate = translateToTarget(prev, resolved); - if (activate == null || !activate.isValid()) { - return future; - } - recorder.requestActivation(activate); - return future; + + return future.thenCompose(__ -> target.activateAsync(prev, resolved)); } @Override public void activate(DebuggerCoordinates coordinates, ActivationCause cause) { - activateAndNotify(coordinates, cause, true); // Drop future on floor + activateAndNotify(coordinates, cause); // Drop future on floor } @Override @@ -1221,6 +1117,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin return current.frame(frameLevel); } + @Override + public DebuggerCoordinates resolvePath(TraceObjectKeyPath path) { + return current.path(path); + } + @Override public DebuggerCoordinates resolveObject(TraceObject object) { return current.object(object); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/workflow/DebuggerWorkflowServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/workflow/DebuggerWorkflowServicePlugin.java index 32fae1a468..867a91929d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/workflow/DebuggerWorkflowServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/workflow/DebuggerWorkflowServicePlugin.java @@ -29,21 +29,17 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent; import ghidra.app.plugin.core.debug.gui.DebuggerResources; -import ghidra.app.services.*; -import ghidra.dbg.DebuggerObjectModel; +import ghidra.app.services.DebuggerWorkflowFrontEndService; import ghidra.debug.api.workflow.DebuggerBot; import ghidra.framework.main.ApplicationLevelOnlyPlugin; import ghidra.framework.main.FrontEndTool; import ghidra.framework.options.*; import ghidra.framework.plugintool.*; -import ghidra.framework.plugintool.AutoService.Wiring; -import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginEventListener; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.util.Msg; import ghidra.util.SystemUtilities; import ghidra.util.classfinder.ClassSearcher; -import ghidra.util.datastruct.CollectionChangeListener; @PluginInfo( shortDescription = "Debugger workflow service", @@ -58,19 +54,6 @@ public class DebuggerWorkflowServicePlugin extends Plugin implements DebuggerWorkflowFrontEndService, ApplicationLevelOnlyPlugin, OptionsChangeListener { - protected class ForBotsModelsChangeListener - implements CollectionChangeListener { - @Override - public void elementAdded(DebuggerObjectModel element) { - dispatch(a -> a.modelAdded(element)); - } - - @Override - public void elementRemoved(DebuggerObjectModel element) { - dispatch(a -> a.modelRemoved(element)); - } - } - protected class ForTrackingOpenStuffPluginEventListener implements PluginEventListener { private final PluginTool t; @@ -117,11 +100,6 @@ public class DebuggerWorkflowServicePlugin extends Plugin } } - // @AutoServiceConsumed via method - private DebuggerModelService modelService; - @SuppressWarnings("unused") - private Wiring autoServiceWiring; - private final ChangeListener botsChangeListener = this::botsChanged; private final Map trackStuffListenersByTool = new HashMap<>(); @@ -133,18 +111,14 @@ public class DebuggerWorkflowServicePlugin extends Plugin @SuppressWarnings("hiding") // I'm FrontEndOnly protected final FrontEndTool tool; - private ForBotsModelsChangeListener modelsChangedListener = - new ForBotsModelsChangeListener(); public DebuggerWorkflowServicePlugin(PluginTool tool) { super(tool); this.tool = (FrontEndTool) tool; // I'm FrontEndOnly - this.autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this); ToolOptions rootOptions = tool.getOptions(DebuggerResources.OPTIONS_CATEGORY_DEBUGGER); rootOptions.addOptionsChangeListener(this); this.options = rootOptions.getOptions(DebuggerResources.OPTIONS_CATEGORY_WORKFLOW); - } @Override @@ -298,18 +272,6 @@ public class DebuggerWorkflowServicePlugin extends Plugin } } - @AutoServiceConsumed - private void setModelService(DebuggerModelService modelService) { - if (this.modelService != null) { - this.modelService.removeModelsChangedListener(modelsChangedListener); - } - this.modelService = modelService; - if (this.modelService != null) { - this.modelService.addModelsChangedListener(modelsChangedListener); - } - // TODO: Invoke models removed/added - } - @Override public Set getAllBots() { synchronized (allBots) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/workflow/DisassemblyInject.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/workflow/DisassemblyInject.java index 60c611b459..d3a8bd3aba 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/workflow/DisassemblyInject.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/workflow/DisassemblyInject.java @@ -15,9 +15,8 @@ */ package ghidra.app.plugin.core.debug.workflow; -import java.util.Arrays; - import ghidra.app.plugin.core.debug.disassemble.TraceDisassembleCommand; +import ghidra.app.plugin.core.debug.workflow.DisassemblyInjectInfo.CompilerInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.lang.Language; @@ -38,9 +37,9 @@ import ghidra.util.classfinder.ExtensionPoint; * multiple injects should ever be needed, but it is supported, just in case. The injects are * invoked in order of priority, starting with the least. Since injects are meant simply to * configure the disassembler (namely seeding its context), the one invoked last will have "the last - * word". As such, each inject should avoid unnecessarily erasing existing context. + * word." As such, each inject should avoid unnecessarily erasing existing context. */ -@DisassemblyInjectInfo(langIDs = {}) // Use as default +@DisassemblyInjectInfo(compilers = {}) // Use as default public interface DisassemblyInject extends ExtensionPoint { /** * If present, get the information annotation on this inject @@ -64,8 +63,15 @@ public interface DisassemblyInject extends ExtensionPoint { * @return true if applicable, false otherwise */ default boolean isApplicable(Trace trace) { - return Arrays.asList(getInfo().langIDs()) - .contains(trace.getBaseLanguage().getLanguageID().toString()); + for (CompilerInfo info : getInfo().compilers()) { + if (info.langID().equals(trace.getBaseLanguage().getLanguageID().toString())) { + if (info.compilerID().isBlank() || info.compilerID() + .equals(trace.getBaseCompilerSpec().getCompilerSpecID().toString())) { + return true; + } + } + } + return false; } /** diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/workflow/DisassemblyInjectInfo.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/workflow/DisassemblyInjectInfo.java index 23a5561364..aa6cbd1475 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/workflow/DisassemblyInjectInfo.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/workflow/DisassemblyInjectInfo.java @@ -17,16 +17,41 @@ package ghidra.app.plugin.core.debug.workflow; import java.lang.annotation.*; +/** + * Information about the applicability of a disassembly inject + */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface DisassemblyInjectInfo { /** - * A list of language IDs for which this inject applies + * A language-compiler-ID pair + */ + public @interface CompilerInfo { + /** + * The language ID, e.g., "x86:64:LE:default" + * + * @return the language id + */ + String langID(); + + /** + * The compiler ID, e.g., "gcc" or "windows" + * + *

    + * Leave as the default ("") to apply to all compilers for the language + * + * @return the compiler id + */ + String compilerID() default ""; + } + + /** + * A list of language-compiler-ID pairs for which this inject applies * * @see DisassemblyInject#isApplicable(ghidra.trace.model.Trace) - * @return the language ID strings + * @return the language-compiler-ID pairs */ - String[] langIDs(); + CompilerInfo[] compilers(); /** * The "position" of this inject's invocation diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/workflow/ShowInterpreterDebuggerBot.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/workflow/ShowInterpreterDebuggerBot.java deleted file mode 100644 index f70ce99ec6..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/workflow/ShowInterpreterDebuggerBot.java +++ /dev/null @@ -1,88 +0,0 @@ -/* ### - * 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.workflow; - -import java.util.concurrent.CompletableFuture; - -import ghidra.app.services.DebuggerInterpreterService; -import ghidra.app.services.DebuggerWorkflowFrontEndService; -import ghidra.dbg.DebugModelConventions; -import ghidra.dbg.DebuggerObjectModel; -import ghidra.dbg.target.TargetInterpreter; -import ghidra.debug.api.workflow.DebuggerBot; -import ghidra.debug.api.workflow.DebuggerBotInfo; -import ghidra.framework.options.annotation.HelpInfo; -import ghidra.framework.plugintool.PluginToolUtils; -import ghidra.util.Msg; -import ghidra.util.Swing; - -@DebuggerBotInfo( - description = "Show debugger interpreters", - details = """ - Listens for new debuggers supporting the interpreter interface, and when found, - displays that interpeter.""", - help = @HelpInfo(anchor = "show_interpreter"), - enabledByDefault = true) -public class ShowInterpreterDebuggerBot implements DebuggerBot { - private DebuggerWorkflowFrontEndService service; - - @Override - public void enable(DebuggerWorkflowFrontEndService service) { - this.service = service; - - // Do not retroactively display interpreters - } - - @Override - public boolean isEnabled() { - return service != null; - } - - @Override - public void disable() { - service = null; - } - - @Override - public void modelAdded(DebuggerObjectModel model) { - model.fetchModelRoot().thenCompose(root -> { - CompletableFuture fi = - DebugModelConventions.findSuitable(TargetInterpreter.class, root); - return fi; - }).thenAccept(interpreter -> { - if (interpreter == null) { - return; - } - DebuggerInterpreterService interpreterService = - PluginToolUtils.getServiceFromRunningCompatibleTool(service.getTool(), - DebuggerInterpreterService.class); - if (interpreterService == null) { - return; - } - Swing.runIfSwingOrRunLater(() -> { - interpreterService.showConsole(interpreter); - }); - }).exceptionally(ex -> { - Msg.error(this, "Error displaying debugger interpreter for " + model, ex); - return null; - }); - } - - @Override - public void modelRemoved(DebuggerObjectModel model) { - // Destruction of interpreter should cause console to become inactive - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java index f81243f9f1..a3d61b246f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java @@ -202,7 +202,7 @@ public enum DebuggerPcodeUtils { "Given trace or platform does not use a Sleigh language"); } DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(tool, - coordinates.getRecorder(), platform, coordinates.getViewSnap()); + coordinates.getTarget(), platform, coordinates.getViewSnap()); PcodeExecutorState shared = new RWTargetMemoryPcodeExecutorState(access.getDataForSharedState(), Mode.RW); if (coordinates.getThread() == null) { diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java index ebdf071507..777e7562d3 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java @@ -53,6 +53,7 @@ import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueObjectPropertyCo import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceInternal; import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin; +import ghidra.app.plugin.core.debug.service.target.DebuggerTargetServicePlugin; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; import ghidra.app.services.*; import ghidra.app.util.viewer.listingpanel.ListingPanel; @@ -545,6 +546,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest protected DebuggerModelService modelService; protected DebuggerModelServiceInternal modelServiceInternal; + protected DebuggerTargetService targetService; protected DebuggerTraceManagerService traceManager; protected ProgramManager programManager; @@ -595,6 +597,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest modelService = tool.getService(DebuggerModelService.class); assertEquals(modelPlugin, modelService); modelServiceInternal = modelPlugin; + targetService = addPlugin(tool, DebuggerTargetServicePlugin.class); addPlugin(tool, DebuggerTraceManagerServicePlugin.class); traceManager = tool.getService(DebuggerTraceManagerService.class); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java index 4df6f83df3..9acbea502c 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java @@ -22,6 +22,7 @@ import java.awt.datatransfer.StringSelection; import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; import org.junit.Before; import org.junit.Test; @@ -45,7 +46,8 @@ import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; import ghidra.app.plugin.core.debug.mapping.ObjectBasedDebuggerTargetTraceMapper; import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin; import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin; -import ghidra.app.services.*; +import ghidra.app.services.DebuggerControlService; +import ghidra.app.services.DebuggerEmulationService; import ghidra.app.services.DebuggerEmulationService.CachedEmulator; import ghidra.app.services.DebuggerEmulationService.EmulationResult; import ghidra.dbg.model.*; @@ -177,11 +179,13 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe waitRecorder(recorder); waitForSwing(); - performEnabledAction(null, controlPlugin.actionTargetResume, true); + DockingAction actionTargetResume = controlPlugin.getTargetAction("Resume"); + performEnabledAction(null, actionTargetResume, true); + waitForTasks(); waitRecorder(recorder); assertEquals(List.of("resume"), commands); waitForSwing(); - assertFalse(controlPlugin.actionTargetResume.isEnabled()); + assertFalse(actionTargetResume.isEnabled()); } @Test @@ -195,17 +199,19 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe waitRecorder(recorder); waitForSwing(); - assertFalse(controlPlugin.actionTargetInterrupt.isEnabled()); + DockingAction actionTargetInterrupt = controlPlugin.getTargetAction("Interrupt"); + assertFalse(actionTargetInterrupt.isEnabled()); waitOn(mb.testThread1.resume()); waitRecorder(recorder); commands.clear(); waitForSwing(); - performEnabledAction(null, controlPlugin.actionTargetInterrupt, true); + performEnabledAction(null, actionTargetInterrupt, true); + waitForTasks(); waitRecorder(recorder); assertEquals(List.of("interrupt"), commands); waitForSwing(); - assertFalse(controlPlugin.actionTargetInterrupt.isEnabled()); + assertFalse(actionTargetInterrupt.isEnabled()); } @Test @@ -219,11 +225,13 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe waitRecorder(recorder); waitForSwing(); - performEnabledAction(null, controlPlugin.actionTargetKill, true); + DockingAction actionTargetKill = controlPlugin.getTargetAction("Kill"); + performEnabledAction(null, actionTargetKill, true); + waitForTasks(); waitRecorder(recorder); assertEquals(List.of("kill"), commands); waitForSwing(); - assertFalse(controlPlugin.actionTargetKill.isEnabled()); + assertFalse(actionTargetKill.isEnabled()); } @Test @@ -235,13 +243,14 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe waitForSwing(); performEnabledAction(null, controlPlugin.actionTargetDisconnect, true); + waitForTasks(); waitRecorder(recorder); assertEquals(List.of("close"), commands); waitForSwing(); waitForPass(() -> assertFalse(controlPlugin.actionTargetDisconnect.isEnabled())); } - protected void runTestTargetStepAction(DockingAction action, TargetStepKind expected) + protected void runTestTargetStepAction(Supplier action, TargetStepKind expected) throws Throwable { createTestModel(); TraceRecorder recorder = recordAndWaitSync(); @@ -252,26 +261,30 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe waitRecorder(recorder); waitForSwing(); - performEnabledAction(null, action, true); + performEnabledAction(null, action.get(), true); + waitForTasks(); waitRecorder(recorder); assertEquals(List.of("step(" + expected + ")"), commands); waitForSwing(); - assertTrue(action.isEnabled()); + assertTrue(action.get().isEnabled()); } @Test public void testTargetStepIntoAction() throws Throwable { - runTestTargetStepAction(controlPlugin.actionTargetStepInto, TargetStepKind.INTO); + runTestTargetStepAction(() -> controlPlugin.getTargetAction("Step Into"), + TargetStepKind.INTO); } @Test public void testTargetStepOverAction() throws Throwable { - runTestTargetStepAction(controlPlugin.actionTargetStepOver, TargetStepKind.OVER); + runTestTargetStepAction(() -> controlPlugin.getTargetAction("Step Over"), + TargetStepKind.OVER); } @Test - public void testTargetStepFinishAction() throws Throwable { - runTestTargetStepAction(controlPlugin.actionTargetStepFinish, TargetStepKind.FINISH); + public void testTargetStepOutAction() throws Throwable { + runTestTargetStepAction(() -> controlPlugin.getTargetAction("Step Out"), + TargetStepKind.FINISH); } TraceThread createToyLoopTrace() throws Throwable { diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPluginTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPluginTest.java index 1286e377d1..a53f0270de 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPluginTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPluginTest.java @@ -15,7 +15,7 @@ */ package ghidra.app.plugin.core.debug.gui.control; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import java.io.IOException; import java.lang.invoke.MethodHandles; @@ -114,14 +114,14 @@ public class DebuggerMethodActionsPluginTest extends AbstractGhidraHeadedDebugge display = "Target", name = "target") Address target) { commands.add("advance(" + target + ")"); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } // Takes no address context @TargetMethod.Export("StepExt") public CompletableFuture stepExt() { commands.add("stepExt"); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } // Takes a second required non-default parameter @@ -136,7 +136,7 @@ public class DebuggerMethodActionsPluginTest extends AbstractGhidraHeadedDebugge display = "Flag", name = "flag") boolean flag) { commands.add("advanceWithFlag(" + address + "," + flag + ")"); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } // Takes a second address parameter @@ -151,7 +151,7 @@ public class DebuggerMethodActionsPluginTest extends AbstractGhidraHeadedDebugge display = "End", name = "end") Address end) { commands.add("between(" + start + "," + end + ")"); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } }; } @@ -197,6 +197,8 @@ public class DebuggerMethodActionsPluginTest extends AbstractGhidraHeadedDebugge ProgramLocationActionContext ctx = new ProgramLocationActionContext(listingPlugin.getProvider(), program, new ProgramLocation(program, addr(program, 0)), null, null); + waitOn(mb.testModel.requestFocus(mb.testProcess1)); + waitForSwing(); assertEquals(List.of(), methodsPlugin.getPopupActions(tool, ctx)); } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlanTests.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlanTest.java similarity index 99% rename from Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlanTests.java rename to Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlanTest.java index 3d871f3df2..207aad3f49 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlanTests.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlanTest.java @@ -52,7 +52,7 @@ import ghidra.trace.model.memory.TraceMemoryFlag; import ghidra.trace.model.memory.TraceMemoryState; import ghidra.util.task.TaskMonitor; -public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest { +public class DebuggerCopyPlanTest extends AbstractGhidraHeadedDebuggerGUITest { public static class TestDynamicDataType extends CountedDynamicDataType { public static final TestDynamicDataType dataType = new TestDynamicDataType(); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java index 5f4af28a75..0a8c88f2ab 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java @@ -49,13 +49,13 @@ import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAct import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow; import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; -import ghidra.app.services.*; +import ghidra.app.services.DebuggerStaticMappingService; +import ghidra.app.services.ProgramManager; import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.async.SwingExecutorService; import ghidra.debug.api.model.TraceRecorder; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.model.*; -import ghidra.lifecycle.Unfinished; import ghidra.plugin.importer.ImporterPlugin; import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; @@ -899,7 +899,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI // Check the default is track pc assertEquals(PCLocationTrackingSpec.INSTANCE, listingProvider.actionTrackLocation.getCurrentUserData()); - assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress()); + waitForPass( + () -> assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress())); goToDyn(tb.addr(0x00400000)); // Ensure it's changed so we know the action is effective @@ -907,13 +908,15 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI assertEquals(tb.addr(0x00400000), listingProvider.getLocation().getAddress()); performAction(listingProvider.actionTrackLocation); - assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress()); + waitForPass( + () -> assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress())); setActionStateWithTrigger(listingProvider.actionTrackLocation, SPLocationTrackingSpec.INSTANCE, EventTrigger.GUI_ACTION); waitForSwing(); - assertEquals(tb.addr(0x1fff8765), listingProvider.getLocation().getAddress()); + waitForPass( + () -> assertEquals(tb.addr(0x1fff8765), listingProvider.getLocation().getAddress())); listingProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE); waitForSwing(); @@ -922,10 +925,28 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI } @Test - @Ignore("Haven't specified this action, yet") - public void testActionTrackOtherRegister() { - // TODO: Actually, can we make this an arbitrary (pcode/sleigh?) expression. - Unfinished.TODO(); + public void testActionTrackWatch() throws Exception { + assertTrue(listingProvider.actionTrackLocation.isEnabled()); + createAndOpenTrace(); + listingProvider.setTrackingSpec(new WatchLocationTrackingSpec("*:4 (r0+0xe000)")); + TraceThread thread; + try (Transaction tx = tb.startTransaction()) { + DBTraceMemoryManager mm = tb.trace.getMemoryManager(); + mm.addRegion("exe:.text", Lifespan.nowOn(0), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + mm.addRegion("[stack]", Lifespan.nowOn(0), tb.range(0x1f000000, 0x1fffffff), + TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); + thread = tb.getOrAddThread("Thread 1", 0); + TraceMemorySpace regs = mm.getMemoryRegisterSpace(thread, true); + Register r0 = tb.language.getRegister("r0"); + regs.setValue(0, new RegisterValue(r0, new BigInteger("00401234", 16))); + } + waitForDomainObject(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + waitForPass( + () -> assertEquals(tb.addr(0x0040f234), listingProvider.getLocation().getAddress())); } @Test diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderLegacyTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderLegacyTest.java index 547faf1837..d56bf538b9 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderLegacyTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderLegacyTest.java @@ -19,9 +19,9 @@ import static org.junit.Assert.*; import java.awt.event.MouseEvent; import java.util.*; -import java.util.concurrent.TimeUnit; -import org.junit.*; +import org.junit.Before; +import org.junit.Test; import org.junit.experimental.categories.Category; import db.Transaction; @@ -39,27 +39,17 @@ import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapModul import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapSectionsAction; import ghidra.app.plugin.core.debug.gui.modules.DebuggerSectionMapProposalDialog.SectionMapTableColumns; import ghidra.app.services.DebuggerListingService; -import ghidra.dbg.attributes.TargetPrimitiveDataType.DefaultTargetPrimitiveDataType; -import ghidra.dbg.attributes.TargetPrimitiveDataType.PrimitiveKind; -import ghidra.dbg.model.TestTargetModule; -import ghidra.dbg.model.TestTargetTypedefDataType; -import ghidra.dbg.util.TargetDataTypeConverter; -import ghidra.debug.api.model.TraceRecorder; import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry; import ghidra.debug.api.modules.SectionMapProposal.SectionMapEntry; import ghidra.framework.main.DataTreeDialog; import ghidra.plugin.importer.ImporterPlugin; import ghidra.program.model.address.AddressSet; -import ghidra.program.model.data.DataType; import ghidra.program.model.mem.MemoryBlock; import ghidra.trace.database.memory.DBTraceMemoryManager; import ghidra.trace.model.Lifespan; -import ghidra.trace.model.Trace; -import ghidra.trace.model.data.TraceBasedDataTypeManager; import ghidra.trace.model.memory.TraceMemoryFlag; import ghidra.trace.model.memory.TraceOverlappedRegionException; import ghidra.trace.model.modules.*; -import ghidra.trace.model.symbol.TraceSymbol; import ghidra.util.exception.DuplicateNameException; @Category(NightlyCategory.class) // this may actually be an @PortSensitive test @@ -548,119 +538,6 @@ public class DebuggerModulesProviderLegacyTest extends AbstractGhidraHeadedDebug new AddressSet(listing.getCurrentSelection())); } - @Test - @Ignore("This action is hidden until supported") - public void testActionCaptureTypes() throws Exception { - assertFalse(modulesProvider.actionCaptureTypes.isEnabled()); - createTestModel(); - mb.createTestProcessesAndThreads(); - - TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, - createTargetTraceMapper(mb.testProcess1)); - Trace trace = recorder.getTrace(); - - // TODO: A region should not be required first. Just to get a memMapper? - mb.testProcess1.addRegion("Memory[first_proc:.text]", mb.rng(0x55550000, 0x555500ff), - "rx"); - TestTargetModule module = - mb.testProcess1.modules.addModule("Modules[first_proc]", - mb.rng(0x55550000, 0x555500ff)); - // NOTE: A section should not be required at this point. - TestTargetTypedefDataType typedef = module.types.addTypedefDataType("myInt", - new DefaultTargetPrimitiveDataType(PrimitiveKind.SINT, 4)); - waitForDomainObject(trace); - - // Still - assertFalse(modulesProvider.actionCaptureTypes.isEnabled()); - - traceManager.activateTrace(trace); - waitForSwing(); - TraceModule traceModule = waitForValue(() -> recorder.getTraceModule(module)); - modulesProvider.setSelectedModules(Set.of(traceModule)); - waitForSwing(); - // TODO: When action is included, put this assertion back - //assertTrue(modulesProvider.actionCaptureTypes.isEnabled()); - - performEnabledAction(modulesProvider, modulesProvider.actionCaptureTypes, true); - waitForBusyTool(tool); - waitForDomainObject(trace); - - // TODO: A separate action/script to transfer types from trace DTM into mapped program DTMs - TraceBasedDataTypeManager dtm = trace.getDataTypeManager(); - TargetDataTypeConverter conv = new TargetDataTypeConverter(dtm); - DataType expType = - conv.convertTargetDataType(typedef).get(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS); - // TODO: Some heuristic or convention to extract the module name, if applicable - waitForPass(() -> { - DataType actType = dtm.getDataType("/Modules[first_proc].Types/myInt"); - assertTypeEquals(expType, actType); - }); - - // TODO: When capture-types action is included, put this assertion back - //assertTrue(modulesProvider.actionCaptureTypes.isEnabled()); - waitForLock(trace); - recorder.stopRecording(); - waitForSwing(); - assertFalse(modulesProvider.actionCaptureTypes.isEnabled()); - } - - @Test - public void testActionCaptureSymbols() throws Exception { - assertFalse(modulesProvider.actionCaptureSymbols.isEnabled()); - createTestModel(); - mb.createTestProcessesAndThreads(); - - TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, - createTargetTraceMapper(mb.testProcess1)); - Trace trace = recorder.getTrace(); - - // TODO: A region should not be required first. Just to get a memMapper? - mb.testProcess1.addRegion("first_proc:.text", mb.rng(0x55550000, 0x555500ff), - "rx"); - TestTargetModule module = - mb.testProcess1.modules.addModule("first_proc", mb.rng(0x55550000, 0x555500ff)); - // NOTE: A section should not be required at this point. - module.symbols.addSymbol("test", mb.addr(0x55550080), 8, - new DefaultTargetPrimitiveDataType(PrimitiveKind.UNDEFINED, 8)); - waitForDomainObject(trace); - - // Still - assertFalse(modulesProvider.actionCaptureSymbols.isEnabled()); - - traceManager.activateTrace(trace); - waitForSwing(); - waitForPass(() -> { - TraceModule traceModule = recorder.getTraceModule(module); - assertNotNull(traceModule); - modulesProvider.setSelectedModules(Set.of(traceModule)); - waitForSwing(); - assertTrue(modulesProvider.actionCaptureSymbols.isEnabled()); - }); - - performEnabledAction(modulesProvider, modulesProvider.actionCaptureSymbols, true); - waitForBusyTool(tool); - waitForDomainObject(trace); - - // TODO: A separate action/script to transfer symbols from trace into mapped programs - // NOTE: Used types must go along. - Collection symbols = - trace.getSymbolManager().allSymbols().getNamed("test"); - assertEquals(1, symbols.size()); - TraceSymbol sym = symbols.iterator().next(); - // TODO: Some heuristic or convention to extract the module name, if applicable - assertEquals("Processes[1].Modules[first_proc].Symbols::test", sym.getName(true)); - // NOTE: builder (b) is not initialized here - assertEquals(trace.getBaseAddressFactory().getDefaultAddressSpace().getAddress(0x55550080), - sym.getAddress()); - // TODO: Check data type once those are captured in Data units. - - assertTrue(modulesProvider.actionCaptureSymbols.isEnabled()); - waitForLock(trace); - recorder.stopRecording(); - waitForSwing(); - assertFalse(modulesProvider.actionCaptureSymbols.isEnabled()); - } - @Test public void testActionImportFromFileSystem() throws Exception { addPlugin(tool, ImporterPlugin.class); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderTest.java index 8094aa9f75..ec02aa45ba 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderTest.java @@ -20,7 +20,6 @@ import static org.junit.Assert.*; import java.awt.event.MouseEvent; import java.io.IOException; import java.util.*; -import java.util.concurrent.TimeUnit; import org.junit.*; import org.junit.experimental.categories.Category; @@ -45,15 +44,12 @@ import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapSecti import ghidra.app.plugin.core.debug.gui.modules.DebuggerSectionMapProposalDialog.SectionMapTableColumns; import ghidra.app.plugin.core.debug.mapping.ObjectBasedDebuggerTargetTraceMapper; import ghidra.app.services.DebuggerListingService; -import ghidra.dbg.attributes.TargetPrimitiveDataType.DefaultTargetPrimitiveDataType; -import ghidra.dbg.attributes.TargetPrimitiveDataType.PrimitiveKind; -import ghidra.dbg.model.TestTargetModule; -import ghidra.dbg.model.TestTargetTypedefDataType; import ghidra.dbg.target.*; import ghidra.dbg.target.schema.SchemaContext; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; import ghidra.dbg.target.schema.XmlSchemaContext; -import ghidra.dbg.util.*; +import ghidra.dbg.util.PathPattern; +import ghidra.dbg.util.PathUtils; import ghidra.debug.api.model.DebuggerTargetTraceMapper; import ghidra.debug.api.model.TraceRecorder; import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry; @@ -62,16 +58,14 @@ import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.main.DataTreeDialog; import ghidra.plugin.importer.ImporterPlugin; import ghidra.program.model.address.*; -import ghidra.program.model.data.DataType; import ghidra.program.model.lang.CompilerSpecID; import ghidra.program.model.lang.LanguageID; import ghidra.program.model.mem.MemoryBlock; import ghidra.trace.database.module.TraceObjectSection; import ghidra.trace.model.Lifespan; import ghidra.trace.model.Trace; -import ghidra.trace.model.data.TraceBasedDataTypeManager; -import ghidra.trace.model.modules.*; -import ghidra.trace.model.symbol.TraceSymbol; +import ghidra.trace.model.modules.TraceObjectModule; +import ghidra.trace.model.modules.TraceStaticMapping; import ghidra.trace.model.target.*; import ghidra.trace.model.target.TraceObject.ConflictResolution; import ghidra.util.table.GhidraTable; @@ -699,118 +693,6 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI new AddressSet(listing.getCurrentSelection())); } - @Test - @Ignore("This action is hidden until supported") - public void testActionCaptureTypes() throws Exception { - assertFalse(provider.actionCaptureTypes.isEnabled()); - createTestModel(); - mb.createTestProcessesAndThreads(); - - TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, - createTargetTraceMapper(mb.testProcess1)); - Trace trace = recorder.getTrace(); - - // TODO: A region should not be required first. Just to get a memMapper? - mb.testProcess1.addRegion("Memory[first_proc:.text]", mb.rng(0x55550000, 0x555500ff), - "rx"); - TestTargetModule module = - mb.testProcess1.modules.addModule("Modules[first_proc]", - mb.rng(0x55550000, 0x555500ff)); - // NOTE: A section should not be required at this point. - TestTargetTypedefDataType typedef = module.types.addTypedefDataType("myInt", - new DefaultTargetPrimitiveDataType(PrimitiveKind.SINT, 4)); - waitForDomainObject(trace); - - // Still - assertFalse(provider.actionCaptureTypes.isEnabled()); - - traceManager.activateTrace(trace); - waitForSwing(); - TraceModule traceModule = waitForValue(() -> recorder.getTraceModule(module)); - provider.setSelectedModules(Set.of(traceModule)); - waitForSwing(); - // TODO: When action is included, put this assertion back - //assertTrue(modulesProvider.actionCaptureTypes.isEnabled()); - - performEnabledAction(provider, provider.actionCaptureTypes, true); - waitForBusyTool(tool); - waitForDomainObject(trace); - - // TODO: A separate action/script to transfer types from trace DTM into mapped program DTMs - TraceBasedDataTypeManager dtm = trace.getDataTypeManager(); - TargetDataTypeConverter conv = new TargetDataTypeConverter(dtm); - DataType expType = - conv.convertTargetDataType(typedef).get(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS); - // TODO: Some heuristic or convention to extract the module name, if applicable - waitForPass(() -> { - DataType actType = dtm.getDataType("/Modules[first_proc].Types/myInt"); - assertTypeEquals(expType, actType); - }); - - // TODO: When capture-types action is included, put this assertion back - //assertTrue(modulesProvider.actionCaptureTypes.isEnabled()); - waitForLock(trace); - recorder.stopRecording(); - waitForSwing(); - assertFalse(provider.actionCaptureTypes.isEnabled()); - } - - @Test - public void testActionCaptureSymbols() throws Throwable { - assertFalse(provider.actionCaptureSymbols.isEnabled()); - createTestModel(); - mb.createTestProcessesAndThreads(); - - TraceRecorder recorder = recordAndWaitSync(); - traceManager.openTrace(recorder.getTrace()); - - // TODO: A region should not be required first. Just to get a memMapper? - mb.testProcess1.addRegion("first_proc:.text", mb.rng(0x55550000, 0x555500ff), - "rx"); - TestTargetModule module = - mb.testProcess1.modules.addModule("first_proc", mb.rng(0x55550000, 0x555500ff)); - // NOTE: A section should not be required at this point. - module.symbols.addSymbol("test", mb.addr(0x55550080), 8, - new DefaultTargetPrimitiveDataType(PrimitiveKind.UNDEFINED, 8)); - waitForDomainObject(tb.trace); - - // Still - assertFalse(provider.actionCaptureSymbols.isEnabled()); - - traceManager.activateTrace(tb.trace); - waitForTasks(); - waitForPass(() -> { - TraceModule traceModule = recorder.getTraceModule(module); - assertNotNull(traceModule); - runSwing(() -> provider.setSelectedModules(Set.of(traceModule))); - assertTrue(provider.actionCaptureSymbols.isEnabled()); - }); - - performEnabledAction(provider, provider.actionCaptureSymbols, true); - waitForBusyTool(tool); - waitForDomainObject(tb.trace); - - // TODO: A separate action/script to transfer symbols from trace into mapped programs - // TODO: Let this action work on the TraceObjects instead of TargetObjects - // NOTE: Used types must go along. - Collection symbols = - tb.trace.getSymbolManager().allSymbols().getNamed("test"); - assertEquals(1, symbols.size()); - TraceSymbol sym = symbols.iterator().next(); - // TODO: Some heuristic or convention to extract the module name, if applicable - assertEquals("Processes[1].Modules[first_proc].Symbols::test", sym.getName(true)); - // NOTE: builder (b) is not initialized here - assertEquals(tb.addr(0x55550080), - sym.getAddress()); - // TODO: Check data type once those are captured in Data units. - - assertTrue(provider.actionCaptureSymbols.isEnabled()); - waitForLock(tb.trace); - recorder.stopRecording(); - waitForSwing(); - assertFalse(provider.actionCaptureSymbols.isEnabled()); - } - @Test public void testActionImportFromFileSystem() throws Exception { addPlugin(tool, ImporterPlugin.class); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java index 8f56fcba21..0e3bd82f5c 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java @@ -676,10 +676,10 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU // Step as written to fill the cache waitOn(traceManager.activateAndNotify(current.time(TraceSchedule.parse("0:t0-1")), - ActivationCause.USER, false)); + ActivationCause.USER)); waitForSwing(); waitOn(traceManager.activateAndNotify(current.time(TraceSchedule.parse("0:t0-2")), - ActivationCause.USER, false)); + ActivationCause.USER)); waitForSwing(); long scratch = traceManager.getCurrentView().getSnap(); @@ -700,10 +700,10 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU // Check the cache is still valid waitOn(traceManager.activateAndNotify(current.time(TraceSchedule.parse("0:t0-1")), - ActivationCause.USER, false)); + ActivationCause.USER)); waitForSwing(); waitOn(traceManager.activateAndNotify(current.time(TraceSchedule.parse("0:t0-2")), - ActivationCause.USER, false)); + ActivationCause.USER)); waitForSwing(); assertEquals(scratch, traceManager.getCurrentView().getSnap()); assertEquals(new BigInteger("1234", 16), diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java index e043ccef3a..aa4f51299e 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java @@ -20,7 +20,6 @@ import static org.junit.Assert.*; import java.awt.Component; import java.util.*; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import javax.swing.JLabel; @@ -31,12 +30,10 @@ import org.junit.experimental.categories.Category; import generic.Unique; import generic.test.category.NightlyCategory; -import ghidra.app.plugin.core.debug.event.ModelObjectFocusedPluginEvent; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractConnectAction; import ghidra.app.plugin.core.debug.service.model.DebuggerConnectDialog.FactoryEntry; import ghidra.app.plugin.core.debug.service.model.TestDebuggerProgramLaunchOpinion.TestDebuggerProgramLaunchOffer; -import ghidra.async.AsyncPairingQueue; import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.model.TestDebuggerModelFactory; @@ -418,30 +415,6 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes () -> assertEquals(mb.testThread4, modelService.getTargetFocus(mb.testProcess3))); } - @Test - public void testFocusGeneratesEvents() throws Exception { - createTestModel(); - mb.createTestProcessesAndThreads(); - - // NOTE: These events are generated whether or not associated with a recorder - AsyncPairingQueue focusEvents = new AsyncPairingQueue<>(); - tool.addListenerForAllPluginEvents(event -> { - if (event instanceof ModelObjectFocusedPluginEvent) { - ModelObjectFocusedPluginEvent evt = (ModelObjectFocusedPluginEvent) event; - focusEvents.give().complete(evt); - } - }); - - mb.testModel.requestFocus(mb.testThread1); - mb.testModel.requestFocus(mb.testThread2); - ModelObjectFocusedPluginEvent evt1 = - focusEvents.take().get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - ModelObjectFocusedPluginEvent evt2 = - focusEvents.take().get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - assertEquals(mb.testThread1, evt1.getFocus()); - assertEquals(mb.testThread2, evt2.getFocus()); - } - @Test public void testCurrentModelNullAfterClose() throws Throwable { createTestModel(); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/debug/flatapi/FlatDebuggerAPITest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/debug/flatapi/FlatDebuggerAPITest.java index c15f581d41..8c14b62b54 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/debug/flatapi/FlatDebuggerAPITest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/debug/flatapi/FlatDebuggerAPITest.java @@ -44,7 +44,6 @@ import ghidra.app.script.GhidraState; import ghidra.app.services.*; import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerObjectModel; -import ghidra.dbg.DebuggerObjectModel.RefreshBehavior; import ghidra.dbg.model.*; import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher; import ghidra.dbg.target.TargetObject; @@ -53,8 +52,8 @@ import ghidra.debug.api.breakpoint.LogicalBreakpoint; import ghidra.debug.api.breakpoint.LogicalBreakpoint.State; import ghidra.debug.api.control.ControlMode; import ghidra.debug.api.model.DebuggerProgramLaunchOffer; -import ghidra.debug.api.model.TraceRecorder; import ghidra.debug.api.model.DebuggerProgramLaunchOffer.LaunchResult; +import ghidra.debug.api.model.TraceRecorder; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderPcodeExecTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderPcodeExecTest.java index e036dff4bf..cb0c18d236 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderPcodeExecTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderPcodeExecTest.java @@ -30,6 +30,7 @@ import ghidra.debug.api.action.ActionSource; import ghidra.debug.api.emulation.PcodeDebuggerAccess; import ghidra.debug.api.model.DebuggerRegisterMapper; import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorState; @@ -44,7 +45,7 @@ import ghidra.trace.model.time.schedule.TraceSchedule; /** * Test the {@link DirectBytesTracePcodeExecutorState} in combination with - * {@link PcodeDebuggerAccess} to ensure it read and writes the target when appropriate. + * {@link PcodeDebuggerAccess} to ensure it reads and writes the target when appropriate. */ public class TraceRecorderPcodeExecTest extends AbstractGhidraHeadedDebuggerGUITest { @@ -67,8 +68,7 @@ public class TraceRecorderPcodeExecTest extends AbstractGhidraHeadedDebuggerGUIT Trace trace = recorder.getTrace(); SleighLanguage language = (SleighLanguage) trace.getBaseLanguage(); - PcodeExpression expr = SleighProgramCompiler - .compileExpression(language, "r0 + r1"); + PcodeExpression expr = SleighProgramCompiler.compileExpression(language, "r0 + r1"); Register r0 = language.getRegister("r0"); Register r1 = language.getRegister("r1"); @@ -83,8 +83,10 @@ public class TraceRecorderPcodeExecTest extends AbstractGhidraHeadedDebuggerGUIT assertTrue(rm.getRegistersOnTarget().contains(r1)); }); + Target target = targetService.getTarget(trace); + assertNotNull(target); PcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates(tool, - DebuggerCoordinates.NOWHERE.recorder(recorder).thread(thread)); + DebuggerCoordinates.NOWHERE.target(target).thread(thread)); // In practice, this should be backgrounded, but we're in a test thread byte[] result = expr.evaluate(executor); @@ -136,8 +138,10 @@ public class TraceRecorderPcodeExecTest extends AbstractGhidraHeadedDebuggerGUIT space.setValue(scratch.getKey(), new RegisterValue(r0, BigInteger.valueOf(10))); } + Target target = targetService.getTarget(trace); + assertNotNull(target); PcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates(tool, - DebuggerCoordinates.NOWHERE.recorder(recorder).thread(thread).time(oneTick)); + DebuggerCoordinates.NOWHERE.target(target).thread(thread).time(oneTick)); // In practice, this should be backgrounded, but we're in a test thread byte[] result = expr.evaluate(executor); @@ -180,8 +184,10 @@ public class TraceRecorderPcodeExecTest extends AbstractGhidraHeadedDebuggerGUIT assertTrue(rm.getRegistersOnTarget().contains(r1)); }); + Target target = targetService.getTarget(trace); + assertNotNull(target); PcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates(tool, - DebuggerCoordinates.NOWHERE.recorder(recorder).thread(thread)); + DebuggerCoordinates.NOWHERE.target(target).thread(thread)); executor.execute(prog, PcodeUseropLibrary.nil()); // Ignore return value. We'll assert that it got written to the trace diff --git a/Ghidra/Debug/Framework-AsyncComm/src/main/java/ghidra/async/AsyncReference.java b/Ghidra/Debug/Framework-AsyncComm/src/main/java/ghidra/async/AsyncReference.java index b8d6232504..a313d9849e 100644 --- a/Ghidra/Debug/Framework-AsyncComm/src/main/java/ghidra/async/AsyncReference.java +++ b/Ghidra/Debug/Framework-AsyncComm/src/main/java/ghidra/async/AsyncReference.java @@ -326,7 +326,7 @@ public class AsyncReference { return CompletableFuture.failedFuture(disposalReason); } if (Objects.equals(this.val, t)) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } CompletableFuture waiter = waitsFor.get(t); if (waiter == null) { diff --git a/Ghidra/Debug/Framework-AsyncComm/src/main/java/ghidra/async/AsyncTimer.java b/Ghidra/Debug/Framework-AsyncComm/src/main/java/ghidra/async/AsyncTimer.java index 6c82fcb4ae..df8e6bf4cb 100644 --- a/Ghidra/Debug/Framework-AsyncComm/src/main/java/ghidra/async/AsyncTimer.java +++ b/Ghidra/Debug/Framework-AsyncComm/src/main/java/ghidra/async/AsyncTimer.java @@ -165,7 +165,7 @@ public class AsyncTimer { */ public CompletableFuture atSystemTime(long timeMillis) { if (timeMillis - System.currentTimeMillis() <= 0) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } long delta = timeMillis - System.currentTimeMillis(); diff --git a/Ghidra/Debug/Framework-AsyncComm/src/main/java/ghidra/async/AsyncUtils.java b/Ghidra/Debug/Framework-AsyncComm/src/main/java/ghidra/async/AsyncUtils.java index 735693779b..6070d6fc18 100644 --- a/Ghidra/Debug/Framework-AsyncComm/src/main/java/ghidra/async/AsyncUtils.java +++ b/Ghidra/Debug/Framework-AsyncComm/src/main/java/ghidra/async/AsyncUtils.java @@ -411,11 +411,15 @@ public interface AsyncUtils { ExecutorService FRAMEWORK_EXECUTOR = Executors.newWorkStealingPool(); ExecutorService SWING_EXECUTOR = SwingExecutorService.LATER; - CompletableFuture NIL = CompletableFuture.completedFuture(null); + // NB. This was a bad idea, because CFs may maintain refs to dependents. + //CompletableFuture NIL = CompletableFuture.completedFuture(null); - @SuppressWarnings({ "unchecked", "rawtypes" }) public static CompletableFuture nil() { - return (CompletableFuture) NIL; + return CompletableFuture.completedFuture(null); + } + + public static CompletableFuture nil(Class cls) { + return CompletableFuture.completedFuture(null); } /** @@ -623,7 +627,7 @@ public interface AsyncUtils { * @return an empty sequence ready to execute actions on the calling thread. */ public static AsyncSequenceWithoutTemp sequence(CompletableFuture on) { - return new AsyncSequenceWithoutTemp<>(on, AsyncUtils.NIL); + return new AsyncSequenceWithoutTemp<>(on, AsyncUtils.nil()); } /** diff --git a/Ghidra/Debug/Framework-AsyncComm/src/test/java/ghidra/async/AsyncLockTest.java b/Ghidra/Debug/Framework-AsyncComm/src/test/java/ghidra/async/AsyncLockTest.java index 6acecee365..810c3d1aef 100644 --- a/Ghidra/Debug/Framework-AsyncComm/src/test/java/ghidra/async/AsyncLockTest.java +++ b/Ghidra/Debug/Framework-AsyncComm/src/test/java/ghidra/async/AsyncLockTest.java @@ -34,12 +34,12 @@ public class AsyncLockTest { private CompletableFuture doCriticalStuff() { // Placeholder for example - return AsyncUtils.NIL; + return AsyncUtils.nil(); } private CompletableFuture doMoreCriticalStuff() { // Placeholder for example - return AsyncUtils.NIL; + return AsyncUtils.nil(); } public CompletableFuture fetchValue() { diff --git a/Ghidra/Debug/Framework-AsyncComm/src/test/java/ghidra/async/AsyncUtilsTest.java b/Ghidra/Debug/Framework-AsyncComm/src/test/java/ghidra/async/AsyncUtilsTest.java index 7f7b2c3725..bf5c6fceae 100644 --- a/Ghidra/Debug/Framework-AsyncComm/src/test/java/ghidra/async/AsyncUtilsTest.java +++ b/Ghidra/Debug/Framework-AsyncComm/src/test/java/ghidra/async/AsyncUtilsTest.java @@ -98,7 +98,7 @@ public class AsyncUtilsTest { // Functions that communicate result via completion handler protected static CompletableFuture append(String message, List to) { to.add(message); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } protected static CompletableFuture add(int a, int b) { @@ -304,7 +304,7 @@ public class AsyncUtilsTest { private CompletableFuture someTask() { // Placeholder for example - return AsyncUtils.NIL; + return AsyncUtils.nil(); } public void exampleLoop2() { diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebugModelConventions.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebugModelConventions.java index 991bbb3bcc..f45080bc46 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebugModelConventions.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebugModelConventions.java @@ -46,7 +46,7 @@ public enum DebugModelConventions { catch (Throwable e) { Msg.error(originator, "Error in " + cbName, e); } - return AsyncUtils.NIL; + return AsyncUtils.nil(); } /** @@ -524,7 +524,7 @@ public enum DebugModelConventions { DebugModelConventions.findSuitable(TargetActiveScope.class, obj); return futureActivator.thenCompose(activator -> { if (activator == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return activator.requestActivation(obj); }); @@ -544,7 +544,7 @@ public enum DebugModelConventions { DebugModelConventions.findSuitable(TargetFocusScope.class, obj); return futureScope.thenCompose(scope -> { if (scope == null) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } return scope.requestFocus(obj); }); diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/AbstractDebuggerObjectModel.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/AbstractDebuggerObjectModel.java index 5a72a305dd..c90772cf20 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/AbstractDebuggerObjectModel.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/AbstractDebuggerObjectModel.java @@ -218,7 +218,7 @@ public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectMo @Override public CompletableFuture close() { clientExecutor.shutdown(); - return AsyncUtils.NIL; + return AsyncUtils.nil(); } public void removeExisting(List path) { diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/DefaultTargetObject.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/DefaultTargetObject.java index e85872ca48..7702f1f787 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/DefaultTargetObject.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/DefaultTargetObject.java @@ -159,7 +159,7 @@ public class DefaultTargetObject * @return a future which completes when the cache has been updated */ protected CompletableFuture requestElements(RefreshBehavior refresh) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } private boolean shouldRequestElements(RefreshBehavior refresh) { @@ -186,7 +186,7 @@ public class DefaultTargetObject if (shouldRequestElements(refresh)) { curElemsRequest = model.gateFuture(requestElements(refresh)); } - req = curElemsRequest == null ? AsyncUtils.NIL : curElemsRequest; + req = curElemsRequest == null ? AsyncUtils.nil() : curElemsRequest; } return req.thenApply(__ -> getCachedElements()); } @@ -347,7 +347,7 @@ public class DefaultTargetObject * @return a future which completes when the cache has been updated */ protected CompletableFuture requestAttributes(RefreshBehavior refresh) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } private boolean shouldRequestAttributes(RefreshBehavior refresh) { @@ -375,7 +375,7 @@ public class DefaultTargetObject if (shouldRequestAttributes(refresh)) { curAttrsRequest = model.gateFuture(requestAttributes(refresh)); } - req = curAttrsRequest == null ? AsyncUtils.NIL : curAttrsRequest; + req = curAttrsRequest == null ? AsyncUtils.nil() : curAttrsRequest; } return req.thenApply(__ -> { synchronized (model.lock) { diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/SpiDebuggerObjectModel.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/SpiDebuggerObjectModel.java index 5cc918b800..9c8cb3d441 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/SpiDebuggerObjectModel.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/SpiDebuggerObjectModel.java @@ -34,7 +34,7 @@ public interface SpiDebuggerObjectModel extends DebuggerObjectModel { @Override default CompletableFuture ping(String content) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } public static CompletableFuture fetchFreshChild(TargetObject obj, String key) { diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java index 45e015577e..b309c64557 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java @@ -528,7 +528,8 @@ public interface TargetObject extends Comparable { * @param refresh true to invalidate all caches involved in handling this request * @return a future which completes with a name-value map of attributes */ - public default CompletableFuture> fetchAttributes(RefreshBehavior refresh) { + public default CompletableFuture> fetchAttributes( + RefreshBehavior refresh) { return getModel().fetchObjectAttributes(getPath(), refresh); } @@ -654,7 +655,8 @@ public interface TargetObject extends Comparable { * @param refresh true to invalidate all caches involved in handling this request * @return a future which completes with a name-value map of children */ - public default CompletableFuture> fetchChildren(RefreshBehavior refresh) { + public default CompletableFuture> fetchChildren( + RefreshBehavior refresh) { AsyncFence fence = new AsyncFence(); Map children = new TreeMap<>(TargetObjectKeyComparator.CHILD); fence.include(fetchElements(refresh).thenAccept(elements -> { @@ -952,7 +954,8 @@ public interface TargetObject extends Comparable { * @param refreshElements as the model to refresh elements, querying the debugger if needed * @return a future which completes when the children are updated. */ - CompletableFuture resync(RefreshBehavior refreshAttributes, RefreshBehavior refreshElements); + CompletableFuture resync(RefreshBehavior refreshAttributes, + RefreshBehavior refreshElements); /** * Refresh the elements of this object @@ -1060,6 +1063,9 @@ public interface TargetObject extends Comparable { * @return the found object, or null */ public default T getCachedSuitable(Class cls) { + if (cls == TargetObject.class) { + return cls.cast(this); + } List found = getModel().getRootSchema().searchForSuitable(cls, getPath()); if (found == null) { return null; @@ -1083,6 +1089,6 @@ public interface TargetObject extends Comparable { * @return a future which completes when the caches are invalidated */ public default CompletableFuture invalidateCaches() { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/TargetObjectSchema.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/TargetObjectSchema.java index 52b26f9eca..4e57f85fc1 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/TargetObjectSchema.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/TargetObjectSchema.java @@ -677,6 +677,11 @@ public interface TargetObjectSchema { return searchForInAggregate(seed, ent -> ent.schema.getInterfaces().contains(type)); } + static List searchForSuitableInAggregate(TargetObjectSchema seed, + TargetObjectSchema schema) { + return searchForInAggregate(seed, ent -> ent.schema == schema); + } + static List searchForSuitableContainerInAggregate(TargetObjectSchema seed, Class type) { return searchForInAggregate(seed, ent -> { @@ -783,6 +788,28 @@ public interface TargetObjectSchema { return null; } + /** + * Search for a suitable object with this schema at the given path + * + * @param schema the schema of object sought + * @param path the path of a seed object + * @return the expected path of the suitable object, or null + */ + default List searchForSuitable(TargetObjectSchema schema, List path) { + List schemas = getSuccessorSchemas(path); + for (; path != null; path = PathUtils.parent(path)) { + TargetObjectSchema check = schemas.get(path.size()); + if (check == schema) { + return path; + } + List inAgg = Private.searchForSuitableInAggregate(check, schema); + if (inAgg != null) { + return PathUtils.extend(path, inAgg); + } + } + return null; + } + /** * Search for all suitable objects with this schema at the given path * diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/DebuggerCallbackReorderer.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/DebuggerCallbackReorderer.java index 184f666319..92c15695f9 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/DebuggerCallbackReorderer.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/DebuggerCallbackReorderer.java @@ -100,7 +100,7 @@ public class DebuggerCallbackReorderer implements DebuggerModelListener { private final DebuggerModelListener listener; private final Map records = new HashMap<>(); - private CompletableFuture lastEvent = AsyncUtils.NIL; + private CompletableFuture lastEvent = AsyncUtils.nil(); private volatile boolean disposed = false; diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestMimickJavaLauncher.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestMimickJavaLauncher.java index a72c45761d..2e8926a5fa 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestMimickJavaLauncher.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestMimickJavaLauncher.java @@ -60,6 +60,6 @@ public class TestMimickJavaLauncher @Override public CompletableFuture launch(Map args) { - return AsyncUtils.NIL; // TODO: Queue and allow test to complete it? + return AsyncUtils.nil(); // TODO: Queue and allow test to complete it? } } diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetSession.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetSession.java index 2cd0bd279d..a67d79fc4f 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetSession.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetSession.java @@ -91,6 +91,6 @@ public class TestTargetSession extends DefaultTargetModelRoot @Override public CompletableFuture launch(Map args) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetThread.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetThread.java index 9cd22cbc99..b6c2a9bd48 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetThread.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetThread.java @@ -68,21 +68,21 @@ public class TestTargetThread @Override public CompletableFuture step(TargetStepKind kind) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } @Override public CompletableFuture resume() { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } @Override public CompletableFuture interrupt() { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } @Override public CompletableFuture kill() { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/target/TargetMethodTest.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/target/TargetMethodTest.java index cac211a928..5622fabdd2 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/target/TargetMethodTest.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/target/TargetMethodTest.java @@ -50,7 +50,7 @@ public class TargetMethodTest implements AsyncTestUtils { @TargetMethod.Export("MyMethod") public CompletableFuture myMethod() { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } }; } @@ -96,7 +96,7 @@ public class TargetMethodTest implements AsyncTestUtils { display = "P1", description = "A boolean param", name = "p1") boolean b) { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } }; } diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/target/schema/AnnotatedTargetObjectSchemaTest.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/target/schema/AnnotatedTargetObjectSchemaTest.java index 305434bf6b..7de666e507 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/target/schema/AnnotatedTargetObjectSchemaTest.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/target/schema/AnnotatedTargetObjectSchemaTest.java @@ -396,7 +396,7 @@ public class AnnotatedTargetObjectSchemaTest { @TargetMethod.Export("MyMethod") public CompletableFuture myMethod() { - return AsyncUtils.NIL; + return AsyncUtils.nil(); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java index 36c2c08731..89f2d54137 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java @@ -26,6 +26,7 @@ import ghidra.dbg.util.PathPattern; import ghidra.dbg.util.PathPredicates; import ghidra.trace.model.*; import ghidra.trace.model.Lifespan.LifeSet; +import ghidra.trace.model.target.TraceObject.ConflictResolution; /** * The trace record of an observed {@link TargetObject} @@ -552,6 +553,9 @@ public interface TraceObject extends TraceUniqueObject { * @return the suitable object, or null if not found */ default TraceObject querySuitableTargetInterface(Class targetIf) { + if (targetIf == TargetObject.class) { + return this; + } List path = getRoot().getTargetSchema() .searchForSuitable(targetIf, getCanonicalPath().getKeyList()); if (path == null) { @@ -560,6 +564,25 @@ public interface TraceObject extends TraceUniqueObject { return getTrace().getObjectManager().getObjectByCanonicalPath(TraceObjectKeyPath.of(path)); } + /** + * Search for a suitable object having the given schema + * + *

    + * This operates by examining the schema for a unique suitable path, without regard to + * lifespans. If needed, the caller should inspect the object's life. + * + * @param schema the schema + * @return the suitable object, or null if not found + */ + default TraceObject querySuitableSchema(TargetObjectSchema schema) { + List path = getRoot().getTargetSchema() + .searchForSuitable(schema, getCanonicalPath().getKeyList()); + if (path == null) { + return null; + } + return getTrace().getObjectManager().getObjectByCanonicalPath(TraceObjectKeyPath.of(path)); + } + /** * Search for a suitable register container * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java index 5c19a936a6..bd2dd24d3b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java @@ -17,23 +17,87 @@ package ghidra.trace.util; import java.math.BigInteger; import java.nio.ByteBuffer; -import java.util.Arrays; +import java.util.*; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.tuple.Pair; import ghidra.pcode.utils.Utils; import ghidra.program.model.address.*; import ghidra.program.model.data.*; -import ghidra.program.model.lang.Register; -import ghidra.program.model.lang.RegisterValue; +import ghidra.program.model.lang.*; import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.listing.TraceData; import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.util.OverlappingObjectIterator.Ranger; public enum TraceRegisterUtils { ; + private static class RegisterIndex { + // Could this all go into RegisterManager instead? + // Would need the overlapping object iterator, or replacement, moved up + private record RegEntry(AddressRange base, Set regs) { + } + + private static final Ranger ENTRY_RANGER = new Ranger<>() { + @Override + public Address getMinAddress(RegEntry t) { + return t.base.getMinAddress(); + } + + @Override + public Address getMaxAddress(RegEntry t) { + return t.base.getMaxAddress(); + } + }; + private NavigableMap map = new TreeMap<>(); + + private RegisterIndex(Language language) { + for (Register reg : language.getRegisters()) { + RegEntry re = map.computeIfAbsent(reg.getAddress(), + k -> new RegEntry(rangeForRegister(reg.getBaseRegister()), new HashSet<>())); + re.regs.add(reg); + } + } + + private Set findIntersecting(AddressSetView set) { + AddressSet inBaseSpace = new AddressSet(); + for (AddressRange rng : set) { + if (rng.getAddressSpace().isOverlaySpace()) { + inBaseSpace.add(rng.getMinAddress().getPhysicalAddress(), + rng.getMaxAddress().getPhysicalAddress()); + } + else { + inBaseSpace.add(rng); + } + } + Set result = new HashSet<>(); + OverlappingObjectIterator ooit = + new OverlappingObjectIterator<>( + inBaseSpace.iterator(), OverlappingObjectIterator.ADDRESS_RANGE, + map.subMap(inBaseSpace.getMinAddress(), true, + inBaseSpace.getMaxAddress(), true).values().iterator(), + ENTRY_RANGER); + while (ooit.hasNext()) { + Pair next = ooit.next(); + for (Register reg : next.getRight().regs) { + if (rangeForRegister(reg).intersects(next.getLeft())) { + result.add(reg); + } + } + } + return result; + } + } + + private static final Map REGISTER_INDICES = new WeakHashMap<>(); + + public static Set registersIntersecting(Language language, AddressSetView set) { + return REGISTER_INDICES.computeIfAbsent(language, RegisterIndex::new).findIntersecting(set); + } + public static AddressRange rangeForRegister(Register register) { Address address = register.getAddress(); return new AddressRangeImpl(address, address.add(register.getNumBytes() - 1)); diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/ghidraclass/debugger/screenshot/TutorialDebuggerScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/ghidraclass/debugger/screenshot/TutorialDebuggerScreenShots.java index 518387b960..bc70ec7bb6 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/ghidraclass/debugger/screenshot/TutorialDebuggerScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/ghidraclass/debugger/screenshot/TutorialDebuggerScreenShots.java @@ -234,7 +234,6 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator workflowService = tool.getService(DebuggerWorkflowService.class); enableBot(MapModulesDebuggerBot.class); enableBot(DisassembleAtPcDebuggerBot.class); - enableBot(ShowInterpreterDebuggerBot.class); modelService = tool.getService(DebuggerModelService.class); gdbFactory = modelService.getModelFactories()