mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
GP-665: Deleting DebuggerTimelineProvider and related.
This commit is contained in:
@@ -1,30 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.timeline;
|
||||
|
||||
import docking.ActionContext;
|
||||
|
||||
public class DebuggerTimelineActionContext extends ActionContext {
|
||||
private final TimelineRow selection;
|
||||
|
||||
public DebuggerTimelineActionContext(TimelineRow selection) {
|
||||
this.selection = selection;
|
||||
}
|
||||
|
||||
public TimelineRow getSelection() {
|
||||
return selection;
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.timeline;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import docking.widgets.*;
|
||||
import docking.widgets.RangeCursorPanel.Direction;
|
||||
import docking.widgets.table.RowObjectTableModel;
|
||||
import docking.widgets.timeline.TimelinePanel;
|
||||
import docking.widgets.timeline.TimelineViewRangeListener;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
public class DebuggerTimelinePanel extends JScrollPane {
|
||||
static class SnapRequestVetoedException extends Exception {
|
||||
private final long requestedSnap;
|
||||
private final long newSnap;
|
||||
|
||||
public SnapRequestVetoedException(long requestedSnap, long newSnap) {
|
||||
this.requestedSnap = requestedSnap;
|
||||
this.newSnap = newSnap;
|
||||
}
|
||||
|
||||
public long getRequestedSnap() {
|
||||
return requestedSnap;
|
||||
}
|
||||
|
||||
public long getNewSnap() {
|
||||
return newSnap;
|
||||
}
|
||||
}
|
||||
|
||||
public interface VetoableSnapRequestListener {
|
||||
void snapRequested(long snap, EventTrigger trigger) throws SnapRequestVetoedException;
|
||||
}
|
||||
|
||||
protected class ObjectTimelinePanel extends TimelinePanel<TimelineRow, Long> {
|
||||
private DebuggerTimelinePanel panel;
|
||||
|
||||
public ObjectTimelinePanel(DebuggerTimelinePanel panel,
|
||||
RowObjectTableModel<TimelineRow> model) {
|
||||
super(model, TimelineRow::getLifespan);
|
||||
this.panel = panel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void selectionChanged(ListSelectionEvent e) {
|
||||
super.selectionChanged(e);
|
||||
List<TimelineRow> items = rows.items();
|
||||
int min = selectionModel.getMinSelectionIndex();
|
||||
if (items != null && min >= 0) {
|
||||
TimelineRow row = items.get(min);
|
||||
Component component = getComponent(row);
|
||||
Point loc = component.getLocation();
|
||||
JViewport vp = panel.getViewport();
|
||||
vp.setViewPosition(new Point(loc.x, vp.getView().getY()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected class ObjectRangeCursorPanel extends RangeCursorPanel {
|
||||
public ObjectRangeCursorPanel(Direction direction) {
|
||||
super(direction);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double adjustRequestedValue(double requested) {
|
||||
double rounded = Math.round(requested);
|
||||
// TODO: Also remove 1-snap view buffer?
|
||||
// Until I figure out the event processing order, leaving the buffer
|
||||
// prevents some stepping glitches.
|
||||
if (range.hasLowerBound() && rounded < range.lowerEndpoint() /*+1*/) {
|
||||
return range.lowerEndpoint(); // +1
|
||||
}
|
||||
if (range.hasUpperBound() && rounded > range.upperEndpoint() /*-1*/) {
|
||||
return range.upperEndpoint(); // -1
|
||||
}
|
||||
return rounded;
|
||||
}
|
||||
}
|
||||
|
||||
protected final ObjectTimelinePanel timeline;
|
||||
protected final RangeCursorPanel topCursor = new ObjectRangeCursorPanel(Direction.SOUTH) {
|
||||
Dimension preferredSize = new Dimension(super.getPreferredSize());
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
preferredSize.width = timeline.getPreferredSize().width;
|
||||
return preferredSize;
|
||||
}
|
||||
};
|
||||
protected final ListenerSet<VetoableSnapRequestListener> listeners =
|
||||
new ListenerSet<>(VetoableSnapRequestListener.class);
|
||||
protected final RangeCursorValueListener positionListener = this::cursorPositionChanged;
|
||||
protected final TimelineViewRangeListener viewRangeListener = this::viewRangeChanged;
|
||||
|
||||
protected ListSelectionModel selectionModel;
|
||||
|
||||
public DebuggerTimelinePanel(RowObjectTableModel<TimelineRow> model) {
|
||||
setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||
setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
|
||||
|
||||
timeline = new ObjectTimelinePanel(this, model);
|
||||
timeline.setCompressed(false);
|
||||
setViewportView(timeline);
|
||||
setColumnHeaderView(topCursor);
|
||||
|
||||
topCursor.addValueListener(positionListener);
|
||||
timeline.addViewRangeListener(viewRangeListener);
|
||||
}
|
||||
|
||||
public void addSnapRequestedListener(VetoableSnapRequestListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
private void cursorPositionChanged(double position, EventTrigger trigger) {
|
||||
// try {
|
||||
// listeners.fire.snapRequested(Math.round(position), trigger);
|
||||
// }
|
||||
// catch (SnapRequestVetoedException e) {
|
||||
// position = e.getNewSnap();
|
||||
// }
|
||||
}
|
||||
|
||||
private void viewRangeChanged(Range<Double> range) {
|
||||
topCursor.setRange(range);
|
||||
}
|
||||
|
||||
public void setSelectionModel(ListSelectionModel selectionModel) {
|
||||
timeline.setSelectionModel(selectionModel);
|
||||
}
|
||||
|
||||
public void setSnap(long snap) {
|
||||
topCursor.requestValue(snap);
|
||||
}
|
||||
|
||||
public void setMaxSnapAtLeast(Long maxSnapAtLeast) {
|
||||
if (maxSnapAtLeast != null) {
|
||||
timeline.setMaxAtLeast(maxSnapAtLeast);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.timeline;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import ghidra.app.events.*;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.ObjectUpdatedEvent;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.program.model.lang.CompilerSpec;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.trace.database.DBTrace;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
@PluginInfo( //
|
||||
shortDescription = "Debugger timeline manager", //
|
||||
description = "GUI to view object timelines", //
|
||||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.UNSTABLE, //
|
||||
eventsConsumed = { //
|
||||
ProgramOpenedPluginEvent.class, // TODO: Remove?
|
||||
ProgramSelectionPluginEvent.class, // TODO: Later or remove
|
||||
ProgramHighlightPluginEvent.class, // TODO: Later or remove
|
||||
ProgramActivatedPluginEvent.class, // TODO: Remove? Covered by Location?
|
||||
ProgramClosedPluginEvent.class, // For marker set cleanup
|
||||
ProgramLocationPluginEvent.class, // For static listing sync
|
||||
TraceActivatedPluginEvent.class, //
|
||||
ObjectUpdatedEvent.class, //
|
||||
}, //
|
||||
servicesRequired = { //
|
||||
DebuggerTraceManagerService.class, //
|
||||
} //
|
||||
)
|
||||
|
||||
public class DebuggerTimelinePlugin extends AbstractDebuggerPlugin {
|
||||
protected DebuggerTimelineProvider provider;
|
||||
private Program currentProgram;
|
||||
private Map<Program, Trace> traceMap = new LinkedHashMap<>();
|
||||
|
||||
public DebuggerTimelinePlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
provider = new DebuggerTimelineProvider(this);
|
||||
super.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispose() {
|
||||
tool.removeComponentProvider(provider);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof ProgramOpenedPluginEvent) {
|
||||
setProgram(((ProgramOpenedPluginEvent) event).getProgram(), false);
|
||||
}
|
||||
if (event instanceof ProgramActivatedPluginEvent) {
|
||||
setProgram(((ProgramActivatedPluginEvent) event).getActiveProgram(), false);
|
||||
}
|
||||
if (event instanceof ProgramClosedPluginEvent) {
|
||||
setProgram(((ProgramClosedPluginEvent) event).getProgram(), true);
|
||||
}
|
||||
if (event instanceof ObjectUpdatedEvent) {
|
||||
provider.update(((ObjectUpdatedEvent) event).getObject());
|
||||
}
|
||||
if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
provider.coordinatesActivated(ev.getActiveCoordinates());
|
||||
}
|
||||
}
|
||||
|
||||
private void setProgram(Program pgm, boolean remove) {
|
||||
currentProgram = pgm;
|
||||
if (pgm == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Trace trace = null;
|
||||
if (traceMap.containsKey(pgm)) {
|
||||
trace = traceMap.get(pgm);
|
||||
if (remove) {
|
||||
traceMap.remove(pgm);
|
||||
trace = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO: I'm not sure what this is doing, but it seems like a bad idea....
|
||||
CompilerSpec cspec = pgm.getCompilerSpec();
|
||||
trace = new DBTrace(pgm.getName(), cspec, this);
|
||||
}
|
||||
// TODO: Nothing seems to put into traceMap, so just commenting out
|
||||
// provider.doSetTrace(trace);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,487 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.timeline;
|
||||
|
||||
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.WindowPosition;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.action.ToolBarData;
|
||||
import docking.widgets.EventTrigger;
|
||||
import docking.widgets.table.*;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer;
|
||||
import ghidra.app.plugin.core.debug.gui.timeline.DebuggerTimelinePanel.VetoableSnapRequestListener;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.framework.model.DomainObject;
|
||||
import ghidra.framework.plugintool.AutoService;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
||||
import ghidra.trace.model.Trace.TraceThreadChangeType;
|
||||
import ghidra.trace.model.TraceDomainObjectListener;
|
||||
import ghidra.trace.model.modules.TraceModule;
|
||||
import ghidra.trace.model.modules.TraceModuleManager;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.thread.TraceThreadManager;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
|
||||
public class DebuggerTimelineProvider extends ComponentProviderAdapter {
|
||||
|
||||
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
|
||||
if (!Objects.equals(a.getTrace(), b.getTrace())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(a.getSnap(), b.getSnap())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private final DebuggerTimelinePlugin plugin;
|
||||
|
||||
@AutoServiceConsumed
|
||||
private DebuggerModelService modelService;
|
||||
@AutoServiceConsumed
|
||||
private DebuggerTraceManagerService traceManager;
|
||||
@SuppressWarnings("unused")
|
||||
private final AutoService.Wiring autoWiring;
|
||||
|
||||
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||
private Trace currentTrace; // Copy for transition
|
||||
private TraceObject currentObject;
|
||||
|
||||
private final ThreadsListener threadsListener = new ThreadsListener();
|
||||
private final VetoableSnapRequestListener snapListener = this::snapRequested;
|
||||
|
||||
protected final EnumeratedColumnTableModel<TimelineRow> timelineTableModel =
|
||||
new DefaultEnumeratedColumnTableModel<>("Objects", TimelineTableColumns.class);
|
||||
|
||||
private JSplitPane mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||
protected GTable objectTable;
|
||||
private DebuggerTimelinePanel timelinePanel;
|
||||
private GhidraTableFilterPanel<TimelineRow> timelineFilterPanel;
|
||||
|
||||
private ActionContext myActionContext;
|
||||
|
||||
StepTraceBackwardAction actionStepTraceBackward;
|
||||
StepTraceForwardAction actionStepTraceForward;
|
||||
SeekTracePresentAction actionSeekTracePresent;
|
||||
|
||||
private Map<String, TraceObject> map = new LinkedHashMap<String, TraceObject>();
|
||||
private Map<String, TimelineRow> rowMap = new LinkedHashMap<String, TimelineRow>();
|
||||
|
||||
public DebuggerTimelineProvider(final DebuggerTimelinePlugin plugin) {
|
||||
super(plugin.getTool(), TITLE_PROVIDER_OBJECTS, plugin.getName());
|
||||
this.plugin = plugin;
|
||||
|
||||
this.autoWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||
|
||||
setIcon(ICON_PROVIDER_OBJECTS);
|
||||
setHelpLocation(HELP_PROVIDER_OBJECTS);
|
||||
setWindowMenuGroup(DebuggerPluginPackage.NAME);
|
||||
|
||||
buildMainPanel();
|
||||
|
||||
// TODO: Consider a custom cell renderer in the table instead of a timeline widget?
|
||||
// TODO: Should I receive clicks on that renderer to seek to a given snap?
|
||||
setDefaultWindowPosition(WindowPosition.BOTTOM);
|
||||
|
||||
myActionContext = new DebuggerSnapActionContext(0);
|
||||
createActions();
|
||||
contextChanged();
|
||||
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
private void doSetTrace(Trace trace) {
|
||||
removeOldListeners();
|
||||
currentTrace = trace;
|
||||
addNewListeners();
|
||||
|
||||
loadObjects();
|
||||
}
|
||||
|
||||
private void doSetObject(TraceObject object) {
|
||||
// TODO: Use a map if speed becomes an issue
|
||||
timelineFilterPanel
|
||||
.setSelectedItem(timelineTableModel.findFirst(row -> row.getObject() == object));
|
||||
currentObject = object;
|
||||
}
|
||||
|
||||
private void doSetSnap(long snap) {
|
||||
timelinePanel.setSnap(snap);
|
||||
myActionContext = new DebuggerSnapActionContext(snap);
|
||||
}
|
||||
|
||||
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||
if (sameCoordinates(current, coordinates)) {
|
||||
current = coordinates;
|
||||
return;
|
||||
}
|
||||
current = coordinates;
|
||||
|
||||
doSetTrace(current.getTrace());
|
||||
doSetSnap(current.getSnap());
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
public void setObject(TraceObject object) {
|
||||
if (currentObject == object) {
|
||||
return;
|
||||
}
|
||||
doSetObject(object);
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
private void removeOldListeners() {
|
||||
if (currentTrace == null) {
|
||||
return;
|
||||
}
|
||||
currentTrace.removeListener(threadsListener);
|
||||
}
|
||||
|
||||
private void addNewListeners() {
|
||||
if (currentTrace == null) {
|
||||
return;
|
||||
}
|
||||
currentTrace.addListener(threadsListener);
|
||||
}
|
||||
|
||||
protected void loadObjects() {
|
||||
map.clear();
|
||||
rowMap.clear();
|
||||
timelineTableModel.clear();
|
||||
Trace curTrace = current.getTrace();
|
||||
if (curTrace == null) {
|
||||
return;
|
||||
}
|
||||
TraceThreadManager manager = curTrace.getThreadManager();
|
||||
Collection<? extends TraceThread> allThreads = manager.getAllThreads();
|
||||
for (TraceThread thread : allThreads) {
|
||||
TraceObject t = new TraceObject(thread);
|
||||
timelineTableModel.add(new TimelineRow(modelService, t));
|
||||
}
|
||||
TraceModuleManager modmgr = curTrace.getModuleManager();
|
||||
Collection<? extends TraceModule> allModules = modmgr.getAllModules();
|
||||
for (TraceModule module : allModules) {
|
||||
TraceObject t = new TraceObject(module);
|
||||
timelineTableModel.add(new TimelineRow(modelService, t));
|
||||
}
|
||||
timelinePanel.setMaxSnapAtLeast(curTrace.getTimeManager().getMaxSnap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLocalAction(DockingActionIf action) {
|
||||
super.addLocalAction(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionContext getActionContext(MouseEvent event) {
|
||||
if (myActionContext == null) {
|
||||
return super.getActionContext(event);
|
||||
}
|
||||
return myActionContext;
|
||||
}
|
||||
|
||||
private void snapRequested(long snap, EventTrigger trigger) {
|
||||
if (snap < 0) {
|
||||
snap = 0;
|
||||
}
|
||||
Trace curTrace = current.getTrace();
|
||||
if (curTrace == null) {
|
||||
snap = 0;
|
||||
}
|
||||
else {
|
||||
Long maxSnap = curTrace.getTimeManager().getMaxSnap();
|
||||
if (maxSnap == null) {
|
||||
maxSnap = 0L;
|
||||
}
|
||||
if (snap > maxSnap) {
|
||||
snap = maxSnap;
|
||||
}
|
||||
}
|
||||
if (trigger == EventTrigger.GUI_ACTION) {
|
||||
traceManager.activateSnap(snap);
|
||||
}
|
||||
myActionContext = new DebuggerSnapActionContext(snap);
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
protected void buildMainPanel() {
|
||||
mainPanel.setContinuousLayout(true);
|
||||
|
||||
JPanel tablePanel = new JPanel(new BorderLayout());
|
||||
objectTable = new GhidraTable(timelineTableModel);
|
||||
objectTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
tablePanel.add(new JScrollPane(objectTable));
|
||||
timelineFilterPanel = new GhidraTableFilterPanel<>(objectTable, timelineTableModel);
|
||||
tablePanel.add(timelineFilterPanel, BorderLayout.SOUTH);
|
||||
mainPanel.setLeftComponent(tablePanel);
|
||||
|
||||
timelinePanel = new DebuggerTimelinePanel(timelineFilterPanel.getTableFilterModel());
|
||||
mainPanel.setRightComponent(timelinePanel);
|
||||
|
||||
mainPanel.setResizeWeight(0.4);
|
||||
|
||||
objectTable.getSelectionModel().addListSelectionListener(evt -> {
|
||||
TimelineRow row = timelineFilterPanel.getSelectedItem();
|
||||
myActionContext = new DebuggerTimelineActionContext(row);
|
||||
contextChanged();
|
||||
if (row != null && traceManager != null) {
|
||||
TraceObject object = row.getObject();
|
||||
if (object.isThread()) {
|
||||
traceManager.activateThread((TraceThread) object.getObject());
|
||||
}
|
||||
}
|
||||
});
|
||||
timelinePanel.setSelectionModel(objectTable.getSelectionModel());
|
||||
timelinePanel.addSnapRequestedListener(snapListener);
|
||||
}
|
||||
|
||||
protected void createActions() {
|
||||
actionStepTraceBackward = new StepTraceBackwardAction();
|
||||
actionStepTraceForward = new StepTraceForwardAction();
|
||||
actionSeekTracePresent = new SeekTracePresentAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return mainPanel;
|
||||
}
|
||||
|
||||
public void update(ObjectContainer container) {
|
||||
String shortName = container.getShortName();
|
||||
if (shortName == null || !shortName.equals("Sequence")) {
|
||||
return;
|
||||
}
|
||||
|
||||
TraceObject object = null;
|
||||
Trace curTrace = current.getTrace();
|
||||
try (UndoableTransaction tid =
|
||||
UndoableTransaction.start(curTrace, "Populate Snapshot", true)) {
|
||||
ObjectContainer ancestor = container;
|
||||
while (ancestor != null && !ancestor.getPrefixedName().contains("[")) {
|
||||
ancestor = ancestor.getParent();
|
||||
}
|
||||
if (ancestor == null) {
|
||||
return;
|
||||
}
|
||||
String objName = ancestor.getPrefixedName();
|
||||
TimelineRow row = null;
|
||||
if (map.containsKey(objName)) {
|
||||
object = map.get(objName);
|
||||
row = rowMap.get(objName);
|
||||
}
|
||||
else {
|
||||
object = new TraceObject(curTrace);
|
||||
object.setName(objName);
|
||||
map.put(objName, object);
|
||||
row = new TimelineRow(modelService, object);
|
||||
rowMap.put(objName, row);
|
||||
timelineTableModel.add(row);
|
||||
}
|
||||
String[] split = container.getName().split(" ");
|
||||
String searchString = container.getTargetObject().getJoinedPath(".");
|
||||
long pos = -1;
|
||||
if (searchString.contains("MinPos")) {
|
||||
System.err.println("minpos");
|
||||
pos = Long.parseLong(split[split.length - 1], 16);
|
||||
object.setCreationSnap(pos);
|
||||
curTrace.getTimeManager()
|
||||
.getSnapshot(pos, true)
|
||||
.setDescription(object + " created");
|
||||
}
|
||||
else if (searchString.contains("MaxPos")) {
|
||||
System.err.println("maxpos");
|
||||
pos = Long.parseLong(split[split.length - 1], 16);
|
||||
object.setDestructionSnap(pos);
|
||||
curTrace.getTimeManager()
|
||||
.getSnapshot(pos, true)
|
||||
.setDescription(object + " destroyed");
|
||||
}
|
||||
else if (searchString.contains("Position")) {
|
||||
/*
|
||||
pos = Long.parseLong(split[split.length - 1], 16);
|
||||
object.setCreationSnap(pos);
|
||||
curTrace.getTimeManager()
|
||||
.getSnapshot(pos, true)
|
||||
.setDescription(object + " mark set");
|
||||
*/
|
||||
}
|
||||
if (pos >= 0L) {
|
||||
timelinePanel.setMaxSnapAtLeast(pos);
|
||||
}
|
||||
timelineTableModel.notifyUpdated(row);
|
||||
mainPanel.validate();
|
||||
mainPanel.repaint();
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
setObject(object);
|
||||
}
|
||||
|
||||
protected class StepTraceBackwardAction extends AbstractStepTraceBackwardAction {
|
||||
public static final String GROUP = GROUP_GENERAL;
|
||||
|
||||
public StepTraceBackwardAction() {
|
||||
super(plugin);
|
||||
setToolBarData(new ToolBarData(ICON, GROUP));
|
||||
addLocalAction(this);
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
traceManager.activateSnap(current.getSnap() - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
if (current.getTrace() == null) {
|
||||
return false;
|
||||
}
|
||||
// TODO: Can I track minTick?
|
||||
if (current.getSnap() <= 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected class StepTraceForwardAction extends AbstractStepTraceForwardAction {
|
||||
public static final String GROUP = GROUP_GENERAL;
|
||||
|
||||
public StepTraceForwardAction() {
|
||||
super(plugin);
|
||||
setToolBarData(new ToolBarData(ICON, GROUP));
|
||||
addLocalAction(this);
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
traceManager.activateSnap(current.getSnap() + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
Trace curTrace = current.getTrace();
|
||||
if (curTrace == null) {
|
||||
return false;
|
||||
}
|
||||
Long maxSnap = curTrace.getTimeManager().getMaxSnap();
|
||||
if (maxSnap == null || current.getSnap() >= maxSnap) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected class SeekTracePresentAction extends AbstractSeekTracePresentAction {
|
||||
public static final String GROUP = GROUP_GENERAL;
|
||||
|
||||
public SeekTracePresentAction() {
|
||||
super(plugin);
|
||||
setToolBarData(new ToolBarData(ICON, GROUP));
|
||||
addLocalAction(this);
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
if (current.getTrace() == null) {
|
||||
return;
|
||||
}
|
||||
TraceRecorder recorder = current.getRecorder();
|
||||
if (recorder == null) {
|
||||
return;
|
||||
}
|
||||
traceManager.activateSnap(recorder.getSnap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
if (current.getTrace() == null) {
|
||||
return false;
|
||||
}
|
||||
TraceRecorder recorder = current.getRecorder();
|
||||
if (recorder == null) {
|
||||
return false;
|
||||
}
|
||||
if (current.getSnap() == recorder.getSnap()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class ThreadsListener extends TraceDomainObjectListener {
|
||||
public ThreadsListener() {
|
||||
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
|
||||
|
||||
listenFor(TraceThreadChangeType.ADDED, this::threadAdded);
|
||||
listenFor(TraceThreadChangeType.CHANGED, this::threadChanged);
|
||||
listenFor(TraceThreadChangeType.LIFESPAN_CHANGED, this::threadChanged);
|
||||
listenFor(TraceThreadChangeType.DELETED, this::threadDeleted);
|
||||
|
||||
listenFor(TraceSnapshotChangeType.ADDED, this::snapshotAdded);
|
||||
listenFor(TraceSnapshotChangeType.DELETED, this::snapshotDeleted);
|
||||
}
|
||||
|
||||
private void objectRestored() {
|
||||
loadObjects();
|
||||
}
|
||||
|
||||
private void threadAdded(TraceThread thread) {
|
||||
timelineTableModel.add(new TimelineRow(modelService, new TraceObject(thread)));
|
||||
}
|
||||
|
||||
private void threadChanged(TraceThread thread) {
|
||||
timelineTableModel.notifyUpdatedWith(row -> row.getObject() == thread);
|
||||
}
|
||||
|
||||
private void threadDeleted(TraceThread thread) {
|
||||
timelineTableModel.deleteWith(row -> row.getObject() == thread);
|
||||
}
|
||||
|
||||
private void snapshotAdded(TraceSnapshot snapshot) {
|
||||
long maxSnap = current.getTrace().getTimeManager().getMaxSnap();
|
||||
timelinePanel.setMaxSnapAtLeast(maxSnap);
|
||||
}
|
||||
|
||||
private void snapshotDeleted(TraceSnapshot snapshot) {
|
||||
timelinePanel.setMaxSnapAtLeast(current.getTrace().getTimeManager().getMaxSnap());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.timeline;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.app.services.DebuggerModelService;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
|
||||
public class TimelineRow {
|
||||
private final DebuggerModelService service;
|
||||
private final TraceObject object;
|
||||
|
||||
public TimelineRow(DebuggerModelService service, TraceObject object) {
|
||||
this.service = service;
|
||||
this.object = object;
|
||||
}
|
||||
|
||||
public TraceObject getObject() {
|
||||
return object;
|
||||
}
|
||||
|
||||
public Trace getTrace() {
|
||||
return object.getTrace();
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
try (UndoableTransaction tid =
|
||||
UndoableTransaction.start(object.getTrace(), "Renamed thread", true)) {
|
||||
object.setName(name);
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return object.getName();
|
||||
}
|
||||
|
||||
public long getCreationTick() {
|
||||
return object.getCreationSnap();
|
||||
}
|
||||
|
||||
public String getDestructionTick() {
|
||||
long tick = object.getDestructionSnap();
|
||||
return tick == Long.MAX_VALUE ? "" : Long.toString(tick);
|
||||
}
|
||||
|
||||
public Range<Long> getLifespan() {
|
||||
return object.getLifespan();
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
try (UndoableTransaction tid =
|
||||
UndoableTransaction.start(object.getTrace(), "Renamed thread", true)) {
|
||||
object.setComment(comment);
|
||||
}
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return object.getComment();
|
||||
}
|
||||
|
||||
public TimelineState getState() {
|
||||
if (!object.isAlive()) {
|
||||
return TimelineState.TERMINATED;
|
||||
}
|
||||
TraceRecorder recorder = service.getRecorder(object.getTrace());
|
||||
if (recorder == null) {
|
||||
return TimelineState.ALIVE;
|
||||
}
|
||||
TargetExecutionState targetState = recorder.getTargetThreadState(object);
|
||||
if (targetState == null) {
|
||||
return TimelineState.UNKNOWN;
|
||||
}
|
||||
switch (targetState) {
|
||||
case ALIVE:
|
||||
return TimelineState.ALIVE;
|
||||
case INACTIVE:
|
||||
return TimelineState.UNKNOWN;
|
||||
case RUNNING:
|
||||
return TimelineState.RUNNING;
|
||||
case STOPPED:
|
||||
return TimelineState.STOPPED;
|
||||
case TERMINATED:
|
||||
return TimelineState.TERMINATED;
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.timeline;
|
||||
|
||||
public enum TimelineState {
|
||||
/**
|
||||
* The last recorded state is alive, but the recorder is not tracking the live thread
|
||||
*
|
||||
* This state is generally erroneous. If it is seen, the recorder has fallen out of sync with
|
||||
* the live session and/or the trace.
|
||||
*/
|
||||
UNKNOWN,
|
||||
/**
|
||||
* The last recorded state is alive, but there is no live session to know STOPPED or RUNNING
|
||||
*/
|
||||
// TODO: Should the thread state transitions be recorded?
|
||||
ALIVE,
|
||||
/**
|
||||
* The thread is alive, but suspended
|
||||
*/
|
||||
STOPPED,
|
||||
/**
|
||||
* The thread is alive and running
|
||||
*/
|
||||
RUNNING,
|
||||
/**
|
||||
* The thread has been terminated (either as recorded, or as reported by the live session)
|
||||
*/
|
||||
TERMINATED;
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.timeline;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||
|
||||
public enum TimelineTableColumns
|
||||
implements EnumeratedTableColumn<TimelineTableColumns, TimelineRow> {
|
||||
NAME("Name", String.class, TimelineRow::getName, TimelineRow::setName),
|
||||
CREATED("Created", Long.class, TimelineRow::getCreationTick),
|
||||
DESTROYED("Destroyed", String.class, TimelineRow::getDestructionTick),
|
||||
STATE("State", TimelineState.class, TimelineRow::getState),
|
||||
COMMENT("Comment", String.class, TimelineRow::getComment, TimelineRow::setComment);
|
||||
|
||||
private final String header;
|
||||
private final Function<TimelineRow, ?> getter;
|
||||
private final BiConsumer<TimelineRow, Object> setter;
|
||||
private final Class<?> cls;
|
||||
|
||||
<T> TimelineTableColumns(String header, Class<T> cls, Function<TimelineRow, T> getter) {
|
||||
this(header, cls, getter, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<T> TimelineTableColumns(String header, Class<T> cls, Function<TimelineRow, T> getter,
|
||||
BiConsumer<TimelineRow, T> setter) {
|
||||
this.header = header;
|
||||
this.cls = cls;
|
||||
this.getter = getter;
|
||||
this.setter = (BiConsumer<TimelineRow, Object>) setter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueClass() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueOf(TimelineRow row) {
|
||||
return getter.apply(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable(TimelineRow row) {
|
||||
return setter != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueOf(TimelineRow row, Object value) {
|
||||
setter.accept(row, value);
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.timeline;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.modules.TraceModule;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
// TODO: Create an interface for time-bound objects
|
||||
// Then either:
|
||||
// 1) The appropriate trace and/or target objects can implement them
|
||||
// 2) We create the necessary wrappers, one for trace threads, and another for TTD target objects
|
||||
public class TraceObject implements TraceThread {
|
||||
|
||||
private Object object;
|
||||
private Trace trace;
|
||||
private String name;
|
||||
private Long creationTick;
|
||||
private Long destructionTick;
|
||||
private Range<Long> lifespan;
|
||||
private String comment;
|
||||
private long key = 0L;
|
||||
private boolean isThread = false;
|
||||
|
||||
public TraceObject(Trace currentTrace) {
|
||||
this.trace = currentTrace;
|
||||
this.object = null;
|
||||
creationTick = 0L;
|
||||
destructionTick = Long.MAX_VALUE;
|
||||
lifespan = Range.atLeast(creationTick);
|
||||
}
|
||||
|
||||
public TraceObject(TraceThread thread) {
|
||||
this.trace = thread.getTrace();
|
||||
this.object = thread;
|
||||
name = thread.getName();
|
||||
creationTick = thread.getCreationSnap();
|
||||
destructionTick = thread.getDestructionSnap();
|
||||
lifespan = thread.getLifespan();
|
||||
key = thread.getKey();
|
||||
comment = thread.getComment();
|
||||
isThread = true;
|
||||
}
|
||||
|
||||
public TraceObject(TraceModule module) {
|
||||
this.trace = module.getTrace();
|
||||
this.object = module;
|
||||
name = module.getName();
|
||||
creationTick = module.getLoadedSnap();
|
||||
destructionTick = module.getUnloadedSnap();
|
||||
lifespan = module.getLifespan();
|
||||
comment = module.toString();
|
||||
}
|
||||
|
||||
public Object getObject() {
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return name; // TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreationSnap(long creationTick) {
|
||||
this.creationTick = creationTick;
|
||||
lifespan = Range.closed(creationTick, destructionTick);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreationSnap() {
|
||||
return creationTick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDestructionSnap(long destructionTick) {
|
||||
this.destructionTick = destructionTick;
|
||||
lifespan = Range.closed(creationTick, destructionTick);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDestructionSnap() {
|
||||
return destructionTick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLifespan(Range<Long> lifespan) {
|
||||
this.lifespan = lifespan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range<Long> getLifespan() {
|
||||
return lifespan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public boolean isThread() {
|
||||
return isThread;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user