diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/sysobj/DebugSystemObjectsImpl1.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/sysobj/DebugSystemObjectsImpl1.java index dbdcc63a48..8e3deeaceb 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/sysobj/DebugSystemObjectsImpl1.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/sysobj/DebugSystemObjectsImpl1.java @@ -103,7 +103,11 @@ public class DebugSystemObjectsImpl1 implements DebugSystemObjectsInternal { @Override public int getNumberThreads() { ULONGByReference pulNumber = new ULONGByReference(); - COMUtils.checkRC(jnaSysobj.GetNumberThreads(pulNumber)); + HRESULT hr = jnaSysobj.GetNumberThreads(pulNumber); + if (hr.equals(COMUtilsExtra.E_UNEXPECTED)) { + return 0; + } + COMUtils.checkRC(hr); return pulNumber.getValue().intValue(); } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgDebugEventCallbacksAdapter.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgDebugEventCallbacksAdapter.java index 925d9ea87b..d836efd2d4 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgDebugEventCallbacksAdapter.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgDebugEventCallbacksAdapter.java @@ -110,7 +110,8 @@ public class DbgDebugEventCallbacksAdapter extends DebugEventCallbacksAdapter { DebugStatus status = DebugStatus.fromArgument(argument); Msg.info(this, "***ExecutionStatus: " + status); if (status.equals(DebugStatus.NO_DEBUGGEE)) { - event.setState(DbgState.SESSION_EXIT); + long processCount = manager.getProcessCount(); + event.setState(processCount > 0 ? DbgState.SESSION_EXIT : DbgState.EXIT); } return checkInterrupt(manager.processEvent(event)); } @@ -131,10 +132,18 @@ public class DbgDebugEventCallbacksAdapter extends DebugEventCallbacksAdapter { return checkInterrupt(DebugStatus.NO_CHANGE); } - //@Override - //public DebugStatus changeDebuggeeState(BitmaskSet flags, long argument) { - // System.err.println("CHANGE_DEBUGGEE_STATE: " + flags + ":" + argument); - // return DebugStatus.NO_CHANGE; - //} + /* + @Override + public DebugStatus changeDebuggeeState(BitmaskSet flags, long argument) { + System.err.println("CHANGE_DEBUGGEE_STATE: " + flags + ":" + argument); + return DebugStatus.NO_CHANGE; + } + + @Override + public DebugStatus sessionStatus(SessionStatus status) { + System.err.println("SESSION_STATUS: " + status); + return DebugStatus.NO_CHANGE; + } + */ } 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 01ff42e0e6..b73e514c2d 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 @@ -110,6 +110,7 @@ public class DbgManagerImpl implements DbgManager { private volatile boolean waiting = false; private boolean kernelMode = false; private CompletableFuture continuation; + private long processCount = 0; /** * Instantiate a new manager @@ -998,6 +999,12 @@ public class DbgManagerImpl implements DbgManager { waiting = true; Long info = evt.getInfo(); + if (info.intValue() >= 0) { + processCount++; + } + else { + processCount--; + } DebugProcessId id = new DebugProcessId(info.intValue()); String key = Integer.toHexString(id.id); @@ -1523,4 +1530,9 @@ public class DbgManagerImpl implements DbgManager { public void setContinuation(CompletableFuture continuation) { this.continuation = continuation; } + + public long getProcessCount() { + return processCount; + } + } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRootImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRootImpl.java index 4f0ca6d995..0a596e8c1a 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRootImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRootImpl.java @@ -27,11 +27,26 @@ import ghidra.dbg.target.*; import ghidra.dbg.target.schema.*; import ghidra.dbg.util.PathUtils; -@TargetObjectSchemaInfo(name = "Debugger", elements = { - @TargetElementType(type = Void.class) }, attributes = { - @TargetAttributeType(name = "Available", type = DbgModelTargetAvailableContainerImpl.class, required = true, fixed = true), - @TargetAttributeType(name = "Connectors", type = DbgModelTargetConnectorContainerImpl.class, required = true, fixed = true), - @TargetAttributeType(name = "Sessions", type = DbgModelTargetSessionContainerImpl.class, required = true, fixed = true), +@TargetObjectSchemaInfo( + name = "Debugger", + elements = { + @TargetElementType(type = Void.class) }, + attributes = { + @TargetAttributeType( + name = "Available", + type = DbgModelTargetAvailableContainerImpl.class, + required = true, + fixed = true), + @TargetAttributeType( + name = "Connectors", + type = DbgModelTargetConnectorContainerImpl.class, + required = true, + fixed = true), + @TargetAttributeType( + name = "Sessions", + type = DbgModelTargetSessionContainerImpl.class, + required = true, + fixed = true), @TargetAttributeType(type = Void.class) }) public class DbgModelTargetRootImpl extends DbgModelDefaultTargetModelRoot implements DbgModelTargetRoot { @@ -120,9 +135,11 @@ public class DbgModelTargetRootImpl extends DbgModelDefaultTargetModelRoot DbgReason reason) { DbgModelTargetThread targetThread = (DbgModelTargetThread) getModel().getModelObject(thread); - changeAttributes(List.of(), List.of(), Map.of( // - TargetEventScope.EVENT_OBJECT_ATTRIBUTE_NAME, targetThread // - ), reason.desc()); + if (targetThread != null) { + changeAttributes(List.of(), List.of(), Map.of( // + TargetEventScope.EVENT_OBJECT_ATTRIBUTE_NAME, targetThread // + ), reason.desc()); + } } @Override diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengInterpreterTest.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengInterpreterTest.java index feea8bb3a5..1af771309a 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengInterpreterTest.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengInterpreterTest.java @@ -68,4 +68,14 @@ public abstract class AbstractModelForDbgengInterpreterTest public DebuggerTestSpecimen getLaunchSpecimen() { return WindowsSpecimen.PRINT; } + + /* + @Override + @Ignore + @Test(expected = DebuggerModelTerminatingException.class) + public void testExecuteQuit() throws Throwable { + // Different behavior for dbg clients vice gdb + } + */ + } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/gadp/GadpModelForDbgengInterpreterTest.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/gadp/GadpModelForDbgengInterpreterTest.java index b65b1f697f..65d603e3b7 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/gadp/GadpModelForDbgengInterpreterTest.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/gadp/GadpModelForDbgengInterpreterTest.java @@ -19,7 +19,6 @@ import org.junit.Ignore; import org.junit.Test; import agent.dbgeng.model.AbstractModelForDbgengInterpreterTest; -import ghidra.dbg.error.DebuggerModelTerminatingException; public class GadpModelForDbgengInterpreterTest extends AbstractModelForDbgengInterpreterTest { @@ -37,11 +36,4 @@ public class GadpModelForDbgengInterpreterTest extends AbstractModelForDbgengInt super.testAttachViaInterpreterShowsInProcessContainer(); } - @Override - @Ignore - @Test(expected = DebuggerModelTerminatingException.class) - public void testExecuteQuit() throws Throwable { - // Hangs after DebuggerModelTerminatingException - } - } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/invm/InVmModelForDbgengInterpreterTest.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/invm/InVmModelForDbgengInterpreterTest.java index fce492faf8..9e3d3982d3 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/invm/InVmModelForDbgengInterpreterTest.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/invm/InVmModelForDbgengInterpreterTest.java @@ -19,6 +19,7 @@ import org.junit.Ignore; import org.junit.Test; import agent.dbgeng.model.AbstractModelForDbgengInterpreterTest; +import ghidra.dbg.error.DebuggerModelTerminatingException; public class InVmModelForDbgengInterpreterTest extends AbstractModelForDbgengInterpreterTest { @Override @@ -32,4 +33,12 @@ public class InVmModelForDbgengInterpreterTest extends AbstractModelForDbgengInt public void testAttachViaInterpreterShowsInProcessContainer() throws Throwable { super.testAttachViaInterpreterShowsInProcessContainer(); } + + @Override + @Ignore + @Test(expected = DebuggerModelTerminatingException.class) + public void testExecuteQuit() throws Throwable { + // Different behavior for dbg clients vice gdb + } + } 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 3bd47184b2..478e9394cb 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 @@ -16,6 +16,7 @@ package agent.dbgmodel.model.impl; import java.util.*; +import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -112,9 +113,11 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject requestElements(boolean refresh) { List nlist = new ArrayList<>(); + List rlist = new ArrayList<>(); return requestNativeElements().thenCompose(list -> { synchronized (elements) { - for (TargetObject element : elements.values()) { + for (Entry entry : elements.entrySet()) { + TargetObject element = entry.getValue(); if (!list.contains(element)) { if (element instanceof DbgStateListener) { getManager().removeStateListener((DbgStateListener) element); @@ -122,6 +125,7 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject { - changeElements(List.of(), nlist, Map.of(), "Refreshed"); + changeElements(rlist, nlist, Map.of(), "Refreshed"); }); } @Override public CompletableFuture requestAttributes(boolean refresh) { Map nmap = new HashMap<>(); + List rlist = new ArrayList<>(); return requestNativeAttributes().thenCompose(map -> { synchronized (attributes) { if (map != null) { Collection values = map.values(); - for (Object attribute : attributes.values()) { + for (Entry entry : attributes.entrySet()) { + Object attribute = entry.getValue(); if (!values.contains(attribute)) { if (attribute instanceof DbgStateListener) { getManager().removeStateListener((DbgStateListener) attribute); @@ -148,6 +154,7 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject path = ref.getPath(); + TargetObject to = (TargetObject) val; + List path = to.getPath(); boolean isLink = PathUtils.isLink(parent.getPath(), xkey, path); boolean isMethod = false; - if (ref instanceof TargetObject) { - TargetObject to = ref; - isMethod = to instanceof TargetMethod; - } + isMethod = to instanceof TargetMethod; if (!(val instanceof DummyTargetObject) && !isMethod) { - return new ObjectContainer(ref, isLink ? xkey : null); + return new ObjectContainer(to, isLink ? xkey : null); } } else { @@ -1786,6 +1799,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter return intrinsicForegroundColor; case OPTION_NAME_INVISIBLE_FOREGROUND_COLOR: return invisibleForegroundColor; + case OPTION_NAME_INVALIDATED_FOREGROUND_COLOR: + return invalidatedForegroundColor; case OPTION_NAME_MODIFIED_FOREGROUND_COLOR: return modifiedForegroundColor; case OPTION_NAME_SUBSCRIBED_FOREGROUND_COLOR: diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/ObjectContainer.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/ObjectContainer.java index 9179d0fc8d..880d9596c3 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/ObjectContainer.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/ObjectContainer.java @@ -16,6 +16,7 @@ package ghidra.app.plugin.core.debug.gui.objects; import java.util.*; +import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; import org.jdom.Element; @@ -64,6 +65,14 @@ public class ObjectContainer implements Comparable { return nc; } + public void updateUsing(ObjectContainer container) { + attributeMap.clear(); + attributeMap.putAll(container.getAttributeMap()); + elementMap.clear(); + elementMap.putAll(container.getElementMap()); + targetObject = container.targetObject; + } + public boolean hasElements() { return !elementMap.isEmpty(); } @@ -168,32 +177,40 @@ public class ObjectContainer implements Comparable { public void augmentElements(Collection elementsRemoved, Map elementsAdded) { Set result = new TreeSet(); + Map newAdds = new HashMap<>(); + for (Entry entry : elementsAdded.entrySet()) { + newAdds.put(entry.getKey(), entry.getValue()); + } boolean structureChanged = false; synchronized (elementMap) { for (ObjectContainer child : currentChildren) { - String name = child.getName(); - if (name.startsWith("[")) { - name = name.substring(1, name.length() - 1); + String key = child.getName(); + if (key.startsWith("[")) { + key = key.substring(1, key.length() - 1); } - if (elementsRemoved.contains(name) && !elementsAdded.containsKey(name)) { - elementMap.remove(name); + if (elementsRemoved.contains(key) && !elementsAdded.containsKey(key)) { + elementMap.remove(key); structureChanged = true; continue; } + if (elementsAdded.containsKey(key)) { + Object val = elementsAdded.get(key); + ObjectContainer newChild = + DebuggerObjectsProvider.buildContainerFromObject(targetObject, key, val, + true); + child.updateUsing(newChild); + newAdds.remove(key); + provider.signalDataChanged(child); + } result.add(child); } for (String key : elementsAdded.keySet()) { TargetObject val = elementsAdded.get(key); ObjectContainer child = DebuggerObjectsProvider.buildContainerFromObject(targetObject, key, val, false); - if (!elementMap.containsKey(key)) { - structureChanged = true; - } - else { - provider.signalDataChanged(child); - } elementMap.put(key, val); result.add(child); + structureChanged = true; } } currentChildren = result; @@ -207,30 +224,38 @@ public class ObjectContainer implements Comparable { public void augmentAttributes(Collection attributesRemoved, Map attributesAdded) { Set result = new TreeSet(); + Map newAdds = new HashMap<>(); + for (Entry entry : attributesAdded.entrySet()) { + newAdds.put(entry.getKey(), entry.getValue()); + } boolean structureChanged = false; synchronized (attributeMap) { for (ObjectContainer child : currentChildren) { - String name = child.getName(); - if (attributesRemoved.contains(name) && !attributesAdded.containsKey(name)) { - attributeMap.remove(name); + String key = child.getName(); + if (attributesRemoved.contains(key) && !attributesAdded.containsKey(key)) { + attributeMap.remove(key); structureChanged = true; continue; } + if (attributesAdded.containsKey(key)) { + Object val = attributesAdded.get(key); + ObjectContainer newChild = + DebuggerObjectsProvider.buildContainerFromObject(targetObject, key, val, + true); + child.updateUsing(newChild); + newAdds.remove(key); + provider.signalDataChanged(child); + } result.add(child); } - for (String key : attributesAdded.keySet()) { - Object val = attributesAdded.get(key); + for (String key : newAdds.keySet()) { + Object val = newAdds.get(key); ObjectContainer child = DebuggerObjectsProvider.buildContainerFromObject(targetObject, key, val, true); if (child != null) { - if (!attributeMap.containsKey(key)) { - structureChanged = true; - } - else { - provider.signalDataChanged(child); - } attributeMap.put(key, val); result.add(child); + structureChanged = true; } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectNode.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectNode.java index 5d61267d11..fa91cf18f1 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectNode.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectNode.java @@ -37,6 +37,8 @@ public class ObjectNode extends GTreeSlowLoadingNode { //extends GTreeNode ResourceManager.loadImage("images/object-populated.png"); static final ImageIcon ICON_EMPTY = ResourceManager.loadImage("images/object-unpopulated.png"); static final ImageIcon ICON_RUNNING = ResourceManager.loadImage("images/object-running.png"); + static final ImageIcon ICON_TERMINATED = + ResourceManager.loadImage("images/object-terminated.png"); static final ImageIcon ICON_EVENT = ResourceManager.loadImage("images/register-marker.png"); private ObjectContainer container; @@ -142,6 +144,9 @@ public class ObjectNode extends GTreeSlowLoadingNode { //extends GTreeNode if (stateful.getExecutionState().equals(TargetExecutionState.RUNNING)) { return ICON_RUNNING; } + if (stateful.getExecutionState().equals(TargetExecutionState.TERMINATED)) { + return ICON_TERMINATED; + } } /* Map attributeMap = container.getAttributeMap(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectTreeCellRenderer.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectTreeCellRenderer.java index a4223ab90c..0b1044a62c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectTreeCellRenderer.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectTreeCellRenderer.java @@ -24,6 +24,8 @@ import javax.swing.tree.TreePath; import docking.widgets.tree.support.GTreeRenderer; import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsProvider; import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer; +import ghidra.dbg.target.TargetExecutionStateful; +import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; import ghidra.dbg.target.TargetObject; // TODO: In the new scheme, I'm not sure this is applicable anymore. @@ -66,6 +68,14 @@ class ObjectTreeCellRenderer extends GTreeRenderer { component.setForeground(provider .getColor(DebuggerObjectsProvider.OPTION_NAME_INVISIBLE_FOREGROUND_COLOR)); } + if (container.getTargetObject() instanceof TargetExecutionStateful) { + TargetExecutionStateful stateful = (TargetExecutionStateful) targetObject; + if (stateful.getExecutionState().equals(TargetExecutionState.TERMINATED)) { + component.setForeground(provider + .getColor( + DebuggerObjectsProvider.OPTION_NAME_INVALIDATED_FOREGROUND_COLOR)); + } + } if (container.isLink()) { component.setForeground( provider.getColor(DebuggerObjectsProvider.OPTION_NAME_LINK_FOREGROUND_COLOR)); diff --git a/Ghidra/Debug/Debugger/src/main/resources/images/object-terminated.png b/Ghidra/Debug/Debugger/src/main/resources/images/object-terminated.png new file mode 100644 index 0000000000..cb1d378235 Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/resources/images/object-terminated.png differ diff --git a/Ghidra/Debug/Debugger/src/main/svg/object-terminated.svg b/Ghidra/Debug/Debugger/src/main/svg/object-terminated.svg new file mode 100644 index 0000000000..980d822578 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/svg/object-terminated.svg @@ -0,0 +1,34 @@ + + + + + + + image/svg+xml + + + + + + + + +