Merge remote-tracking branch 'origin/GP-4292_Dan_expandMeansRefresh--SQUASHED'

This commit is contained in:
Ryan Kurtz 2024-02-15 11:21:47 -05:00
commit 79b04c5a04
7 changed files with 184 additions and 15 deletions

View file

@ -277,6 +277,9 @@ def start_trace(name):
with STATE.trace.open_tx("Create Root Object"): with STATE.trace.open_tx("Create Root Object"):
root = STATE.trace.create_root_object(schema_xml, 'Session') root = STATE.trace.create_root_object(schema_xml, 'Session')
root.set_value('_display', 'GNU gdb ' + util.GDB_VERSION.full) root.set_value('_display', 'GNU gdb ' + util.GDB_VERSION.full)
STATE.trace.create_object(AVAILABLES_PATH).insert()
STATE.trace.create_object(BREAKPOINTS_PATH).insert()
STATE.trace.create_object(INFERIORS_PATH).insert()
gdb.set_convenience_variable('_ghidra_tracing', True) gdb.set_convenience_variable('_ghidra_tracing', True)
@ -1057,7 +1060,7 @@ def put_available():
procobj = STATE.trace.create_object(ppath) procobj = STATE.trace.create_object(ppath)
keys.append(AVAILABLE_KEY_PATTERN.format(pid=proc.pid)) keys.append(AVAILABLE_KEY_PATTERN.format(pid=proc.pid))
procobj.set_value('_pid', proc.pid) procobj.set_value('_pid', proc.pid)
procobj.set_value('_display', '{} {}'.format(proc.pid, proc.name)) procobj.set_value('_display', '{} {}'.format(proc.pid, proc.name()))
procobj.insert() procobj.insert()
STATE.trace.proxy_object_path(AVAILABLES_PATH).retain_values(keys) STATE.trace.proxy_object_path(AVAILABLES_PATH).retain_values(keys)

View file

@ -428,7 +428,8 @@ public class TraceRmiTarget extends AbstractTarget {
@Override @Override
protected Map<String, ActionEntry> collectRefreshActions(ActionContext context) { protected Map<String, ActionEntry> collectRefreshActions(ActionContext context) {
return collectByName(ActionName.REFRESH, context); return collectFromMethods(connection.getMethods().getByAction(ActionName.REFRESH), context,
true, false, false);
} }
@Override @Override

View file

@ -141,6 +141,7 @@ public class DebuggerConsolePlugin extends Plugin implements DebuggerConsoleServ
* For testing: get the number of rows having a given class of action context * For testing: get the number of rows having a given class of action context
* *
* @param ctxCls the context class * @param ctxCls the context class
* @return the number of rows
*/ */
public long getRowCount(Class<? extends ActionContext> ctxCls) { public long getRowCount(Class<? extends ActionContext> ctxCls) {
return provider.getRowCount(ctxCls); return provider.getRowCount(ctxCls);

View file

@ -15,6 +15,8 @@
*/ */
package ghidra.app.plugin.core.debug.gui.control; package ghidra.app.plugin.core.debug.gui.control;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.services.DebuggerConsoleService; import ghidra.app.services.DebuggerConsoleService;
import ghidra.app.services.ProgressService; import ghidra.app.services.ProgressService;
@ -25,25 +27,78 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task; import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/**
* A task for executing a target {@link ActionEntry}.
*
* <p>
* This also has some static convenience methods for scheduling this and other types of tasks in the
* Debugger tool.
*/
public class TargetActionTask extends Task { public class TargetActionTask extends Task {
public static void executeTask(PluginTool tool, Task task) { /**
* Execute a task
*
* <p>
* If available, this simply delegates to {@link ProgressService#execute(Task)}. If not, then
* this falls back to {@link PluginTool#execute(Task)}.
*
* @param tool the tool in which to execute
* @param task the task to execute
* @return a future that completes (perhaps exceptionally) when the task is finished or
* cancelled
*/
public static CompletableFuture<Void> executeTask(PluginTool tool, Task task) {
ProgressService progressService = tool.getService(ProgressService.class); ProgressService progressService = tool.getService(ProgressService.class);
if (progressService != null) { if (progressService != null) {
progressService.execute(task); return progressService.execute(task);
} }
else { CompletableFuture<Void> future = new CompletableFuture<>();
tool.execute(
new Task(task.getTaskTitle(), task.canCancel(), task.hasProgress(), task.isModal(),
task.getWaitForTaskCompleted()) {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
try {
task.run(monitor);
future.complete(null);
}
catch (CancelledException e) {
future.cancel(false);
}
catch (Throwable e) {
future.completeExceptionally(e);
}
}
});
tool.execute(task); tool.execute(task);
} return future;
} }
public static void runAction(PluginTool tool, String title, ActionEntry entry) { /**
executeTask(tool, new TargetActionTask(tool, title, entry)); * Execute an {@link ActionEntry}
*
* @param tool the tool in which to execute
* @param title the title, often {@link ActionEntry#display()}
* @param entry the action to execute
* @return a future that completes (perhaps exceptionally) when the task is finished or
* cancelled
*/
public static CompletableFuture<Void> runAction(PluginTool tool, String title,
ActionEntry entry) {
return executeTask(tool, new TargetActionTask(tool, title, entry));
} }
private final PluginTool tool; private final PluginTool tool;
private final ActionEntry entry; private final ActionEntry entry;
/**
* Construct a task fore the given action
*
* @param tool the plugin tool
* @param title the title, often {@link ActionEntry#display()}
* @param entry the action to execute
*/
public TargetActionTask(PluginTool tool, String title, ActionEntry entry) { public TargetActionTask(PluginTool tool, String title, ActionEntry entry) {
super(title, false, false, false); super(title, false, false, false);
this.tool = tool; this.tool = tool;

View file

@ -18,13 +18,16 @@ package ghidra.app.plugin.core.debug.gui.model;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.util.*;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.concurrent.CompletableFuture;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.tree.TreePath;
import docking.*; import docking.*;
import docking.action.DockingAction; import docking.action.DockingAction;
@ -39,12 +42,17 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.CloneWindowAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.CloneWindowAction;
import ghidra.app.plugin.core.debug.gui.MultiProviderSaveBehavior.SaveableProvider; import ghidra.app.plugin.core.debug.gui.MultiProviderSaveBehavior.SaveableProvider;
import ghidra.app.plugin.core.debug.gui.control.TargetActionTask;
import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActivationListener; import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActivationListener;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ObjectRow; import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ObjectRow;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow; import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.AbstractNode; import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.*;
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.RootNode;
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow; import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.DebuggerTraceManagerService;
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.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
@ -501,11 +509,26 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
setPath(value == null ? TraceObjectKeyPath.of() : value.getCanonicalPath(), setPath(value == null ? TraceObjectKeyPath.of() : value.getCanonicalPath(),
objectsTreePanel, EventOrigin.INTERNAL_GENERATED); objectsTreePanel, EventOrigin.INTERNAL_GENERATED);
}); });
objectsTreePanel.tree.addTreeExpansionListener(new TreeExpansionListener() {
@Override
public void treeExpanded(TreeExpansionEvent expEvent) {
if (EventQueue.getCurrentEvent() instanceof InputEvent event &&
!event.isShiftDown()) {
refreshTargetChildren(expEvent.getPath());
}
}
@Override
public void treeCollapsed(TreeExpansionEvent event) {
// I don't care
}
});
objectsTreePanel.tree.addMouseListener(new MouseAdapter() { objectsTreePanel.tree.addMouseListener(new MouseAdapter() {
@Override @Override
public void mouseClicked(MouseEvent e) { public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) { if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
activateObjectSelectedInTree(); activateObjectSelectedInTree();
e.consume();
} }
} }
}); });
@ -572,6 +595,54 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
rebuildPanels(); rebuildPanels();
} }
private TraceObjectValue getObject(TreePath path) {
if (path.getLastPathComponent() instanceof CanonicalNode node) {
return node.getValue();
}
if (path.getLastPathComponent() instanceof RootNode node) {
return node.getValue();
}
return null;
}
private void refreshTargetChildren(TreePath path) {
TraceObjectValue value = getObject(path);
if (value == null) {
return;
}
DebuggerCoordinates current = traceManager.getCurrentFor(value.getTrace());
if (!current.isAliveAndPresent() && limitToSnap) {
return;
}
Target target = current.getTarget();
if (target == null) {
return;
}
Map<String, ActionEntry> actions = target.collectActions(ActionName.REFRESH,
new DebuggerObjectActionContext(List.of(value), this, objectsTreePanel));
for (ActionEntry ent : actions.values()) {
if (ent.requiresPrompt()) {
continue;
}
if (path.getLastPathComponent() instanceof AbstractNode node) {
/**
* This pending node does not duplicate what the lazy node already does. For all
* it's concerned, once it has loaded the entries from the database, it is done.
* This task asks the target to update that database, so it needs its own indicator.
*/
PendingNode pending = new PendingNode();
node.addNode(0, pending);
CompletableFuture<Void> future =
TargetActionTask.runAction(plugin.getTool(), ent.display(), ent);
future.handle((__, ex) -> {
node.removeNode(pending);
return null;
});
return;
}
}
}
private void activateObjectSelectedInTree() { private void activateObjectSelectedInTree() {
List<AbstractNode> sel = objectsTreePanel.getSelectedItems(); List<AbstractNode> sel = objectsTreePanel.getSelectedItems();
if (sel.size() != 1) { if (sel.size() != 1) {
@ -580,7 +651,7 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
return; return;
} }
TraceObjectValue value = sel.get(0).getValue(); TraceObjectValue value = sel.get(0).getValue();
if (value.getValue() instanceof TraceObject child) { if (value != null && value.getValue() instanceof TraceObject child) {
activatePath(child.getCanonicalPath()); activatePath(child.getCanonicalPath());
} }
} }

View file

@ -22,6 +22,7 @@ import javax.swing.Icon;
import docking.widgets.tree.GTreeLazyNode; import docking.widgets.tree.GTreeLazyNode;
import docking.widgets.tree.GTreeNode; import docking.widgets.tree.GTreeNode;
import generic.theme.GIcon;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator; import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
@ -35,6 +36,7 @@ import ghidra.util.datastruct.WeakValueHashMap;
import utilities.util.IDKeyed; import utilities.util.IDKeyed;
public class ObjectTreeModel implements DisplaysModified { public class ObjectTreeModel implements DisplaysModified {
public static final GIcon ICON_PENDING = new GIcon("icon.pending");
class ListenerForChanges extends TraceDomainObjectListener class ListenerForChanges extends TraceDomainObjectListener
implements DomainObjectClosedListener { implements DomainObjectClosedListener {
@ -193,6 +195,38 @@ public class ObjectTreeModel implements DisplaysModified {
} }
} }
public static class PendingNode extends GTreeLazyNode {
@Override
public String getName() {
return ""; // Want it sorted to the front
}
@Override
public String getDisplayText() {
return "Refreshing...";
}
@Override
public Icon getIcon(boolean expanded) {
return ICON_PENDING;
}
@Override
public boolean isLeaf() {
return true;
}
@Override
protected List<GTreeNode> generateChildren() {
return List.of();
}
@Override
public String getToolTip() {
return null;
}
}
public abstract class AbstractNode extends GTreeLazyNode { public abstract class AbstractNode extends GTreeLazyNode {
public abstract TraceObjectValue getValue(); public abstract TraceObjectValue getValue();
@ -332,7 +366,7 @@ public class ObjectTreeModel implements DisplaysModified {
} }
} }
class RootNode extends AbstractNode { public class RootNode extends AbstractNode {
@Override @Override
public TraceObjectValue getValue() { public TraceObjectValue getValue() {
if (trace == null) { if (trace == null) {

View file

@ -398,7 +398,11 @@ public class ObjectsTreePanel extends JPanel {
protected <R, A> R getItemsFromPaths(TreePath[] paths, protected <R, A> R getItemsFromPaths(TreePath[] paths,
Collector<? super AbstractNode, A, R> collector) { Collector<? super AbstractNode, A, R> collector) {
return Stream.of(paths) return Stream.of(paths)
.map(p -> (AbstractNode) p.getLastPathComponent()) .<AbstractNode> mapMulti((path, consumer) -> {
if (path.getLastPathComponent() instanceof AbstractNode node) {
consumer.accept(node);
}
})
.collect(collector); .collect(collector);
} }