diff --git a/Ghidra/Debug/Debugger-agent-dbgmodel-traceloader/ghidra_scripts/PopulateMemviewLocal.java b/Ghidra/Debug/Debugger-agent-dbgmodel-traceloader/ghidra_scripts/PopulateMemviewLocal.java new file mode 100644 index 0000000000..13f81d54a9 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgmodel-traceloader/ghidra_scripts/PopulateMemviewLocal.java @@ -0,0 +1,284 @@ + +/* ### + * 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. + */ +/* ### + * 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. + */ +import java.io.File; +import java.util.*; + +import com.google.common.collect.Range; +import com.sun.jna.Pointer; + +import agent.dbgeng.dbgeng.DebugClient; +import agent.dbgeng.dbgeng.DebugControl; +import agent.dbgmodel.dbgmodel.DbgModel; +import agent.dbgmodel.dbgmodel.bridge.HostDataModelAccess; +import agent.dbgmodel.dbgmodel.main.ModelMethod; +import agent.dbgmodel.dbgmodel.main.ModelObject; +import agent.dbgmodel.impl.dbgmodel.bridge.HDMAUtil; +import ghidra.app.plugin.core.debug.gui.memview.*; +import ghidra.app.script.GhidraScript; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; +import ghidra.trace.model.memory.TraceMemoryFlag; +import ghidra.util.Swing; + +public class PopulateMemviewLocal extends GhidraScript { + + private Language lang; + + private AddressSpace defaultSpace; + + private HostDataModelAccess access; + private DebugClient client; + private DebugControl control; + private HDMAUtil util; + + private Map boxes = new HashMap(); + private Set eventSnaps = new HashSet(); + private MemviewService memview; + + /** + * Create an address in the processor's (x86_64) default space. + * + * @param offset the byte offset + * @return the address + */ + protected Address addr(long offset) { + return defaultSpace.getAddress(offset); + } + + /** + * Create an address range in the processor's default space. + * + * @param min the minimum byte offset + * @param max the maximum (inclusive) byte offset + * @return the range + */ + protected AddressRange rng(long min, long max) { + return new AddressRangeImpl(addr(min), addr(max)); + } + + /** + * Get a register by name + * + * @param name the name + * @return the register + */ + protected Register reg(String name) { + return lang.getRegister(name); + } + + @Override + protected void run() throws Exception { + + memview = state.getTool().getService(MemviewService.class); + if (memview == null) { + throw new RuntimeException("Unable to find DebuggerMemviewPlugin"); + } + + access = DbgModel.debugCreate(); + client = access.getClient(); + control = client.getControl(); + util = new HDMAUtil(access); + + File f = askFile("Trace", "Load"); + + lang = currentProgram.getLanguage(); + defaultSpace = lang.getAddressFactory().getDefaultAddressSpace(); + + client.openDumpFileWide(f.getAbsolutePath()); + control.waitForEvent(); + + List children = util.getElements( + List.of("Debugger", "State", "DebuggerVariables", "curprocess", "TTD", "Events")); + + Map maxPos = util.getAttributes(List.of("Debugger", "State", + "DebuggerVariables", "curprocess", "TTD", "Lifetime", "MaxPosition")); + Long max = (Long) maxPos.get("Sequence").getValue(); + + for (ModelObject event : children) { + Map eventMap = event.getKeyValueMap(); + ModelObject pos = eventMap.get("Position"); + ModelObject seq = pos.getKeyValue("Sequence"); + //ModelObject step = pos.getKeyValue("Steps"); + ModelObject type = eventMap.get("Type"); + String display = type.getValueString(); + Long snap = (Long) seq.getValue(); + if (display.contains("ModuleLoaded") || display.contains("ModuleUnloaded")) { + ModelObject module = eventMap.get("Module"); + Map moduleMap = module.getKeyValueMap(); + ModelObject name = moduleMap.get("Name"); + ModelObject address = moduleMap.get("Address"); + ModelObject size = moduleMap.get("Size"); + String moduleId = name.getValueString(); + display += " " + moduleId; + //Address base = currentProgram.getAddressFactory().getAddress(address.getValueString()); + if (display.contains("ModuleLoaded")) { + Long start = (Long) address.getValue(); + Long sz = (Long) size.getValue(); + AddressRange rng = rng(start, start + sz - 1); + addLoadedModule(moduleId, moduleId, Range.atLeast(snap), rng); + //addRegion(moduleId, Range.atLeast(snap), rng, TraceMemoryFlag.READ, + // TraceMemoryFlag.WRITE, TraceMemoryFlag.EXECUTE); + } + else { + markModuleClosed(moduleId, snap); + } + } + else if (display.contains("ThreadCreated") || display.contains("ThreadTerminated")) { + ModelObject thread = eventMap.get("Thread"); + //ModelObject uid = thread.getKeyValue("UniqueId"); + ModelObject id = thread.getKeyValue("Id"); + String threadId = id.getValueString(); + int iid = Integer.parseInt(threadId, 16); + AddressRange rng = rng(iid, iid + 1); + display += " " + threadId; + if (display.contains("ThreadCreated")) { + addThread("Thread " + threadId, Range.atLeast(snap), rng); + } + else { + markThreadClosed(threadId, snap); + } + } + if (snap < 0) { + snap = ++max; + } + //timeManager.getSnapshot(snap, true).setDescription(display); + eventSnaps.add(snap); + } + + for (Long snap : eventSnaps) { + control.execute("!tt " + Long.toHexString(snap) + ":0"); + control.waitForEvent(); + + List modelThreads = util.getElements( + List.of("Debugger", "State", "DebuggerVariables", "curprocess", "Threads")); + Map modelThreadMap = new HashMap(); + for (ModelObject obj : modelThreads) { + modelThreadMap.put(obj.getSearchKey(), obj); + } + } + + ModelObject currentSession = util.getCurrentSession(); + ModelObject data = currentSession.getKeyValue("TTD").getKeyValue("Data"); + ModelMethod heap = data.getMethod("Heap"); + Pointer[] args = new Pointer[0]; + ModelObject ret = heap.call(data, 0, args); + for (ModelObject heapObj : ret.getElements()) { + Map heapMap = heapObj.getKeyValueMap(); + ModelObject address = heapMap.get("Address"); + ModelObject size = heapMap.get("Size"); + ModelObject timeStart = heapMap.get("TimeStart").getKeyValue("Sequence"); + ModelObject timeEnd = heapMap.get("TimeEnd").getKeyValue("Sequence"); + if (address == null) { + continue; + } + Long start = (Long) address.getValue(); + if (size == null) { + continue; + } + Long sz = (Long) size.getValue(); + if (sz == null) { + continue; + } + AddressRange rng = rng(start, start + sz); + String heapId = "Heap " + address.getValueString(); + Long startTick = (Long) timeStart.getValue(); + Long stopTick = (Long) timeEnd.getValue(); + Range interval = + (stopTick > 0) ? Range.open(startTick, stopTick) : Range.atLeast(startTick); + addHeap(heapId, interval, rng, TraceMemoryFlag.READ, TraceMemoryFlag.WRITE, + TraceMemoryFlag.EXECUTE); + } + + /** + * Give a program view to Ghidra's program manager + * + * NOTE: Eventually, there will probably be a TraceManager service as well, but to use the + * familiar UI components, we generally take orders from the ProgramManager. + */ + //manager.openTrace(trace); + //manager.activateTrace(trace); + List boxList = new ArrayList<>(); + for (MemoryBox memoryBox : boxes.values()) { + boxList.add(memoryBox); + } + Swing.runIfSwingOrRunLater(() -> { + memview.setBoxes(boxList); + memview.setProgram(currentProgram); + memview.initViews(); + }); + } + + private void addHeap(String heapId, Range interval, AddressRange rng, + TraceMemoryFlag read, TraceMemoryFlag write, TraceMemoryFlag execute) { + MemoryBox box = new MemoryBox(heapId, MemviewBoxType.HEAP_CREATE, rng, interval); + boxes.put(box.getId(), box); + } + + private void addThread(String threadId, Range interval, AddressRange rng) { + MemoryBox box = new MemoryBox(threadId, MemviewBoxType.THREAD, rng, interval); + boxes.put(box.getId(), box); + } + + private void addRegion(String regionId, Range interval, AddressRange rng, + TraceMemoryFlag read, TraceMemoryFlag write, TraceMemoryFlag execute) { + MemoryBox box = new MemoryBox(regionId, MemviewBoxType.IMAGE, rng, interval); + boxes.put(box.getId(), box); + } + + private void addLoadedModule(String moduleId, String moduleId2, Range interval, + AddressRange rng) { + MemoryBox box = new MemoryBox(moduleId, MemviewBoxType.MODULE, rng, interval); + boxes.put(box.getId(), box); + } + + private void markThreadClosed(String threadId, Long end) { + MemoryBox box = boxes.get(threadId); + if (box != null) { + if (end < 0) { + end = Long.MAX_VALUE; + } + box.setEnd(end); + } + } + + private void markModuleClosed(String moduleId, Long end) { + MemoryBox box = boxes.get(moduleId); + if (box != null) { + if (end < 0) { + end = Long.MAX_VALUE; + } + box.setEnd(end); + } + } + +} diff --git a/Ghidra/Debug/Debugger/certification.manifest b/Ghidra/Debug/Debugger/certification.manifest index 21cfd14c2d..cee9787af6 100644 --- a/Ghidra/Debug/Debugger/certification.manifest +++ b/Ghidra/Debug/Debugger/certification.manifest @@ -42,6 +42,13 @@ src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html||GHID src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerGoToDialog.png||GHIDRA||||END| src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerListingPlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerModuleImportDialog.png||GHIDRA||||END| +src/main/help/help/topics/DebuggerMemviewPlugin/DebuggerMemviewPlugin.html||GHIDRA||||END| +src/main/help/help/topics/DebuggerMemviewPlugin/images/DebuggerMemviewPlugin.png||GHIDRA||||END| +src/main/help/help/topics/DebuggerMemviewPlugin/images/filter_off.png||GHIDRA||||END| +src/main/help/help/topics/DebuggerMemviewPlugin/images/sync_enabled.png||GHIDRA||||END| +src/main/help/help/topics/DebuggerMemviewPlugin/images/view-refresh.png||Tango Icons - Public Domain|||tango icon set|END| +src/main/help/help/topics/DebuggerMemviewPlugin/images/zoom_in.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| +src/main/help/help/topics/DebuggerMemviewPlugin/images/zoom_out.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/help/help/topics/DebuggerModelServicePlugin/DebuggerModelServicePlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerModulesPlugin/images/DebuggerModuleMapProposalDialog.png||GHIDRA||||END| 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 52045e9a48..848df0c1aa 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml @@ -132,8 +132,12 @@ sortgroup="n" target="help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html" /> + + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/DebuggerMemviewPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/DebuggerMemviewPlugin.html new file mode 100644 index 0000000000..9a678ad06c --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/DebuggerMemviewPlugin.html @@ -0,0 +1,84 @@ + + + + + + + Debugger: Memory View + + + + + +

Debugger: Memory View

+ + + + + + + +
+ +

As in the Threads view, you may wish to follow the evolution of various objects over time. + The Debugger Memview Plugin provides a generic time vs. memory (or memory vs. time) plot of + objects marked as of interest. The plot may be populated from either a script, typically + processing an entire trace, or updated on-the-fly from a live target trace. The objects are + listed in the viewer's table on the left for the purpose of quick identification and sorting, + and displayed in a scrolled plotting panel on the right.

+ +

The panel does NOT preserve distances. Rather, the objects are plotted based on the order of + their lower/upper time/memory bounds. In other words, if two objects are plotted with address + ranges [0x100-0x102] and [0x10000000-0x20000000], positions 1-4 on the address axis will + correspond to the addresses 0x100, 0x102, 0x10000000, and 0x20000000. Mousing over the object + should display it's true bounds, but, in some cases, you will need to locate on the top left + corner to get an accurate tool tip. Points past the maximum time or address will not display + values for that coordinate.

+ +

Memview Table

+ +

The table on the right provides the standard Ghidra table-style interface listing each + object, its starting and ending times and starting and ending addresses, if available. Clicking + on start/end addresses will navigate in the listing. All columns are sortable, and the tables + filterable, as usual. A single selected row will cause a red arrow to be drawn at the top left + corner of the corresponding object in the panel. Applying the filter will redisplay only the + selected objects in the panel, if that option is enabled.

+ +

Memview Panel

+ +

The panel displays all of the object in the table or the table's selection, depending on the + options enabled. Clicking on the panel will set the red arrow at that position and display the + current position in the title. Doubling-clicking on object will cause it to be centered in the + display. The size of the panel can be expanded or contracted on either axis with the Zoom + buttons. The top left corner of the panel (which may or may not be visible depending on + scrolling) always corresponds to the first address and the first tick. Scroll bars are enabled + if any object lies outside the viewable portion of the panel. Drag&drop can also be used to + position the panel view. Ctrl-drag&drop draws a box around a region of the display, and the + enclosed objects are selected in the table.

+ +

Navigation

+ +

Zoom

+ +

The four zoom buttons either contract or expand the view along the time or address axes.

+ +

Toggle Layout

+ +

The default panel view is time vs address. The toggle button serves as both the way to + switch views and to refresh the display.

+ +

Toggle Process + Trace

+ +

By default, as new objects are added to a debugger trace, they appear in the Memview table + and panel. The behavior can be toggled on or off at the user's discretion.

+ +

Toggle Apply + Filter

+ +

This button determines whether the table filter affects the display panel. When toggled on, + only selected objects are displayed in the panel.

+ + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/DebuggerMemviewPlugin.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/DebuggerMemviewPlugin.png new file mode 100644 index 0000000000..25da6faaf5 Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/DebuggerMemviewPlugin.png differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/filter_off.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/filter_off.png new file mode 100644 index 0000000000..6c3918e5f2 Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/filter_off.png differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/sync_enabled.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/sync_enabled.png new file mode 100644 index 0000000000..73137044d7 Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/sync_enabled.png differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/view-refresh.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/view-refresh.png new file mode 100644 index 0000000000..cab4d02c75 Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/view-refresh.png differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/zoom_in.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/zoom_in.png new file mode 100644 index 0000000000..cdf0a52fe0 Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/zoom_in.png differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/zoom_out.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/zoom_out.png new file mode 100644 index 0000000000..07bf98a79c Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemviewPlugin/images/zoom_out.png differ diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewPlugin.java new file mode 100644 index 0000000000..2244a5036c --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewPlugin.java @@ -0,0 +1,98 @@ +/* ### + * 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.memview; + +import java.util.List; + +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.services.DebuggerTraceManagerService; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.program.model.listing.Program; + +@PluginInfo( // + shortDescription = "Displays memory vs time", // + description = "Provides visualiztion/navigation across time/address axes", // + category = PluginCategoryNames.DEBUGGER, // + packageName = DebuggerPluginPackage.NAME, // + status = PluginStatus.RELEASED, // + eventsConsumed = { // + TraceActivatedPluginEvent.class // + }, // + servicesRequired = { // + DebuggerTraceManagerService.class // + }, // + servicesProvided = { // + MemviewService.class // + } // +) +public class DebuggerMemviewPlugin extends AbstractDebuggerPlugin implements MemviewService { + + protected MemviewProvider provider; + private DebuggerMemviewTraceListener listener; + + public DebuggerMemviewPlugin(PluginTool tool) { + super(tool); + } + + @Override + protected void init() { + provider = new MemviewProvider(getTool(), this); + listener = new DebuggerMemviewTraceListener(provider); + super.init(); + } + + @Override + protected void dispose() { + tool.removeComponentProvider(provider); + super.dispose(); + } + + @Override + public void processEvent(PluginEvent event) { + super.processEvent(event); + if (event instanceof TraceActivatedPluginEvent) { + TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event; + listener.coordinatesActivated(ev.getActiveCoordinates()); + } + } + + public MemviewProvider getProvider() { + return provider; + } + + public void toggleTrackTrace() { + listener.toggleTrackTrace(); + } + + @Override + public void setBoxes(List boxList) { + provider.setBoxes(boxList); + } + + @Override + public void initViews() { + provider.initViews(); + } + + @Override + public void setProgram(Program program) { + provider.setProgram(program); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewTraceListener.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewTraceListener.java new file mode 100644 index 0000000000..cfd3132ffd --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewTraceListener.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.memview; + +import java.util.Collection; +import java.util.Objects; + +import com.google.common.collect.Range; + +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.services.TraceRecorder; +import ghidra.async.AsyncDebouncer; +import ghidra.async.AsyncTimer; +import ghidra.program.model.address.*; +import ghidra.trace.model.*; +import ghidra.trace.model.Trace.*; +import ghidra.trace.model.breakpoint.TraceBreakpoint; +import ghidra.trace.model.breakpoint.TraceBreakpointManager; +import ghidra.trace.model.memory.TraceMemoryManager; +import ghidra.trace.model.memory.TraceMemoryRegion; +import ghidra.trace.model.modules.*; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.thread.TraceThreadManager; +import ghidra.util.Swing; + +public class DebuggerMemviewTraceListener extends TraceDomainObjectListener { + + protected MemviewProvider provider; + DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; + Trace currentTrace; + TraceRecorder currentRecorder; + + private boolean trackTrace = true; + private boolean trackThreads = true; + private boolean trackRegions = true; + private boolean trackModules = true; + private boolean trackSections = true; + private boolean trackBreakpoints = true; + private boolean trackBytes = true; + + private final AsyncDebouncer updateLabelDebouncer = + new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, 100); + + public DebuggerMemviewTraceListener(MemviewProvider provider) { + this.provider = provider; + + updateLabelDebouncer.addListener(__ -> Swing.runIfSwingOrRunLater(() -> doUpdateLabel())); + + listenFor(TraceThreadChangeType.ADDED, this::threadChanged); + listenFor(TraceThreadChangeType.CHANGED, this::threadChanged); + listenFor(TraceThreadChangeType.LIFESPAN_CHANGED, this::threadChanged); + listenFor(TraceThreadChangeType.DELETED, this::threadChanged); + + listenFor(TraceMemoryRegionChangeType.ADDED, this::regionChanged); + listenFor(TraceMemoryRegionChangeType.CHANGED, this::regionChanged); + listenFor(TraceMemoryRegionChangeType.LIFESPAN_CHANGED, this::regionChanged); + listenFor(TraceMemoryRegionChangeType.DELETED, this::regionChanged); + + listenFor(TraceModuleChangeType.ADDED, this::moduleChanged); + listenFor(TraceModuleChangeType.CHANGED, this::moduleChanged); + listenFor(TraceModuleChangeType.LIFESPAN_CHANGED, this::moduleChanged); + listenFor(TraceModuleChangeType.DELETED, this::moduleChanged); + + listenFor(TraceSectionChangeType.ADDED, this::sectionChanged); + listenFor(TraceSectionChangeType.CHANGED, this::sectionChanged); + listenFor(TraceSectionChangeType.DELETED, this::sectionChanged); + + listenFor(TraceBreakpointChangeType.ADDED, this::breakpointChanged); + listenFor(TraceBreakpointChangeType.CHANGED, this::breakpointChanged); + listenFor(TraceBreakpointChangeType.LIFESPAN_CHANGED, this::breakpointChanged); + listenFor(TraceBreakpointChangeType.DELETED, this::breakpointChanged); + + listenFor(TraceMemoryBytesChangeType.CHANGED, this::bytesChanged); + } + + public MemviewProvider getProvider() { + return provider; + } + + protected AddressRange rng(AddressSpace space, long min, long max) { + return new AddressRangeImpl(space.getAddress(min), space.getAddress(max)); + } + + private void threadChanged(TraceThread thread) { + if (!trackThreads || !trackTrace) { + return; + } + AddressFactory factory = thread.getTrace().getBaseAddressFactory(); + AddressSpace defaultSpace = factory.getDefaultAddressSpace(); + Long threadId = thread.getKey(); + AddressRange rng = rng(defaultSpace, threadId, threadId); + MemoryBox box = new MemoryBox("Thread " + thread.getName(), MemviewBoxType.THREAD, rng, + thread.getLifespan()); + provider.addBox(box); + } + + private void regionChanged(TraceMemoryRegion region) { + if (!trackRegions || !trackTrace) { + return; + } + MemoryBox box = new MemoryBox("Region " + region.getName(), MemviewBoxType.VIRTUAL_ALLOC, + region.getRange(), region.getLifespan()); + provider.addBox(box); + updateLabelDebouncer.contact(null); + } + + private void moduleChanged(TraceModule module) { + if (!trackModules || !trackTrace) { + return; + } + AddressRange range = module.getRange(); + AddressRange xrange = new AddressRangeImpl(range.getMinAddress(), range.getMaxAddress()); + MemoryBox box = new MemoryBox("Module " + module.getName(), MemviewBoxType.MODULE, xrange, + module.getLifespan()); + provider.addBox(box); + updateLabelDebouncer.contact(null); + } + + private void sectionChanged(TraceSection section) { + if (!trackSections || !trackTrace) { + return; + } + MemoryBox box = new MemoryBox("Section " + section.getName(), MemviewBoxType.IMAGE, + section.getRange(), section.getModule().getLifespan()); + provider.addBox(box); + updateLabelDebouncer.contact(null); + } + + private void breakpointChanged(TraceBreakpoint bpt) { + if (!trackBreakpoints || !trackTrace) { + return; + } + MemoryBox box = new MemoryBox("Breakpoint " + bpt.getName(), MemviewBoxType.BREAKPOINT, + bpt.getRange(), bpt.getLifespan()); + provider.addBox(box); + updateLabelDebouncer.contact(null); + } + + private void bytesChanged(TraceAddressSnapRange range) { + if (!trackBytes || !trackTrace) { + return; + } + Range lifespan = range.getLifespan(); + Range newspan = Range.closedOpen(lifespan.lowerEndpoint(), lifespan.lowerEndpoint()); + MemoryBox box = new MemoryBox("BytesChanged " + range.description(), + MemviewBoxType.WRITE_MEMORY, range.getRange(), newspan); + provider.addBox(box); + updateLabelDebouncer.contact(null); + } + + private void doUpdateLabel() { + //updateLocationLabel(); + } + + 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) { + if (current.equals(coordinates)) { + current = coordinates; + return; + } + boolean doListeners = !Objects.equals(current.getTrace(), coordinates.getTrace()); + if (doListeners) { + removeListener(); + } + current = coordinates; + if (doListeners) { + addListener(); + } + } + + protected DebuggerCoordinates adjustCoordinates(DebuggerCoordinates coordinates) { + // Because the view's snap is changing with or without us.... So go with. + return current.withSnap(coordinates.getSnap()); + } + + public void coordinatesActivated(DebuggerCoordinates coordinates) { + //DebuggerCoordinates adjusted = adjustCoordinates(coordinates); + setCoordinates(coordinates); + Trace trace = coordinates.getTrace(); + if (trace != null) { + Swing.runLater(new Runnable() { + @Override + public void run() { + processTrace(trace); + } + }); + } + } + + public void traceClosed(Trace trace) { + if (current.getTrace() == trace) { + setCoordinates(DebuggerCoordinates.NOWHERE); + } + } + + public void toggleTrackTrace() { + trackTrace = !trackTrace; + } + + private void processTrace(Trace trace) { + provider.reset(); + TraceThreadManager threadManager = trace.getThreadManager(); + for (TraceThread thread : threadManager.getAllThreads()) { + threadChanged(thread); + } + TraceModuleManager moduleManager = trace.getModuleManager(); + for (TraceModule module : moduleManager.getAllModules()) { + moduleChanged(module); + Collection sections = module.getSections(); + for (TraceSection section : sections) { + sectionChanged(section); + } + } + TraceMemoryManager memoryManager = trace.getMemoryManager(); + for (TraceMemoryRegion region : memoryManager.getAllRegions()) { + regionChanged(region); + } + TraceBreakpointManager breakpointManager = trace.getBreakpointManager(); + for (TraceBreakpoint bpt : breakpointManager.getAllBreakpoints()) { + breakpointChanged(bpt); + } + updateLabelDebouncer.contact(null); + } + +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemoryBox.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemoryBox.java new file mode 100644 index 0000000000..0195d0c588 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemoryBox.java @@ -0,0 +1,249 @@ +/* ### + * 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.memview; + +import java.awt.Color; +import java.awt.Graphics; +import java.util.HashMap; +import java.util.Map; + +import com.google.common.collect.Range; + +import ghidra.program.model.address.AddressRange; + +public class MemoryBox { + + protected String id; + protected MemviewBoxType type; + protected AddressRange range; + protected long start; + protected long stop = Long.MAX_VALUE; + protected long startAddr; + protected long stopAddr = -1; + protected long startTime; + protected long stopTime = -1; + protected Color color = Color.BLUE; + + protected int pixAstart; + protected int pixAend; + protected int boundA; + protected int pixTstart; + protected int pixTend; + protected int boundT; + + protected boolean current; + + public MemoryBox(String id, MemviewBoxType type, AddressRange range, long tick) { + this.id = id; + this.type = type; + this.range = range; + this.start = tick; + this.color = type.getColor(); + } + + public MemoryBox(String id, MemviewBoxType type, AddressRange range, Range trange) { + this(id, type, range, trange.lowerEndpoint()); + if (trange.hasUpperBound()) { + setEnd(trange.upperEndpoint()); + } + } + + public String getId() { + return id; + } + + public MemviewBoxType getType() { + return type; + } + + public AddressRange getRange() { + return range; + } + + public Range getSpan() { + return Range.openClosed(start, stop); + } + + public long getStart() { + return start; + } + + public long getEnd() { + return stop; + } + + public void setEnd(long tick) { + this.stop = stop < tick ? stop : tick; + } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color; + } + + public void setColor(Color base, int type) { + setColor(new Color(base.getRed(), (base.getGreen() + type) % 255, base.getBlue())); + } + + public void setColor(Color base, int type, int src) { + setColor(new Color(base.getRed(), (base.getGreen() + type * 8) % 255, + (base.getBlue() + src * 16) % 255)); + } + + public int getAddressPixelStart() { + return pixAstart; + } + + public int getAddressPixelWidth() { + if (pixAend - pixAstart <= 0) + return 1; + return pixAend - pixAstart; + } + + public int getTimePixelStart() { + return pixTstart; + } + + public int getTimePixelWidth() { + if (pixTend < pixTstart) { + pixTend = boundT; + } + if (pixTend - pixTstart == 0) + return 1; + return pixTend - pixTstart; + } + + public int getX(boolean vertical) { + return vertical ? pixTstart : pixAstart; + } + + public int getY(boolean vertical) { + return vertical ? pixAstart : pixTstart; + } + + public void setCurrent(boolean current) { + this.current = current; + } + + public boolean isCurrent() { + return current; + } + + public void render(Graphics g, boolean vertical) { + int x = vertical ? getTimePixelStart() : getAddressPixelStart(); + int w = vertical ? getTimePixelWidth() : getAddressPixelWidth(); + int y = vertical ? getAddressPixelStart() : getTimePixelStart(); + int h = vertical ? getAddressPixelWidth() : getTimePixelWidth(); + g.setColor(Color.BLACK); + g.fillRect(x - 1, y - 1, w + 2, h + 2); + g.setColor(color); + g.fillRect(x, y, w, h); + } + + public void renderBA(Graphics g, boolean vertical, int sz) { + int x = vertical ? 0 : getAddressPixelStart(); + int w = vertical ? sz : getAddressPixelWidth(); + int y = vertical ? getAddressPixelStart() : 0; + int h = vertical ? getAddressPixelWidth() : sz; + g.setColor(Color.BLACK); + g.fillRect(x - 1, y - 1, w + 2, h + 2); + g.setColor(color); + g.fillRect(x, y, w, h); + } + + public void renderBT(Graphics g, boolean vertical, int sz, int bound) { + int x = vertical ? getTimePixelStart() : 0; + int w = vertical ? 1 : sz; + int y = vertical ? 0 : getTimePixelStart(); + int h = vertical ? sz : 1; + g.setColor(Color.BLACK); + g.fillRect(x - 1, y - 1, w + 2, h + 2); + g.setColor(color); + g.fillRect(x, y, w, h); + } + + public void setAddressBounds(MemviewMap map, int bound) { + if (stopAddr < 0) { + stopAddr = startAddr; + } + pixAstart = getPixel(map, startAddr, bound); + pixAend = getPixel(map, stopAddr, bound); + boundA = bound; + } + + public void setTimeBounds(MemviewMap map, int bound) { + pixTstart = getPixel(map, startTime, bound); + pixTend = getPixel(map, stopTime, bound); + boundT = bound; + } + + protected int getPixel(MemviewMap map, long offset, int bound) { + return map.getPixel(offset); + } + + public long getStartAddress() { + return startAddr; + } + + public void setStartAddress(long val) { + startAddr = val; + } + + public long getStopAddress() { + return stopAddr; + } + + public void setStopAddress(long val) { + stopAddr = val; + } + + public long getStartTime() { + return startTime; + } + + public void setStartTime(long val) { + startTime = val; + } + + public long getStopTime() { + return stopTime; + } + + public void setStopTime(long val) { + stopTime = val; + } + + public boolean inPixelRange(long pos) { + if (pos < pixTstart) + return false; + if (pixTend <= 0) + return true; + return pos <= pixTend; + } + + public Map getAttributeMap() { + Map map = new HashMap<>(); + map.put("Id", getId()); + map.put("StartAddr", getStartAddress()); + map.put("StopAddr", getStopAddress()); + map.put("StartTime", getStartTime()); + map.put("StopTIme", getStopTime()); + return map; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewBoxType.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewBoxType.java new file mode 100644 index 0000000000..ff6408e8e0 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewBoxType.java @@ -0,0 +1,62 @@ +/* ### + * 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.memview; + +import java.awt.Color; + +public enum MemviewBoxType { + INSTRUCTIONS, + PROCESS, + THREAD, + MODULE, + REGION, + IMAGE, + VIRTUAL_ALLOC, + HEAP_CREATE, + HEAP_ALLOC, + POOL, + STACK, + PERFINFO, + READ_MEMORY, + WRITE_MEMORY, + BREAKPOINT; + + Color[] colors = { // + new Color(128, 000, 000), // INSTRUCTIONS + new Color(200, 200, 255), // PROCESS + new Color(200, 255, 255), // THREAD + Color.GREEN, //new Color(000, 150, 200), // MODULE + Color.YELLOW, //new Color(000, 150, 200), // REGION + Color.MAGENTA, //new Color(050, 100, 255), // IMAGE + Color.LIGHT_GRAY, // VIRTUAL_ALLOC + Color.BLUE, // HEAP_CREATE + new Color(000, 100, 050), // HEAP_ALLOC + new Color(100, 000, 150), // POOL + Color.CYAN, // STACK + Color.LIGHT_GRAY, // PERFINFO + Color.DARK_GRAY, // READ_MEMORY + Color.BLUE, // WRITE_MEMORY + Color.RED, // WRITE_MEMORY + }; + + public Color getColor() { + return colors[this.ordinal()]; + } + + public void setColor(Color color) { + colors[this.ordinal()] = color; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewMap.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewMap.java new file mode 100644 index 0000000000..df02e5bdc7 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewMap.java @@ -0,0 +1,58 @@ +/* ### + * 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.memview; + +public class MemviewMap { + + private long max; + private long sz; + private double elementsPerPixel; + private double multiplier; + + public MemviewMap(long elems, long pixels) { + max = sz = elems; + elementsPerPixel = pixels == 0 ? 0 : elems / pixels; + multiplier = 1.0; + } + + public void createMapping(double mult) { + this.multiplier = mult; + } + + public long getOffset(int pixel) { + return Math.round(pixel * elementsPerPixel / multiplier); + } + + public int getPixel(long offset) { + if (offset < 0) { + offset = max; + } + double doffset = offset * multiplier / elementsPerPixel; + return (int) Math.round(doffset); + } + + public long getSize() { + return getPixel(max); + } + + public double getMultiplier() { + return multiplier; + } + + public double getOriginalElemPerPixel() { + return elementsPerPixel; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewMapModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewMapModel.java new file mode 100644 index 0000000000..d150793213 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewMapModel.java @@ -0,0 +1,245 @@ +/* ### + * 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.memview; + +import java.util.*; + +import docking.widgets.table.AbstractSortedTableModel; +import ghidra.program.model.address.Address; + +class MemviewMapModel extends AbstractSortedTableModel { + + final static byte NAME = 0; + final static byte ASTART = 1; + final static byte ASTOP = 2; + final static byte TSTART = 3; + final static byte TSTOP = 4; + + final static String NAME_COL = "Name"; + final static String ASTART_COL = "Start Address"; + final static String ASTOP_COL = "End Address"; + final static String TSTART_COL = "Start Time"; + final static String TSTOP_COL = "End Time"; + + private List memList = new ArrayList<>(); + private Map memMap = new HashMap<>(); + private MemviewProvider provider; + + private final static String COLUMN_NAMES[] = + { NAME_COL, ASTART_COL, ASTOP_COL, TSTART_COL, TSTOP_COL }; + + public MemviewMapModel(MemviewProvider provider) { + super(ASTART); + this.provider = provider; + } + + public List getBoxes() { + return memList; + } + + public void addBoxes(Collection boxes) { + if (memList == null) { + memList = new ArrayList<>(); + } + for (MemoryBox b : boxes) { + if (memMap.containsKey(b.getId())) { + MemoryBox mb = memMap.get(b.getId()); + memList.remove(mb); + } + memList.add(b); + memMap.put(b.getId(), b); + } + fireTableDataChanged(); + } + + public void setBoxes(Collection boxes) { + memList = new ArrayList<>(); + for (MemoryBox b : boxes) { + memList.add(b); + memMap.put(b.getId(), b); + } + fireTableDataChanged(); + } + + public void reset() { + memList = new ArrayList<>(); + memMap.clear(); + fireTableDataChanged(); + } + + void update() { + } + + @Override + public boolean isSortable(int columnIndex) { + return true; + } + + @Override + public String getName() { + return "Memory vs Time Map"; + } + + @Override + public int getColumnCount() { + return COLUMN_NAMES.length; + } + + @Override + public String getColumnName(int column) { + + if (column < 0 || column >= COLUMN_NAMES.length) { + return "UNKNOWN"; + } + + return COLUMN_NAMES[column]; + } + + /** + * Convenience method for locating columns by name. + * Implementation is naive so this should be overridden if + * this method is to be called often. This method is not + * in the TableModel interface and is not used by the JTable. + */ + @Override + public int findColumn(String columnName) { + for (int i = 0; i < COLUMN_NAMES.length; i++) { + if (COLUMN_NAMES[i].equals(columnName)) { + return i; + } + } + return 0; + } + + /** + * Returns Object.class by default + */ + @Override + public Class getColumnClass(int columnIndex) { + if (columnIndex == ASTART || columnIndex == ASTOP) { + return Address.class; + } + return String.class; + } + + /** + * Return whether this column is editable. + */ + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + + /** + * Returns the number of records managed by the data source object. A + * JTable uses this method to determine how many rows it + * should create and display. This method should be quick, as it + * is call by JTable quite frequently. + * + * @return the number or rows in the model + * @see #getColumnCount + */ + @Override + public int getRowCount() { + return memList.size(); + } + + public MemoryBox getBoxAt(int rowIndex) { + if (memList == null) { + return null; + } + if (rowIndex < 0 || rowIndex >= memList.size()) { + return null; + } + MemoryBox box = memList.get(rowIndex); + try { + box.getStart(); + } + catch (ConcurrentModificationException e) { + update(); + } + return memList.get(rowIndex); + } + + public int getIndexForBox(MemoryBox box) { + return memList.indexOf(box); + } + + @Override + public Object getColumnValueForRow(MemoryBox box, int columnIndex) { + try { + switch (columnIndex) { + case NAME: + return box.getId(); + case ASTART: + return box.getRange().getMinAddress(); + case ASTOP: + return box.getRange().getMaxAddress(); + case TSTART: + return Long.toString(box.getStart()); + case TSTOP: + long end = box.getEnd(); + if (end == Long.MAX_VALUE) { + return "+" + '\u221e' + '\u2025'; + } + return Long.toString(end); + default: + return "UNKNOWN"; + } + } + catch (ConcurrentModificationException e) { + update(); + } + return null; + } + + @Override + public List getModelData() { + return memList; + } + + @Override + protected Comparator createSortComparator(int columnIndex) { + return new MemoryMapComparator(columnIndex); + } + + private class MemoryMapComparator implements Comparator { + private final int sortColumn; + + public MemoryMapComparator(int sortColumn) { + this.sortColumn = sortColumn; + } + + @Override + public int compare(MemoryBox b1, MemoryBox b2) { + + switch (sortColumn) { + case NAME: + return b1.getId().compareToIgnoreCase(b2.getId()); + case ASTART: + return (int) (b1.getStartAddress() - b2.getStartAddress()); + case ASTOP: + return (int) (b1.getStopAddress() - b2.getStopAddress()); + case TSTART: + return (int) (b1.getStartTime() - b2.getStartTime()); + case TSTOP: + return (int) (b1.getStopTime() - b2.getStopTime()); + default: + return 0; + } + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewPanel.java new file mode 100644 index 0000000000..5f20740de9 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewPanel.java @@ -0,0 +1,489 @@ +/* ### + * 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.memview; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import java.util.List; + +import javax.swing.*; + +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressRange; + +public class MemviewPanel extends JPanel implements MouseListener, MouseMotionListener { + private static final long serialVersionUID = 1L; + + private MemviewProvider provider; + private MemviewMap amap; + private MemviewMap tmap; + private List boxList = new ArrayList(); + + private int pressedX; + private int pressedY; + private boolean enableDrag = false; + private boolean ctrlPressed = false; + private int barWidth = 1000; + private int barHeight = 500; + private boolean vertical = false; + + private int currentPixelAddr = -1; + private int currentPixelTime = -1; + private Rectangle currentRectangle = null; + + private List blist = null; + private Map bmap = new HashMap<>(); + + private TreeSet
addresses = new TreeSet<>(); + private TreeSet times = new TreeSet<>(); + private Address[] addressArray; + private Long[] timesArray; + private Map> addr2box = new HashMap<>(); + private Map> time2box = new HashMap<>(); + + public MemviewPanel(MemviewProvider provider) { + super(); + this.provider = provider; + setPreferredSize(new Dimension(barWidth, barHeight)); + setSize(getPreferredSize()); + setBorder(BorderFactory.createLineBorder(Color.BLACK, 1)); + setFocusable(true); + + addMouseListener(this); + addMouseMotionListener(this); + ToolTipManager.sharedInstance().registerComponent(this); + } + + @Override + public Dimension getPreferredSize() { + int asz = amap != null ? (int) (amap.getSize()) : 500; + int tsz = tmap != null ? (int) (tmap.getSize()) : 500; + int w = vertical ? tsz : asz; + int h = vertical ? asz : tsz; + return new Dimension(w, h); + } + + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + g.setColor(getBackground()); + Rectangle clip = g.getClipBounds(); + g.fillRect(clip.x, clip.y, clip.width, clip.height); + + //If the width has changed, force a refresh + int height = getHeight(); + int width = getWidth(); + if (vertical && clip.height > height || !vertical && clip.width > width) { + refresh(); + return; + } + + g.fillRect(0, 0, width, height); + + for (MemoryBox box : boxList) { + box.render(g, vertical); + } + + //Draw the current location arrow + if (currentPixelAddr >= 0) { + drawArrow(g); + } + if (currentRectangle != null) { + drawFrame(g); + } + } + + private static final int LOCATION_BASE_WIDTH = 1; + private static final int LOCATION_BASE_HEIGHT = 6; + private static final int LOCATION_ARROW_WIDTH = 3; + private static final int LOCATION_ARROW_HEIGHT = 9; + private static final int[] locXs = { 0, -LOCATION_BASE_WIDTH, -LOCATION_BASE_WIDTH, + -LOCATION_ARROW_WIDTH, 0, LOCATION_ARROW_WIDTH, LOCATION_BASE_WIDTH, LOCATION_BASE_WIDTH }; + private static final int[] locYs = { 0, 0, LOCATION_BASE_HEIGHT, LOCATION_BASE_HEIGHT, + LOCATION_ARROW_HEIGHT, LOCATION_BASE_HEIGHT, LOCATION_BASE_HEIGHT, 0 }; + + private void drawArrow(Graphics g) { + Graphics2D g2 = (Graphics2D) g; + if (vertical) { + g2.rotate(90.0f / 180.0f * Math.PI); + g2.translate(0, LOCATION_ARROW_HEIGHT); + g.translate(currentPixelAddr, -currentPixelTime); + g2.rotate(Math.PI); + } + else { + g2.translate(0, -LOCATION_ARROW_HEIGHT); + g.translate(currentPixelAddr, currentPixelTime); + } + + g.setColor(Color.RED); + g.fillPolygon(locXs, locYs, locXs.length); + + if (vertical) { + g2.rotate(Math.PI); + g.translate(-currentPixelAddr, currentPixelTime); + g2.translate(0, -LOCATION_ARROW_HEIGHT); + g2.rotate(-90.0f / 180.0f * Math.PI); + } + else { + g.translate(-currentPixelAddr, -currentPixelTime); + g2.translate(0, LOCATION_ARROW_HEIGHT); + } + } + + private void drawFrame(Graphics g) { + int x = currentRectangle.x; + int y = currentRectangle.y; + int w = currentRectangle.width; + int h = currentRectangle.height; + g.setColor(Color.RED); + g.fillRect(x - 1, y - 1, 1, h + 2); + g.fillRect(x - 1, y - 1, w + 2, 1); + g.fillRect(x + w + 1, y - 1, 1, h + 2); + g.fillRect(x - 1, y + h + 1, w + 2, 1); + } + + void initViews() { + setSize(new Dimension(vertical ? times.size() : addresses.size(), + vertical ? addresses.size() : times.size())); + this.amap = new MemviewMap(addresses.size(), addresses.size()); + this.tmap = new MemviewMap(times.size(), times.size()); + } + + public void refresh() { + if (amap == null || tmap == null) { + return; + } + if (vertical) { + amap.createMapping(provider.getZoomAmountA()); + tmap.createMapping(provider.getZoomAmountT()); + } + else { + amap.createMapping(provider.getZoomAmountA()); + tmap.createMapping(provider.getZoomAmountT()); + } + + updateBoxes(); + } + + void updateBoxes() { + if (!this.isShowing()) + return; + + boxList = new ArrayList(); + Collection boxes = getBoxes(); + if (boxes == null) { + return; + } + for (MemoryBox box : boxes) { + if (box == null) + continue; + + int bound = vertical ? getHeight() - 1 : getWidth() - 1; + box.setAddressBounds(amap, bound); + bound = vertical ? getWidth() - 1 : getHeight() - 1; + box.setTimeBounds(tmap, bound); + + boxList.add(box); + } + + repaint(0, 0, getWidth(), getHeight()); + } + + @Override + public void mousePressed(MouseEvent e) { + requestFocus(); + + ctrlPressed = false; + currentRectangle = null; + + if (e.getButton() == MouseEvent.BUTTON1) { + enableDrag = true; + pressedX = e.getX(); + pressedY = e.getY(); + currentPixelAddr = vertical ? pressedY : pressedX; + currentPixelTime = vertical ? pressedX : pressedY; + provider.selectTableEntry(getBoxesAt(pressedX, pressedY)); + provider.refresh(); + } + + if (e.getButton() == MouseEvent.BUTTON2) { + System.err.println("BUTTON2"); + } + + if (e.getButton() == MouseEvent.BUTTON3) { + ctrlPressed = true; + enableDrag = true; + pressedX = e.getX(); + pressedY = e.getY(); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + enableDrag = false; + } + + @Override + public void mouseClicked(MouseEvent e) { + enableDrag = false; + } + + @Override + public void mouseEntered(MouseEvent e) { + // Nothing to do + } + + @Override + public void mouseExited(MouseEvent e) { + // Nothing to do + } + + @Override + public void mouseDragged(MouseEvent e) { + if (enableDrag) { + if (!ctrlPressed) { + provider.goTo(pressedX - e.getX(), pressedY - e.getY()); + } + else { + currentRectangle = + new Rectangle(pressedX, pressedY, e.getX() - pressedX, e.getY() - pressedY); + provider.selectTableEntry(getBoxesIn(currentRectangle)); + provider.refresh(); + } + } + } + + @Override + public void mouseMoved(MouseEvent e) { + // Nothing to do + } + + public void setSelection(Set boxes) { + for (MemoryBox memoryBox : boxes) { + currentPixelAddr = memoryBox.pixAstart; + currentPixelTime = memoryBox.pixTstart; + refresh(); + } + } + + public String getTitleAnnotation() { + if (currentPixelAddr < 0 || addressArray == null) { + return ""; + } + String aval = getTagForAddr(currentPixelAddr); + String tval = getTagForTick(currentPixelTime); + String vals = vertical ? tval + ":" + aval : aval + ":" + tval; + return "curpos=[" + vals + "]"; + } + + public Set getBoxesAt(int x, int y) { + long addr = getAddr(x, y); + long tick = getTick(x, y); + long pos = vertical ? x : y; + Set matches = new HashSet<>(); + Set mboxes = addr2box.get(addr); + if (mboxes != null && tick < timesArray.length) { + for (MemoryBox memoryBox : mboxes) { + if (memoryBox.inPixelRange(pos)) { + matches.add(memoryBox); + } + } + } + return matches; + } + + public Set getBoxesIn(Rectangle r) { + long startAddr = getAddr(r.x, r.y); + long startTick = getTick(r.x, r.y); + long stopAddr = getAddr(r.x + r.width, r.y + r.height); + long stopTick = getTick(r.x + r.width, r.y + r.height); + Set matches = new HashSet<>(); + for (long addr = startAddr; addr < stopAddr; addr++) { + Set mboxes = addr2box.get(addr); + for (MemoryBox memoryBox : mboxes) { + if (memoryBox.getStartTime() >= startTick || memoryBox.getStopTime() <= stopTick) { + matches.add(memoryBox); + } + } + } + return matches; + } + + @Override + public String getToolTipText(MouseEvent e) { + if (amap == null || tmap == null) { + return e.getX() + ":" + e.getY(); + } + long addr = getAddr(e.getX(), e.getY()); + long tick = getTick(e.getX(), e.getY()); + String aval = getTagForAddr(addr); + String tval = getTagForTick(tick); + Set boxes = getBoxesAt(e.getX(), e.getY()); + for (MemoryBox memoryBox : boxes) { + aval = memoryBox.getId(); + } + return vertical ? tval + ":" + aval : aval + ":" + tval; + } + + private void parseBoxes(Collection boxes) { + addresses.clear(); + times.clear(); + addr2box.clear(); + time2box.clear(); + + for (MemoryBox box : boxes) { + AddressRange range = box.getRange(); + if (range != null) { + addresses.add(range.getMinAddress()); + addresses.add(range.getMaxAddress()); + } + long start = box.getStart(); + long end = box.getEnd(); + times.add(start); + times.add(end); + } + + initViews(); + addressArray = new Address[addresses.size()]; + timesArray = new Long[times.size()]; + addresses.toArray(addressArray); + times.toArray(timesArray); + + for (MemoryBox box : boxes) { + AddressRange range = box.getRange(); + if (range != null) { + box.setStartAddress(addresses.headSet(range.getMinAddress()).size()); + box.setStopAddress(addresses.headSet(range.getMaxAddress()).size()); + } + box.setStartTime(times.headSet(box.getStart()).size()); + box.setStopTime(times.headSet(box.getEnd()).size()); + + Set mboxes = addr2box.get(box.getStartAddress()); + if (mboxes == null) { + mboxes = new HashSet(); + } + mboxes.add(box); + addr2box.put(box.getStartAddress(), mboxes); + mboxes = addr2box.get(box.getStopAddress()); + if (mboxes == null) { + mboxes = new HashSet(); + } + mboxes.add(box); + addr2box.put(box.getStopAddress(), mboxes); + + mboxes = time2box.get(box.getStartTime()); + if (mboxes == null) { + mboxes = new HashSet(); + } + mboxes.add(box); + time2box.put(box.getStartTime(), mboxes); + mboxes = time2box.get(box.getStopTime()); + if (mboxes == null) { + mboxes = new HashSet(); + } + mboxes.add(box); + time2box.put(box.getStopTime(), mboxes); + } + refresh(); + } + + public List getBoxes() { + return blist; + } + + public void setBoxes(List boxes) { + this.blist = boxes; + for (MemoryBox b : boxes) { + bmap.put(b.getId(), b); + } + parseBoxes(blist); + } + + public void addBoxes(List boxes) { + if (blist == null) { + blist = new ArrayList(); + } + for (MemoryBox b : boxes) { + if (bmap.containsKey(b.getId())) { + MemoryBox box = bmap.get(b.getId()); + blist.remove(box); + } + blist.add(b); + bmap.put(b.getId(), b); + } + parseBoxes(blist); + } + + public void reset() { + blist = new ArrayList(); + bmap.clear(); + parseBoxes(blist); + } + + void setAddressPixelMap(MemviewMap map) { + this.amap = map; + } + + void setTimePixelMap(MemviewMap tmap) { + this.tmap = tmap; + } + + public boolean getVerticalMode() { + return vertical; + } + + public void setVerticalMode(boolean vertical) { + this.vertical = vertical; + } + + public long getAddr(int x, int y) { + if (amap == null) + return 0; + return vertical ? amap.getOffset(y) : amap.getOffset(x); + } + + public long getTick(int x, int y) { + if (tmap == null) + return 0; + return vertical ? tmap.getOffset(x) : tmap.getOffset(y); + } + + public String getTagForAddr(long addr) { + String aval = ""; + if (0 <= addr && addr < addressArray.length) { + aval = addressArray[(int) addr].toString(); + } + return aval; + } + + public String getTagForTick(long tick) { + String tval = ""; + if (0 <= tick && tick < timesArray.length) { + tval = Long.toString(timesArray[(int) tick]); + } + return tval; + } + + public void scaleCurrentPixelAddr(double changeAmount) { + this.currentPixelAddr = (int) (currentPixelAddr * Math.pow(2.0, changeAmount)); + } + + public void scaleCurrentPixelTime(double changeAmount) { + this.currentPixelTime = (int) (currentPixelTime * Math.pow(2.0, changeAmount)); + } + +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewProvider.java new file mode 100644 index 0000000000..e8d09bcfcf --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewProvider.java @@ -0,0 +1,302 @@ +/* ### + * 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.memview; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import java.util.List; + +import javax.swing.*; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.action.builder.ToggleActionBuilder; +import ghidra.app.plugin.core.debug.DebuggerPluginPackage; +import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractRefreshAction; +import ghidra.app.plugin.core.debug.gui.memview.actions.*; +import ghidra.app.services.DebuggerListingService; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.AutoService.Wiring; +import ghidra.framework.plugintool.annotation.AutoServiceConsumed; +import ghidra.program.model.listing.Program; +import ghidra.util.HelpLocation; +import ghidra.util.Swing; + +public class MemviewProvider extends ComponentProviderAdapter { + + private static final String TITLE = "Memview"; + + private Wiring autoServiceWiring; + @AutoServiceConsumed + private DebuggerListingService listingService; + + private DebuggerMemviewPlugin plugin; + private JComponent mainPanel; + private MemviewPanel memviewPanel; + private MemviewTable memviewTable; + private JScrollPane scrollPane; + + //private Address location; + private boolean vertical = true; + private boolean applyFilter = true; + + private double zoomAmountA = 1.0; + private double zoomAmountT = 1.0; + long halfPage = 512L; + + public MemviewProvider(PluginTool tool, DebuggerMemviewPlugin plugin) { + super(tool, TITLE, plugin.getName()); + this.plugin = plugin; + + this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); + + //setIcon(DebuggerResources.ICON_PROVIDER_REGIONS); + //setHelpLocation(DebuggerResources.HELP_PROVIDER_REGIONS); + setWindowMenuGroup(DebuggerPluginPackage.NAME); + + mainPanel = new JPanel(new BorderLayout()); + mainPanel.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + resized(); + } + }); + tool.addComponentProvider(this, false); + createActions(); + buildPanel(); + } + + private void createActions() { + + DockingAction zoomInAAction = new ZoomInAAction(this); + tool.addLocalAction(this, zoomInAAction); + + DockingAction zoomOutAAction = new ZoomOutAAction(this); + tool.addLocalAction(this, zoomOutAAction); + + DockingAction zoomInTAction = new ZoomInTAction(this); + tool.addLocalAction(this, zoomInTAction); + + DockingAction zoomOutTAction = new ZoomOutTAction(this); + tool.addLocalAction(this, zoomOutTAction); + + new ToggleActionBuilder("Toggle Layout", plugin.getName()) // + //.menuPath("&Toggle layout") // + .toolBarIcon(AbstractRefreshAction.ICON) + .helpLocation(new HelpLocation(plugin.getName(), "toggle_layout")) // + .onAction(ctx -> performToggleLayout(ctx)) + .buildAndInstallLocal(this); + + new ToggleActionBuilder("Toggle Process Trace", plugin.getName()) // + //.menuPath("&Toggle layout") // + .toolBarIcon(DebuggerResources.ICON_SYNC) + .helpLocation(new HelpLocation(plugin.getName(), "toggle_process_trace")) // + .onAction(ctx -> performToggleTrace(ctx)) + .selected(true) + .buildAndInstallLocal(this); + + new ToggleActionBuilder("Apply Filter To Panel", plugin.getName()) // + //.menuPath("&Toggle layout") // + .toolBarIcon(DebuggerResources.ICON_FILTER) + .helpLocation(new HelpLocation(plugin.getName(), "apply_to_panel")) // + .onAction(ctx -> performApplyFilterToPanel(ctx)) + .selected(true) + .buildAndInstallLocal(this); + + } + + void buildPanel() { + mainPanel.removeAll(); + memviewPanel = new MemviewPanel(this); + memviewTable = new MemviewTable(this); + + scrollPane = new JScrollPane(memviewPanel); + scrollPane.setPreferredSize(memviewPanel.getSize()); + + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + splitPane.setRightComponent(scrollPane); + splitPane.setLeftComponent(memviewTable.getComponent()); + splitPane.setDividerLocation(0.5); + mainPanel.add(splitPane, BorderLayout.CENTER); + //mainPanel.add(memviewTable.getComponent(), BorderLayout.WEST); + + setDirection(); + + mainPanel.validate(); + } + + private void performToggleLayout(ActionContext ctx) { + vertical = !vertical; + setDirection(); + refresh(); + } + + private void performToggleTrace(ActionContext ctx) { + plugin.toggleTrackTrace(); + } + + private void performApplyFilterToPanel(ActionContext ctx) { + applyFilter = !isApplyFilter(); + if (applyFilter) { + memviewTable.applyFilter(); + } + else { + memviewPanel.setBoxes(memviewTable.getBoxes()); + } + refresh(); + } + + private void setDirection() { + memviewPanel.setVerticalMode(vertical); + } + + void dispose() { + tool.removeComponentProvider(this); + } + + @Override + public ActionContext getActionContext(MouseEvent event) { + if (event != null && event.getSource() == mainPanel) { + return new ActionContext(this, mainPanel); + } + if (event != null && event.getSource() == memviewPanel) { + return new ActionContext(this, memviewPanel); + } + return null; + } + + @Override + public JComponent getComponent() { + return mainPanel; + } + + @Override + public HelpLocation getHelpLocation() { + return new HelpLocation(plugin.getName(), plugin.getName()); + } + + public void setProgram(Program program) { + memviewTable.setProgram(program); + } + + public void initViews() { + memviewPanel.initViews(); + memviewPanel.setPreferredSize(new Dimension(300, 100)); + memviewTable.setListingService(listingService); + mainPanel.doLayout(); + mainPanel.repaint(); + } + + public void refresh() { + String subTitle = " (" + zoomAmountA + "x:" + zoomAmountT + ") "; + subTitle += memviewPanel.getTitleAnnotation(); + setSubTitle(subTitle); + memviewPanel.refresh(); + scrollPane.getViewport().doLayout(); + } + + public void goTo(int x, int y) { + Rectangle bounds = scrollPane.getBounds(); + scrollPane.getViewport() + .scrollRectToVisible(new Rectangle(x, y, bounds.width, bounds.height)); + scrollPane.getViewport().doLayout(); + } + + public void goTo(MemoryBox box) { + Point p = new Point(box.getX(vertical) - 10, box.getY(vertical) - 10); + Point p0 = scrollPane.getViewport().getViewPosition(); + int w = scrollPane.getViewport().getWidth(); + int h = scrollPane.getViewport().getHeight(); + if (p.x > p0.x && p.x < p0.x + w && p.y > p0.y && p.y < p0.y + h) { + return; + } + scrollPane.getViewport().setViewPosition(p); + } + + public void selectTableEntry(Set boxes) { + memviewTable.setSelection(boxes); + } + + public void selectPanelPosition(Set boxes) { + memviewPanel.setSelection(boxes); + if (boxes.size() == 1) { + Iterator iterator = boxes.iterator(); + goTo(iterator.next()); + } + } + + public void changeZoomA(int changeAmount) { + this.zoomAmountA = (float) (zoomAmountA * Math.pow(2.0, changeAmount)); + memviewPanel.scaleCurrentPixelAddr(changeAmount); + } + + public double getZoomAmountA() { + return zoomAmountA; + } + + public void changeZoomT(int changeAmount) { + this.zoomAmountT = (float) (zoomAmountT * Math.pow(2.0, changeAmount)); + memviewPanel.scaleCurrentPixelTime(changeAmount); + } + + public double getZoomAmountT() { + return zoomAmountT; + } + + void resized() { + memviewPanel.refresh(); + } + + public void setBoxes(List blist) { + Swing.runIfSwingOrRunLater(() -> { + memviewTable.setBoxes(blist); + memviewPanel.setBoxes(blist); + }); + } + + public void setBoxesInPanel(List blist) { + Swing.runIfSwingOrRunLater(() -> { + memviewPanel.setBoxes(blist); + memviewPanel.refresh(); + }); + } + + public void addBox(MemoryBox box) { + List blist = new ArrayList<>(); + blist.add(box); + addBoxes(blist); + } + + public void addBoxes(List blist) { + Swing.runIfSwingOrRunLater(() -> { + memviewTable.addBoxes(blist); + memviewPanel.addBoxes(blist); + }); + } + + public boolean isApplyFilter() { + return applyFilter; + } + + public void reset() { + Swing.runIfSwingOrRunLater(() -> { + memviewTable.reset(); + memviewPanel.reset(); + }); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewService.java new file mode 100644 index 0000000000..921705a65a --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewService.java @@ -0,0 +1,38 @@ +/* ### + * 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.memview; + +import java.util.List; + +import ghidra.framework.plugintool.ServiceInfo; +import ghidra.program.model.listing.Program; + +/** + * The MemviewService provides a general service for displaying objects + * on time vs. memory axes (a la Boxes) + */ +@ServiceInfo(defaultProvider = DebuggerMemviewPlugin.class, description = "Display memory vs. time events") +public interface MemviewService { + + public void setBoxes(List boxList); + + public void initViews(); + + public void setProgram(Program currentProgram); + + public MemviewProvider getProvider(); + +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewTable.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewTable.java new file mode 100644 index 0000000000..39d68a351e --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewTable.java @@ -0,0 +1,288 @@ +/* ### + * 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.memview; + +import java.awt.BorderLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.*; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import docking.widgets.filter.FilterListener; +import ghidra.app.services.DebuggerListingService; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressRangeImpl; +import ghidra.program.model.listing.Program; +import ghidra.util.table.GhidraTable; +import ghidra.util.table.GhidraTableFilterPanel; +import ghidra.util.task.SwingUpdateManager; +import resources.ResourceManager; + +public class MemviewTable { + + public static final ImageIcon ICON_TABLE = ResourceManager.loadImage("images/table.png"); + + private MemviewMapModel model; + private GhidraTable table; + private GhidraTableFilterPanel filterPanel; + private FilterListener filterListener; + private SwingUpdateManager applyFilterManager = new SwingUpdateManager(this::applyFilter); + private JPanel component; + + private MemviewProvider provider; + private Program program; + private DebuggerListingService listingService; + + public MemviewTable(MemviewProvider provider) { + this.provider = provider; + this.model = new MemviewMapModel(provider); + this.table = new GhidraTable(model); + table.setHTMLRenderingEnabled(true); + this.component = new JPanel(new BorderLayout()); + JScrollPane scrollPane = new JScrollPane(table); + filterPanel = new GhidraTableFilterPanel<>(table, model); + component.add(scrollPane, BorderLayout.CENTER); + component.add(filterPanel, BorderLayout.SOUTH); + table.setAutoscrolls(true); + + table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting()) { + return; + } + int modelRow = filterPanel.getModelRow(table.getSelectedRow()); + MemoryBox box = model.getBoxAt(modelRow); + if (box != null) { + Set boxes = new HashSet(); + boxes.add(box); + provider.selectPanelPosition(boxes); + } + } + }); + table.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + navigateToSelectedObject(); + } + } + }); + + filterListener = new FilterActionFilterListener(); + filterPanel.addFilterChagnedListener(filterListener); + } + + public JComponent getComponent() { + return component; + } + + public JComponent getPrincipalComponent() { + return table; + } + + public void setProgram(Program program) { + this.program = program; + } + + public void setListingService(DebuggerListingService listingService) { + this.listingService = listingService; + } + + public void setBoxes(Collection blist) { + model.setBoxes(blist); + } + + public void addBoxes(Collection blist) { + model.addBoxes(blist); + } + + public void reset() { + model.reset(); + } + + public List getBoxes() { + return model.getBoxes(); + } + + public void setSelection(Set set) { + table.clearSelection(); + for (MemoryBox box : set) { + int index = model.getIndexForBox(box); + int viewRow = filterPanel.getViewRow(index); + if (viewRow >= 0) { + table.addRowSelectionInterval(viewRow, viewRow); + table.scrollToSelectedRow(); + } + } + } + + protected void navigateToSelectedObject() { + int selectedRow = table.getSelectedRow(); + int selectedColumn = table.getSelectedColumn(); + Object value = table.getValueAt(selectedRow, selectedColumn); + Address addr = null; + if (value instanceof Address) { + addr = (Address) value; + } + if (value instanceof AddressRangeImpl) { + AddressRangeImpl range = (AddressRangeImpl) value; + addr = range.getMinAddress(); + } + if (value instanceof Long) { + Long lval = (Long) value; + if (program != null) { + addr = program.getAddressFactory().getAddressSpace("ram").getAddress(lval); + } + } + if (listingService != null) { + listingService.goTo(addr, true); + } + } + + public void applyFilter() { + List blist = new ArrayList<>(); + for (int i = 0; i < filterPanel.getRowCount(); i++) { + int row = filterPanel.getModelRow(i); + if (row >= 0) { + blist.add(model.getBoxAt(row)); + } + } + provider.setBoxesInPanel(blist); + } + + private class FilterActionFilterListener implements FilterListener { + @Override + public void filterChanged(String text) { + if (provider.isApplyFilter()) { + applyFilterManager.updateLater(); + } + } + } + + /* + private List generateRows(Collection changed) { + List list = new ArrayList<>(); + for (MemoryBox box : changed) { + list.add(new MemviewRow(box)); + } + if (model instanceof EnumeratedColumnTableModel) { + @SuppressWarnings("unchecked") + EnumeratedColumnTableModel m = + (EnumeratedColumnTableModel) model; + m.clear(); + m.addAll(list); + } + setColumns(); + model.fireTableStructureChanged(); + return list; + } + */ + + /* + private MemviewRow findMatch(MemoryBox changed) { + MemviewRow match = null; + for (int i = 0; i < model.getRowCount(); i++) { + MemviewRow row = model.getRowObject(i); + if (row.getBox().equals(changed)) { + row.setAttributes(changed.getAttributeMap()); + match = row; + break; + } + } + return match; + } + */ + + /* + private List updateMatch(MemviewRow match) { + MemviewEnumeratedColumnTableModel m = (MemviewEnumeratedColumnTableModel) model; + m.updateColumns(match); + m.fireTableDataChanged(); + List list = new ArrayList<>(); + if (match != null) { + list.add(match); + model.setLastSelectedObjects(list); + model.fireTableStructureChanged(); + } + return list; + } + */ + + /* + public void setColumns() { + MemviewEnumeratedColumnTableModel m = (MemviewEnumeratedColumnTableModel) model; + for (int i = 0; i < model.getRowCount(); i++) { + MemviewRow r = model.getRowObject(i); + m.updateColumns(r); + } + m.fireTableStructureChanged(); + } + */ + + /* + public TargetObject getSelectedObject() { + int selectedColumn = table.getSelectedColumn(); + R r = model.getRowObject(table.getSelectedRow()); + if (r instanceof ObjectAttributeRow) { + ObjectAttributeRow row = (ObjectAttributeRow) r; + return row.getTargetObject(); + } + if (r instanceof MemviewRow) { + MemviewRow row = (MemviewRow) r; + TargetObject targetObject = row.getTargetObject(); + if (selectedColumn > 0) { + List keys = row.getKeys(); + if (selectedColumn >= keys.size()) { + selectedColumn = 0; + } + String key = keys.get(selectedColumn); + Map attributes = targetObject.getCachedAttributes(); + Object object = attributes.get(key); + if (object instanceof TargetObject) { + return (TargetObject) object; + } + } + return targetObject; + } + return null; + } + */ + + /* + public void setSelectedObject(MemoryBox selection) { + for (int i = 0; i < model.getRowCount(); i++) { + MemviewRow row = model.getRowObject(i); + if (row.getBox().equals(selection)) { + table.selectRow(i); + break; + } + } + } + */ + + /* + public void setFocus(MemoryBox focused) { + Swing.runIfSwingOrRunLater(() -> { + setSelectedObject(focused); + }); + } + */ + +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/actions/ZoomInAAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/actions/ZoomInAAction.java new file mode 100644 index 0000000000..cca269873e --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/actions/ZoomInAAction.java @@ -0,0 +1,55 @@ +/* ### + * 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.memview.actions; + +import javax.swing.ImageIcon; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.action.ToolBarData; +import ghidra.app.plugin.core.debug.gui.memview.MemviewProvider; +import ghidra.util.HelpLocation; +import resources.ResourceManager; + +public class ZoomInAAction extends DockingAction { + + private final ImageIcon ICON = ResourceManager.loadImage("images/zoom_in.png"); + + private MemviewProvider provider; + + public ZoomInAAction(MemviewProvider provider) { + super("Zoom In (Addrs)", provider.getName()); + this.provider = provider; + setEnabled(true); + + this.setToolBarData(new ToolBarData(ICON, "aoverview")); + + setDescription("Zoom In (A)"); + setHelpLocation(new HelpLocation("DebuggerMemviewPlugin", "zoom")); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return true; + } + + @Override + public void actionPerformed(ActionContext context) { + provider.changeZoomA(1); + provider.refresh(); + } + +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/actions/ZoomInTAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/actions/ZoomInTAction.java new file mode 100644 index 0000000000..0effc0c1fe --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/actions/ZoomInTAction.java @@ -0,0 +1,55 @@ +/* ### + * 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.memview.actions; + +import javax.swing.ImageIcon; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.action.ToolBarData; +import ghidra.app.plugin.core.debug.gui.memview.MemviewProvider; +import ghidra.util.HelpLocation; +import resources.ResourceManager; + +public class ZoomInTAction extends DockingAction { + + private final ImageIcon ICON = ResourceManager.loadImage("images/zoom_in.png"); + + private MemviewProvider provider; + + public ZoomInTAction(MemviewProvider provider) { + super("Zoom In (Time)", provider.getName()); + this.provider = provider; + setEnabled(true); + + this.setToolBarData(new ToolBarData(ICON, "aoverview")); + + setDescription("Zoom In (T)"); + setHelpLocation(new HelpLocation("DebuggerMemviewPlugin", "zoom")); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return true; + } + + @Override + public void actionPerformed(ActionContext context) { + provider.changeZoomT(1); + provider.refresh(); + } + +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/actions/ZoomOutAAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/actions/ZoomOutAAction.java new file mode 100644 index 0000000000..f3174c729c --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/actions/ZoomOutAAction.java @@ -0,0 +1,55 @@ +/* ### + * 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.memview.actions; + +import javax.swing.ImageIcon; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.action.ToolBarData; +import ghidra.app.plugin.core.debug.gui.memview.MemviewProvider; +import ghidra.util.HelpLocation; +import resources.ResourceManager; + +public class ZoomOutAAction extends DockingAction { + + private final ImageIcon ICON = ResourceManager.loadImage("images/zoom_out.png"); + + private MemviewProvider provider; + + public ZoomOutAAction(MemviewProvider provider) { + super("Zoom Out (Addrs)", provider.getName()); + this.provider = provider; + setEnabled(true); + + this.setToolBarData(new ToolBarData(ICON, "aoverview")); + + setDescription("Zoom Out (A)"); + setHelpLocation(new HelpLocation("DebuggerMemviewPlugin", "zoom")); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return true; + } + + @Override + public void actionPerformed(ActionContext context) { + provider.changeZoomA(-1); + provider.refresh(); + } + +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/actions/ZoomOutTAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/actions/ZoomOutTAction.java new file mode 100644 index 0000000000..0243042d94 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/actions/ZoomOutTAction.java @@ -0,0 +1,55 @@ +/* ### + * 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.memview.actions; + +import javax.swing.ImageIcon; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.action.ToolBarData; +import ghidra.app.plugin.core.debug.gui.memview.MemviewProvider; +import ghidra.util.HelpLocation; +import resources.ResourceManager; + +public class ZoomOutTAction extends DockingAction { + + private final ImageIcon ICON = ResourceManager.loadImage("images/zoom_out.png"); + + private MemviewProvider provider; + + public ZoomOutTAction(MemviewProvider provider) { + super("Zoom Out (Time)", provider.getName()); + this.provider = provider; + setEnabled(true); + + this.setToolBarData(new ToolBarData(ICON, "aoverview")); + + setDescription("Zoom Out (T)"); + setHelpLocation(new HelpLocation("DebuggerMemviewPlugin", "zoom")); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return true; + } + + @Override + public void actionPerformed(ActionContext context) { + provider.changeZoomT(-1); + provider.refresh(); + } + +}