diff --git a/Ghidra/Debug/Debugger/certification.manifest b/Ghidra/Debug/Debugger/certification.manifest index 95fcd10836..b6b5a7111e 100644 --- a/Ghidra/Debug/Debugger/certification.manifest +++ b/Ghidra/Debug/Debugger/certification.manifest @@ -56,6 +56,7 @@ src/main/help/help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugi src/main/help/help/topics/DebuggerStaticMappingPlugin/images/DebuggerStaticMappingPlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerThreadsPlugin/images/DebuggerThreadsPlugin.png||GHIDRA||||END| +src/main/help/help/topics/DebuggerTimeOverviewPlugin/DebuggerTimeOverviewPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerTimePlugin/images/DebuggerTimePlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html||GHIDRA||||END| diff --git a/Ghidra/Debug/Debugger/data/debugger.theme.properties b/Ghidra/Debug/Debugger/data/debugger.theme.properties index 5a783a3be9..7078465228 100644 --- a/Ghidra/Debug/Debugger/data/debugger.theme.properties +++ b/Ghidra/Debug/Debugger/data/debugger.theme.properties @@ -20,6 +20,35 @@ color.debugger.plugin.memview.box.type.read.memory = color.palette.darkgray color.debugger.plugin.memview.box.type.write.memory = color.palette.blue color.debugger.plugin.memview.box.type.breakpoint = color.palette.red +color.debugger.plugin.timeoverview.box = color.palette.blue +color.debugger.plugin.timeoverview.box.type.instructions = color.palette.darkred +color.debugger.plugin.timeoverview.box.type.process = color.palette.lightcornflowerblue +color.debugger.plugin.timeoverview.box.type.thread.added = color.palette.lightskyblue +color.debugger.plugin.timeoverview.box.type.thread.removed = color.palette.lightskyblue +color.debugger.plugin.timeoverview.box.type.thread.changed = color.palette.lightskyblue +color.debugger.plugin.timeoverview.box.type.module.added = color.palette.lime +color.debugger.plugin.timeoverview.box.type.module.removed = color.palette.lime +color.debugger.plugin.timeoverview.box.type.module.changed = color.palette.lime +color.debugger.plugin.timeoverview.box.type.region.added = color.palette.yellow +color.debugger.plugin.timeoverview.box.type.region.removed = color.palette.yellow +color.debugger.plugin.timeoverview.box.type.region.changed = color.palette.yellow +color.debugger.plugin.timeoverview.box.type.image = color.palette.magenta +color.debugger.plugin.timeoverview.box.type.virtual.alloc = color.palette.lightgray +color.debugger.plugin.timeoverview.box.type.heap.create = color.palette.blue +color.debugger.plugin.timeoverview.box.type.heap.alloc = color.palette.darkgreen +color.debugger.plugin.timeoverview.box.type.pool = color.palette.indigo +color.debugger.plugin.timeoverview.box.type.stack = color.palette.cyan +color.debugger.plugin.timeoverview.box.type.perfinfo = color.palette.lightgray +color.debugger.plugin.timeoverview.box.type.read.memory = color.palette.darkgray +color.debugger.plugin.timeoverview.box.type.write.memory = color.palette.blue +color.debugger.plugin.timeoverview.box.type.breakpoint.added = color.palette.red +color.debugger.plugin.timeoverview.box.type.breakpoint.removed = color.palette.red +color.debugger.plugin.timeoverview.box.type.breakpoint.changed = color.palette.red +color.debugger.plugin.timeoverview.box.type.snap.added = color.palette.lightgray +color.debugger.plugin.timeoverview.box.type.snap.removed = color.palette.lightgray +color.debugger.plugin.timeoverview.box.type.snap.changed = color.palette.lightgray +color.debugger.plugin.timeoverview.box.type.undefined = color.palette.black + color.bg.debugger.plugin.objects.default = color.bg color.fg.debugger.plugin.objects.default = color.fg color.fg.debugger.plugin.objects.invisible = color.palette.lightgray diff --git a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml index 749040e985..5d8cfa8ec9 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml @@ -106,6 +106,10 @@ sortgroup="u" target="help/topics/DebuggerMemviewPlugin/DebuggerMemviewPlugin.html" /> + + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimeOverviewPlugin/DebuggerTimeOverviewPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimeOverviewPlugin/DebuggerTimeOverviewPlugin.html new file mode 100644 index 0000000000..1c88190bad --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimeOverviewPlugin/DebuggerTimeOverviewPlugin.html @@ -0,0 +1,44 @@ + + + + + + + Debugger: Time Overview + + + + + +

Debugger: Time Overview

+ +

Patterned on the Program Overview, this plugin provides a pair of sidebars for the + Dynamic Listing that indicate the history of the current trace, similar to those in the + Time + and Memview plugins. + The Trace Overview bar gives a compressed view of various events + (thread creation/destruction, module loads/unloads, et cetera). Events are added in time + snap order without spaces between consecutive events, as in the Memview display. The Trace + Selection sidebar allows the user to click & drag over a section of either sidebar and zooms + in on that time span. In the zoomed version, events are in snap order with intervening space + to indicate the actual time delays (although these are dictated, in part, by the numbering + schema for events). +

+ + +

Navigation

+ +

Zoom

+ +

Clicking and dragging over a region of the Trace Overview causes that + span to be displayed uncompressed in the Trace Selection. The same action can also + be applied to the Trace Selection itself, resulting in a more detailed zoom.

+ +

Move

+ +

Shift click & drag on the Trace Selection allows the user to move forward and + backward in the view without rescaling. +

+ + diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorComponent.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorComponent.java new file mode 100644 index 0000000000..a07d3841c6 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorComponent.java @@ -0,0 +1,251 @@ +/* ### + * 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.timeoverview; + +import java.awt.*; +import java.awt.event.*; +import java.util.List; +import java.util.TreeSet; + +import javax.swing.*; + +import docking.action.DockingActionIf; +import ghidra.app.nav.Navigatable; +import ghidra.app.util.viewer.listingpanel.OverviewProvider; +import ghidra.app.util.viewer.util.AddressIndexMap; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; +import ghidra.trace.model.Lifespan; +import ghidra.util.task.SwingUpdateManager; + +/** + * Overview bar component. Uses color to indicate various snap-based properties for a program. + * Uses an {@link TimeOverviewColorService} to get the appropriate color for a snaps. + */ +public class TimeOverviewColorComponent extends JPanel implements OverviewProvider { + private static final Color DEFAULT_COLOR = Color.GRAY; + protected TimeOverviewColorService service; + private Color[] colorsByPixel = new Color[0]; + private final SwingUpdateManager refreshUpdater = + new SwingUpdateManager(100, 15000, () -> doRefresh()); + + private PluginTool tool; + private List actions; + private TimeOverviewColorPlugin plugin; + + /** + * Constructor + * + * @param tool the PluginTool + * @param overviewColorService the {@link TimeOverviewColorService} that provides colors for + * various snaps. + */ + public TimeOverviewColorComponent(PluginTool tool, + TimeOverviewColorService overviewColorService) { + this.tool = tool; + this.service = overviewColorService; + overviewColorService.setOverviewComponent(this); + addMouseListener(new MouseAdapter() { + private int pressedY; + private boolean enableDrag = false; + + @Override + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + Long snap = service.getSnap(e.getY()); + gotoSnap(snap); + } + } + + @Override + public void mousePressed(MouseEvent e) { + enableDrag = true; + pressedY = e.getY(); + } + + @Override + public void mouseReleased(MouseEvent e) { + if (enableDrag) { + Long start = service.getSnap(pressedY); + Long stop = service.getSnap(e.getY()); + if (start == null || stop == null) { + return; + } + Lifespan span; + if ((e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0) { + Lifespan prev = getLifespan(); + if (prev != null) { + int shift = stop.intValue() - start.intValue(); + span = Lifespan.span(prev.lmin() - shift, prev.lmax() - shift); + } + else { + span = prev; + } + } + else { + if (start > stop) { + Long tmp = stop; + stop = start; + start = tmp; + } + span = Lifespan.span(start, stop); + } + plugin.setLifespan(span); + enableDrag = false; + } + } + + }); + ToolTipManager.sharedInstance().registerComponent(this); + actions = service.getActions(); + } + + /** + * Installs actions for this component + */ + public void installActions() { + if (actions == null) { + return; + } + for (DockingActionIf action : actions) { + tool.addAction(action); + } + } + + /** + * Removes previous installed actions for this component. + */ + public void uninstallActions() { + if (actions == null) { + return; + } + for (DockingActionIf action : actions) { + tool.removeAction(action); + } + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(16, 1); + } + + protected void gotoSnap(Long snap) { + plugin.gotoSnap(snap); + } + + @Override + public String getToolTipText(MouseEvent e) { + Long snap = service.getSnap(e.getY()); + return service.getToolTipText(snap); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + int width = getWidth(); + int pixelCount = getOverviewPixelCount(); + + g.setColor(getBackground()); + g.fillRect(0, 0, width - 1, getHeight() - 1); + + if (service.getTrace() == null) { + return; + } + + for (int i = 0; i < pixelCount; i++) { + Color color = getColor(i); + g.setColor(color); + g.fillRect(1, i, width - 3, 1); + } + if (colorsByPixel.length != pixelCount) { + colorsByPixel = new Color[pixelCount]; + refreshUpdater.updateLater(); + } + } + + private Color getColor(int index) { + if (colorsByPixel != null && index < colorsByPixel.length) { + return colorsByPixel[index]; + } + return DEFAULT_COLOR; + } + + public int getOverviewPixelCount() { + return Math.max(getHeight(), 0); + } + + private void doRefresh() { + for (int i = 0; i < colorsByPixel.length; i++) { + if (colorsByPixel[i] == null) { + Long snap = service.getSnap(i); + colorsByPixel[i] = service.getColor(snap); + } + } + repaint(); + } + + @Override + public JComponent getComponent() { + return this; + } + + public void setLifeSet(TreeSet set) { + service.setIndices(set); + colorsByPixel = new Color[getOverviewPixelCount()]; + refreshUpdater.updateLater(); + } + + /** + * Causes this component to completely compute the colors used to paint the overview bar. + */ + public void refreshAll() { + colorsByPixel = new Color[getOverviewPixelCount()]; + refreshUpdater.updateLater(); + } + + /** + * Returns the PluginTool + * + * @return the PluginTool + */ + public PluginTool getTool() { + return tool; + } + + public void setPlugin(TimeOverviewColorPlugin plugin) { + this.plugin = plugin; + service.setPlugin(plugin); + } + + public Lifespan getLifespan() { + return service.getBounds(); + } + + public void setLifespan(Lifespan bounds) { + service.setBounds(bounds); + } + + @Override + public void setProgram(Program program, AddressIndexMap map) { + // Ignored + } + + @Override + public void setNavigatable(Navigatable navigatable) { + // Ignored + } + +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorPlugin.java new file mode 100644 index 0000000000..6ff5707e75 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorPlugin.java @@ -0,0 +1,374 @@ +/* ### + * 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.timeoverview; + +import java.util.*; +import java.util.stream.Collectors; + +import javax.swing.SwingUtilities; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import docking.ActionContext; +import docking.action.*; +import docking.menu.MultiActionDockingAction; +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.*; +import ghidra.app.plugin.core.debug.gui.timeoverview.timetype.TimeType; +import ghidra.app.services.DebuggerListingService; +import ghidra.app.services.DebuggerTraceManagerService; +import ghidra.debug.api.tracemgr.DebuggerCoordinates; +import ghidra.framework.options.SaveState; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.annotation.AutoServiceConsumed; +import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.trace.model.Lifespan; +import ghidra.trace.model.Trace; +import ghidra.trace.model.bookmark.TraceBookmark; +import ghidra.trace.model.bookmark.TraceBookmarkManager; +import ghidra.trace.model.breakpoint.*; +import ghidra.trace.model.memory.*; +import ghidra.trace.model.modules.*; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.thread.*; +import ghidra.util.HelpLocation; +import ghidra.util.Msg; +import ghidra.util.classfinder.ClassSearcher; +import resources.ResourceManager; + +/** + * Plugin to manage {@link TimeOverviewColorService}s. It creates actions for each service and + * installs and removes {@link TimeOverviewColorComponent} as indicated by the action. + */ + +@PluginInfo( + status = PluginStatus.UNSTABLE, + packageName = DebuggerPluginPackage.NAME, + category = PluginCategoryNames.DEBUGGER, + shortDescription = "Time Overview Color Manager", + description = "Provides various color mappings for the trace snap space.", + eventsConsumed = { + TraceOpenedPluginEvent.class, // + TraceClosedPluginEvent.class, // + TraceActivatedPluginEvent.class, // + }, // + servicesRequired = { // + DebuggerTraceManagerService.class, // + } // +) + +public class TimeOverviewColorPlugin extends AbstractDebuggerPlugin { + + @AutoServiceConsumed + private DebuggerTraceManagerService traceManager; + @AutoServiceConsumed + private DebuggerListingService listingService; + @SuppressWarnings("unused") + private final AutoService.Wiring autoServiceWiring; + + + public static final String HELP_TOPIC = "OverviewPlugin"; + private static final String ACTIVE_SERVICES = "ActiveServices"; + private List allServices; + private Map activeServices = + new LinkedHashMap<>(); // maintain the left to right order of the active overview bars. + private Map actionMap = new HashMap<>(); + private MultiActionDockingAction multiAction; + + private Trace currentTrace; + private final TimeOverviewEventListener eventListener = new TimeOverviewEventListener(this); + private Map> sets = new WeakHashMap<>(); + private Map>> types = new HashMap<>(); + private long LMAX = Lifespan.ALL.lmax(); + + public TimeOverviewColorPlugin(PluginTool tool) { + super(tool); + autoServiceWiring = AutoService.wireServicesConsumed(tool, this); + } + + @Override + protected void init() { + super.init(); + allServices = ClassSearcher.getInstances(TimeOverviewColorService.class); + createActions(); + for (TimeOverviewColorService service : allServices) { + service.initialize(tool); + } + } + + @Override + public void readConfigState(SaveState saveState) { + String[] activeServiceNames = saveState.getStrings(ACTIVE_SERVICES, new String[0]); + for (String serviceName : activeServiceNames) { + TimeOverviewColorService service = getService(serviceName); + if (service == null) { + Msg.warn(this, "Can't restore TimeOverviewColorService: " + serviceName); + continue; + } + OverviewToggleAction action = actionMap.get(service); + action.setSelected(true); + // do this later so that they show up to the left of the standard marker service overview. + SwingUtilities.invokeLater(() -> installOverview(service)); + } + } + + private TimeOverviewColorService getService(String serviceName) { + for (TimeOverviewColorService service : allServices) { + if (service.getName().equals(serviceName)) { + return service; + } + } + return null; + } + + @Override + protected void cleanup() { + + List services = new ArrayList<>(activeServices.keySet()); + for (TimeOverviewColorService service : services) { + uninstallOverview(service); + } + listingService.removeLocalAction(multiAction); + } + + @Override + public void writeConfigState(SaveState saveState) { + saveState.putStrings(ACTIVE_SERVICES, getActiveServiceNames()); + } + + private String[] getActiveServiceNames() { + List names = + activeServices.keySet().stream().map(s -> s.getName()).collect(Collectors.toList()); + + return names.toArray(new String[names.size()]); + } + + private void createActions() { + for (TimeOverviewColorService overviewColorService : allServices) { + actionMap.put(overviewColorService, + new OverviewToggleAction(getName(), overviewColorService)); + } + multiAction = new MultiActionDockingAction("TimeOverview", getName()); + //multiAction.setPerformActionOnButtonClick(false); + multiAction.setActions(new ArrayList(actionMap.values())); + multiAction.setToolBarData( + new ToolBarData(ResourceManager.loadImage("images/x-office-document-template.png"))); + listingService.addLocalAction(multiAction); + multiAction.setDescription("Toggles trace overview margin displays."); + multiAction.setHelpLocation( + new HelpLocation(TimeOverviewColorPlugin.HELP_TOPIC, + TimeOverviewColorPlugin.HELP_TOPIC)); + + } + + private class OverviewToggleAction extends ToggleDockingAction { + + private TimeOverviewColorService service; + + public OverviewToggleAction(String owner, TimeOverviewColorService service) { + super(service.getName(), owner); + this.service = service; + setMenuBarData(new MenuData(new String[] { "Show " + service.getName() })); + setHelpLocation(service.getHelpLocation()); + } + + @Override + public void actionPerformed(ActionContext context) { + if (isSelected()) { + installOverview(service); + } + else { + uninstallOverview(service); + } + } + + } + + /** + * Installs the given {@link TimeOverviewColorService} into the Listing margin bars. This is + * public only for testing and screenshot purposes. + * + * @param overviewColorService the service to display colors in the Listing's margin bars. + */ + public void installOverview(TimeOverviewColorService overviewColorService) { + overviewColorService.setTrace(currentTrace); + TimeOverviewColorComponent overview = + new TimeOverviewColorComponent(tool, overviewColorService); + activeServices.put(overviewColorService, overview); + listingService.addOverviewProvider(overview); + overview.installActions(); + overview.setPlugin(this); + } + + private void uninstallOverview(TimeOverviewColorService overviewColorService) { + TimeOverviewColorComponent overviewComponent = activeServices.get(overviewColorService); + overviewComponent.uninstallActions(); + listingService.removeOverviewProvider(overviewComponent); + activeServices.remove(overviewColorService); + overviewColorService.setTrace(null); + } + + protected void traceActivated(Trace trace) { + if (trace != null && trace != currentTrace) { + if (currentTrace != null) { + currentTrace.removeListener(eventListener); + } + currentTrace = trace; + currentTrace.addListener(eventListener); + for (TimeOverviewColorService service : activeServices.keySet()) { + service.setTrace(trace); + } + updateMap(); + } + } + + protected void traceDeactivated(Trace trace) { + if (trace == currentTrace) { + currentTrace.removeListener(eventListener); + currentTrace = null; + for (TimeOverviewColorService service : activeServices.keySet()) { + service.setTrace(null); + } + } + } + + @Override + public void processEvent(PluginEvent event) { + super.processEvent(event); + if (event instanceof TraceActivatedPluginEvent ev) { + DebuggerCoordinates coordinates = ev.getActiveCoordinates(); + traceActivated(coordinates.getTrace()); + eventListener.coordinatesActivated(coordinates); + } + else if (event instanceof TraceClosedPluginEvent ev) { + Trace trace = ev.getTrace(); + if (trace == currentTrace) { + sets.remove(trace); + traceDeactivated(trace); + } + } + } + + void updateMap() { + TreeSet set = new TreeSet<>(); + Trace trace = traceManager.getCurrentTrace(); + TraceThreadManager threadManager = trace.getThreadManager(); + for (TraceThread thread : threadManager.getAllThreads()) { + if (thread instanceof TraceObjectThread objThread) { + addObject(set, objThread.getObject()); + } + } + TraceModuleManager moduleManager = trace.getModuleManager(); + for (TraceModule module : moduleManager.getAllModules()) { + if (module instanceof TraceObjectModule objModule) { + addObject(set, objModule.getObject()); + } + } + TraceMemoryManager memoryManager = trace.getMemoryManager(); + for (TraceMemoryRegion region : memoryManager.getAllRegions()) { + if (region instanceof TraceObjectMemoryRegion objRegion) { + addObject(set, objRegion.getObject()); + } + } + TraceBreakpointManager breakpointManager = trace.getBreakpointManager(); + for (TraceBreakpoint bpt : breakpointManager.getAllBreakpoints()) { + if (bpt instanceof TraceObjectBreakpointLocation objBreakpoint) { + addObject(set, objBreakpoint.getObject()); + } + } + TraceBookmarkManager bookmarkManager = trace.getBookmarkManager(); + for (TraceBookmark mark : bookmarkManager.getAllBookmarks()) { + Lifespan span = mark.getLifespan(); + set.add(span.min()); + if (span.lmax() == LMAX) { + set.add(span.max()); + } + } + for (TimeOverviewColorComponent provider : activeServices.values()) { + provider.setLifeSet(set); + } + } + + private void addObject(TreeSet set, TraceObject obj) { + for (Lifespan span : obj.getLife().spans()) { + set.add(span.min()); + if (span.lmax() == LMAX) { + set.add(span.max()); + } + } + } + + void updateMap(long offset, TimeType type, String desc, boolean override) { + TreeSet set = getLifeSet(); + if (!override && set.contains(offset)) { + return; + } + if (offset == LMAX) { + return; + } + set.add(offset); + setLifespanType(offset, type, desc); + for (TimeOverviewColorComponent provider : activeServices.values()) { + provider.setLifeSet(set); + } + } + + TreeSet getLifeSet() { + TreeSet set = sets.get(currentTrace); + if (set == null) { + set = new TreeSet<>(); + sets.put(currentTrace, set); + } + return set; + } + + /** + * Determines the {@link TimeType} for the given offset + * + * @param offset the offset for which to get an LifespanType. + * @return the {@link TimeType} for the given offset. + */ + public Set> getTypes(Long offset) { + Set> set = types.get(offset); + if (set == null) { + set = new HashSet<>(); + } + return set; + } + + void setLifespanType(Long offset, TimeType type, String desc) { + Set> set = getTypes(offset); + set.add(new ImmutablePair(type, desc)); + types.put(offset, set); + } + + public void gotoSnap(Long offset) { + if (offset == null) { + offset = 0L; + } + traceManager.activateSnap(offset); + } + + public void setLifespan(Lifespan span) { + for (TimeOverviewColorComponent provider : activeServices.values()) { + provider.setLifespan(span); + } + } + +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorService.java new file mode 100644 index 0000000000..f310bfd6e2 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorService.java @@ -0,0 +1,139 @@ +/* ### + * 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.timeoverview; + +import java.awt.Color; +import java.util.*; + +import docking.action.DockingActionIf; +import ghidra.framework.plugintool.PluginTool; +import ghidra.trace.model.Lifespan; +import ghidra.trace.model.Trace; +import ghidra.util.HelpLocation; +import ghidra.util.classfinder.ExtensionPoint; + +/** + * Interface for services that know how to associate colors with any snap in a program. Instances + * of these services are discovered and presented as options on the Listing's right margin area. + */ +public interface TimeOverviewColorService extends ExtensionPoint { + + /** + * Returns the name of this color service. + * + * @return the name of this color service. + */ + public String getName(); + + /** + * Returns the color that this service associates with the given snap. + * + * @param snap the snap to convert to a color. + * @return the color that this service associates with the given snap. + */ + public Color getColor(Long snap); + + /** + * Sets the trace that this service will provide snap colors for. + * + * @param trace the program that this service will provide snap colors for. + */ + public void setTrace(Trace trace); + + /** + * Sets the component that will be displaying the colors for this + * service. + * + * @param component the {@link TimeOverviewColorComponent} that will be displaying the colors + * for this service. + */ + public void setOverviewComponent(TimeOverviewColorComponent component); + + /** + * Returns the tool tip that the {@link TimeOverviewColorComponent} should display when the + * mouse is hovering on the pixel that maps to the given snap. + * + * @param snap the snap for which to get a tooltip. + * @return the tooltip text for the given snap. + */ + public String getToolTipText(Long snap); + + /** + * Returns a list of popup actions to be shown when the user right-clicks on the + * {@link TimeOverviewColorComponent} associated with this service. + * + * @return the list of popup actions. + */ + public List getActions(); + + /** + * Returns the {@link HelpLocation} for this service + * + * @return the {@link HelpLocation} for this service + */ + public HelpLocation getHelpLocation(); + + /** + * Initialize the service which typically is used to read options for the service. + * + * @param tool the {@link PluginTool} using this service. + */ + public void initialize(PluginTool tool); + + /** + * Returns the current trace used by the service. + * + * @return the current trace used by the service. + */ + public Trace getTrace(); + + /** + * Set the plugin + * + * @param plugin overview plugin + */ + public void setPlugin(TimeOverviewColorPlugin plugin); + + /** + * Get the snap for a given pixel's time coordinate + * + * @param pixel location in the display + * @return snap + */ + public Long getSnap(int pixel); + + /** + * Set the indices for mapping pixels->indices->snaps (and vice-versa) + * + * @param set tree-set of snaps + */ + public void setIndices(TreeSet set); + + /** + * Get the display bounds + * + * @return bounds time-range to display + */ + public Lifespan getBounds(); + + /** + * Set the display bounds + * + * @param bounds time-range to display + */ + public void setBounds(Lifespan bounds); + +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewEventListener.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewEventListener.java new file mode 100644 index 0000000000..9a7b278f0f --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewEventListener.java @@ -0,0 +1,329 @@ +/* ### + * 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.timeoverview; + +import java.util.Objects; + +import ghidra.app.plugin.core.debug.gui.timeoverview.timetype.TimeType; +import ghidra.debug.api.tracemgr.DebuggerCoordinates; +import ghidra.framework.model.DomainObjectChangeRecord; +import ghidra.framework.model.DomainObjectEvent; +import ghidra.trace.model.*; +import ghidra.trace.model.bookmark.TraceBookmark; +import ghidra.trace.model.breakpoint.*; +import ghidra.trace.model.memory.*; +import ghidra.trace.model.modules.*; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.thread.*; +import ghidra.trace.util.TraceEvents; +import ghidra.util.Swing; + +public class TimeOverviewEventListener extends TraceDomainObjectListener { + + private TimeOverviewColorPlugin p; + private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; + + public TimeOverviewEventListener(TimeOverviewColorPlugin plugin) { + + this.p = plugin; + + listenForUntyped(DomainObjectEvent.RESTORED, this::objectRestored); + + listenFor(TraceEvents.THREAD_ADDED, this::threadAdded); + listenFor(TraceEvents.THREAD_CHANGED, this::threadChanged); + listenFor(TraceEvents.THREAD_LIFESPAN_CHANGED, this::threadChanged); + listenFor(TraceEvents.THREAD_DELETED, this::threadDeleted); + + listenFor(TraceEvents.MODULE_ADDED, this::moduleAdded); + listenFor(TraceEvents.MODULE_CHANGED, this::moduleChanged); + listenFor(TraceEvents.MODULE_LIFESPAN_CHANGED, this::moduleChanged); + listenFor(TraceEvents.MODULE_DELETED, this::moduleDeleted); + + listenFor(TraceEvents.REGION_ADDED, this::regionAdded); + listenFor(TraceEvents.REGION_CHANGED, this::regionChanged); + listenFor(TraceEvents.REGION_LIFESPAN_CHANGED, this::regionChanged); + listenFor(TraceEvents.REGION_DELETED, this::regionDeleted); + + listenFor(TraceEvents.BREAKPOINT_ADDED, this::bptAdded); + listenFor(TraceEvents.BREAKPOINT_CHANGED, this::bptChanged); + listenFor(TraceEvents.BREAKPOINT_LIFESPAN_CHANGED, this::bptChanged); + listenFor(TraceEvents.BREAKPOINT_DELETED, this::bptDeleted); + + listenFor(TraceEvents.BOOKMARK_ADDED, this::bookmarkAdded); + listenFor(TraceEvents.BOOKMARK_CHANGED, this::bookmarkChanged); + listenFor(TraceEvents.BOOKMARK_LIFESPAN_CHANGED, this::bookmarkChanged); + listenFor(TraceEvents.BOOKMARK_DELETED, this::bookmarkDeleted); + + } + + public void coordinatesActivated(DebuggerCoordinates coordinates) { + //DebuggerCoordinates adjusted = adjustCoordinates(coordinates); + setCoordinates(coordinates); + Trace trace = coordinates.getTrace(); + if (trace != null) { + Swing.runLater(() -> processTrace(trace)); + } + } + + protected void addListener() { + Trace trace = current.getTrace(); + if (trace != null) { + trace.addListener(this); + } + } + + protected void removeListener() { + Trace trace = current.getTrace(); + if (trace != null) { + trace.removeListener(this); + } + } + + public void setCoordinates(DebuggerCoordinates coordinates) { + boolean doListeners = !Objects.equals(current.getTrace(), coordinates.getTrace()); + if (doListeners) { + removeListener(); + } + current = coordinates; + if (doListeners) { + addListener(); + } + } + + private void processTrace(Trace trace) { + //updateList.clear(); + TraceThreadManager threadManager = trace.getThreadManager(); + for (TraceThread thread : threadManager.getAllThreads()) { + threadChanged(thread); + } + TraceModuleManager moduleManager = trace.getModuleManager(); + for (TraceModule module : moduleManager.getAllModules()) { + moduleChanged(module); + } + TraceMemoryManager memoryManager = trace.getMemoryManager(); + for (TraceMemoryRegion region : memoryManager.getAllRegions()) { + regionChanged(region); + } + TraceBreakpointManager breakpointManager = trace.getBreakpointManager(); + for (TraceBreakpoint bpt : breakpointManager.getAllBreakpoints()) { + bptChanged(bpt); + } + } + + private void threadAdded(TraceThread thread) { + if (!(thread instanceof TraceObjectThread objThread)) { + return; + } + + TraceObject obj = objThread.getObject(); + obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true) + .forEach(v -> { + long snap = v.getMinSnap(); + p.updateMap(snap, TimeType.BPT_ADDED, thread.getName(snap), true); + }); + } + + private void threadChanged(TraceThread thread) { + if (!(thread instanceof TraceObjectThread objThread)) { + return; + } + + TraceObject obj = objThread.getObject(); + obj.getOrderedValues(Lifespan.ALL, TraceObjectThread.KEY_TID, true) + .forEach(v -> { + long snapMin = v.getMinSnap(); + long snapMax = v.getMaxSnap(); + if (snapMin == snapMax) { + p.updateMap(snapMin, TimeType.THREAD_CHANGED, thread.getName(snapMin), true); + } + else { + p.updateMap(snapMin, TimeType.THREAD_ADDED, thread.getName(snapMin), true); + p.updateMap(snapMax, TimeType.THREAD_REMOVED, thread.getName(snapMax), true); + } + }); + } + + private void threadDeleted(TraceThread thread) { + if (!(thread instanceof TraceObjectThread objThread)) { + return; + } + + TraceObject obj = objThread.getObject(); + obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true) + .forEach(v -> { + long snap = v.getMaxSnap(); + p.updateMap(snap, TimeType.THREAD_REMOVED, thread.getName(snap), true); + }); + } + + private void moduleAdded(TraceModule module) { + if (!(module instanceof TraceObjectModule objMod)) { + return; + } + + TraceObject obj = objMod.getObject(); + obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true) + .forEach(v -> { + long snap = v.getMinSnap(); + p.updateMap(snap, TimeType.MODULE_ADDED, module.getName(snap), true); + }); + } + + private void moduleChanged(TraceModule module) { + if (!(module instanceof TraceObjectModule objMod)) { + return; + } + + TraceObject obj = objMod.getObject(); + obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true) + .forEach(v -> { + long snapMin = v.getMinSnap(); + long snapMax = v.getMaxSnap(); + if (snapMin == snapMax) { + p.updateMap(snapMin, TimeType.MODULE_CHANGED, module.getName(snapMin), true); + } + else { + p.updateMap(snapMin, TimeType.MODULE_ADDED, module.getName(snapMin), true); + p.updateMap(snapMax, TimeType.MODULE_REMOVED, module.getName(snapMax), true); + } + }); + } + + private void moduleDeleted(TraceModule module) { + if (!(module instanceof TraceObjectModule objMod)) { + return; + } + + TraceObject obj = objMod.getObject(); + obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true) + .forEach(v -> { + long snap = v.getMaxSnap(); + p.updateMap(snap, TimeType.MODULE_REMOVED, module.getName(snap), true); + }); + } + + private void regionAdded(TraceMemoryRegion region) { + if (!(region instanceof TraceObjectMemoryRegion objReg)) { + return; + } + + TraceObject obj = objReg.getObject(); + obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true) + .forEach(v -> { + long snap = v.getMinSnap(); + p.updateMap(snap, TimeType.REGION_ADDED, region.getName(snap), true); + }); + } + + private void regionChanged(TraceMemoryRegion region) { + if (!(region instanceof TraceObjectMemoryRegion objReg)) { + return; + } + + TraceObject obj = objReg.getObject(); + obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true) + .forEach(v -> { + long snapMin = v.getMinSnap(); + long snapMax = v.getMaxSnap(); + if (snapMin == snapMax) { + p.updateMap(snapMin, TimeType.REGION_CHANGED, region.getName(snapMin), true); + } + else { + p.updateMap(snapMin, TimeType.REGION_ADDED, region.getName(snapMin), true); + p.updateMap(snapMax, TimeType.REGION_REMOVED, region.getName(snapMax), true); + } + }); + } + + private void regionDeleted(TraceMemoryRegion region) { + if (!(region instanceof TraceObjectMemoryRegion objReg)) { + return; + } + + TraceObject obj = objReg.getObject(); + obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true) + .forEach(v -> { + long snap = v.getMaxSnap(); + p.updateMap(snap, TimeType.REGION_REMOVED, region.getName(snap), true); + }); + } + + private void bptAdded(TraceBreakpoint bpt) { + if (!(bpt instanceof TraceObjectBreakpointLocation objBpt)) { + return; + } + + TraceObject obj = objBpt.getObject(); + obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true) + .forEach(v -> { + long snap = v.getMinSnap(); + p.updateMap(snap, TimeType.BPT_ADDED, bpt.getName(snap), true); + }); + } + + private void bptChanged(TraceBreakpoint bpt) { + if (!(bpt instanceof TraceObjectBreakpointLocation objBpt)) { + return; + } + + TraceObject obj = objBpt.getObject(); + obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true) + .forEach(v -> { + long snapMin = v.getMinSnap(); + long snapMax = v.getMaxSnap(); + if (snapMin == snapMax) { + p.updateMap(snapMin, TimeType.BPT_CHANGED, bpt.getName(snapMin), true); + } + else { + p.updateMap(snapMin, TimeType.BPT_ADDED, bpt.getName(snapMin), true); + p.updateMap(snapMax, TimeType.BPT_REMOVED, bpt.getName(snapMax), true); + } + }); + } + + private void bptDeleted(TraceBreakpoint bpt) { + if (!(bpt instanceof TraceObjectBreakpointLocation objBpt)) { + return; + } + + TraceObject obj = objBpt.getObject(); + obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true) + .forEach(v -> { + long snap = v.getMaxSnap(); + p.updateMap(snap, TimeType.BPT_REMOVED, bpt.getName(snap), true); + }); + } + + private void bookmarkAdded(TraceBookmark bookmark) { + long snap = bookmark.getLifespan().lmin(); + p.updateMap(snap, TimeType.BOOKMARK_ADDED, bookmark.getComment(), true); + } + + private void bookmarkChanged(TraceBookmark bookmark) { + long snapMin = bookmark.getLifespan().lmin(); + p.updateMap(snapMin, TimeType.BOOKMARK_CHANGED, bookmark.getComment(), false); + } + + private void bookmarkDeleted(TraceBookmark bookmark) { + long snap = bookmark.getLifespan().lmax(); + p.updateMap(snap, TimeType.BOOKMARK_REMOVED, bookmark.getComment(), true); + } + + private void objectRestored(DomainObjectChangeRecord domainobjectchangerecord1) { + p.updateMap(); + } + +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeSelectionOverviewColorService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeSelectionOverviewColorService.java new file mode 100644 index 0000000000..c473e20c25 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeSelectionOverviewColorService.java @@ -0,0 +1,60 @@ +/* ### + * 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.timeoverview.timetype; + +import java.util.*; + +import ghidra.trace.model.Lifespan; + +public class TimeSelectionOverviewColorService + extends TimeTypeOverviewColorService { + + @Override + public String getName() { + return "Trace Selection"; + } + + + @Override + public void setIndices(TreeSet set) { + snapToIndex = new HashMap<>(); + indexToSnap = new HashMap<>(); + if (bounds != null) { + set.add(bounds.min()); + int splits = overviewComponent.getOverviewPixelCount(); + float span = (float)(bounds.lmax() - bounds.lmin())/splits; + for (int i = 0; i < splits; i++) { + long snap = (long)(bounds.lmin() + i*span); + snapToIndex.put(snap, i); + indexToSnap.put(i, snap); + } + } + } + + @Override + public Lifespan getBounds() { + return bounds; + } + + @Override + public void setBounds(Lifespan bounds) { + this.bounds = bounds; + TreeSet minset = new TreeSet<>(); + minset.add(bounds.min()); + overviewComponent.setLifeSet(minset); + } + +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeType.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeType.java new file mode 100644 index 0000000000..87a61b8832 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeType.java @@ -0,0 +1,71 @@ +/* ### + * 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.timeoverview.timetype; + +import java.awt.Color; + +import generic.theme.GColor; + +/** + * An enum for the different types that are represented by unique colors by the + * {@link TimeTypeOverviewColorService} + */ +public enum TimeType { + THREAD_ADDED("+T", new GColor("color.debugger.plugin.timeoverview.box.type.thread.added")), + THREAD_REMOVED("-T", new GColor("color.debugger.plugin.timeoverview.box.type.thread.removed")), + THREAD_CHANGED("*T", new GColor("color.debugger.plugin.timeoverview.box.type.thread.changed")), + MODULE_ADDED("+M", new GColor("color.debugger.plugin.timeoverview.box.type.module.added")), + MODULE_REMOVED("-M", new GColor("color.debugger.plugin.timeoverview.box.type.module.removed")), + MODULE_CHANGED("*M", new GColor("color.debugger.plugin.timeoverview.box.type.module.changed")), + REGION_ADDED("+R", new GColor("color.debugger.plugin.timeoverview.box.type.region.added")), + REGION_REMOVED("-R", new GColor("color.debugger.plugin.timeoverview.box.type.region.removed")), + REGION_CHANGED("*R", new GColor("color.debugger.plugin.timeoverview.box.type.region.changed")), + BPT_ADDED("+B", new GColor("color.debugger.plugin.timeoverview.box.type.breakpoint.added")), + BPT_REMOVED("-B", new GColor("color.debugger.plugin.timeoverview.box.type.breakpoint.removed")), + BPT_CHANGED("*B", new GColor("color.debugger.plugin.timeoverview.box.type.breakpoint.changed")), + BPT_HIT(">B", new GColor("color.debugger.plugin.timeoverview.box.type.breakpoint.hit")), + BOOKMARK_ADDED("+MK", new GColor("color.debugger.plugin.timeoverview.box.type.bookmark.added")), + BOOKMARK_REMOVED("-MK", new GColor("color.debugger.plugin.timeoverview.box.type.bookmark.removed")), + BOOKMARK_CHANGED("*MK", new GColor("color.debugger.plugin.timeoverview.box.type.bookmark.changed")), + UNDEFINED("", new GColor("color.debugger.plugin.timeoverview.box.type.undefined")); + + final private String description; + final private Color color; + + TimeType(String description, Color color) { + this.description = description; + this.color = color; + } + + /** + * Returns a description of this enum value. + * + * @return a description of this enum value. + */ + public String getDescription() { + return description; + } + + /** + * Returns a color of this enum value. + * + * @return a color of this enum value. + */ + public Color getDefaultColor() { + return color; + } + +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeTypeOverviewColorService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeTypeOverviewColorService.java new file mode 100644 index 0000000000..7b83164e9e --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeTypeOverviewColorService.java @@ -0,0 +1,209 @@ +/* ### + * 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.timeoverview.timetype; + +import java.awt.Color; +import java.math.BigInteger; +import java.util.*; + +import org.apache.commons.lang3.tuple.Pair; + +import docking.DialogComponentProvider; +import docking.action.DockingActionIf; +import docking.action.builder.ActionBuilder; +import generic.theme.GThemeDefaults.Colors; +import ghidra.app.plugin.core.debug.gui.timeoverview.*; +import ghidra.app.plugin.core.overview.OverviewColorLegendDialog; +import ghidra.app.plugin.core.overview.OverviewColorPlugin; +import ghidra.framework.options.ToolOptions; +import ghidra.framework.plugintool.PluginTool; +import ghidra.trace.model.Lifespan; +import ghidra.trace.model.Trace; +import ghidra.util.*; + +public class TimeTypeOverviewColorService implements TimeOverviewColorService { + private static final String OPTIONS_NAME = "Time Overview"; + private static final Color DEFAULT_UNDEFINED_COLOR = Color.LIGHT_GRAY; + private static final Color DEFAULT_UNINITIALIZED_COLOR = Color.GRAY; + + Map colorMap = new HashMap<>(); + Color undefinedColor = DEFAULT_UNDEFINED_COLOR; + Color uninitializedColor = DEFAULT_UNINITIALIZED_COLOR; + + private Trace trace; + protected TimeOverviewColorComponent overviewComponent; + private PluginTool tool; + private DialogComponentProvider legendDialog; + private TimeTypeOverviewLegendPanel legendPanel; + private TimeOverviewColorPlugin plugin; + + protected Map indexToSnap = new HashMap<>(); + protected Map snapToIndex = new HashMap<>(); + protected Lifespan bounds; + + @Override + public String getName() { + return "Trace Overview"; + } + + @Override + public HelpLocation getHelpLocation() { + return new HelpLocation("DebuggerTimeOverviewPlugin", "plugin"); + } + + @Override + public Color getColor(Long snap) { + Set> types = plugin.getTypes(snap); + Color c = Colors.BACKGROUND; + for (Pair pair : types) { + c = ColorUtils.addColors(c, pair.getLeft().getDefaultColor()); + } + return c; + } + + @Override + public String getToolTipText(Long snap) { + // TODO: Right now, there's an inconsistency in how time is rendered + // the Time Table uses decimal; the Model Tree, Memview, and Overview + // use hex + if (snap == null) { + return ""; + } + Set> types = plugin.getTypes(snap); + StringBuffer buffer = new StringBuffer(); + buffer.append(""); + buffer.append(HTMLUtilities.escapeHTML(getName())); + buffer.append(" ("); + buffer.append(Long.toHexString(snap)); + buffer.append(")"); + buffer.append("\n"); + for (Pair pair : types) { + TimeType tt = pair.getLeft(); + String key = pair.getRight(); + buffer.append(tt.getDescription() + " : " + key + "\n"); + } + return HTMLUtilities.toWrappedHTML(buffer.toString(), 0); + } + + @Override + public List getActions() { + List actions = new ArrayList<>(); + actions.add(new ActionBuilder("Show Legend", getName()) + .popupMenuPath("Show Legend") + .description("Show types and associated colors") + .helpLocation(getHelpLocation()) + .enabledWhen(c -> c.getContextObject() == overviewComponent) + .onAction(c -> tool.showDialog(getLegendDialog())) + .build()); + + return actions; + } + + @Override + public void setTrace(Trace trace) { + this.trace = trace; + } + + @Override + public void initialize(PluginTool pluginTool) { + this.tool = pluginTool; + } + + @Override + public void setOverviewComponent(TimeOverviewColorComponent component) { + this.overviewComponent = component; + } + + /** + * Returns the color associated with the given {@link TimeType} + * + * @param timeType the span type for which to get a color. + * @return the color associated with the given {@link TimeType} + */ + public Color getColor(TimeType timeType) { + Color color = colorMap.get(timeType); + if (color == null) { + colorMap.put(timeType, timeType.getDefaultColor()); + } + return color; + } + + /** + * Sets the color to be associated with a given {@link TimeType} + * + * @param type the LifespanType for which to assign the color. + * @param newColor the new color for the given {@link TimeType} + */ + public void setColor(TimeType type, Color newColor) { + ToolOptions options = tool.getOptions(OPTIONS_NAME); + options.setColor(type.getDescription(), newColor); + } + + private DialogComponentProvider getLegendDialog() { + if (legendDialog == null) { + legendPanel = new TimeTypeOverviewLegendPanel(this); + + legendDialog = + new OverviewColorLegendDialog("Overview Legend", legendPanel, getHelpLocation()); + } + return legendDialog; + } + + @Override + public Trace getTrace() { + return trace; + } + + @Override + public void setPlugin(TimeOverviewColorPlugin plugin) { + this.plugin = plugin; + } + + @Override + public Long getSnap(int pixelIndex) { + BigInteger bigHeight = BigInteger.valueOf(overviewComponent.getOverviewPixelCount()); + BigInteger bigPixelIndex = BigInteger.valueOf(pixelIndex); + + BigInteger span = BigInteger.valueOf(indexToSnap.size()); + BigInteger offset = span.multiply(bigPixelIndex).divide(bigHeight); + return indexToSnap.get(offset.intValue()); + } + + @Override + public void setIndices(TreeSet set) { + snapToIndex = new HashMap<>(); + indexToSnap = new HashMap<>(); + int index = 0; + Iterator iterator = set.iterator(); + while (iterator.hasNext()) { + Long snap = iterator.next(); + snapToIndex.put(snap, index); + indexToSnap.put(index, snap); + index++; + } + } + + @Override + public Lifespan getBounds() { + return bounds; + } + + @Override + public void setBounds(Lifespan bounds) { + this.bounds = bounds; + } + +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeTypeOverviewLegendPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeTypeOverviewLegendPanel.java new file mode 100644 index 0000000000..27bbb50913 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeTypeOverviewLegendPanel.java @@ -0,0 +1,82 @@ +/* ### + * 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.timeoverview.timetype; + +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +import javax.swing.*; + +import docking.widgets.label.GLabel; +import ghidra.util.layout.PairLayout; + +/** + * A component for displaying the color legend for the {@link TimeTypeOverviewColorService} + */ +public class TimeTypeOverviewLegendPanel extends JPanel { + private static Dimension COLOR_SIZE = new Dimension(15, 15); + private TimeTypeOverviewColorService colorService; + + public TimeTypeOverviewLegendPanel(TimeTypeOverviewColorService colorService) { + this.colorService = colorService; + setLayout(new PairLayout(4, 10)); + setBorder(BorderFactory.createEmptyBorder(4, 20, 4, 30)); + buildLegend(); + } + + /** + * Kick to repaint when the colors have changed. + */ + public void updateColors() { + repaint(); + } + + private void buildLegend() { + removeAll(); + TimeType[] values = TimeType.values(); + for (TimeType timeType : values) { + JPanel panel = new ColorPanel(timeType); + add(panel); + add(new GLabel(timeType.getDescription())); + } + } + + private class ColorPanel extends JPanel { + private TimeType type; + + ColorPanel(TimeType type) { + this.type = type; + setPreferredSize(COLOR_SIZE); + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + Color newColor = + JColorChooser.showDialog(ColorPanel.this, "Select Color", getBackground()); + colorService.setColor(type, newColor); + } + }); + } + + @Override + protected void paintComponent(Graphics g) { + setBackground(colorService.getColor(type)); + super.paintComponent(g); + } + + } + +}