mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-1608: DebuggerListing use GTabPanel. No tabs in Threads.
This commit is contained in:
parent
1fa19633d3
commit
04d2e88c2d
26 changed files with 579 additions and 653 deletions
|
@ -135,6 +135,13 @@ public interface Target {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describe the target for display in the UI
|
||||||
|
*
|
||||||
|
* @return the description
|
||||||
|
*/
|
||||||
|
String describe();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the target is still valid
|
* Check if the target is still valid
|
||||||
*
|
*
|
||||||
|
|
|
@ -45,6 +45,16 @@ import ghidra.util.NotOwnerException;
|
||||||
|
|
||||||
public class DebuggerCoordinates {
|
public class DebuggerCoordinates {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coordinates that indicate no trace is active in the Debugger UI.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Typically, that only happens when no trace is open. Telling the trace manager to activate
|
||||||
|
* {@code NOWHERE} will cause it to instead activate the most recently active trace, which may
|
||||||
|
* very well be the current trace, resulting in no change. Internally, the trace manager will
|
||||||
|
* activate {@code NOWHERE} whenever the current trace is closed, effectively activating the
|
||||||
|
* most recent trace other than the one just closed.
|
||||||
|
*/
|
||||||
public static final DebuggerCoordinates NOWHERE =
|
public static final DebuggerCoordinates NOWHERE =
|
||||||
new DebuggerCoordinates(null, null, null, null, null, null, null, null);
|
new DebuggerCoordinates(null, null, null, null, null, null, null, null);
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,12 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
this.supportedBreakpointKinds = computeSupportedBreakpointKinds();
|
this.supportedBreakpointKinds = computeSupportedBreakpointKinds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String describe() {
|
||||||
|
return "%s in %s at %s (rmi)".formatted(getTrace().getDomainFile().getName(),
|
||||||
|
connection.getDescription(), connection.getRemoteAddress());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid() {
|
public boolean isValid() {
|
||||||
return !connection.isClosed() && connection.isTarget(trace);
|
return !connection.isClosed() && connection.isTarget(trace);
|
||||||
|
|
|
@ -1614,7 +1614,7 @@ public interface DebuggerResources {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CloseAllTracesAction extends CloseTraceAction {
|
interface CloseAllTracesAction extends CloseTraceAction {
|
||||||
String NAME = NAME_PREFIX + " All Traces";
|
String NAME = NAME_PREFIX + "All Traces";
|
||||||
String DESCRIPTION = "Close all traces";
|
String DESCRIPTION = "Close all traces";
|
||||||
String HELP_ANCHOR = "close_all_traces";
|
String HELP_ANCHOR = "close_all_traces";
|
||||||
|
|
||||||
|
@ -1641,7 +1641,7 @@ public interface DebuggerResources {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CloseOtherTracesAction extends CloseTraceAction {
|
interface CloseOtherTracesAction extends CloseTraceAction {
|
||||||
String NAME = NAME_PREFIX + " Other Traces";
|
String NAME = NAME_PREFIX + "Other Traces";
|
||||||
String DESCRIPTION = "Close all traces except the current one";
|
String DESCRIPTION = "Close all traces except the current one";
|
||||||
String HELP_ANCHOR = "close_other_traces";
|
String HELP_ANCHOR = "close_other_traces";
|
||||||
|
|
||||||
|
@ -1668,7 +1668,7 @@ public interface DebuggerResources {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CloseDeadTracesAction extends CloseTraceAction {
|
interface CloseDeadTracesAction extends CloseTraceAction {
|
||||||
String NAME = NAME_PREFIX + " Dead Traces";
|
String NAME = NAME_PREFIX + "Dead Traces";
|
||||||
String DESCRIPTION = "Close all traces not being recorded";
|
String DESCRIPTION = "Close all traces not being recorded";
|
||||||
String HELP_ANCHOR = "close_dead_traces";
|
String HELP_ANCHOR = "close_dead_traces";
|
||||||
|
|
||||||
|
|
|
@ -55,9 +55,6 @@ import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.trace.util.TraceEvents;
|
import ghidra.trace.util.TraceEvents;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.Swing;
|
import ghidra.util.Swing;
|
||||||
import ghidra.util.exception.CancelledException;
|
|
||||||
import ghidra.util.task.Task;
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
|
||||||
|
|
||||||
@PluginInfo(
|
@PluginInfo(
|
||||||
shortDescription = "Debugger global controls",
|
shortDescription = "Debugger global controls",
|
||||||
|
@ -357,18 +354,7 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TargetActionTask.executeTask(tool, new Task("Disconnect", false, false, false) {
|
TargetActionTask.executeTask(tool, new DisconnectTask(tool, List.of(target)));
|
||||||
@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 haveEmuAndTrace() {
|
private boolean haveEmuAndTrace() {
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/* ###
|
||||||
|
* 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 java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import ghidra.debug.api.target.Target;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.task.Task;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
public class DisconnectTask extends Task {
|
||||||
|
private final PluginTool tool;
|
||||||
|
private final List<Target> targets;
|
||||||
|
|
||||||
|
public DisconnectTask(PluginTool tool, Collection<Target> targets) {
|
||||||
|
super("Disconnect", false, true, false);
|
||||||
|
this.tool = tool;
|
||||||
|
this.targets = List.copyOf(targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(TaskMonitor monitor) throws CancelledException {
|
||||||
|
monitor.initialize(targets.size(), "Disconnecting...");
|
||||||
|
for (Target target : targets) {
|
||||||
|
try {
|
||||||
|
monitor.setMessage("Disconnecting " + target.describe());
|
||||||
|
target.disconnect();
|
||||||
|
monitor.increment();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
tool.setStatusInfo("Disconnect failed: " + e, true);
|
||||||
|
Msg.error(this, "Disconnect failed: " + e, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -57,6 +57,8 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAc
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.OpenProgramAction;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.OpenProgramAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.action.*;
|
import ghidra.app.plugin.core.debug.gui.action.*;
|
||||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext;
|
import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.thread.DebuggerTraceFileActionContext;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.trace.DebuggerTraceTabPanel;
|
||||||
import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils;
|
import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils;
|
||||||
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
|
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
|
||||||
import ghidra.app.plugin.core.marker.MarkerMarginProvider;
|
import ghidra.app.plugin.core.marker.MarkerMarginProvider;
|
||||||
|
@ -342,6 +344,7 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
protected final ListenerSet<LocationTrackingSpecChangeListener> trackingSpecChangeListeners =
|
protected final ListenerSet<LocationTrackingSpecChangeListener> trackingSpecChangeListeners =
|
||||||
new ListenerSet<>(LocationTrackingSpecChangeListener.class, true);
|
new ListenerSet<>(LocationTrackingSpecChangeListener.class, true);
|
||||||
|
|
||||||
|
protected final DebuggerTraceTabPanel traceTabs;
|
||||||
protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel();
|
protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel();
|
||||||
protected final JLabel trackingLabel = new JLabel();
|
protected final JLabel trackingLabel = new JLabel();
|
||||||
|
|
||||||
|
@ -372,6 +375,8 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.isMainListing = isConnected;
|
this.isMainListing = isConnected;
|
||||||
|
|
||||||
|
// TODO: An icon to distinguish dynamic from static
|
||||||
|
|
||||||
syncTrait = new ForListingSyncTrait();
|
syncTrait = new ForListingSyncTrait();
|
||||||
goToTrait = new ForListingGoToTrait();
|
goToTrait = new ForListingGoToTrait();
|
||||||
trackingTrait = new ForListingTrackingTrait();
|
trackingTrait = new ForListingTrackingTrait();
|
||||||
|
@ -394,13 +399,21 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
readsMemTrait.goToCoordinates(current);
|
readsMemTrait.goToCoordinates(current);
|
||||||
locationLabel.goToCoordinates(current);
|
locationLabel.goToCoordinates(current);
|
||||||
|
|
||||||
// TODO: An icon to distinguish dynamic from static
|
if (isConnected) {
|
||||||
|
traceTabs = new DebuggerTraceTabPanel(plugin);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
traceTabs = null;
|
||||||
|
}
|
||||||
|
|
||||||
addDisplayListener(readsMemTrait.getDisplayListener());
|
addDisplayListener(readsMemTrait.getDisplayListener());
|
||||||
|
|
||||||
JPanel northPanel = new JPanel(new BorderLayout());
|
JPanel northPanel = new JPanel(new BorderLayout());
|
||||||
northPanel.add(locationLabel);
|
northPanel.add(locationLabel);
|
||||||
northPanel.add(trackingLabel, BorderLayout.EAST);
|
northPanel.add(trackingLabel, BorderLayout.EAST);
|
||||||
|
if (traceTabs != null) {
|
||||||
|
northPanel.add(traceTabs, BorderLayout.NORTH);
|
||||||
|
}
|
||||||
this.setNorthComponent(northPanel);
|
this.setNorthComponent(northPanel);
|
||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
setTitle(DebuggerResources.TITLE_PROVIDER_LISTING);
|
setTitle(DebuggerResources.TITLE_PROVIDER_LISTING);
|
||||||
|
@ -929,6 +942,12 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ActionContext getActionContext(MouseEvent event) {
|
public ActionContext getActionContext(MouseEvent event) {
|
||||||
|
if (traceTabs != null) {
|
||||||
|
DebuggerTraceFileActionContext traceCtx = traceTabs.getActionContext(event);
|
||||||
|
if (traceCtx != null) {
|
||||||
|
return traceCtx;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (event == null || event.getSource() != locationLabel) {
|
if (event == null || event.getSource() != locationLabel) {
|
||||||
return super.getActionContext(event);
|
return super.getActionContext(event);
|
||||||
}
|
}
|
||||||
|
@ -1036,7 +1055,6 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
// Attempt to open probable matches. All others, list to import
|
// Attempt to open probable matches. All others, list to import
|
||||||
// TODO: What if sections are not presented?
|
|
||||||
for (TraceModule mod : modules) {
|
for (TraceModule mod : modules) {
|
||||||
DomainFile match = mappingService.findBestModuleProgram(space, mod);
|
DomainFile match = mappingService.findBestModuleProgram(space, mod);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
|
@ -1064,8 +1082,9 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
new DebuggerMissingModuleActionContext(mod));
|
new DebuggerMissingModuleActionContext(mod));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Once the programs are opened, including those which are successfully imported, the mapper
|
* Once the programs are opened, including those which are successfully imported, the
|
||||||
* bot should take over, eventually invoking callbacks to our mapping change listener.
|
* automatic mapper should take effect, eventually invoking callbacks to our mapping change
|
||||||
|
* listener.
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,14 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||||
coords);
|
coords);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DebuggerCoordinates coordsForObject(TraceObject object) {
|
||||||
|
if (provider.current.getTrace() != object.getTrace()) {
|
||||||
|
// This can happen transiently, so just find something graceful
|
||||||
|
return DebuggerCoordinates.NOWHERE.object(object);
|
||||||
|
}
|
||||||
|
return provider.current.object(object);
|
||||||
|
}
|
||||||
|
|
||||||
private class ThreadPcColumn extends TraceValueObjectPropertyColumn<Address> {
|
private class ThreadPcColumn extends TraceValueObjectPropertyColumn<Address> {
|
||||||
public ThreadPcColumn() {
|
public ThreadPcColumn() {
|
||||||
super(Address.class);
|
super(Address.class);
|
||||||
|
@ -85,7 +93,8 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||||
@Override
|
@Override
|
||||||
public ValueProperty<Address> getProperty(ValueRow row) {
|
public ValueProperty<Address> getProperty(ValueRow row) {
|
||||||
TraceObject obj = row.getValue().getChild();
|
TraceObject obj = row.getValue().getChild();
|
||||||
DebuggerCoordinates coords = provider.current.object(obj);
|
|
||||||
|
DebuggerCoordinates coords = coordsForObject(obj);
|
||||||
return new ValueAddressProperty(row) {
|
return new ValueAddressProperty(row) {
|
||||||
@Override
|
@Override
|
||||||
public Address getValue() {
|
public Address getValue() {
|
||||||
|
@ -111,7 +120,7 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||||
public Function getValue(ValueRow rowObject, Settings settings, Trace data,
|
public Function getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||||
TraceObject obj = rowObject.getValue().getChild();
|
TraceObject obj = rowObject.getValue().getChild();
|
||||||
DebuggerCoordinates coords = provider.current.object(obj);
|
DebuggerCoordinates coords = coordsForObject(obj);
|
||||||
Address pc = computeProgramCounter(coords);
|
Address pc = computeProgramCounter(coords);
|
||||||
if (pc == null) {
|
if (pc == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -130,7 +139,7 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||||
public String getValue(ValueRow rowObject, Settings settings, Trace data,
|
public String getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||||
TraceObject obj = rowObject.getValue().getChild();
|
TraceObject obj = rowObject.getValue().getChild();
|
||||||
DebuggerCoordinates coords = provider.current.object(obj);
|
DebuggerCoordinates coords = coordsForObject(obj);
|
||||||
Address pc = computeProgramCounter(coords);
|
Address pc = computeProgramCounter(coords);
|
||||||
if (pc == null) {
|
if (pc == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -147,7 +156,7 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||||
@Override
|
@Override
|
||||||
public ValueProperty<Address> getProperty(ValueRow row) {
|
public ValueProperty<Address> getProperty(ValueRow row) {
|
||||||
TraceObject obj = row.getValue().getChild();
|
TraceObject obj = row.getValue().getChild();
|
||||||
DebuggerCoordinates coords = provider.current.object(obj);
|
DebuggerCoordinates coords = coordsForObject(obj);
|
||||||
return new ValueAddressProperty(row) {
|
return new ValueAddressProperty(row) {
|
||||||
@Override
|
@Override
|
||||||
public Address getValue() {
|
public Address getValue() {
|
||||||
|
|
|
@ -28,7 +28,8 @@ import docking.WindowPosition;
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
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.services.*;
|
import ghidra.app.services.DebuggerEmulationService;
|
||||||
|
import ghidra.app.services.DebuggerTargetService;
|
||||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||||
import ghidra.framework.model.DomainObjectChangeRecord;
|
import ghidra.framework.model.DomainObjectChangeRecord;
|
||||||
import ghidra.framework.model.DomainObjectEvent;
|
import ghidra.framework.model.DomainObjectEvent;
|
||||||
|
@ -97,8 +98,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
DebuggerTargetService targetService;
|
DebuggerTargetService targetService;
|
||||||
// @AutoServiceConsumed // via method
|
|
||||||
private DebuggerTraceManagerService traceManager;
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final AutoService.Wiring autoServiceWiring;
|
private final AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
|
@ -106,7 +105,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
private JPanel mainPanel;
|
private JPanel mainPanel;
|
||||||
|
|
||||||
DebuggerTraceTabPanel traceTabs;
|
|
||||||
JPopupMenu traceTabPopupMenu;
|
JPopupMenu traceTabPopupMenu;
|
||||||
DebuggerThreadsPanel panel;
|
DebuggerThreadsPanel panel;
|
||||||
DebuggerLegacyThreadsPanel legacyPanel;
|
DebuggerLegacyThreadsPanel legacyPanel;
|
||||||
|
@ -133,12 +131,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AutoServiceConsumed
|
|
||||||
public void setTraceManager(DebuggerTraceManagerService traceManager) {
|
|
||||||
this.traceManager = traceManager;
|
|
||||||
contextChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
public void setEmulationService(DebuggerEmulationService emulationService) {
|
public void setEmulationService(DebuggerEmulationService emulationService) {
|
||||||
contextChanged();
|
contextChanged();
|
||||||
|
@ -152,7 +144,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
current = coordinates;
|
current = coordinates;
|
||||||
|
|
||||||
traceTabs.coordinatesActivated(coordinates);
|
|
||||||
if (Trace.isLegacy(coordinates.getTrace())) {
|
if (Trace.isLegacy(coordinates.getTrace())) {
|
||||||
panel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
|
panel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
|
||||||
legacyPanel.coordinatesActivated(coordinates);
|
legacyPanel.coordinatesActivated(coordinates);
|
||||||
|
@ -191,10 +182,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
myActionContext = legacyPanel.getActionContext();
|
myActionContext = legacyPanel.getActionContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
void traceTabsContextChanged() {
|
|
||||||
myActionContext = traceTabs.getActionContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ActionContext getActionContext(MouseEvent event) {
|
public ActionContext getActionContext(MouseEvent event) {
|
||||||
if (myActionContext == null) {
|
if (myActionContext == null) {
|
||||||
|
@ -211,10 +198,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
panel = new DebuggerThreadsPanel(this);
|
panel = new DebuggerThreadsPanel(this);
|
||||||
legacyPanel = new DebuggerLegacyThreadsPanel(plugin, this);
|
legacyPanel = new DebuggerLegacyThreadsPanel(plugin, this);
|
||||||
mainPanel.add(panel);
|
mainPanel.add(panel);
|
||||||
|
|
||||||
traceTabs = new DebuggerTraceTabPanel(this);
|
|
||||||
|
|
||||||
mainPanel.add(traceTabs, BorderLayout.NORTH);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createActions() {
|
protected void createActions() {
|
||||||
|
|
|
@ -13,27 +13,24 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.thread;
|
package ghidra.app.plugin.core.debug.gui.trace;
|
||||||
|
|
||||||
import java.awt.Rectangle;
|
import java.awt.event.MouseEvent;
|
||||||
import java.awt.event.*;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import javax.swing.JList;
|
|
||||||
import javax.swing.event.ListSelectionEvent;
|
|
||||||
|
|
||||||
import docking.action.DockingAction;
|
import docking.action.DockingAction;
|
||||||
import docking.widgets.HorizontalTabPanel;
|
import docking.widgets.tab.GTabPanel;
|
||||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
import ghidra.app.plugin.core.debug.event.*;
|
||||||
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.plugin.core.debug.gui.DebuggerResources.*;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.thread.DebuggerTraceFileActionContext;
|
||||||
|
import ghidra.app.plugin.core.progmgr.MultiTabPlugin;
|
||||||
import ghidra.app.services.DebuggerTargetService;
|
import ghidra.app.services.DebuggerTargetService;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService;
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
import ghidra.debug.api.target.Target;
|
import ghidra.debug.api.target.Target;
|
||||||
import ghidra.debug.api.target.TargetPublicationListener;
|
import ghidra.debug.api.target.TargetPublicationListener;
|
||||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.framework.plugintool.util.PluginEventListener;
|
import ghidra.framework.plugintool.util.PluginEventListener;
|
||||||
|
@ -42,26 +39,21 @@ import ghidra.util.Swing;
|
||||||
import utilities.util.SuppressableCallback;
|
import utilities.util.SuppressableCallback;
|
||||||
import utilities.util.SuppressableCallback.Suppression;
|
import utilities.util.SuppressableCallback.Suppression;
|
||||||
|
|
||||||
public class DebuggerTraceTabPanel extends HorizontalTabPanel<Trace>
|
public class DebuggerTraceTabPanel extends GTabPanel<Trace>
|
||||||
implements PluginEventListener {
|
implements PluginEventListener, DomainObjectListener {
|
||||||
|
|
||||||
private class TargetsChangeListener implements TargetPublicationListener {
|
private class TargetsChangeListener implements TargetPublicationListener {
|
||||||
@Override
|
@Override
|
||||||
public void targetPublished(Target target) {
|
public void targetPublished(Target target) {
|
||||||
Swing.runIfSwingOrRunLater(() -> repaint());
|
Swing.runIfSwingOrRunLater(() -> refreshTab(target.getTrace()));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void targetWithdrawn(Target target) {
|
public void targetWithdrawn(Target target) {
|
||||||
Swing.runIfSwingOrRunLater(() -> repaint());
|
Swing.runIfSwingOrRunLater(() -> refreshTab(target.getTrace()));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final DebuggerThreadsPlugin plugin;
|
|
||||||
private final DebuggerThreadsProvider provider;
|
|
||||||
|
|
||||||
// @AutoServiceConsumed by method
|
// @AutoServiceConsumed by method
|
||||||
DebuggerTargetService targetService;
|
DebuggerTargetService targetService;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
|
@ -78,101 +70,80 @@ public class DebuggerTraceTabPanel extends HorizontalTabPanel<Trace>
|
||||||
|
|
||||||
private final SuppressableCallback<Void> cbCoordinateActivation = new SuppressableCallback<>();
|
private final SuppressableCallback<Void> cbCoordinateActivation = new SuppressableCallback<>();
|
||||||
|
|
||||||
private DebuggerTraceFileActionContext myActionContext;
|
public DebuggerTraceTabPanel(Plugin plugin) {
|
||||||
|
super("Trace");
|
||||||
public DebuggerTraceTabPanel(DebuggerThreadsProvider provider) {
|
|
||||||
this.plugin = provider.plugin;
|
|
||||||
this.provider = provider;
|
|
||||||
|
|
||||||
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||||
|
|
||||||
PluginTool tool = plugin.getTool();
|
PluginTool tool = plugin.getTool();
|
||||||
tool.addEventListener(TraceOpenedPluginEvent.class, this);
|
tool.addEventListener(TraceOpenedPluginEvent.class, this);
|
||||||
|
tool.addEventListener(TraceActivatedPluginEvent.class, this);
|
||||||
tool.addEventListener(TraceClosedPluginEvent.class, this);
|
tool.addEventListener(TraceClosedPluginEvent.class, this);
|
||||||
|
|
||||||
list.setCellRenderer(new TabListCellRenderer<>() {
|
setNameFunction(this::getNameForTrace);
|
||||||
protected String getText(Trace value) {
|
setIconFunction(this::getIconForTrace);
|
||||||
return value.getName();
|
setToolTipFunction(this::getTipForTrace);
|
||||||
}
|
setSelectedTabConsumer(this::traceTabSelected);
|
||||||
|
// Cannot use method ref here, since traceManager is still null
|
||||||
protected Icon getIcon(Trace value) {
|
setCloseTabConsumer(t -> traceManager.closeTrace(t));
|
||||||
if (targetService == null) {
|
|
||||||
return super.getIcon(value);
|
|
||||||
}
|
|
||||||
Target target = targetService.getTarget(value);
|
|
||||||
if (target == null || !target.isValid()) {
|
|
||||||
return super.getIcon(value);
|
|
||||||
}
|
|
||||||
return DebuggerResources.ICON_RECORD;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
list.getSelectionModel().addListSelectionListener(this::traceTabSelected);
|
|
||||||
list.addFocusListener(new FocusAdapter() {
|
|
||||||
@Override
|
|
||||||
public void focusGained(FocusEvent e) {
|
|
||||||
setTraceTabActionContext(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
list.addMouseListener(new MouseAdapter() {
|
|
||||||
@Override
|
|
||||||
public void mousePressed(MouseEvent e) {
|
|
||||||
setTraceTabActionContext(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
actionCloseTrace = CloseTraceAction.builderPopup(plugin)
|
actionCloseTrace = CloseTraceAction.builderPopup(plugin)
|
||||||
.withContext(DebuggerTraceFileActionContext.class)
|
.withContext(DebuggerTraceFileActionContext.class)
|
||||||
.popupWhen(c -> c.getTrace() != null)
|
.popupWhen(c -> {
|
||||||
|
Trace trace = c.getTrace();
|
||||||
|
if (trace == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
actionCloseTrace.getPopupMenuData()
|
||||||
|
.setMenuItemName(CloseTraceAction.NAME_PREFIX + getNameForTrace(trace));
|
||||||
|
return true;
|
||||||
|
})
|
||||||
.onAction(c -> traceManager.closeTrace(c.getTrace()))
|
.onAction(c -> traceManager.closeTrace(c.getTrace()))
|
||||||
.buildAndInstallLocal(provider);
|
.buildAndInstall(tool);
|
||||||
actionCloseAllTraces = CloseAllTracesAction.builderPopup(plugin)
|
actionCloseAllTraces = CloseAllTracesAction.builderPopup(plugin)
|
||||||
.withContext(DebuggerTraceFileActionContext.class)
|
.withContext(DebuggerTraceFileActionContext.class)
|
||||||
.popupWhen(c -> !traceManager.getOpenTraces().isEmpty())
|
.popupWhen(c -> !traceManager.getOpenTraces().isEmpty())
|
||||||
.onAction(c -> traceManager.closeAllTraces())
|
.onAction(c -> traceManager.closeAllTraces())
|
||||||
.buildAndInstallLocal(provider);
|
.buildAndInstall(tool);
|
||||||
actionCloseOtherTraces = CloseOtherTracesAction.builderPopup(plugin)
|
actionCloseOtherTraces = CloseOtherTracesAction.builderPopup(plugin)
|
||||||
.withContext(DebuggerTraceFileActionContext.class)
|
.withContext(DebuggerTraceFileActionContext.class)
|
||||||
.popupWhen(c -> traceManager.getOpenTraces().size() > 1 && c.getTrace() != null)
|
.popupWhen(c -> traceManager.getOpenTraces().size() > 1 && c.getTrace() != null)
|
||||||
.onAction(c -> traceManager.closeOtherTraces(c.getTrace()))
|
.onAction(c -> traceManager.closeOtherTraces(c.getTrace()))
|
||||||
.buildAndInstallLocal(provider);
|
.buildAndInstall(tool);
|
||||||
actionCloseDeadTraces = CloseDeadTracesAction.builderPopup(plugin)
|
actionCloseDeadTraces = CloseDeadTracesAction.builderPopup(plugin)
|
||||||
.withContext(DebuggerTraceFileActionContext.class)
|
.withContext(DebuggerTraceFileActionContext.class)
|
||||||
.popupWhen(c -> !traceManager.getOpenTraces().isEmpty() && targetService != null)
|
.popupWhen(c -> !traceManager.getOpenTraces().isEmpty() && targetService != null)
|
||||||
.onAction(c -> traceManager.closeDeadTraces())
|
.onAction(c -> traceManager.closeDeadTraces())
|
||||||
.buildAndInstallLocal(provider);
|
.buildAndInstall(tool);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Trace computeClickedTraceTab(MouseEvent e) {
|
private String getNameForTrace(Trace trace) {
|
||||||
JList<Trace> list = getList();
|
return DomainObjectDisplayUtils.getTabText(trace);
|
||||||
int i = list.locationToIndex(e.getPoint());
|
}
|
||||||
if (i < 0) {
|
|
||||||
|
private Icon getIconForTrace(Trace trace) {
|
||||||
|
if (targetService == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Rectangle cell = list.getCellBounds(i, i);
|
Target target = targetService.getTarget(trace);
|
||||||
if (!cell.contains(e.getPoint())) {
|
if (target == null || !target.isValid()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return getItem(i);
|
return DebuggerResources.ICON_RECORD;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Trace setTraceTabActionContext(MouseEvent e) {
|
private String getTipForTrace(Trace trace) {
|
||||||
Trace newTrace = e == null ? getSelectedItem() : computeClickedTraceTab(e);
|
return DomainObjectDisplayUtils.getToolTip(trace);
|
||||||
actionCloseTrace.getPopupMenuData()
|
|
||||||
.setMenuItemName(
|
|
||||||
CloseTraceAction.NAME_PREFIX + (newTrace == null ? "..." : newTrace.getName()));
|
|
||||||
myActionContext = new DebuggerTraceFileActionContext(newTrace);
|
|
||||||
provider.traceTabsContextChanged();
|
|
||||||
return newTrace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DebuggerTraceFileActionContext getActionContext() {
|
public DebuggerTraceFileActionContext getActionContext(MouseEvent e) {
|
||||||
return myActionContext;
|
if (e == null) {
|
||||||
}
|
return null;
|
||||||
|
|
||||||
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
|
||||||
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
|
|
||||||
setSelectedItem(coordinates.getTrace());
|
|
||||||
}
|
}
|
||||||
|
Trace trace = getValueFor(e);
|
||||||
|
if (trace == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new DebuggerTraceFileActionContext(trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
|
@ -186,28 +157,46 @@ public class DebuggerTraceTabPanel extends HorizontalTabPanel<Trace>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void add(Trace trace) {
|
||||||
|
addTab(trace);
|
||||||
|
trace.removeListener(this);
|
||||||
|
trace.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void remove(Trace trace) {
|
||||||
|
trace.removeListener(this);
|
||||||
|
removeTab(trace);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void eventSent(PluginEvent event) {
|
public void eventSent(PluginEvent event) {
|
||||||
if (Objects.equals(event.getSourceName(), plugin.getName())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event instanceof TraceOpenedPluginEvent evt) {
|
if (event instanceof TraceOpenedPluginEvent evt) {
|
||||||
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
|
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
|
||||||
addItem(evt.getTrace());
|
add(evt.getTrace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (event instanceof TraceActivatedPluginEvent evt) {
|
||||||
|
Trace trace = evt.getActiveCoordinates().getTrace();
|
||||||
|
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
|
||||||
|
selectTab(trace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (event instanceof TraceClosedPluginEvent evt) {
|
else if (event instanceof TraceClosedPluginEvent evt) {
|
||||||
|
Trace trace = evt.getTrace();
|
||||||
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
|
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
|
||||||
removeItem(evt.getTrace());
|
remove(trace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void traceTabSelected(ListSelectionEvent e) {
|
@Override
|
||||||
if (e.getValueIsAdjusting()) {
|
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
||||||
return;
|
if (ev.getSource() instanceof Trace trace) {
|
||||||
|
refreshTab(trace);
|
||||||
}
|
}
|
||||||
Trace newTrace = setTraceTabActionContext(null);
|
}
|
||||||
|
|
||||||
|
private void traceTabSelected(Trace newTrace) {
|
||||||
cbCoordinateActivation.invoke(() -> {
|
cbCoordinateActivation.invoke(() -> {
|
||||||
traceManager.activateTrace(newTrace);
|
traceManager.activateTrace(newTrace);
|
||||||
});
|
});
|
|
@ -90,6 +90,12 @@ public class TraceRecorderTarget extends AbstractTarget {
|
||||||
this.recorder = recorder;
|
this.recorder = recorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String describe() {
|
||||||
|
return "%s in %s (recorder)".formatted(getTrace().getDomainFile().getName(),
|
||||||
|
recorder.getTarget().getModel().getBrief());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid() {
|
public boolean isValid() {
|
||||||
return recorder.isRecording();
|
return recorder.isRecording();
|
||||||
|
|
|
@ -410,7 +410,7 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try (PeekOpenedDomainObject peek = new PeekOpenedDomainObject(df)) {
|
try (PeekOpenedDomainObject peek = new PeekOpenedDomainObject(df)) {
|
||||||
if (programs.contains(peek.object)) {
|
if (peek.object != null && programs.contains(peek.object)) {
|
||||||
result.add(e);
|
result.add(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,10 +26,13 @@ import java.util.stream.Stream;
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.action.DockingAction;
|
import docking.action.DockingAction;
|
||||||
import docking.action.ToggleDockingAction;
|
import docking.action.ToggleDockingAction;
|
||||||
|
import docking.widgets.OptionDialog;
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.event.*;
|
import ghidra.app.plugin.core.debug.event.*;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.control.DisconnectTask;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.control.TargetActionTask;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.services.DebuggerControlService.ControlModeChangeListener;
|
import ghidra.app.services.DebuggerControlService.ControlModeChangeListener;
|
||||||
import ghidra.async.*;
|
import ghidra.async.*;
|
||||||
|
@ -135,8 +138,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
|
|
||||||
private void threadDeleted(TraceThread thread) {
|
private void threadDeleted(TraceThread thread) {
|
||||||
synchronized (listenersByTrace) {
|
synchronized (listenersByTrace) {
|
||||||
DebuggerCoordinates last = lastCoordsByTrace.get(trace);
|
LastCoords last = lastCoordsByTrace.get(trace);
|
||||||
if (last != null && last.getThread() == thread) {
|
if (last != null && last.coords.getThread() == thread) {
|
||||||
lastCoordsByTrace.remove(trace);
|
lastCoordsByTrace.remove(trace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -261,7 +264,20 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final Map<Trace, DebuggerCoordinates> lastCoordsByTrace = new WeakHashMap<>();
|
protected record LastCoords(Long time, DebuggerCoordinates coords) {
|
||||||
|
|
||||||
|
public static final LastCoords NEVER = new LastCoords(null, DebuggerCoordinates.NOWHERE);
|
||||||
|
|
||||||
|
public LastCoords(DebuggerCoordinates coords) {
|
||||||
|
this(System.currentTimeMillis(), coords);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LastCoords keepTime(DebuggerCoordinates adjusted) {
|
||||||
|
return new LastCoords(time, adjusted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final Map<Trace, LastCoords> lastCoordsByTrace = new WeakHashMap<>();
|
||||||
protected final Map<Trace, ListenerForTraceChanges> listenersByTrace = new WeakHashMap<>();
|
protected final Map<Trace, ListenerForTraceChanges> listenersByTrace = new WeakHashMap<>();
|
||||||
protected final Set<Trace> tracesView = Collections.unmodifiableSet(listenersByTrace.keySet());
|
protected final Set<Trace> tracesView = Collections.unmodifiableSet(listenersByTrace.keySet());
|
||||||
|
|
||||||
|
@ -275,6 +291,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
protected final AsyncReference<Boolean, Void> saveTracesByDefault = new AsyncReference<>(true);
|
protected final AsyncReference<Boolean, Void> saveTracesByDefault = new AsyncReference<>(true);
|
||||||
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
|
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
|
||||||
protected final AsyncReference<Boolean, Void> autoCloseOnTerminate = new AsyncReference<>(true);
|
protected final AsyncReference<Boolean, Void> autoCloseOnTerminate = new AsyncReference<>(true);
|
||||||
|
// Do not save this one, it's for testing only
|
||||||
|
protected boolean ensureActiveTrace = true;
|
||||||
|
|
||||||
// @AutoServiceConsumed via method
|
// @AutoServiceConsumed via method
|
||||||
private DebuggerTargetService targetService;
|
private DebuggerTargetService targetService;
|
||||||
|
@ -431,37 +449,21 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void closeAllTraces() {
|
public void closeAllTraces() {
|
||||||
Swing.runIfSwingOrRunLater(() -> {
|
checkCloseTraces(getOpenTraces());
|
||||||
for (Trace trace : getOpenTraces()) {
|
|
||||||
closeTrace(trace);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void closeOtherTraces(Trace keep) {
|
public void closeOtherTraces(Trace keep) {
|
||||||
Swing.runIfSwingOrRunLater(() -> {
|
checkCloseTraces(getOpenTraces().stream().filter(t -> t != keep).toList());
|
||||||
for (Trace trace : getOpenTraces()) {
|
|
||||||
if (trace != keep) {
|
|
||||||
closeTrace(trace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void closeDeadTraces() {
|
public void closeDeadTraces() {
|
||||||
Swing.runIfSwingOrRunLater(() -> {
|
checkCloseTraces(targetService == null
|
||||||
if (targetService == null) {
|
? getOpenTraces()
|
||||||
return;
|
: getOpenTraces().stream()
|
||||||
}
|
.filter(t -> targetService.getTarget(t) == null)
|
||||||
for (Trace trace : getOpenTraces()) {
|
.toList());
|
||||||
Target target = targetService.getTarget(trace);
|
|
||||||
if (target == null) {
|
|
||||||
closeTrace(trace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
|
@ -549,7 +551,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
}
|
}
|
||||||
current = newCurrent;
|
current = newCurrent;
|
||||||
if (newCurrent.getTrace() != null) {
|
if (newCurrent.getTrace() != null) {
|
||||||
lastCoordsByTrace.put(newCurrent.getTrace(), newCurrent);
|
lastCoordsByTrace.put(newCurrent.getTrace(), new LastCoords(newCurrent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
contextChanged();
|
contextChanged();
|
||||||
|
@ -610,11 +612,10 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
if (!listenersByTrace.containsKey(trace)) {
|
if (!listenersByTrace.containsKey(trace)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DebuggerCoordinates cur =
|
LastCoords cur = lastCoordsByTrace.getOrDefault(trace, LastCoords.NEVER);
|
||||||
lastCoordsByTrace.getOrDefault(trace, DebuggerCoordinates.NOWHERE);
|
|
||||||
DebuggerCoordinates adj =
|
DebuggerCoordinates adj =
|
||||||
cur.platform(getPlatformForMapper(trace, cur.getObject(), mapper));
|
cur.coords.platform(getPlatformForMapper(trace, cur.coords.getObject(), mapper));
|
||||||
lastCoordsByTrace.put(trace, adj);
|
lastCoordsByTrace.put(trace, cur.keepTime(adj));
|
||||||
if (trace == current.getTrace()) {
|
if (trace == current.getTrace()) {
|
||||||
current = adj;
|
current = adj;
|
||||||
fireLocationEvent(adj, ActivationCause.MAPPER_CHANGED);
|
fireLocationEvent(adj, ActivationCause.MAPPER_CHANGED);
|
||||||
|
@ -670,7 +671,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
synchronized (listenersByTrace) {
|
synchronized (listenersByTrace) {
|
||||||
// If known, fill in target ASAP, so it determines the time
|
// If known, fill in target ASAP, so it determines the time
|
||||||
return fillInTarget(trace,
|
return fillInTarget(trace,
|
||||||
lastCoordsByTrace.getOrDefault(trace, DebuggerCoordinates.NOWHERE));
|
lastCoordsByTrace.getOrDefault(trace, LastCoords.NEVER).coords);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -917,6 +918,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
new TaskLauncher(new Task("Save New Trace", true, true, true) {
|
new TaskLauncher(new Task("Save New Trace", true, true, true) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(TaskMonitor monitor) throws CancelledException {
|
public void run(TaskMonitor monitor) throws CancelledException {
|
||||||
String filename = trace.getName();
|
String filename = trace.getName();
|
||||||
|
@ -958,6 +960,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
future.completeExceptionally(e);
|
future.completeExceptionally(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return future;
|
return future;
|
||||||
|
@ -994,20 +997,64 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected void doCloseTraces(Collection<Trace> traces, Collection<Target> targets) {
|
||||||
public void closeTrace(Trace trace) {
|
for (Trace t : traces) {
|
||||||
|
if (t.getConsumerList().contains(this)) {
|
||||||
|
firePluginEvent(new TraceClosedPluginEvent(getName(), t));
|
||||||
|
doTraceClosed(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetActionTask.executeTask(tool, new DisconnectTask(tool, targets));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final String MSGPAT_TERMINATE = """
|
||||||
|
<html>
|
||||||
|
<body width="300px">
|
||||||
|
<p>This will terminate the following targets:</p>
|
||||||
|
<ul>
|
||||||
|
%s
|
||||||
|
</ul>
|
||||||
|
<p>Proceed?</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
""";
|
||||||
|
|
||||||
|
protected static String formatTargets(Collection<Target> targets) {
|
||||||
|
return targets.stream()
|
||||||
|
.map(t -> "<li>%s</li>".formatted(HTMLUtilities.escapeHTML(t.describe())))
|
||||||
|
.sorted()
|
||||||
|
.collect(Collectors.joining("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkCloseTraces(Collection<Trace> traces) {
|
||||||
|
List<Target> live =
|
||||||
|
traces.stream()
|
||||||
|
.map(t -> targetService.getTarget(t))
|
||||||
|
.filter(t -> t != null)
|
||||||
|
.toList();
|
||||||
/**
|
/**
|
||||||
* A provider may be reading the trace, likely via the Swing thread, so schedule this on the
|
* A provider may be reading a trace, likely via the Swing thread, so schedule this on the
|
||||||
* same thread to avoid a ClosedException.
|
* same thread to avoid a ClosedException.
|
||||||
*/
|
*/
|
||||||
Swing.runIfSwingOrRunLater(() -> {
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
if (trace.getConsumerList().contains(this)) {
|
if (live.isEmpty()) {
|
||||||
firePluginEvent(new TraceClosedPluginEvent(getName(), trace));
|
doCloseTraces(traces, live);
|
||||||
doTraceClosed(trace);
|
return;
|
||||||
|
}
|
||||||
|
String msg = MSGPAT_TERMINATE.formatted(formatTargets(live));
|
||||||
|
int response = OptionDialog.showYesNoDialog(null, "Terminate", msg);
|
||||||
|
switch (response) {
|
||||||
|
case OptionDialog.YES_OPTION -> doCloseTraces(traces, live);
|
||||||
|
case OptionDialog.NO_OPTION -> List.of();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeTrace(Trace trace) {
|
||||||
|
checkCloseTraces(List.of(trace));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void dispose() {
|
protected void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
@ -1034,6 +1081,20 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
return elem.toString();
|
return elem.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the most recent coordinates among those traces still open
|
||||||
|
*/
|
||||||
|
protected DebuggerCoordinates getMostRecentCoordinates() {
|
||||||
|
synchronized (listenersByTrace) {
|
||||||
|
return lastCoordsByTrace.values()
|
||||||
|
.stream()
|
||||||
|
.sorted(Comparator.comparing(l -> -l.time))
|
||||||
|
.findFirst()
|
||||||
|
.map(l -> l.coords)
|
||||||
|
.orElse(DebuggerCoordinates.NOWHERE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates,
|
public CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates,
|
||||||
ActivationCause cause) {
|
ActivationCause cause) {
|
||||||
|
@ -1048,6 +1109,9 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Trace must be opened before activated: " + newTrace);
|
"Trace must be opened before activated: " + newTrace);
|
||||||
}
|
}
|
||||||
|
if (newTrace == null && ensureActiveTrace) {
|
||||||
|
coordinates = getMostRecentCoordinates(); // Might still be NOWHERE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause == ActivationCause.FOLLOW_PRESENT) {
|
if (cause == ActivationCause.FOLLOW_PRESENT) {
|
||||||
|
@ -1228,8 +1292,13 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
traces = tracesView.stream().filter(t -> {
|
traces = tracesView.stream().filter(t -> {
|
||||||
ProjectLocator loc = t.getDomainFile().getProjectLocator();
|
ProjectLocator loc = t.getDomainFile().getProjectLocator();
|
||||||
return loc != null && !loc.isTransient();
|
return loc != null && !loc.isTransient();
|
||||||
}).collect(Collectors.toList());
|
}).sorted(Comparator.comparingLong(t -> {
|
||||||
coordsByTrace = Map.copyOf(lastCoordsByTrace);
|
LastCoords last = lastCoordsByTrace.get(t);
|
||||||
|
return last == null ? -1 : last.time;
|
||||||
|
})).toList();
|
||||||
|
coordsByTrace = lastCoordsByTrace.entrySet()
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().coords));
|
||||||
}
|
}
|
||||||
|
|
||||||
saveState.putInt(KEY_TRACE_COUNT, traces.size());
|
saveState.putInt(KEY_TRACE_COUNT, traces.size());
|
||||||
|
@ -1246,6 +1315,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
@Override
|
@Override
|
||||||
public void readDataState(SaveState saveState) {
|
public void readDataState(SaveState saveState) {
|
||||||
synchronized (listenersByTrace) {
|
synchronized (listenersByTrace) {
|
||||||
|
long baseTime = System.currentTimeMillis();
|
||||||
int traceCount = saveState.getInt(KEY_TRACE_COUNT, 0);
|
int traceCount = saveState.getInt(KEY_TRACE_COUNT, 0);
|
||||||
for (int index = 0; index < traceCount; index++) {
|
for (int index = 0; index < traceCount; index++) {
|
||||||
String stateName = PREFIX_OPEN_TRACE + index;
|
String stateName = PREFIX_OPEN_TRACE + index;
|
||||||
|
@ -1253,7 +1323,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
DebuggerCoordinates coords =
|
DebuggerCoordinates coords =
|
||||||
DebuggerCoordinates.readDataState(tool, saveState, stateName);
|
DebuggerCoordinates.readDataState(tool, saveState, stateName);
|
||||||
if (coords.getTrace() != null) {
|
if (coords.getTrace() != null) {
|
||||||
lastCoordsByTrace.put(coords.getTrace(), coords);
|
lastCoordsByTrace.put(coords.getTrace(),
|
||||||
|
new LastCoords(baseTime + index, coords));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/* ###
|
||||||
|
* 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.listing;
|
||||||
|
|
||||||
|
import ghidra.app.plugin.core.debug.gui.trace.DebuggerTraceTabPanel;
|
||||||
|
|
||||||
|
public class DebuggerListingProviderTestAccess {
|
||||||
|
public static DebuggerTraceTabPanel getTraceTabs(DebuggerListingProvider provider) {
|
||||||
|
return provider.traceTabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ import ghidra.app.plugin.core.debug.gui.modules.DebuggerModuleMapProposalDialog.
|
||||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapModulesAction;
|
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapModulesAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapSectionsAction;
|
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapSectionsAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerSectionMapProposalDialog.SectionMapTableColumns;
|
import ghidra.app.plugin.core.debug.gui.modules.DebuggerSectionMapProposalDialog.SectionMapTableColumns;
|
||||||
|
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServiceTestAccess;
|
||||||
import ghidra.app.services.DebuggerListingService;
|
import ghidra.app.services.DebuggerListingService;
|
||||||
import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry;
|
import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry;
|
||||||
import ghidra.debug.api.modules.SectionMapProposal.SectionMapEntry;
|
import ghidra.debug.api.modules.SectionMapProposal.SectionMapEntry;
|
||||||
|
@ -302,6 +303,7 @@ public class DebuggerModulesProviderLegacyTest extends AbstractGhidraHeadedDebug
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivatingNoTraceEmptiesProvider() throws Exception {
|
public void testActivatingNoTraceEmptiesProvider() throws Exception {
|
||||||
|
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
|
|
||||||
addModules();
|
addModules();
|
||||||
|
|
|
@ -42,6 +42,7 @@ import ghidra.app.plugin.core.debug.gui.modules.DebuggerModuleMapProposalDialog.
|
||||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapModulesAction;
|
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapModulesAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapSectionsAction;
|
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapSectionsAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerSectionMapProposalDialog.SectionMapTableColumns;
|
import ghidra.app.plugin.core.debug.gui.modules.DebuggerSectionMapProposalDialog.SectionMapTableColumns;
|
||||||
|
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServiceTestAccess;
|
||||||
import ghidra.app.services.DebuggerListingService;
|
import ghidra.app.services.DebuggerListingService;
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.dbg.target.schema.SchemaContext;
|
import ghidra.dbg.target.schema.SchemaContext;
|
||||||
|
@ -434,6 +435,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivatingNoTraceEmptiesProvider() throws Exception {
|
public void testActivatingNoTraceEmptiesProvider() throws Exception {
|
||||||
|
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
|
|
||||||
addModules();
|
addModules();
|
||||||
|
|
|
@ -17,9 +17,7 @@ package ghidra.app.plugin.core.debug.gui.thread;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -29,8 +27,8 @@ import db.Transaction;
|
||||||
import generic.test.category.NightlyCategory;
|
import generic.test.category.NightlyCategory;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||||
import ghidra.app.plugin.core.debug.gui.thread.DebuggerLegacyThreadsPanel.ThreadTableColumns;
|
import ghidra.app.plugin.core.debug.gui.thread.DebuggerLegacyThreadsPanel.ThreadTableColumns;
|
||||||
|
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServiceTestAccess;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.trace.model.Trace;
|
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.thread.TraceThreadManager;
|
import ghidra.trace.model.thread.TraceThreadManager;
|
||||||
import ghidra.trace.model.time.TraceTimeManager;
|
import ghidra.trace.model.time.TraceTimeManager;
|
||||||
|
@ -60,32 +58,6 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check that there exist no tabs, and that the tab row is invisible
|
|
||||||
*/
|
|
||||||
protected void assertZeroTabs() {
|
|
||||||
assertEquals(0, threadsProvider.traceTabs.getList().getModel().getSize());
|
|
||||||
assertEquals("Tab row should not be visible", 0,
|
|
||||||
threadsProvider.traceTabs.getVisibleRect().height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check that exactly one tab exists, and that the tab row is visible
|
|
||||||
*/
|
|
||||||
protected void assertOneTabPopulated() {
|
|
||||||
assertEquals(1, threadsProvider.traceTabs.getList().getModel().getSize());
|
|
||||||
assertNotEquals("Tab row should be visible", 0,
|
|
||||||
threadsProvider.traceTabs.getVisibleRect().height);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertNoTabSelected() {
|
|
||||||
assertTabSelected(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertTabSelected(Trace trace) {
|
|
||||||
assertEquals(trace, threadsProvider.traceTabs.getSelectedItem());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertThreadsEmpty() {
|
protected void assertThreadsEmpty() {
|
||||||
List<ThreadRow> threadsDisplayed =
|
List<ThreadRow> threadsDisplayed =
|
||||||
threadsProvider.legacyPanel.threadTableModel.getModelData();
|
threadsProvider.legacyPanel.threadTableModel.getModelData();
|
||||||
|
@ -122,7 +94,6 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertProviderEmpty() {
|
protected void assertProviderEmpty() {
|
||||||
assertZeroTabs();
|
|
||||||
assertThreadsEmpty();
|
assertThreadsEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,45 +103,9 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
|
||||||
assertProviderEmpty();
|
assertProviderEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOpenTracePopupatesTab() throws Exception {
|
|
||||||
createAndOpenTrace();
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertOneTabPopulated();
|
|
||||||
assertNoTabSelected();
|
|
||||||
assertThreadsEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testActivateTraceSelectsTab() throws Exception {
|
|
||||||
createAndOpenTrace();
|
|
||||||
traceManager.activateTrace(tb.trace);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertOneTabPopulated();
|
|
||||||
assertTabSelected(tb.trace);
|
|
||||||
|
|
||||||
traceManager.activateTrace(null);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertOneTabPopulated();
|
|
||||||
assertNoTabSelected();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSelectTabActivatesTrace() throws Exception {
|
|
||||||
createAndOpenTrace();
|
|
||||||
waitForSwing();
|
|
||||||
threadsProvider.traceTabs.setSelectedItem(tb.trace);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertEquals(tb.trace, traceManager.getCurrentTrace());
|
|
||||||
assertEquals(tb.trace, threadsProvider.current.getTrace());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateNoTraceEmptiesProvider() throws Exception {
|
public void testActivateNoTraceEmptiesProvider() throws Exception {
|
||||||
|
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
addThreads();
|
addThreads();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
|
@ -184,22 +119,6 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
|
||||||
assertThreadsEmpty();
|
assertThreadsEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCurrentTraceClosedUpdatesTabs() throws Exception {
|
|
||||||
createAndOpenTrace();
|
|
||||||
traceManager.activateTrace(tb.trace);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertOneTabPopulated();
|
|
||||||
assertTabSelected(tb.trace);
|
|
||||||
|
|
||||||
traceManager.closeTrace(tb.trace);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertZeroTabs();
|
|
||||||
assertNoTabSelected();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCurrentTraceClosedEmptiesProvider() throws Exception {
|
public void testCurrentTraceClosedEmptiesProvider() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
|
@ -215,25 +134,6 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
|
||||||
assertThreadsEmpty();
|
assertThreadsEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCloseTraceTabPopupMenuItem() throws Exception {
|
|
||||||
createAndOpenTrace();
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertOneTabPopulated(); // pre-check
|
|
||||||
clickListItem(threadsProvider.traceTabs.getList(), 0, MouseEvent.BUTTON3);
|
|
||||||
waitForSwing();
|
|
||||||
Set<String> expected = Set.of("Close " + tb.trace.getName());
|
|
||||||
assertMenu(expected, expected);
|
|
||||||
|
|
||||||
clickSubMenuItemByText("Close " + tb.trace.getName());
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
waitForPass(() -> {
|
|
||||||
assertEquals(Set.of(), traceManager.getOpenTraces());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateThenAddThreadsPopulatesProvider() throws Exception {
|
public void testActivateThenAddThreadsPopulatesProvider() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
|
|
|
@ -17,10 +17,8 @@ package ghidra.app.plugin.core.debug.gui.thread;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
import org.junit.experimental.categories.Category;
|
import org.junit.experimental.categories.Category;
|
||||||
|
@ -31,6 +29,7 @@ import generic.test.category.NightlyCategory;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.*;
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.*;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.QueryPanelTestHelper;
|
import ghidra.app.plugin.core.debug.gui.model.QueryPanelTestHelper;
|
||||||
|
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServiceTestAccess;
|
||||||
import ghidra.dbg.target.TargetExecutionStateful;
|
import ghidra.dbg.target.TargetExecutionStateful;
|
||||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||||
import ghidra.dbg.target.schema.SchemaContext;
|
import ghidra.dbg.target.schema.SchemaContext;
|
||||||
|
@ -119,32 +118,6 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check that there exist no tabs, and that the tab row is invisible
|
|
||||||
*/
|
|
||||||
protected void assertZeroTabs() {
|
|
||||||
assertEquals(0, provider.traceTabs.getList().getModel().getSize());
|
|
||||||
assertEquals("Tab row should not be visible", 0,
|
|
||||||
provider.traceTabs.getVisibleRect().height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check that exactly one tab exists, and that the tab row is visible
|
|
||||||
*/
|
|
||||||
protected void assertOneTabPopulated() {
|
|
||||||
assertEquals(1, provider.traceTabs.getList().getModel().getSize());
|
|
||||||
assertNotEquals("Tab row should be visible", 0,
|
|
||||||
provider.traceTabs.getVisibleRect().height);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertNoTabSelected() {
|
|
||||||
assertTabSelected(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertTabSelected(Trace trace) {
|
|
||||||
assertEquals(trace, provider.traceTabs.getSelectedItem());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertThreadsTableSize(int size) {
|
protected void assertThreadsTableSize(int size) {
|
||||||
assertEquals(size, provider.panel.getAllItems().size());
|
assertEquals(size, provider.panel.getAllItems().size());
|
||||||
}
|
}
|
||||||
|
@ -195,7 +168,6 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertProviderEmpty() {
|
protected void assertProviderEmpty() {
|
||||||
assertZeroTabs();
|
|
||||||
assertThreadsEmpty();
|
assertThreadsEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,53 +190,9 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||||
waitForPass(() -> assertProviderEmpty());
|
waitForPass(() -> assertProviderEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOpenTracePopupatesTab() throws Exception {
|
|
||||||
createAndOpenTrace();
|
|
||||||
waitForTasks();
|
|
||||||
|
|
||||||
waitForPass(() -> {
|
|
||||||
assertOneTabPopulated();
|
|
||||||
assertNoTabSelected();
|
|
||||||
assertThreadsEmpty();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testActivateTraceSelectsTab() throws Exception {
|
|
||||||
createAndOpenTrace();
|
|
||||||
traceManager.activateTrace(tb.trace);
|
|
||||||
waitForTasks();
|
|
||||||
|
|
||||||
waitForPass(() -> {
|
|
||||||
assertOneTabPopulated();
|
|
||||||
assertTabSelected(tb.trace);
|
|
||||||
});
|
|
||||||
|
|
||||||
traceManager.activateTrace(null);
|
|
||||||
waitForTasks();
|
|
||||||
|
|
||||||
waitForPass(() -> {
|
|
||||||
assertOneTabPopulated();
|
|
||||||
assertNoTabSelected();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSelectTabActivatesTrace() throws Exception {
|
|
||||||
createAndOpenTrace();
|
|
||||||
waitForTasks();
|
|
||||||
provider.traceTabs.setSelectedItem(tb.trace);
|
|
||||||
waitForTasks();
|
|
||||||
|
|
||||||
waitForPass(() -> {
|
|
||||||
assertEquals(tb.trace, traceManager.getCurrentTrace());
|
|
||||||
assertEquals(tb.trace, provider.current.getTrace());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateNoTraceEmptiesProvider() throws Exception {
|
public void testActivateNoTraceEmptiesProvider() throws Exception {
|
||||||
|
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
addThreads();
|
addThreads();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
|
@ -278,26 +206,6 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||||
waitForPass(() -> assertThreadsEmpty());
|
waitForPass(() -> assertThreadsEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCurrentTraceClosedUpdatesTabs() throws Exception {
|
|
||||||
createAndOpenTrace();
|
|
||||||
traceManager.activateTrace(tb.trace);
|
|
||||||
waitForTasks();
|
|
||||||
|
|
||||||
waitForPass(() -> {
|
|
||||||
assertOneTabPopulated();
|
|
||||||
assertTabSelected(tb.trace);
|
|
||||||
});
|
|
||||||
|
|
||||||
traceManager.closeTrace(tb.trace);
|
|
||||||
waitForTasks();
|
|
||||||
|
|
||||||
waitForPass(() -> {
|
|
||||||
assertZeroTabs();
|
|
||||||
assertNoTabSelected();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCurrentTraceClosedEmptiesProvider() throws Exception {
|
public void testCurrentTraceClosedEmptiesProvider() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
|
@ -313,25 +221,6 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||||
waitForPass(() -> assertThreadsEmpty());
|
waitForPass(() -> assertThreadsEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCloseTraceTabPopupMenuItem() throws Exception {
|
|
||||||
createAndOpenTrace();
|
|
||||||
waitForTasks();
|
|
||||||
|
|
||||||
waitForPass(() -> assertOneTabPopulated());
|
|
||||||
clickListItem(provider.traceTabs.getList(), 0, MouseEvent.BUTTON3);
|
|
||||||
waitForTasks();
|
|
||||||
Set<String> expected = Set.of("Close " + tb.trace.getName());
|
|
||||||
assertMenu(expected, expected);
|
|
||||||
|
|
||||||
clickSubMenuItemByText("Close " + tb.trace.getName());
|
|
||||||
waitForTasks();
|
|
||||||
|
|
||||||
waitForPass(() -> {
|
|
||||||
assertEquals(Set.of(), traceManager.getOpenTraces());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateThenAddThreadsPopulatesProvider() throws Exception {
|
public void testActivateThenAddThreadsPopulatesProvider() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
/* ###
|
||||||
|
* 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.trace;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.listing.*;
|
||||||
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
|
|
||||||
|
public class DebuggerTraceTabPanelTest extends AbstractGhidraHeadedDebuggerTest {
|
||||||
|
private DebuggerTraceTabPanel traceTabs;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUpTabTest() throws Throwable {
|
||||||
|
addPlugin(tool, DebuggerListingPlugin.class);
|
||||||
|
DebuggerListingProvider listingProvider =
|
||||||
|
waitForComponentProvider(DebuggerListingProvider.class);
|
||||||
|
traceTabs = DebuggerListingProviderTestAccess.getTraceTabs(listingProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmpty() {
|
||||||
|
assertEquals(List.of(), traceTabs.getTabValues());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpenTraceAddsTab() throws Throwable {
|
||||||
|
createAndOpenTrace();
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertEquals(List.of(tb.trace), traceTabs.getTabValues());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActivateTraceSelectsTab() throws Throwable {
|
||||||
|
try (
|
||||||
|
ToyDBTraceBuilder tb1 = new ToyDBTraceBuilder(getName() + "_1", LANGID_TOYBE64);
|
||||||
|
ToyDBTraceBuilder tb2 = new ToyDBTraceBuilder(getName() + "_2", LANGID_TOYBE64)) {
|
||||||
|
traceManager.openTrace(tb1.trace);
|
||||||
|
traceManager.openTrace(tb2.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
traceManager.activateTrace(tb1.trace);
|
||||||
|
waitForSwing();
|
||||||
|
assertEquals(tb1.trace, traceTabs.getSelectedTabValue());
|
||||||
|
|
||||||
|
traceManager.activateTrace(tb2.trace);
|
||||||
|
waitForSwing();
|
||||||
|
assertEquals(tb2.trace, traceTabs.getSelectedTabValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSelectTabActivatesTrace() throws Throwable {
|
||||||
|
try (
|
||||||
|
ToyDBTraceBuilder tb1 = new ToyDBTraceBuilder(getName() + "_1", LANGID_TOYBE64);
|
||||||
|
ToyDBTraceBuilder tb2 = new ToyDBTraceBuilder(getName() + "_2", LANGID_TOYBE64)) {
|
||||||
|
traceManager.openTrace(tb1.trace);
|
||||||
|
traceManager.openTrace(tb2.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
traceTabs.selectTab(tb1.trace);
|
||||||
|
waitForSwing();
|
||||||
|
assertEquals(tb1.trace, traceManager.getCurrentTrace());
|
||||||
|
|
||||||
|
traceTabs.selectTab(tb2.trace);
|
||||||
|
waitForSwing();
|
||||||
|
assertEquals(tb2.trace, traceManager.getCurrentTrace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCloseTraceRemovesTab() throws Throwable {
|
||||||
|
try (
|
||||||
|
ToyDBTraceBuilder tb1 = new ToyDBTraceBuilder(getName() + "_1", LANGID_TOYBE64);
|
||||||
|
ToyDBTraceBuilder tb2 = new ToyDBTraceBuilder(getName() + "_2", LANGID_TOYBE64)) {
|
||||||
|
traceManager.openTrace(tb1.trace);
|
||||||
|
traceManager.openTrace(tb2.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertEquals(List.of(tb1.trace, tb2.trace), traceTabs.getTabValues());
|
||||||
|
|
||||||
|
traceManager.closeTrace(tb1.trace);
|
||||||
|
waitForSwing();
|
||||||
|
assertEquals(List.of(tb2.trace), traceTabs.getTabValues());
|
||||||
|
|
||||||
|
traceManager.closeTrace(tb2.trace);
|
||||||
|
waitForSwing();
|
||||||
|
assertEquals(List.of(), traceTabs.getTabValues());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,6 +46,11 @@ class MockTarget implements Target {
|
||||||
this.trace = trace;
|
this.trace = trace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String describe() {
|
||||||
|
return "Mock Target";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid() {
|
public boolean isValid() {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -141,6 +141,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
assertEquals(thread, traceManager.getCurrentThread());
|
assertEquals(thread, traceManager.getCurrentThread());
|
||||||
|
|
||||||
|
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||||
traceManager.activateTrace(null);
|
traceManager.activateTrace(null);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
|
@ -177,6 +178,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
assertEquals(5, traceManager.getCurrentSnap());
|
assertEquals(5, traceManager.getCurrentSnap());
|
||||||
|
|
||||||
|
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||||
traceManager.activateTrace(null);
|
traceManager.activateTrace(null);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
|
@ -207,6 +209,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
assertEquals(5, traceManager.getCurrentFrame());
|
assertEquals(5, traceManager.getCurrentFrame());
|
||||||
|
|
||||||
|
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||||
traceManager.activateTrace(null);
|
traceManager.activateTrace(null);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
|
@ -251,6 +254,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
||||||
assertEquals(objThread0, traceManager.getCurrentObject());
|
assertEquals(objThread0, traceManager.getCurrentObject());
|
||||||
assertEquals(thread, traceManager.getCurrentThread());
|
assertEquals(thread, traceManager.getCurrentThread());
|
||||||
|
|
||||||
|
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||||
traceManager.activateTrace(null);
|
traceManager.activateTrace(null);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* ###
|
||||||
|
* 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.tracemgr;
|
||||||
|
|
||||||
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
|
|
||||||
|
public class DebuggerTraceManagerServiceTestAccess {
|
||||||
|
public static void setEnsureActiveTrace(DebuggerTraceManagerService traceManager, boolean b) {
|
||||||
|
((DebuggerTraceManagerServicePlugin) traceManager).ensureActiveTrace = b;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 docking.widgets;
|
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.border.BevelBorder;
|
|
||||||
import javax.swing.event.ChangeEvent;
|
|
||||||
|
|
||||||
// TODO: I'd like "close" buttons on the tabs, optionally.
|
|
||||||
// For now, client must use a popup menu.
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public class HorizontalTabPanel<T> extends JPanel {
|
|
||||||
public static Color copyColor(Color c) {
|
|
||||||
return new Color(c.getRGB());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TabListCellRenderer<T> implements ListCellRenderer<T> {
|
|
||||||
protected final Box hBox = Box.createHorizontalBox();
|
|
||||||
protected final JLabel label = new JLabel();
|
|
||||||
|
|
||||||
{
|
|
||||||
hBox.setBorder(new BevelBorder(BevelBorder.RAISED));
|
|
||||||
hBox.setOpaque(true);
|
|
||||||
hBox.add(label);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getText(T value) {
|
|
||||||
return value.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Icon getIcon(T value) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Component getListCellRendererComponent(JList<? extends T> list,
|
|
||||||
T value, int index, boolean isSelected, boolean cellHasFocus) {
|
|
||||||
label.setText(getText(value));
|
|
||||||
label.setIcon(getIcon(value));
|
|
||||||
|
|
||||||
if (isSelected) {
|
|
||||||
//label.setForeground(list.getSelectionForeground());
|
|
||||||
label.setForeground(copyColor(list.getSelectionForeground()));
|
|
||||||
hBox.setBackground(list.getSelectionBackground());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
label.setForeground(list.getForeground());
|
|
||||||
hBox.setBackground(list.getBackground());
|
|
||||||
}
|
|
||||||
hBox.validate();
|
|
||||||
return hBox;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final JList<T> list = new JList<>();
|
|
||||||
private final JScrollPane scroll = new JScrollPane(list);
|
|
||||||
private final JViewport viewport = scroll.getViewport();
|
|
||||||
private final DefaultListModel<T> model = new DefaultListModel<>();
|
|
||||||
private final JButton left = new JButton("<");
|
|
||||||
private final JButton right = new JButton(">");
|
|
||||||
|
|
||||||
{
|
|
||||||
list.setModel(model);
|
|
||||||
// TODO: Experiment with multiple traces in one timeline
|
|
||||||
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
|
||||||
list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
|
|
||||||
list.setVisibleRowCount(1);
|
|
||||||
list.setCellRenderer(new TabListCellRenderer<>());
|
|
||||||
list.setOpaque(false);
|
|
||||||
|
|
||||||
scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
|
|
||||||
scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
|
||||||
scroll.setBorder(null);
|
|
||||||
|
|
||||||
viewport.addChangeListener(this::viewportChanged);
|
|
||||||
|
|
||||||
left.setBorder(null);
|
|
||||||
right.setBorder(null);
|
|
||||||
left.setContentAreaFilled(false);
|
|
||||||
right.setContentAreaFilled(false);
|
|
||||||
left.setOpaque(true);
|
|
||||||
right.setOpaque(true);
|
|
||||||
left.addActionListener(this::leftActivated);
|
|
||||||
right.addActionListener(this::rightActivated);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HorizontalTabPanel() {
|
|
||||||
super();
|
|
||||||
setLayout(new BorderLayout());
|
|
||||||
list.setBackground(getBackground());
|
|
||||||
add(scroll, BorderLayout.CENTER);
|
|
||||||
add(left, BorderLayout.WEST);
|
|
||||||
add(right, BorderLayout.EAST);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void viewportChanged(ChangeEvent e) {
|
|
||||||
Dimension paneSize = getSize();
|
|
||||||
Dimension listSize = list.getSize();
|
|
||||||
boolean buttonsVisible = paneSize.getWidth() < listSize.getWidth();
|
|
||||||
left.setVisible(buttonsVisible);
|
|
||||||
right.setVisible(buttonsVisible);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the first cell which is even partially visible
|
|
||||||
*
|
|
||||||
* @param reverse true to search from right to left
|
|
||||||
* @return the cell index
|
|
||||||
*/
|
|
||||||
private int findFirstVisible(boolean reverse) {
|
|
||||||
int n = model.getSize();
|
|
||||||
Rectangle vis = list.getVisibleRect();
|
|
||||||
for (int i = reverse ? n - 1 : 0; reverse ? i >= 0 : i < n; i += reverse ? -1 : 1) {
|
|
||||||
Rectangle b = list.getCellBounds(i, i);
|
|
||||||
if (vis.intersects(b)) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the first cell <em>after</em> a given start which is even partially occluded
|
|
||||||
*
|
|
||||||
* @param start the starting cell index
|
|
||||||
* @param reverse true to search from right to left
|
|
||||||
* @return the cell index
|
|
||||||
*/
|
|
||||||
private int findNextOccluded(int start, boolean reverse) {
|
|
||||||
if (start == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int n = model.getSize();
|
|
||||||
Rectangle vis = list.getVisibleRect();
|
|
||||||
for (int i = reverse ? start - 1 : start + 1; reverse ? i >= 0 : i < n; i +=
|
|
||||||
reverse ? -1 : 1) {
|
|
||||||
Rectangle b = list.getCellBounds(i, i);
|
|
||||||
if (!vis.contains(b)) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void leftActivated(ActionEvent e) {
|
|
||||||
list.ensureIndexIsVisible(findNextOccluded(findFirstVisible(true), true));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void rightActivated(ActionEvent e) {
|
|
||||||
list.ensureIndexIsVisible(findNextOccluded(findFirstVisible(false), false));
|
|
||||||
}
|
|
||||||
|
|
||||||
public JList<T> getList() {
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addItem(T item) {
|
|
||||||
model.addElement(item);
|
|
||||||
revalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeItem(T item) {
|
|
||||||
model.removeElement(item);
|
|
||||||
revalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public T getSelectedItem() {
|
|
||||||
int index = list.getSelectedIndex();
|
|
||||||
return index < 0 ? null : list.getModel().getElementAt(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelectedItem(T item) {
|
|
||||||
// NOTE: For large lists, this could get slow
|
|
||||||
int index = model.indexOf(item);
|
|
||||||
if (index < 0) {
|
|
||||||
list.clearSelection();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
list.setSelectedIndex(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public T getItem(int index) {
|
|
||||||
return list.getModel().getElementAt(index);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -222,51 +222,12 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener, Opti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String getStringUsedInList(Program program) {
|
private String getToolTip(Program program) {
|
||||||
DomainFile df = program.getDomainFile();
|
return DomainObjectDisplayUtils.getToolTip(program);
|
||||||
String changeIndicator = program.isChanged() ? "*" : "";
|
|
||||||
String pathString = getShortPath(df);
|
|
||||||
if (!df.isInWritableProject()) {
|
|
||||||
return pathString + " [Read-Only]" + changeIndicator;
|
|
||||||
}
|
|
||||||
return pathString + changeIndicator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getShortPath(DomainFile df) {
|
private String getTabName(Program program) {
|
||||||
String pathString = df.toString();
|
return DomainObjectDisplayUtils.getTabText(program);
|
||||||
int length = pathString.length();
|
|
||||||
if (length < 100) {
|
|
||||||
return pathString;
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] pathParts = pathString.split("/");
|
|
||||||
if (pathParts.length == 2) { // at least 2 for project name and filename
|
|
||||||
return pathString;
|
|
||||||
}
|
|
||||||
|
|
||||||
String projectName = df.getProjectLocator().getName();
|
|
||||||
int parentFolderIndex = pathParts.length - 2;
|
|
||||||
String parentName = pathParts[parentFolderIndex];
|
|
||||||
String filename = df.getName();
|
|
||||||
pathString = projectName + ":/.../" + parentName + "/" + filename;
|
|
||||||
return pathString;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getToolTip(Program program) {
|
|
||||||
return getStringUsedInList(program);
|
|
||||||
}
|
|
||||||
|
|
||||||
String getName(Program program) {
|
|
||||||
DomainFile df = program.getDomainFile();
|
|
||||||
String tabName = df.getName();
|
|
||||||
if (df.isReadOnly()) {
|
|
||||||
int version = df.getVersion();
|
|
||||||
if (!df.canSave() && version != DomainFile.DEFAULT_VERSION) {
|
|
||||||
tabName += "@" + version;
|
|
||||||
}
|
|
||||||
tabName = tabName + " [Read-Only]";
|
|
||||||
}
|
|
||||||
return tabName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void keyTypedFromListWindow(KeyEvent e) {
|
void keyTypedFromListWindow(KeyEvent e) {
|
||||||
|
@ -334,22 +295,6 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener, Opti
|
||||||
return EMPTY8_ICON;
|
return EMPTY8_ICON;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getTabName(Program program) {
|
|
||||||
DomainFile df = program.getDomainFile();
|
|
||||||
String tabName = df.getName();
|
|
||||||
if (df.isReadOnly()) {
|
|
||||||
int version = df.getVersion();
|
|
||||||
if (!df.canSave() && version != DomainFile.DEFAULT_VERSION) {
|
|
||||||
tabName += "@" + version;
|
|
||||||
}
|
|
||||||
tabName = tabName + " [Read-Only]";
|
|
||||||
}
|
|
||||||
if (program.isChanged()) {
|
|
||||||
tabName = "*" + tabName;
|
|
||||||
}
|
|
||||||
return tabName;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean removeProgram(Program program) {
|
boolean removeProgram(Program program) {
|
||||||
return progService.closeProgram(program, false);
|
return progService.closeProgram(program, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
/* ###
|
||||||
|
* 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.framework.model;
|
||||||
|
|
||||||
|
import ghidra.framework.store.FileSystem;
|
||||||
|
|
||||||
|
public class DomainObjectDisplayUtils {
|
||||||
|
private static final String VERSION_SEP = "@";
|
||||||
|
private static final String CHANGE_INDICATOR = "*";
|
||||||
|
private static final String READ_ONLY = " [Read-Only]";
|
||||||
|
private static final String PROJECT_SEP_ELLIPSES =
|
||||||
|
":" + FileSystem.SEPARATOR + "..." + FileSystem.SEPARATOR;
|
||||||
|
|
||||||
|
private DomainObjectDisplayUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getShortPath(DomainFile df) {
|
||||||
|
String pathString = df.toString();
|
||||||
|
int length = pathString.length();
|
||||||
|
if (length < 100) {
|
||||||
|
return pathString;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] pathParts = pathString.split(FileSystem.SEPARATOR);
|
||||||
|
if (pathParts.length == 2) { // at least 2 for project name and filename
|
||||||
|
return pathString;
|
||||||
|
}
|
||||||
|
|
||||||
|
String projectName = df.getProjectLocator().getName();
|
||||||
|
int parentFolderIndex = pathParts.length - 2;
|
||||||
|
String parentName = pathParts[parentFolderIndex];
|
||||||
|
String filename = df.getName();
|
||||||
|
pathString =
|
||||||
|
projectName + PROJECT_SEP_ELLIPSES + parentName + FileSystem.SEPARATOR + filename;
|
||||||
|
return pathString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getToolTip(DomainObject object) {
|
||||||
|
DomainFile df = object.getDomainFile();
|
||||||
|
String changeIndicator = object.isChanged() ? CHANGE_INDICATOR : "";
|
||||||
|
String pathString = getShortPath(df);
|
||||||
|
if (!df.isInWritableProject()) {
|
||||||
|
return pathString + READ_ONLY + changeIndicator;
|
||||||
|
}
|
||||||
|
return pathString + changeIndicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getTabText(DomainFile df) {
|
||||||
|
String tabName = df.getName();
|
||||||
|
if (df.isReadOnly()) {
|
||||||
|
int version = df.getVersion();
|
||||||
|
if (!df.canSave() && version != DomainFile.DEFAULT_VERSION) {
|
||||||
|
tabName += VERSION_SEP + version;
|
||||||
|
}
|
||||||
|
tabName = tabName + READ_ONLY;
|
||||||
|
}
|
||||||
|
return tabName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getTabText(DomainObject object) {
|
||||||
|
if (object.isChanged()) {
|
||||||
|
return CHANGE_INDICATOR + getTabText(object.getDomainFile());
|
||||||
|
}
|
||||||
|
return getTabText(object.getDomainFile());
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import org.junit.Test;
|
||||||
|
|
||||||
import db.Transaction;
|
import db.Transaction;
|
||||||
import generic.Unique;
|
import generic.Unique;
|
||||||
|
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServiceTestAccess;
|
||||||
import ghidra.app.script.GhidraState;
|
import ghidra.app.script.GhidraState;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService;
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
import ghidra.debug.api.breakpoint.LogicalBreakpoint;
|
import ghidra.debug.api.breakpoint.LogicalBreakpoint;
|
||||||
|
@ -172,6 +173,7 @@ public class DeadFlatDebuggerAPITest extends AbstractFlatDebuggerAPITest<FlatDeb
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateTraceNull() throws Throwable {
|
public void testActivateTraceNull() throws Throwable {
|
||||||
|
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
@ -217,6 +219,7 @@ public class DeadFlatDebuggerAPITest extends AbstractFlatDebuggerAPITest<FlatDeb
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateThreadNull() throws Throwable {
|
public void testActivateThreadNull() throws Throwable {
|
||||||
|
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||||
api.activateThread(null);
|
api.activateThread(null);
|
||||||
assertEquals(null, traceManager.getCurrentThread());
|
assertEquals(null, traceManager.getCurrentThread());
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue