diff --git a/Ghidra/Debug/Debugger/data/ExtensionPoint.manifest b/Ghidra/Debug/Debugger/data/ExtensionPoint.manifest index bfe4c6157a..733b35db76 100644 --- a/Ghidra/Debug/Debugger/data/ExtensionPoint.manifest +++ b/Ghidra/Debug/Debugger/data/ExtensionPoint.manifest @@ -8,3 +8,4 @@ DebuggerRegisterColumnFactory DisassemblyInject EmulatorFactory LocationTrackingSpecFactory +TimeOverviewColorService diff --git a/Ghidra/Debug/Debugger/data/debugger.theme.properties b/Ghidra/Debug/Debugger/data/debugger.theme.properties index 6d938c8293..819376d34b 100644 --- a/Ghidra/Debug/Debugger/data/debugger.theme.properties +++ b/Ghidra/Debug/Debugger/data/debugger.theme.properties @@ -49,6 +49,14 @@ color.debugger.plugin.timeoverview.box.type.snap.removed = color.palette.lightgr color.debugger.plugin.timeoverview.box.type.snap.changed = color.palette.lightgray color.debugger.plugin.timeoverview.box.type.undefined = color.palette.black +color.debugger.plugin.breakpoint.timeline.grid = color.fg +color.debugger.plugin.breakpoint.timeline.selection = color.palette.lime +color.debugger.plugin.breakpoint.timeline.hover = color.palette.lime +color.debugger.plugin.breakpoint.timeline.current = color.palette.lime +color.debugger.plugin.breakpoint.timeline.type.read.memory = color.palette.lightcornflowerblue +color.debugger.plugin.breakpoint.timeline.type.write.memory = color.palette.lemonchiffon +color.debugger.plugin.breakpoint.timeline.type.instructions = color.palette.red + 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 @@ -238,6 +246,13 @@ icon.debugger.select.registers = select-registers.png icon.debugger.enable.edits = editbytes.gif icon.debugger.disassemble = editbytes.gif // TODO this icon was missing 'disassemble.png' - +icon.debugger.breakpoint.timeline.outline = tick.png +icon.debugger.breakpoint.timeline.no_outline = edit-delete.png +icon.debugger.breakpoint.timeline.grid = color_swatch.png +icon.debugger.breakpoint.timeline.single_column = StackFrame_Red.png +icon.debugger.breakpoint.timeline.zoom_in = zoom_in.png +icon.debugger.breakpoint.timeline.zoom_out = zoom_out.png +icon.debugger.breakpoint.timeline.zoom_out_max = zoom.png +icon.debugger.breakpoint.timeline.close_all_zoom_windows = delete.png [Dark Defaults] diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/timeline/BreakpointTimelinePanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/timeline/BreakpointTimelinePanel.java new file mode 100644 index 0000000000..672489271c --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/timeline/BreakpointTimelinePanel.java @@ -0,0 +1,473 @@ +/* ### + * 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.breakpoint.timeline; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import java.util.List; + +import javax.swing.JPanel; + +import generic.theme.GColor; +import generic.theme.GThemeDefaults.Colors; +import ghidra.app.plugin.core.debug.gui.breakpoint.timeline.BreakpointTimelineProvider.BreakpointHitEvent; +import ghidra.app.services.DebuggerTraceManagerService; +import ghidra.trace.model.breakpoint.TraceBreakpointKind; +import ghidra.util.ColorUtils; +import ghidra.util.Swing; + +class BreakpointTimelinePanel extends JPanel { + private static class CachedIndex { + private final long startSnap; + private final long stopSnap; + private final List breakpointEvents; + Rectangle rect; + long index; + + CachedIndex(long startSnap, long stopSnap, Rectangle rect, long index) { + this.startSnap = startSnap; + this.stopSnap = stopSnap; + this.rect = rect; + this.index = index; + breakpointEvents = new ArrayList<>(); + } + + void addEvent(BreakpointHitEvent event) { + breakpointEvents.add(event); + } + + Color getColor() { + Color retColor = null; + final List uniqueBreakTypes = + breakpointEvents.stream().map(BreakpointHitEvent::breakType).distinct().toList(); + final double blendRatio = 1.0d / uniqueBreakTypes.size(); + + for (final TraceBreakpointKind bt : uniqueBreakTypes) { + if (retColor == null) { + retColor = BreakpointTimelinePanel.BREAKTYPE_TO_COLOR.get(bt); + continue; + } + retColor = ColorUtils.blend(BreakpointTimelinePanel.BREAKTYPE_TO_COLOR.get(bt), + retColor, blendRatio); + } + + return (retColor == null) ? BreakpointTimelinePanel.BG_COLOR : retColor; + } + + long getIndex() { + return index; + } + + BreakpointHitEvent getMainEvent() { + if (breakpointEvents.isEmpty()) { + return null; + } + return breakpointEvents.getFirst(); + } + + long getMainSnap() { + final BreakpointHitEvent mainEvent = getMainEvent(); + return (mainEvent != null) ? mainEvent.snap() : startSnap; + } + + Rectangle getRect() { + return rect; + } + + long getStartSnap() { + return startSnap; + } + + long getStopSnap() { + return stopSnap; + } + } + + private static boolean singleColumn = false; + private static boolean showGridOutline = true; + + private static GColor BG_COLOR = Colors.BACKGROUND; + private static GColor GRID_COLOR = Colors.FOREGROUND_DISABLED; + private static GColor SELECTION_COLOR = + new GColor("color.debugger.plugin.breakpoint.timeline.selection"); + private static GColor HOVER_COLOR = + new GColor("color.debugger.plugin.breakpoint.timeline.hover"); + private static GColor CURRENT_SNAP_COLOR = + new GColor("color.debugger.plugin.breakpoint.timeline.current"); + private static GColor INSTRUCTION_HIT_COLOR = + new GColor("color.debugger.plugin.breakpoint.timeline.type.instructions"); + private static GColor MEMORY_READ_COLOR = + new GColor("color.debugger.plugin.breakpoint.timeline.type.read.memory"); + private static GColor MEMORY_WRITE_COLOR = + new GColor("color.debugger.plugin.breakpoint.timeline.type.write.memory"); + private static Map BREAKTYPE_TO_COLOR = Map.ofEntries( + Map.entry(TraceBreakpointKind.HW_EXECUTE, BreakpointTimelinePanel.INSTRUCTION_HIT_COLOR), + Map.entry(TraceBreakpointKind.SW_EXECUTE, BreakpointTimelinePanel.INSTRUCTION_HIT_COLOR), + Map.entry(TraceBreakpointKind.READ, BreakpointTimelinePanel.MEMORY_READ_COLOR), + Map.entry(TraceBreakpointKind.WRITE, BreakpointTimelinePanel.MEMORY_WRITE_COLOR)); + + private long defaultCellSize = 10; + + private List events; + private final BreakpointTimelineProvider provider; + + private long cellWidth = defaultCellSize; + private long cellHeight = defaultCellSize; + private long visibleStart; + + private long visibleEnd; + private Point dragStart; + private Point dragEnd; + private Point mousePos; + + private long gridWidth; + private long gridHeight; + + private CachedIndex lastHighlightedIndex; + private CachedIndex startDragIndex; + private CachedIndex endDragIndex; + + private final Map cells = new HashMap<>(); + + BreakpointTimelinePanel(BreakpointTimelineProvider provider) { + events = null; + this.provider = provider; + setup(); + } + + private void calculateGridAndBuildCache() { + Swing.runIfSwingOrRunLater(this::doCalculateGridAndBuildCache); + } + + private void click(Point p) { + final Optional first = + cells.values().stream().filter(s -> s.getRect().contains(p)).findFirst(); + if (first.isPresent()) { + getTraceManagerService().activateSnap(first.get().getMainSnap()); + } + } + + void decreaseDefaultCellSize() { + defaultCellSize = Math.max(cellHeight - 1, 1); + calculateGridAndBuildCache(); + } + + private void doCalculateGridAndBuildCache() { + if ((visibleStart == 0) && (visibleEnd == 0)) { + cells.clear(); + repaint(); + return; + } + final int width = getWidth(); + final int height = getHeight(); + + if ((width <= 0) && (height <= 0)) { + return; + } + + if (!BreakpointTimelinePanel.singleColumn) { + final long numCells = visibleEnd - visibleStart; + + double xSideLength; + double ySideLength; + + final double xPixelsPerCell = Math.ceil(Math.sqrt((numCells * width) / height)); + if ((Math.floor((xPixelsPerCell * height) / width) * xPixelsPerCell) < numCells) { + xSideLength = (height / Math.ceil((height * xPixelsPerCell) / width)); + } + else { + xSideLength = width / xPixelsPerCell; + } + + final double yPixelsPerCell = Math.ceil(Math.sqrt((numCells * height) / width)); + if ((Math.floor((yPixelsPerCell * width) / height) * yPixelsPerCell) < numCells) { + ySideLength = (width / Math.ceil((width * yPixelsPerCell) / height)); + } + else { + ySideLength = height / yPixelsPerCell; + } + + final long potentialSideLength = (long) Math.max(xSideLength, ySideLength); + + cellWidth = Math.max(defaultCellSize, potentialSideLength); + cellHeight = cellWidth; + + gridWidth = Math.max(width / cellWidth, 1); + } + else { + final long numCells = visibleEnd - visibleStart; + final long cellHeightOption = height / numCells; + cellHeight = Math.max(defaultCellSize, cellHeightOption); + cellWidth = width; + gridWidth = 1; + } + gridHeight = Math.max(height / cellHeight, 1); + + cells.clear(); + + final long totalCells = gridWidth * gridHeight; + final long range = visibleEnd - visibleStart; + final long span = Math.max(((range + totalCells) - 1) / totalCells, 1); + long index = 0; + + for (long i = 0; i < range; i += span) { + final long row = index / gridWidth; + final long col = index % gridWidth; + final long x = col * cellWidth; + final long y = row * cellHeight; + final long startSnap = i + visibleStart; + final long stopSnap = i + visibleStart + Math.min(span, range - i); + cells.put(index, new CachedIndex(startSnap, stopSnap, + new Rectangle((int) x, (int) y, (int) cellWidth, (int) cellHeight), index)); + index++; + } + + setPreferredSize( + new Dimension((int) (gridWidth * cellWidth), (int) (gridHeight * cellHeight))); + + for (final BreakpointHitEvent event : events) { + if ((event.snap() >= visibleStart) && (event.snap() < visibleEnd)) { + final long cellIndex = (event.snap() - visibleStart) / span; + final CachedIndex curSpan = cells.get(cellIndex); + curSpan.addEvent(event); + } + } + + repaint(); + } + + @Override + public String getToolTipText(MouseEvent event) { + final Optional first = + cells.values().stream().filter(s -> s.getRect().contains(event.getPoint())).findFirst(); + if (first.isPresent()) { + final CachedIndex index = first.get(); + final BreakpointHitEvent mainEvent = index.getMainEvent(); + if (mainEvent != null) { + return """ + Snapshots %d - %d + Jumps to snapshot %d + Event type %s + From %s + """.formatted(index.getStartSnap(), index.getStopSnap() - 1, + index.getMainSnap(), mainEvent.breakType(), mainEvent.breakpointName()); + } + return """ + Snapshots %d - %d + Jumps to snapshot %d + """.formatted(index.getStartSnap(), index.getStopSnap() - 1, + index.getMainSnap()); + } + return ""; + } + + private DebuggerTraceManagerService getTraceManagerService() { + return provider.getTool().getService(DebuggerTraceManagerService.class); + } + + void increaseDefaultCellSize() { + defaultCellSize = cellHeight + 1; + calculateGridAndBuildCache(); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + if ((visibleStart == 0) && (visibleEnd == 0)) { + return; + } + + if ((gridWidth == 0) || (gridHeight == 0)) { + calculateGridAndBuildCache(); + } + + final Graphics2D g2d = (Graphics2D) g; + + for (final Long cellIndex : cells.keySet()) { + final CachedIndex curSpan = cells.get(cellIndex); + if ((startDragIndex != null) && (endDragIndex != null) && + (cellIndex >= startDragIndex.getIndex()) && + (cellIndex <= endDragIndex.getIndex())) { + g2d.setColor(BreakpointTimelinePanel.SELECTION_COLOR); + } + else if ((getTraceManagerService().getCurrentSnap() >= curSpan.getStartSnap()) && + (getTraceManagerService().getCurrentSnap() < curSpan.getStopSnap())) { + g2d.setColor(BreakpointTimelinePanel.CURRENT_SNAP_COLOR); + } + else if ((mousePos != null) && curSpan.getRect().contains(mousePos)) { + g2d.setColor(BreakpointTimelinePanel.HOVER_COLOR); + } + else { + g2d.setColor(curSpan.getColor()); + } + g2d.fillRect(curSpan.getRect().x, curSpan.getRect().y, curSpan.getRect().width, + curSpan.getRect().height); + if (BreakpointTimelinePanel.showGridOutline) { + g2d.setColor(BreakpointTimelinePanel.GRID_COLOR); + g2d.drawRect(curSpan.getRect().x, curSpan.getRect().y, curSpan.getRect().width, + curSpan.getRect().height); + } + } + } + + void refresh() { + calculateGridAndBuildCache(); + } + + void setEventsAndVisibleRange(List events, long start, long stop) { + this.events = events; + visibleStart = start; + visibleEnd = stop; + calculateGridAndBuildCache(); + } + + void setMinimumDefaultCellSize() { + defaultCellSize = 1; + calculateGridAndBuildCache(); + } + + private void setup() { + setToolTipText(""); + setBackground(BreakpointTimelinePanel.BG_COLOR); + + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + calculateGridAndBuildCache(); + } + }); + + addMouseListener(new MouseAdapter() { + @Override + public void mouseExited(MouseEvent e) { + mousePos = null; + repaint(); + } + + @Override + public void mousePressed(MouseEvent e) { + dragStart = e.getPoint(); + } + + @Override + public void mouseReleased(MouseEvent e) { + if (dragEnd != null) { + zoom(); + } + else { + click(dragStart); + } + dragStart = null; + dragEnd = null; + startDragIndex = null; + endDragIndex = null; + repaint(); + } + }); + + addMouseWheelListener(new MouseAdapter() { + + @Override + public void mouseWheelMoved(MouseWheelEvent e) { + final int rotation = e.getWheelRotation(); + if (rotation > 0) { + getTraceManagerService() + .activateSnap(getTraceManagerService().getCurrentSnap() + 1); + } + else { + getTraceManagerService() + .activateSnap(getTraceManagerService().getCurrentSnap() - 1); + + } + repaint(); + } + }); + + addMouseMotionListener(new MouseAdapter() { + @Override + public void mouseDragged(MouseEvent e) { + dragEnd = e.getPoint(); + final Optional start = cells.values() + .stream() + .filter(s -> s.getRect().contains(dragStart)) + .findFirst(); + if (start.isPresent()) { + final Optional end = cells.values() + .stream() + .filter(s -> s.getRect().contains(dragEnd)) + .findFirst(); + if (end.isPresent()) { + if (start.get().getIndex() < end.get().getIndex()) { + startDragIndex = start.get(); + endDragIndex = end.get(); + } + else { + startDragIndex = end.get(); + endDragIndex = start.get(); + } + } + } + repaint(); + } + + @Override + public void mouseMoved(MouseEvent e) { + mousePos = e.getPoint(); + final Optional indexSpan = + cells.values().stream().filter(s -> s.getRect().contains(mousePos)).findFirst(); + if (indexSpan.isPresent() && (lastHighlightedIndex != indexSpan.get())) { + lastHighlightedIndex = indexSpan.get(); + repaint(); + } + } + }); + } + + void toggleGridOrColumn() { + BreakpointTimelinePanel.singleColumn = !BreakpointTimelinePanel.singleColumn; + calculateGridAndBuildCache(); + } + + void toggleGridOutline() { + BreakpointTimelinePanel.showGridOutline = !BreakpointTimelinePanel.showGridOutline; + repaint(); + } + + private void zoom() { + final Optional start = + cells.values().stream().filter(s -> s.getRect().contains(dragStart)).findFirst(); + if (start.isPresent()) { + long startSnap = start.get().getStartSnap(); + + final Optional stop = + cells.values().stream().filter(s -> s.getRect().contains(dragEnd)).findFirst(); + if (stop.isPresent()) { + long endSnap = stop.get().getStopSnap(); + + if (startSnap > endSnap) { + final long temp = startSnap; + startSnap = endSnap; + endSnap = temp; + } + + final String zoomName = "Timeline Zoom: %d - %d".formatted(startSnap, endSnap - 1); + provider.createZoomProvider(zoomName, startSnap, endSnap); + } + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/timeline/BreakpointTimelinePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/timeline/BreakpointTimelinePlugin.java new file mode 100644 index 0000000000..67b16a4654 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/timeline/BreakpointTimelinePlugin.java @@ -0,0 +1,147 @@ +/* ### + * 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.breakpoint.timeline; + +import java.util.*; + +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.event.TraceClosedPluginEvent; +import ghidra.app.services.DebuggerTraceManagerService; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.trace.model.Trace; + +@PluginInfo( + shortDescription = "Debugger breakpoint hit timeline", + description = "Timeline of all snapshots showing breakpoint hits", + category = PluginCategoryNames.DEBUGGER, + packageName = DebuggerPluginPackage.NAME, + status = PluginStatus.UNSTABLE, + servicesRequired = { DebuggerTraceManagerService.class, }, + eventsConsumed = { TraceClosedPluginEvent.class, TraceActivatedPluginEvent.class, } +) +public class BreakpointTimelinePlugin extends AbstractDebuggerPlugin { + BreakpointTimelineProvider provider; + private Trace currentTrace; + + private final Map> traceSpecificZoomProviders = + new HashMap<>(); + + public BreakpointTimelinePlugin(PluginTool tool) { + super(tool); + } + + void createZoomProvider(String title, long start, long stop) { + traceSpecificZoomProviders.computeIfAbsent(currentTrace, k -> new ArrayList<>()) + .add(new BreakpointTimelineProvider(provider, title, start, stop)); + } + + @Override + protected void dispose() { + for (final var providers : traceSpecificZoomProviders.values()) { + for (final var provider : providers) { + tool.removeComponentProvider(provider); + } + } + tool.removeComponentProvider(provider); + super.dispose(); + } + + private void hideZoomProviders(Trace t) { + final List zoomProviders = traceSpecificZoomProviders.get(t); + if (zoomProviders != null) { + for (final BreakpointTimelineProvider p : zoomProviders) { + tool.showComponentProvider(p, false); + } + } + } + + @Override + protected void init() { + super.init(); + provider = new BreakpointTimelineProvider(this); + } + + @Override + public void processEvent(PluginEvent event) { + super.processEvent(event); + switch (event) { + case final TraceClosedPluginEvent evt -> { + final Trace t = evt.getTrace(); + + if (currentTrace == t) { + currentTrace = null; + provider.setTrace(null); + } + removeZoomProviders(t); + } + case final TraceActivatedPluginEvent evt -> { + final Trace t = evt.getActiveCoordinates().getTrace(); + if (t == null) { + provider.setTrace(null); + } + else if (currentTrace != t) { + hideZoomProviders(currentTrace); + currentTrace = t; + provider.setTrace(currentTrace); + showZoomProviders(currentTrace); + } + else { + refreshAllProviders(null); + } + } + default -> { + } + } + } + + void refreshAllProviders(BreakpointTimelineProvider currentProvider) { + final List zoomProviders = + traceSpecificZoomProviders.get(currentTrace); + if (zoomProviders != null) { + for (final BreakpointTimelineProvider p : zoomProviders) { + if (p != currentProvider) { + p.refresh(); + } + } + } + + if (provider != currentProvider) { + provider.refresh(); + } + } + + void removeZoomProviders(Trace t) { + final List zoomProviders = traceSpecificZoomProviders.remove(t); + if (zoomProviders != null) { + for (final BreakpointTimelineProvider p : zoomProviders) { + tool.removeComponentProvider(p); + } + } + } + + private void showZoomProviders(Trace t) { + final List zoomProviders = traceSpecificZoomProviders.get(t); + if (zoomProviders != null) { + for (final BreakpointTimelineProvider p : zoomProviders) { + tool.showComponentProvider(p, true); + } + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/timeline/BreakpointTimelineProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/timeline/BreakpointTimelineProvider.java new file mode 100644 index 0000000000..59f7ca7821 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/timeline/BreakpointTimelineProvider.java @@ -0,0 +1,316 @@ +/* ### + * 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.breakpoint.timeline; + +import java.awt.BorderLayout; +import java.util.*; + +import javax.swing.JComponent; +import javax.swing.JPanel; + +import docking.ActionContext; +import docking.ComponentProvider; +import docking.action.DockingAction; +import docking.action.ToolBarData; +import generic.theme.GIcon; +import ghidra.program.model.address.AddressRange; +import ghidra.program.model.symbol.RefType; +import ghidra.trace.model.*; +import ghidra.trace.model.breakpoint.*; +import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet; +import ghidra.trace.model.stack.TraceStackFrame; +import ghidra.trace.model.symbol.TraceReference; +import ghidra.trace.model.target.TraceObjectValue; +import ghidra.trace.util.TraceEvents; + +class BreakpointTimelineProvider extends ComponentProvider { + record BreakpointHitEvent(long snap, TraceBreakpointKind breakType, String breakpointName) {} + + private class BreakpointTimeOverviewEventListener extends TraceDomainObjectListener { + + public BreakpointTimeOverviewEventListener() { + listenFor(TraceEvents.BREAKPOINT_CHANGED, this::breakpointChanged); + listenFor(TraceEvents.BREAKPOINT_DELETED, this::breakpointDeleted); + } + + void breakpointChanged(TraceBreakpointLocation tb) { + refreshBreakpointHits(); + breakpointTimelinePlugin.refreshAllProviders(null); + } + + void breakpointDeleted(TraceBreakpointLocation tb) { + refreshBreakpointHits(); + breakpointTimelinePlugin.refreshAllProviders(null); + } + + } + + private class CloseAllZoomWindowsAction extends DockingAction { + private final GIcon ICON = new GIcon("icon.debugger.breakpoint.timeline.close_all_zoom_windows"); + + CloseAllZoomWindowsAction(ComponentProvider provider) { + super("Close all zoom windows", provider.getOwner()); + setEnabled(true); + setToolBarData(new ToolBarData(ICON, "1")); + } + + @Override + public void actionPerformed(ActionContext context) { + breakpointTimelinePlugin.removeZoomProviders(currentTrace); + } + } + + private class SmallestCellSizeAction extends DockingAction { + private final GIcon ICON = new GIcon("icon.debugger.breakpoint.timeline.zoom_out_max"); + + SmallestCellSizeAction(ComponentProvider provider) { + super("Set default cell size to the smallest", provider.getOwner()); + setEnabled(true); + setToolBarData(new ToolBarData(ICON, "zoom")); + } + + @Override + public void actionPerformed(ActionContext context) { + breakpointTimelinePanel.setMinimumDefaultCellSize(); + } + } + + private class ToggleGridAction extends DockingAction { + private final GIcon OUTLINE_ICON = new GIcon("icon.debugger.breakpoint.timeline.outline"); + private final GIcon NO_OUTLINE_ICON = new GIcon("icon.debugger.breakpoint.timeline.no_outline"); + private boolean grid = true; + + ToggleGridAction(ComponentProvider provider) { + super("Toggle Grid Outline", provider.getOwner()); + setEnabled(true); + setToolBarData(new ToolBarData(OUTLINE_ICON, "2")); + } + + @Override + public void actionPerformed(ActionContext context) { + grid = !grid; + getToolBarData().setIcon(grid ? OUTLINE_ICON : NO_OUTLINE_ICON); + breakpointTimelinePanel.toggleGridOutline(); + breakpointTimelinePlugin.refreshAllProviders(BreakpointTimelineProvider.this); + + } + } + + private class ToggleGridOrColumnAction extends DockingAction { + private final GIcon GRID_ICON = new GIcon("icon.debugger.breakpoint.timeline.grid"); + private final GIcon SINGLE_COLUMN_ICON = + new GIcon("icon.debugger.breakpoint.timeline.single_column"); + private boolean grid = true; + + ToggleGridOrColumnAction(ComponentProvider provider) { + super("Toggle between grid and single column", provider.getOwner()); + setEnabled(true); + setToolBarData(new ToolBarData(GRID_ICON, "2")); + } + + @Override + public void actionPerformed(ActionContext context) { + grid = !grid; + getToolBarData().setIcon(grid ? GRID_ICON : SINGLE_COLUMN_ICON); + breakpointTimelinePanel.toggleGridOrColumn(); + breakpointTimelinePlugin.refreshAllProviders(BreakpointTimelineProvider.this); + } + } + + private class ZoomInAction extends DockingAction { + private final GIcon ICON = new GIcon("icon.debugger.breakpoint.timeline.zoom_in"); + + ZoomInAction(ComponentProvider provider) { + super("Increase cell size", provider.getOwner()); + setEnabled(true); + setToolBarData(new ToolBarData(ICON, "zoom")); + } + + @Override + public void actionPerformed(ActionContext context) { + breakpointTimelinePanel.increaseDefaultCellSize(); + } + } + + private class ZoomOutAction extends DockingAction { + private final GIcon ICON = new GIcon("icon.debugger.breakpoint.timeline.zoom_out"); + + ZoomOutAction(ComponentProvider provider) { + super("Decrease cell size", provider.getOwner()); + setEnabled(true); + setToolBarData(new ToolBarData(ICON, "zoom")); + } + + @Override + public void actionPerformed(ActionContext context) { + breakpointTimelinePanel.decreaseDefaultCellSize(); + } + } + + private Trace currentTrace; + private final BreakpointTimelinePanel breakpointTimelinePanel; + + private final JPanel wrapperPanel; + private final BreakpointTimelinePlugin breakpointTimelinePlugin; + private final BreakpointTimeOverviewEventListener listener = + new BreakpointTimeOverviewEventListener(); + private List breakpointHits; + + BreakpointTimelineProvider(BreakpointTimelinePlugin breakpointTimelinePlugin) { + this(breakpointTimelinePlugin, false); + } + + BreakpointTimelineProvider(BreakpointTimelinePlugin breakpointTimelinePlugin, + boolean makeTransient) { + super(breakpointTimelinePlugin.getTool(), "Breakpoint Timeline", + breakpointTimelinePlugin.getName()); + this.breakpointTimelinePlugin = breakpointTimelinePlugin; + wrapperPanel = new JPanel(new BorderLayout()); + breakpointTimelinePanel = new BreakpointTimelinePanel(this); + breakpointTimelinePanel.setFocusable(true); + wrapperPanel.add(breakpointTimelinePanel, BorderLayout.CENTER); + breakpointHits = new ArrayList<>(); + + if (makeTransient) { + setTransient(); + } + + dockingTool.addComponentProvider(this, true); + createActions(); + + } + + BreakpointTimelineProvider(BreakpointTimelineProvider provider, String title, long start, + long end) { + this(provider.breakpointTimelinePlugin, true); + currentTrace = provider.currentTrace; + setTitle(title); + breakpointHits = provider.breakpointHits; + breakpointTimelinePanel.setEventsAndVisibleRange(breakpointHits, start, end); + } + + private void createActions() { + dockingTool.addLocalAction(this, new ToggleGridOrColumnAction(this)); + dockingTool.addLocalAction(this, new ToggleGridAction(this)); + dockingTool.addLocalAction(this, new ZoomInAction(this)); + dockingTool.addLocalAction(this, new ZoomOutAction(this)); + dockingTool.addLocalAction(this, new CloseAllZoomWindowsAction(this)); + dockingTool.addLocalAction(this, new SmallestCellSizeAction(this)); + } + + void createZoomProvider(String title, long start, long stop) { + breakpointTimelinePlugin.createZoomProvider(title, start, stop); + } + + private void findAndAddAllBreakpointHitsAtLocation(TraceBreakpointLocation breakpointLocation, + AddressRange range, String kind) { + for (final TraceBreakpointKind breakpointKind : TraceBreakpointKindSet.decode(kind, false)) { + switch (breakpointKind) { + case HW_EXECUTE, SW_EXECUTE -> findAndAddExecuteBreakpointHits(breakpointLocation, + range); + case READ, WRITE -> findAndAddMemoryBreakpointHits(breakpointLocation, range, + breakpointKind); + } + } + } + + private void findAndAddExecuteBreakpointHits(TraceBreakpointLocation breakpointLocation, + AddressRange range) { + final Collection intersecting = currentTrace.getObjectManager() + .getValuesIntersecting(Lifespan.ALL, range, TraceStackFrame.KEY_PC); + for (final TraceObjectValue tov : intersecting) { + breakpointHits.add(new BreakpointHitEvent(tov.getMinSnap(), + TraceBreakpointKind.SW_EXECUTE, breakpointLocation.getName(tov.getMinSnap()))); + } + } + + private void findAndAddMemoryBreakpointHits(TraceBreakpointLocation breakpointLocation, + AddressRange range, TraceBreakpointKind kind) { + for (final TraceReference reference : currentTrace.getReferenceManager() + .getReferencesToRange(Lifespan.ALL, range)) { + + if ((reference.getReferenceType() == RefType.READ) && + (kind == TraceBreakpointKind.READ)) { + breakpointHits.add( + new BreakpointHitEvent(reference.getStartSnap(), TraceBreakpointKind.READ, + breakpointLocation.getName(reference.getStartSnap()))); + } + + if ((reference.getReferenceType() == RefType.WRITE) && + (kind == TraceBreakpointKind.WRITE)) { + breakpointHits.add( + new BreakpointHitEvent(reference.getStartSnap(), TraceBreakpointKind.WRITE, + breakpointLocation.getName(reference.getStartSnap()))); + } + } + } + + @Override + public JComponent getComponent() { + return wrapperPanel; + } + + void refresh() { + breakpointTimelinePanel.refresh(); + } + + private void refreshBreakpointHits() { + breakpointHits.clear(); + + if (currentTrace == null) { + return; + } + + // LATER: Check if breakpoint is enabled after GP-6441 is done + for (final TraceBreakpointLocation breakpointLocation : currentTrace.getBreakpointManager() + .getAllBreakpointLocations()) { + + for (final TraceObjectValue traceVal : breakpointLocation.getObject() + .getValues(Lifespan.ALL, TraceBreakpointLocation.KEY_RANGE)) { + if (!(traceVal.getValue() instanceof final AddressRange range)) { + continue; + } + for (final TraceObjectValue specValue : breakpointLocation.getSpecification() + .getObject() + .getValues(Lifespan.ALL, TraceBreakpointSpec.KEY_KINDS)) { + + if (!(specValue.getValue() instanceof final String kind)) { + continue; + } + + findAndAddAllBreakpointHitsAtLocation(breakpointLocation, range, kind); + } + } + } + } + + void setTrace(Trace trace) { + if (currentTrace != null) { + currentTrace.removeListener(listener); + } + currentTrace = trace; + refreshBreakpointHits(); + + if (currentTrace != null) { + breakpointTimelinePanel.setEventsAndVisibleRange(breakpointHits, 0, + currentTrace.getTimeManager().getMaxSnap()); + currentTrace.addListener(listener); + } + else { + breakpointTimelinePanel.setEventsAndVisibleRange(null, 0, 0); + } + } +} \ No newline at end of file diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/breakpoint/BreakTypeOverviewLegendPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/breakpoint/BreakTypeOverviewLegendPanel.java new file mode 100644 index 0000000000..ea597b1bf1 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/breakpoint/BreakTypeOverviewLegendPanel.java @@ -0,0 +1,79 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.gui.timeoverview.breakpoint; + +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; + +public class BreakTypeOverviewLegendPanel extends JPanel { + private static Dimension COLOR_SIZE = new Dimension(15, 15); + private BreakpointTimeOverviewColorService colorService; + + public BreakTypeOverviewLegendPanel(BreakpointTimeOverviewColorService 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(); + CellType[] values = CellType.values(); + for (CellType breakType : values) { + JPanel panel = new ColorPanel(breakType); + add(panel); + add(new GLabel(breakType.getDescription())); + } + } + + private class ColorPanel extends JPanel { + private CellType type; + + ColorPanel(CellType 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); + } + + } + +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/breakpoint/BreakpointTimeOverviewColorService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/breakpoint/BreakpointTimeOverviewColorService.java new file mode 100644 index 0000000000..f621d61365 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/breakpoint/BreakpointTimeOverviewColorService.java @@ -0,0 +1,361 @@ +/* ### + * 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.breakpoint; + +import java.awt.Color; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.math.BigInteger; +import java.util.*; + +import javax.swing.SwingUtilities; + +import docking.DialogComponentProvider; +import docking.action.DockingActionIf; +import docking.action.builder.ActionBuilder; +import generic.ULongSpan; +import generic.theme.GThemeDefaults.Colors; +import ghidra.app.plugin.core.debug.gui.timeoverview.*; +import ghidra.app.plugin.core.overview.OverviewColorLegendDialog; +import ghidra.app.services.DebuggerTraceManagerService; +import ghidra.framework.options.ToolOptions; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.AddressRange; +import ghidra.program.model.symbol.RefType; +import ghidra.trace.model.*; +import ghidra.trace.model.breakpoint.*; +import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet; +import ghidra.trace.model.stack.TraceStackFrame; +import ghidra.trace.model.symbol.TraceReference; +import ghidra.trace.model.target.TraceObjectValue; +import ghidra.trace.util.TraceEvents; +import ghidra.util.ColorUtils; +import ghidra.util.HelpLocation; + +record BreakpointEvent(long snap, CellType breakType) {} + +public class BreakpointTimeOverviewColorService implements TimeOverviewColorService { + private class BreakpointTimeOverviewEventListener extends TraceDomainObjectListener { + + public BreakpointTimeOverviewEventListener() { + listenFor(TraceEvents.BREAKPOINT_ADDED, this::breakpointAdded); + listenFor(TraceEvents.BREAKPOINT_CHANGED, this::breakpointChanged); + listenFor(TraceEvents.BREAKPOINT_DELETED, this::breakpointDeleted); + listenFor(TraceEvents.BREAKPOINT_LIFESPAN_CHANGED, this::breakpointLifespanChanged); + } + + void breakpointAdded(TraceBreakpointLocation tb) { + // Empty because all new breakpoints fire a breakpoint change + // event + } + + void breakpointChanged(TraceBreakpointLocation tb) { + SwingUtilities.invokeLater(() -> { + calculateBreakpointHits(); + calculateHelperMaps(); + }); + } + + void breakpointDeleted(TraceBreakpointLocation tb) { + SwingUtilities.invokeLater(() -> { + calculateBreakpointHits(); + calculateHelperMaps(); + }); + } + + void breakpointLifespanChanged(TraceBreakpointLocation tb) { + SwingUtilities.invokeLater(() -> { + calculateBreakpointHits(); + calculateHelperMaps(); + }); + } + + } + + private static String OPTIONS_NAME = "Breakpoint Hit Timeline"; + + private PluginTool tool; + + Trace currentTrace; + TimeOverviewColorComponent overviewComponent; + DialogComponentProvider legendDialog; + BreakTypeOverviewLegendPanel legendPanel; + TimeOverviewColorPlugin plugin; + + private final BreakpointTimeOverviewEventListener eventListener = + new BreakpointTimeOverviewEventListener(); + + private final Map colorMap = new HashMap<>(); + private final Map indexToSnap = new HashMap<>(); + private final Map snapToColor = new HashMap<>(); + private final Map snapToTooltip = new HashMap<>(); + private final Map snapToRange = new HashMap<>(); + + List snapsWithBreakpointsHit = new ArrayList<>(); + + Lifespan bounds; + private DebuggerTraceManagerService debuggerTraceManagerService; + + protected void calculateBreakpointHits() { + snapsWithBreakpointsHit.clear(); + + // LATER: Check if breakpoint is enabled after GP-6441 is done + for (final TraceBreakpointLocation breakpointLocation : getTrace().getBreakpointManager() + .getAllBreakpointLocations()) { + + for (final TraceObjectValue traceVal : breakpointLocation.getObject() + .getValues(Lifespan.ALL, TraceBreakpointLocation.KEY_RANGE)) { + if (!(traceVal.getValue() instanceof final AddressRange range)) { + continue; + } + for (final TraceObjectValue specValue : breakpointLocation.getSpecification() + .getObject() + .getValues(Lifespan.ALL, TraceBreakpointSpec.KEY_KINDS)) { + + if (!(specValue.getValue() instanceof final String kind)) { + continue; + } + + findAndAddAllBreakpointHitsAtLocation(breakpointLocation, range, kind); + } + } + } + } + + protected void calculateHelperMaps() { + indexToSnap.clear(); + snapToColor.clear(); + if (bounds == null) { + return; + } + final long splits = overviewComponent.getOverviewPixelCount(); + final long snapSpanPerCell = Math.max((bounds.lmax() - bounds.lmin()) / splits, 1); + + int cellIndex = 0; + for (long i = 0; i < bounds.lmax(); i += snapSpanPerCell) { + boolean hasBreakpointHit = false; + Color cellColor = Colors.BACKGROUND; + + for (final BreakpointEvent event : snapsWithBreakpointsHit) { + if ((i <= event.snap()) && (event.snap() <= (i + snapSpanPerCell))) { + hasBreakpointHit = true; + indexToSnap.put(cellIndex, event.snap()); + snapToTooltip.put(event.snap(), """ + Snapshot %d + Break type %s""".formatted(event.snap(), event.breakType())); + snapToRange.put(event.snap(), ULongSpan.span(i, i + snapSpanPerCell)); + + cellColor = + ColorUtils.addColors(cellColor, event.breakType().getDefaultColor()); + } + } + + if (!hasBreakpointHit) { + // Just use the first snap of this span + indexToSnap.put(cellIndex, i); + snapToRange.put(i, ULongSpan.span(i, i + snapSpanPerCell)); + snapToTooltip.put(i, "Snapshot %d".formatted(i)); + } + + snapToColor.put(indexToSnap.get(cellIndex), cellColor); + + cellIndex++; + } + + } + + private void findAndAddAllBreakpointHitsAtLocation(TraceBreakpointLocation breakpointLocation, + AddressRange range, String kind) { + for (final TraceBreakpointKind breakpointKind : TraceBreakpointKindSet.decode(kind, + false)) { + switch (breakpointKind) { + case HW_EXECUTE, SW_EXECUTE -> findAndAddExecuteBreakpointHits(breakpointLocation, + range); + case READ, WRITE -> findAndAddMemoryBreakpointHits(breakpointLocation, range, + breakpointKind); + } + } + } + + private void findAndAddExecuteBreakpointHits(TraceBreakpointLocation breakpointLocation, + AddressRange range) { + final Collection intersecting = currentTrace.getObjectManager() + .getValuesIntersecting(Lifespan.ALL, range, TraceStackFrame.KEY_PC); + for (final TraceObjectValue tov : intersecting) { + snapsWithBreakpointsHit + .add(new BreakpointEvent(tov.getMinSnap(), CellType.INSTRUCTION_EXECUTED)); + } + } + + private void findAndAddMemoryBreakpointHits(TraceBreakpointLocation breakpointLocation, + AddressRange range, TraceBreakpointKind kind) { + for (final TraceReference reference : currentTrace.getReferenceManager() + .getReferencesToRange(Lifespan.ALL, range)) { + + if ((reference.getReferenceType() == RefType.READ) && + (kind == TraceBreakpointKind.READ)) { + snapsWithBreakpointsHit + .add(new BreakpointEvent(reference.getStartSnap(), CellType.MEMORY_READ)); + } + + if ((reference.getReferenceType() == RefType.WRITE) && + (kind == TraceBreakpointKind.WRITE)) { + snapsWithBreakpointsHit.add( + new BreakpointEvent(reference.getStartSnap(), CellType.MEMORY_WRITTEN)); + } + } + } + + @Override + public List getActions() { + final 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 Lifespan getBounds() { + return bounds; + } + + /** + * Returns the color associated with the given {@link CellType} + * + * @param breakType the span type for which to get a color. + * @return the color associated with the given {@link CellType} + */ + public Color getColor(CellType breakType) { + final Color color = colorMap.get(breakType); + if (color == null) { + colorMap.put(breakType, breakType.getDefaultColor()); + } + return color; + } + + @Override + public Color getColor(Long snap) { + final Color c = Colors.BACKGROUND; + + if (snap != null) { + final ULongSpan range = snapToRange.get(snap); + final long currentSnap = debuggerTraceManagerService.getCurrentSnap(); + if ((range != null) && (currentSnap > range.min()) && (currentSnap < range.max())) { + return Color.GREEN; + } + return snapToColor.get(snap); + } + return c; + } + + @Override + public HelpLocation getHelpLocation() { + return null; + } + + private DialogComponentProvider getLegendDialog() { + if (legendDialog == null) { + legendPanel = new BreakTypeOverviewLegendPanel(this); + + legendDialog = + new OverviewColorLegendDialog("Overview Legend", legendPanel, getHelpLocation()); + } + return legendDialog; + } + + @Override + public String getName() { + return OPTIONS_NAME; + } + + @Override + public Long getSnap(int pixelIndex) { + final BigInteger bigHeight = BigInteger.valueOf(overviewComponent.getOverviewPixelCount()); + final BigInteger bigPixelIndex = BigInteger.valueOf(pixelIndex); + + final BigInteger span = BigInteger.valueOf(indexToSnap.size()); + final BigInteger offset = span.multiply(bigPixelIndex).divide(bigHeight); + return indexToSnap.get(offset.intValue()); + } + + @Override + public String getToolTipText(Long snap) { + return snapToTooltip.getOrDefault(snap, "Snapshot %d".formatted(snap)); + } + + @Override + public Trace getTrace() { + return currentTrace; + } + + @Override + public void initialize(PluginTool pluginTool) { + tool = pluginTool; + debuggerTraceManagerService = tool.getService(DebuggerTraceManagerService.class); + } + + @Override + public void setBounds(Lifespan bounds) { + if (currentTrace != null) { + this.bounds = Lifespan.span(0, getTrace().getTimeManager().getMaxSnap()); + } + } + + public void setColor(CellType type, Color newColor) { + final ToolOptions options = tool.getOptions(OPTIONS_NAME); + options.setColor(type.getDescription(), newColor); + } + + @Override + public void setIndices(TreeSet set) { + // Empty because we do not change indices once a trace is set + } + + @Override + public void setOverviewComponent(TimeOverviewColorComponent component) { + overviewComponent = component; + overviewComponent.addComponentListener(new ComponentAdapter() { + + @Override + public void componentResized(ComponentEvent e) { + SwingUtilities.invokeLater(() -> calculateHelperMaps()); + } + }); + } + + @Override + public void setPlugin(TimeOverviewColorPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void setTrace(Trace trace) { + if ((trace != null) && (trace != currentTrace)) { + if (currentTrace != null) { + currentTrace.removeListener(eventListener); + } + currentTrace = trace; + currentTrace.addListener(eventListener); + SwingUtilities.invokeLater(this::calculateHelperMaps); + bounds = Lifespan.span(0, getTrace().getTimeManager().getMaxSnap()); + } + } +} \ No newline at end of file diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/breakpoint/CellType.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/breakpoint/CellType.java new file mode 100644 index 0000000000..0ee4029a60 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/breakpoint/CellType.java @@ -0,0 +1,61 @@ +/* ### + * 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.breakpoint; + +import java.awt.Color; + +import generic.theme.GColor; + +/** + * An enum for the different types that are represented by unique colors by the + * {@link BreakpointTimeOverviewColorService} + */ +public enum CellType { + INSTRUCTION_EXECUTED("Instruction Executed", new GColor( + "color.debugger.plugin.timeoverview.box.type.instructions")), + MEMORY_READ("Memory Read", new GColor( + "color.debugger.plugin.timeoverview.box.type.read.memory")), + MEMORY_WRITTEN("Memory Written", new GColor( + "color.debugger.plugin.timeoverview.box.type.write.memory")), + CURRENT_LOCATION("Current Location", new GColor("color.palette.green")); + + final private String description; + final private Color color; + + CellType(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; + } + +}