diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerLocationLabel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerLocationLabel.java new file mode 100644 index 0000000000..2ee23c1306 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerLocationLabel.java @@ -0,0 +1,196 @@ +/* ### + * 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; + +import java.util.*; + +import javax.swing.JLabel; + +import org.apache.commons.collections4.ComparatorUtils; + +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.async.AsyncDebouncer; +import ghidra.async.AsyncTimer; +import ghidra.program.model.address.Address; +import ghidra.trace.model.Trace; +import ghidra.trace.model.Trace.*; +import ghidra.trace.model.TraceDomainObjectListener; +import ghidra.trace.model.memory.TraceMemoryRegion; +import ghidra.trace.model.modules.TraceModule; +import ghidra.trace.model.modules.TraceSection; +import ghidra.trace.model.program.TraceProgramView; +import ghidra.util.Swing; + +public class DebuggerLocationLabel extends JLabel { + + protected class ForLocationLabelTraceListener extends TraceDomainObjectListener { + private final AsyncDebouncer updateLabelDebouncer = + new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, 100); + + public ForLocationLabelTraceListener() { + updateLabelDebouncer + .addListener(__ -> Swing.runIfSwingOrRunLater(() -> doUpdateLabel())); + + listenFor(TraceMemoryRegionChangeType.ADDED, this::regionChanged); + listenFor(TraceMemoryRegionChangeType.CHANGED, this::regionChanged); + listenFor(TraceMemoryRegionChangeType.LIFESPAN_CHANGED, this::regionChanged); + listenFor(TraceMemoryRegionChangeType.DELETED, this::regionChanged); + + 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); + } + + private void doUpdateLabel() { + updateLabel(); + } + + private void regionChanged(TraceMemoryRegion region) { + updateLabelDebouncer.contact(null); + } + + private void moduleChanged(TraceModule module) { + updateLabelDebouncer.contact(null); + } + + private void sectionChanged(TraceSection section) { + updateLabelDebouncer.contact(null); + } + } + + protected final ForLocationLabelTraceListener listener = new ForLocationLabelTraceListener(); + + private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; + private Address address = null; + + protected boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) { + if (!Objects.equals(a.getView(), b.getView())) { + return false; // Subsumes trace + } + if (!Objects.equals(a.getTime(), b.getTime())) { + return false; + } + return true; + } + + protected void addNewListeners() { + Trace trace = current.getTrace(); + if (trace != null) { + trace.addListener(listener); + } + } + + protected void removeOldListeners() { + Trace trace = current.getTrace(); + if (trace != null) { + trace.removeListener(listener); + } + } + + public void goToCoordinates(DebuggerCoordinates coordinates) { + if (sameCoordinates(current, coordinates)) { + current = coordinates; + return; + } + boolean doListeners = !Objects.equals(current.getTrace(), coordinates.getTrace()); + if (doListeners) { + removeOldListeners(); + } + current = coordinates; + if (doListeners) { + addNewListeners(); + } + updateLabel(); + } + + public void goToAddress(Address address) { + this.address = address; + updateLabel(); + } + + protected TraceSection getNearestSectionContaining() { + if (current.getView() == null) { + return null; + } + Trace trace = current.getTrace(); + List sections = + new ArrayList<>(trace.getModuleManager().getSectionsAt(current.getSnap(), address)); + if (sections.isEmpty()) { + return null; + } + // TODO: DB's R-Tree could probably do this natively + sections.sort(ComparatorUtils.chainedComparator(List.of( + Comparator.comparing(s -> s.getRange().getMinAddress()), + Comparator.comparing(s -> -s.getRange().getLength())))); + return sections.get(sections.size() - 1); + } + + protected TraceModule getNearestModuleContaining() { + if (current.getView() == null) { + return null; + } + Trace trace = current.getTrace(); + List modules = + new ArrayList<>(trace.getModuleManager().getModulesAt(current.getSnap(), address)); + if (modules.isEmpty()) { + return null; + } + // TODO: DB's R-Tree could probably do this natively + modules.sort(ComparatorUtils.chainedComparator(List.of( + Comparator.comparing(m -> m.getRange().getMinAddress()), + Comparator.comparing(m -> -m.getRange().getLength())))); + return modules.get(modules.size() - 1); + } + + protected TraceMemoryRegion getRegionContaining() { + if (current.getView() == null) { + return null; + } + Trace trace = current.getTrace(); + return trace.getMemoryManager().getRegionContaining(current.getSnap(), address); + } + + protected String computeLocationString() { + TraceProgramView view = current.getView(); + if (view == null) { + return ""; + } + if (address == null) { + return "(nowhere)"; + } + TraceSection section = getNearestSectionContaining(); + if (section != null) { + return section.getModule().getName() + ":" + section.getName(); + } + TraceModule module = getNearestModuleContaining(); + if (module != null) { + return module.getName(); + } + TraceMemoryRegion region = getRegionContaining(); + if (region != null) { + return region.getName(); + } + return "(unknown)"; + } + + public void updateLabel() { + setText(computeLocationString()); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java index 32c34bc208..53fb1bf3c2 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java @@ -257,9 +257,7 @@ public interface DebuggerResources { Color DEFAULT_COLOR_BACKGROUND_ERROR = new Color(1.0f, 0.75f, 0.75f); int PRIORITY_REGISTER_MARKER = 10; - // TODO: Is this the right name? Used by Location Tracking, which could be anything - // Close enough for now - String OPTION_NAME_COLORS_REGISTER_MARKERS = "Colors.Register Markers"; + String OPTION_NAME_COLORS_TRACKING_MARKERS = "Colors.Tracking Markers"; Color DEFAULT_COLOR_REGISTER_MARKERS = new Color(0.75f, 0.875f, 0.75f); ImageIcon ICON_REGISTER_MARKER = ResourceManager.loadImage("images/register-marker.png"); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerGoToDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToDialog.java similarity index 88% rename from Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerGoToDialog.java rename to Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToDialog.java index f1a3bbf78a..230b187730 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerGoToDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToDialog.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.app.plugin.core.debug.gui.listing; +package ghidra.app.plugin.core.debug.gui.action; import java.awt.BorderLayout; import java.util.List; @@ -34,14 +34,15 @@ import ghidra.util.Msg; public class DebuggerGoToDialog extends DialogComponentProvider { - private final DebuggerListingProvider provider; - final JTextField textExpression; - final JComboBox comboSpaces; + private final DebuggerGoToTrait trait; private final DefaultComboBoxModel modelSpaces; - protected DebuggerGoToDialog(DebuggerListingProvider provider) { + final JTextField textExpression; + final JComboBox comboSpaces; + + public DebuggerGoToDialog(DebuggerGoToTrait trait) { super("Go To", true, true, true, false); - this.provider = provider; + this.trait = trait; textExpression = new JTextField(); modelSpaces = new DefaultComboBoxModel<>(); @@ -91,11 +92,11 @@ public class DebuggerGoToDialog extends DialogComponentProvider { } } - @Override - protected void okCallback() { + @Override // public for tests + public void okCallback() { CompletableFuture future; try { - future = provider.goToSleigh((String) comboSpaces.getSelectedItem(), + future = trait.goToSleigh((String) comboSpaces.getSelectedItem(), textExpression.getText()); } catch (Throwable t) { @@ -117,12 +118,16 @@ public class DebuggerGoToDialog extends DialogComponentProvider { } @Override - protected void cancelCallback() { + public void cancelCallback() { close(); } public void show(SleighLanguage language) { populateSpaces(language); - provider.getTool().showDialog(this); + trait.tool.showDialog(this); + } + + public void setExpression(String expression) { + textExpression.setText(expression); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToTrait.java new file mode 100644 index 0000000000..b0bce6a90a --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToTrait.java @@ -0,0 +1,104 @@ +/* ### + * 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.action; + +import java.util.concurrent.CompletableFuture; + +import docking.ActionContext; +import docking.ComponentProvider; +import docking.action.DockingAction; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.GoToAction; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.framework.plugintool.Plugin; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.exec.*; +import ghidra.pcode.utils.Utils; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Language; +import ghidra.trace.model.program.TraceProgramView; + +public abstract class DebuggerGoToTrait { + protected DockingAction action; + + protected final PluginTool tool; + protected final Plugin plugin; + protected final ComponentProvider provider; + + protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; + + protected final DebuggerGoToDialog goToDialog; + + public DebuggerGoToTrait(PluginTool tool, Plugin plugin, ComponentProvider provider) { + this.tool = tool; + this.plugin = plugin; + this.provider = provider; + + goToDialog = new DebuggerGoToDialog(this); + } + + protected abstract boolean goToAddress(Address address); + + public void goToCoordinates(DebuggerCoordinates coordinates) { + current = coordinates; + } + + public DockingAction installAction() { + action = GoToAction.builder(plugin) + .enabledWhen(ctx -> current.getView() != null) + .onAction(this::activatedGoTo) + .buildAndInstallLocal(provider); + action.setEnabled(false); + return action; + } + + private void activatedGoTo(ActionContext context) { + TraceProgramView view = current.getView(); + if (view == null) { + return; + } + Language language = view.getLanguage(); + if (!(language instanceof SleighLanguage)) { + return; + } + goToDialog.show((SleighLanguage) language); + } + + public CompletableFuture goToSleigh(String spaceName, String expression) { + Language language = current.getView().getLanguage(); + if (!(language instanceof SleighLanguage)) { + throw new IllegalStateException("Current trace does not use Sleigh"); + } + SleighLanguage slang = (SleighLanguage) language; + AddressSpace space = language.getAddressFactory().getAddressSpace(spaceName); + if (space == null) { + throw new IllegalArgumentException("No such address space: " + spaceName); + } + SleighExpression expr = SleighProgramCompiler.compileExpression(slang, expression); + return goToSleigh(space, expr); + } + + public CompletableFuture goToSleigh(AddressSpace space, SleighExpression expression) { + AsyncPcodeExecutor executor = TracePcodeUtils.executorForCoordinates(current); + CompletableFuture result = expression.evaluate(executor); + return result.thenApply(offset -> { + Address address = space.getAddress( + Utils.bytesToLong(offset, offset.length, expression.getLanguage().isBigEndian())); + return goToAddress(address); + }); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java new file mode 100644 index 0000000000..b9545eab32 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java @@ -0,0 +1,271 @@ +/* ### + * 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.action; + +import java.lang.invoke.MethodHandles; +import java.util.Objects; + +import docking.ActionContext; +import docking.ComponentProvider; +import docking.action.DockingAction; +import docking.action.ToolBarData; +import docking.menu.ActionState; +import docking.menu.MultiStateDockingAction; +import docking.widgets.EventTrigger; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractCaptureSelectedMemoryAction; +import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec; +import ghidra.app.plugin.core.debug.utils.BackgroundUtils; +import ghidra.app.services.TraceRecorder; +import ghidra.app.services.TraceRecorderListener; +import ghidra.app.util.viewer.listingpanel.AddressSetDisplayListener; +import ghidra.framework.options.SaveState; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.annotation.AutoConfigStateField; +import ghidra.program.model.address.AddressSetView; +import ghidra.trace.model.Trace; +import ghidra.trace.model.Trace.TraceSnapshotChangeType; +import ghidra.trace.model.TraceDomainObjectListener; +import ghidra.trace.model.time.TraceSnapshot; +import ghidra.util.Msg; +import ghidra.util.Swing; + +public abstract class DebuggerReadsMemoryTrait { + protected static final AutoConfigState.ClassHandler CONFIG_STATE_HANDLER = + AutoConfigState.wireHandler(DebuggerReadsMemoryTrait.class, MethodHandles.lookup()); + + protected class CaptureSelectedMemoryAction extends AbstractCaptureSelectedMemoryAction { + public static final String GROUP = DebuggerResources.GROUP_GENERAL; + + public CaptureSelectedMemoryAction() { + super(plugin); + setToolBarData(new ToolBarData(ICON, GROUP)); + setEnabled(false); + } + + @Override + public void actionPerformed(ActionContext context) { + AddressSetView selection = getSelection(); + if (selection == null || selection.isEmpty() || !current.isAliveAndReadsPresent()) { + return; + } + Trace trace = current.getTrace(); + TraceRecorder recorder = current.getRecorder(); + BackgroundUtils.async(tool, trace, NAME, true, true, false, + (__, monitor) -> recorder.captureProcessMemory(selection, monitor, false)); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + AddressSetView selection = getSelection(); + if (selection == null || selection.isEmpty() || !current.isAliveAndReadsPresent()) { + return false; + } + TraceRecorder recorder = current.getRecorder(); + // TODO: Either allow partial, or provide action to intersect with accessible + if (!recorder.getAccessibleProcessMemory().contains(selection)) { + return false; + } + return true; + } + + public void updateEnabled(ActionContext context) { + setEnabled(isEnabledForContext(context)); + } + } + + protected class ForCaptureEnabledTraceListener extends TraceDomainObjectListener { + public ForCaptureEnabledTraceListener() { + listenFor(TraceSnapshotChangeType.ADDED, this::snapshotAdded); + } + + private void snapshotAdded(TraceSnapshot snapshot) { + actionCaptureSelected.updateEnabled(null); + } + } + + protected class ForAccessRecorderListener implements TraceRecorderListener { + @Override + public void processMemoryAccessibilityChanged(TraceRecorder recorder) { + Swing.runIfSwingOrRunLater(() -> { + actionCaptureSelected.updateEnabled(null); + }); + } + } + + protected class ForVisibilityListener implements AddressSetDisplayListener { + @Override + public void visibleAddressesChanged(AddressSetView visibleAddresses) { + if (Objects.equals(visible, visibleAddresses)) { + return; + } + visible = visibleAddresses; + doAutoRead(); + } + } + + protected MultiStateDockingAction actionAutoRead; + protected CaptureSelectedMemoryAction actionCaptureSelected; + + private final AutoReadMemorySpec defaultAutoSpec = + AutoReadMemorySpec.fromConfigName(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME); + + @AutoConfigStateField(codec = AutoReadMemorySpecConfigFieldCodec.class) + protected AutoReadMemorySpec autoSpec = defaultAutoSpec; + + protected final PluginTool tool; + protected final Plugin plugin; + protected final ComponentProvider provider; + + protected final ForCaptureEnabledTraceListener traceListener = + new ForCaptureEnabledTraceListener(); + protected final ForAccessRecorderListener recorderListener = new ForAccessRecorderListener(); + protected final ForVisibilityListener displayListener = new ForVisibilityListener(); + + protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; + protected AddressSetView visible; + + public DebuggerReadsMemoryTrait(PluginTool tool, Plugin plugin, ComponentProvider provider) { + this.tool = tool; + this.plugin = plugin; + this.provider = provider; + } + + protected boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) { + if (!Objects.equals(a.getView(), b.getView())) { + return false; // Subsumes trace + } + if (!Objects.equals(a.getTime(), b.getTime())) { + return false; + } + if (!Objects.equals(a.getRecorder(), b.getRecorder())) { + return false; + } + return true; + } + + protected void addNewTraceListener() { + if (current.getTrace() != null) { + current.getTrace().addListener(traceListener); + } + } + + protected void removeOldTraceListener() { + if (current.getTrace() != null) { + current.getTrace().removeListener(traceListener); + } + } + + protected void addNewRecorderListener() { + if (current.getRecorder() != null) { + current.getRecorder().addListener(recorderListener); + } + } + + protected void removeOldRecorderListener() { + if (current.getRecorder() != null) { + current.getRecorder().removeListener(recorderListener); + } + } + + public void goToCoordinates(DebuggerCoordinates coordinates) { + if (sameCoordinates(current, coordinates)) { + current = coordinates; + return; + } + boolean doTraceListener = !Objects.equals(current.getTrace(), coordinates.getTrace()); + boolean doRecListener = !Objects.equals(current.getRecorder(), coordinates.getRecorder()); + if (doTraceListener) { + removeOldTraceListener(); + } + if (doRecListener) { + removeOldRecorderListener(); + } + current = coordinates; + if (doTraceListener) { + addNewTraceListener(); + } + if (doRecListener) { + addNewRecorderListener(); + } + + doAutoRead(); + // NB. provider should call contextChanged, updating actions + } + + protected void doAutoRead() { + autoSpec.readMemory(tool, current, visible).exceptionally(ex -> { + Msg.error(this, "Could not auto-read memory: " + ex); + return null; + }); + } + + public MultiStateDockingAction installAutoReadAction() { + actionAutoRead = DebuggerAutoReadMemoryAction.builder(plugin) + .onAction(this::activatedAutoRead) + .onActionStateChanged(this::changedAutoReadMemory) + .buildAndInstallLocal(provider); + actionAutoRead.setCurrentActionStateByUserData(defaultAutoSpec); + return actionAutoRead; + } + + protected void activatedAutoRead(ActionContext ctx) { + doAutoRead(); + } + + protected void changedAutoReadMemory(ActionState newState, + EventTrigger trigger) { + doSetAutoRead(newState.getUserData()); + } + + protected void doSetAutoRead(AutoReadMemorySpec spec) { + this.autoSpec = spec; + if (visible != null) { + doAutoRead(); + } + } + + public DockingAction installCaptureSelectedAction() { + actionCaptureSelected = new CaptureSelectedMemoryAction(); + provider.addLocalAction(actionCaptureSelected); + return actionCaptureSelected; + } + + public AddressSetDisplayListener getDisplayListener() { + return displayListener; + } + + public void writeConfigState(SaveState saveState) { + CONFIG_STATE_HANDLER.writeConfigState(this, saveState); + } + + public void readConfigState(SaveState saveState) { + CONFIG_STATE_HANDLER.readConfigState(this, saveState); + actionAutoRead.setCurrentActionStateByUserData(autoSpec); + } + + public void setAutoSpec(AutoReadMemorySpec autoSpec) { + // TODO: What if action == null? + actionAutoRead.setCurrentActionStateByUserData(autoSpec); + } + + public AutoReadMemorySpec getAutoSpec() { + return autoSpec; + } + + protected abstract AddressSetView getSelection(); +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationAction.java index 988c0dc29f..1ebde6c315 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationAction.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationAction.java @@ -20,6 +20,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction; import ghidra.framework.plugintool.Plugin; public interface DebuggerTrackLocationAction extends TrackLocationAction { + // TODO: Update the action when new specs enter the class path? static MultiStateActionBuilder builder(Plugin owner) { MultiStateActionBuilder builder = TrackLocationAction.builder(owner); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java new file mode 100644 index 0000000000..6f343444ad --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java @@ -0,0 +1,305 @@ +/* ### + * 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.action; + +import java.awt.Color; +import java.lang.invoke.MethodHandles; +import java.math.BigInteger; +import java.util.List; +import java.util.Objects; + +import docking.ActionContext; +import docking.ComponentProvider; +import docking.menu.ActionState; +import docking.menu.MultiStateDockingAction; +import docking.widgets.EventTrigger; +import docking.widgets.fieldpanel.support.BackgroundColorModel; +import docking.widgets.fieldpanel.support.FieldSelection; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec.TrackingSpecConfigFieldCodec; +import ghidra.app.plugin.core.debug.gui.colors.*; +import ghidra.app.plugin.core.debug.gui.colors.MultiSelectionBlendedLayoutBackgroundColorManager.ColoredFieldSelection; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerTrackedRegisterListingBackgroundColorModel; +import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel; +import ghidra.app.util.viewer.listingpanel.ListingPanel; +import ghidra.framework.options.AutoOptions; +import ghidra.framework.options.SaveState; +import ghidra.framework.options.annotation.AutoOptionConsumed; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.annotation.AutoConfigStateField; +import ghidra.program.model.address.Address; +import ghidra.program.util.ProgramLocation; +import ghidra.trace.model.*; +import ghidra.trace.model.Trace.TraceMemoryBytesChangeType; +import ghidra.trace.model.Trace.TraceStackChangeType; +import ghidra.trace.model.stack.TraceStack; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.util.TraceAddressSpace; + +public class DebuggerTrackLocationTrait { + protected static final AutoConfigState.ClassHandler CONFIG_STATE_HANDLER = + AutoConfigState.wireHandler(DebuggerTrackLocationTrait.class, MethodHandles.lookup()); + + protected class ForTrackingListener extends TraceDomainObjectListener { + + public ForTrackingListener() { + listenFor(TraceMemoryBytesChangeType.CHANGED, this::registersChanged); + listenFor(TraceStackChangeType.CHANGED, this::stackChanged); + } + + private void registersChanged(TraceAddressSpace space, TraceAddressSnapRange range, + byte[] oldValue, byte[] newValue) { + if (current.getView() == null || spec == null) { + // Should only happen during transitional times, if at all. + return; + } + if (!spec.affectedByRegisterChange(space, range, current)) { + return; + } + doTrack(); + } + + private void stackChanged(TraceStack stack) { + if (current.getView() == null || spec == null) { + // Should only happen during transitional times, if at all. + return; + } + if (!spec.affectedByStackChange(stack, current)) { + return; + } + doTrack(); + } + } + + // TODO: This may already be deprecated.... + protected class ColorModel extends DebuggerTrackedRegisterBackgroundColorModel { + public ColorModel() { + super(plugin); + } + + @Override + protected ProgramLocation getTrackedLocation() { + return trackedLocation; + } + } + + protected class ListingColorModel + extends DebuggerTrackedRegisterListingBackgroundColorModel { + + public ListingColorModel(ListingPanel listingPanel) { + super(plugin, listingPanel); + } + + @Override + protected ProgramLocation getTrackedLocation() { + return trackedLocation; + } + } + + protected class TrackSelectionGenerator implements SelectionGenerator { + @AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_TRACKING_MARKERS) + private Color trackingColor; + @SuppressWarnings("unused") + private final AutoOptions.Wiring autoOptionsWiring; + + public TrackSelectionGenerator() { + autoOptionsWiring = AutoOptions.wireOptions(plugin, this); + } + + @Override + public void addSelections(BigInteger layoutIndex, SelectionTranslator translator, + List selections) { + if (trackedLocation == null || trackingColor == null) { + return; + } + FieldSelection fieldSel = + translator.convertAddressToField(trackedLocation.getAddress()); + selections.add(new ColoredFieldSelection(fieldSel, trackingColor)); + } + } + + protected MultiStateDockingAction action; + + private final LocationTrackingSpec defaultSpec = + LocationTrackingSpec.fromConfigName(PCLocationTrackingSpec.CONFIG_NAME); + + @AutoConfigStateField(codec = TrackingSpecConfigFieldCodec.class) + protected LocationTrackingSpec spec = defaultSpec; + + protected final PluginTool tool; + protected final Plugin plugin; + protected final ComponentProvider provider; + + protected final ForTrackingListener listener = new ForTrackingListener(); + + protected final ColorModel colorModel; + protected final TrackSelectionGenerator selectionGenerator; + + protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; + protected ProgramLocation trackedLocation; + + public DebuggerTrackLocationTrait(PluginTool tool, Plugin plugin, ComponentProvider provider) { + this.tool = tool; + this.plugin = plugin; + this.provider = provider; + + this.colorModel = new ColorModel(); + this.selectionGenerator = new TrackSelectionGenerator(); + } + + public BackgroundColorModel getBackgroundColorModel() { + return colorModel; + } + + public ListingBackgroundColorModel createListingBackgroundColorModel( + ListingPanel listingPanel) { + return new ListingColorModel(listingPanel); + } + + public SelectionGenerator getSelectionGenerator() { + return selectionGenerator; + } + + protected boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) { + if (!Objects.equals(a.getView(), b.getView())) { + return false; // Subsumes trace + } + if (!Objects.equals(a.getTime(), b.getTime())) { + return false; + } + if (!Objects.equals(a.getThread(), b.getThread())) { + return false; + } + if (!Objects.equals(a.getFrame(), b.getFrame())) { + return false; + } + return true; + } + + public void setSpec(LocationTrackingSpec spec) { + // TODO: What if action == null? + action.setCurrentActionStateByUserData(spec); + } + + public LocationTrackingSpec getSpec() { + return spec; + } + + public ProgramLocation getTrackedLocation() { + return trackedLocation; + } + + public MultiStateDockingAction installAction() { + // TODO: Add "other" option, and present most-recent in menu, too + // TODO: "other" as in arbitrary expression? + // Only those applicable to the current thread's registers, though. + action = DebuggerTrackLocationAction.builder(plugin) + .onAction(this::clickedSpecButton) + .onActionStateChanged(this::clickedSpecMenu) + .buildAndInstallLocal(provider); + action.setCurrentActionStateByUserData(defaultSpec); + return action; + } + + protected void clickedSpecButton(ActionContext ctx) { + doTrack(); + } + + protected void clickedSpecMenu(ActionState newState, + EventTrigger trigger) { + doSetSpec(newState.getUserData()); + } + + protected void doSetSpec(LocationTrackingSpec spec) { + if (this.spec != spec) { + this.spec = spec; + specChanged(); + } + doTrack(); + } + + protected ProgramLocation computeTrackedLocation() { + // Change of register values (for current frame) + // Change of stack pc (for current frame) + // Change of current view (if not caused by goTo) + // Change of current thread + // Change of current snap + // Change of current frame + // Change of tracking settings + DebuggerCoordinates cur = current; + TraceThread thread = cur.getThread(); + if (thread == null || spec == null) { + return null; + } + // NB: view's snap may be forked for emulation + Address address = spec.computeTraceAddress(tool, cur, current.getView().getSnap()); + return address == null ? null : new ProgramLocation(current.getView(), address); + } + + protected void doTrack() { + trackedLocation = computeTrackedLocation(); + locationTracked(); + } + + protected void addNewListeners() { + Trace trace = current.getTrace(); + if (trace != null) { + trace.addListener(listener); + } + } + + protected void removeOldListeners() { + Trace trace = current.getTrace(); + if (trace != null) { + trace.removeListener(listener); + } + } + + public void goToCoordinates(DebuggerCoordinates coordinates) { + if (sameCoordinates(current, coordinates)) { + current = coordinates; + return; + } + boolean doListeners = !Objects.equals(current.getTrace(), coordinates.getTrace()); + if (doListeners) { + removeOldListeners(); + } + current = coordinates; + if (doListeners) { + addNewListeners(); + } + doTrack(); + } + + public void writeConfigState(SaveState saveState) { + CONFIG_STATE_HANDLER.writeConfigState(this, saveState); + } + + public void readConfigState(SaveState saveState) { + CONFIG_STATE_HANDLER.readConfigState(this, saveState); + + action.setCurrentActionStateByUserData(spec); + } + + protected void locationTracked() { + // Listener method + } + + protected void specChanged() { + // Listener method + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/colors/DebuggerTrackedRegisterBackgroundColorModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/colors/DebuggerTrackedRegisterBackgroundColorModel.java new file mode 100644 index 0000000000..1fad82bc90 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/colors/DebuggerTrackedRegisterBackgroundColorModel.java @@ -0,0 +1,78 @@ +/* ### + * 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.colors; + +import java.awt.Color; +import java.math.BigInteger; + +import docking.widgets.fieldpanel.support.BackgroundColorModel; +import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.util.viewer.util.AddressIndexMap; +import ghidra.framework.options.AutoOptions; +import ghidra.framework.options.annotation.AutoOptionConsumed; +import ghidra.framework.plugintool.Plugin; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; + +public abstract class DebuggerTrackedRegisterBackgroundColorModel implements BackgroundColorModel { + protected Color defaultBackgroundColor; + protected Program program; + protected AddressIndexMap addressIndexMap; + + // TODO: Seems I should at least rename this option + @AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_TRACKING_MARKERS) + Color trackingColor; + @SuppressWarnings("unused") + private final AutoOptions.Wiring autoOptionsWiring; + + public DebuggerTrackedRegisterBackgroundColorModel(Plugin plugin) { + autoOptionsWiring = AutoOptions.wireOptions(plugin, this); + } + + /** + * Get the location which is to be highlighted as "tracked." + * + * @return the location + */ + protected abstract ProgramLocation getTrackedLocation(); + + @Override + public Color getBackgroundColor(BigInteger index) { + if (addressIndexMap == null) { + return defaultBackgroundColor; + } + ProgramLocation loc = getTrackedLocation(); + if (loc == null) { + return defaultBackgroundColor; + } + Address address = addressIndexMap.getAddress(index); + if (!loc.getAddress().equals(address)) { + return defaultBackgroundColor; + } + return trackingColor; + } + + @Override + public Color getDefaultBackgroundColor() { + return defaultBackgroundColor; + } + + @Override + public void setDefaultBackgroundColor(Color c) { + defaultBackgroundColor = c; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/colors/MultiSelectionBlendedLayoutBackgroundColorManager.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/colors/MultiSelectionBlendedLayoutBackgroundColorManager.java new file mode 100644 index 0000000000..285281a48c --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/colors/MultiSelectionBlendedLayoutBackgroundColorManager.java @@ -0,0 +1,269 @@ +/* ### + * 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.colors; + +import java.awt.Color; +import java.math.BigInteger; +import java.util.*; +import java.util.stream.Collectors; + +import docking.widgets.fieldpanel.internal.*; +import docking.widgets.fieldpanel.support.*; +import ghidra.util.ColorUtils.ColorBlender; + +public class MultiSelectionBlendedLayoutBackgroundColorManager + implements LayoutBackgroundColorManager { + + public static class ColoredFieldSelection { + FieldSelection selection; + Color color; + + public ColoredFieldSelection(FieldSelection selection, Color color) { + this.selection = Objects.requireNonNull(selection); + this.color = Objects.requireNonNull(color); + } + + public ColoredFieldSelection intersect(BigInteger index) { + return new ColoredFieldSelection(selection.intersect(index), color); + } + + public boolean isTotal(BigInteger index) { + return selection.getNumRanges() == 1 && + selection.getFieldRange(0).containsEntirely(index); + } + + public boolean isEmpty() { + return selection.isEmpty(); + } + + public boolean contains(FieldLocation loc) { + return selection.contains(loc); + } + + public boolean containsEntirely(FieldRange range) { + return selection.containsEntirely(range); + } + + public boolean excludesEntirely(FieldRange range) { + return selection.excludesEntirely(range); + } + } + + public static LayoutBackgroundColorManager getLayoutColorMap(BigInteger index, + Collection selections, Color backgroundColor, + boolean isBackgroundDefault) { + List intersections = + selections.stream().map(cfs -> cfs.intersect(index)).collect(Collectors.toList()); + + List empties = + intersections.stream().filter(cfs -> cfs.isEmpty()).collect(Collectors.toList()); + // Check for completely empty, i.e., use the background + if (empties.size() == intersections.size()) { + return new EmptyLayoutBackgroundColorManager(backgroundColor); + } + + ColorBlender blender = new ColorBlender(); + if (!isBackgroundDefault) { + blender.add(backgroundColor); + } + + List totals = + intersections.stream().filter(cfs -> cfs.isTotal(index)).collect(Collectors.toList()); + if (totals.size() + empties.size() == intersections.size()) { + totals.forEach(cfs -> blender.add(cfs.color)); + return new EmptyLayoutBackgroundColorManager(blender.getColor(backgroundColor)); + } + + FieldLocation startOfLine = new FieldLocation(index, 0, 0, 0); + FieldLocation endOfLine = + new FieldLocation(index, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); + for (ColoredFieldSelection cfs : intersections) { + if (cfs.contains(startOfLine)) { + blender.add(cfs.color); + } + } + ColorBlender blenderR = new ColorBlender(); + if (!isBackgroundDefault) { + blenderR.add(backgroundColor); + } + for (ColoredFieldSelection cfs : intersections) { + if (cfs.contains(endOfLine)) { + blenderR.add(cfs.color); + } + } + + return new MultiSelectionBlendedLayoutBackgroundColorManager(index, intersections, + backgroundColor, + blender.getColor(backgroundColor), blenderR.getColor(backgroundColor)); + } + + public static class MultiSelectionBlendedFieldBackgroundColorManager + implements FieldBackgroundColorManager { + + private final BigInteger index; + private final int fieldNum; + private final MultiSelectionBlendedLayoutBackgroundColorManager layoutSelection; + private final List selections; + private final Color backgroundColor; + + public MultiSelectionBlendedFieldBackgroundColorManager(BigInteger index, int fieldNum, + MultiSelectionBlendedLayoutBackgroundColorManager layoutSelection, + List selections, Color backgroundColor) { + this.index = index; + this.fieldNum = fieldNum; + this.layoutSelection = layoutSelection; + this.selections = selections; + this.backgroundColor = backgroundColor; + } + + @Override + public Color getBackgroundColor() { + return layoutSelection.dontPaintBg(backgroundColor); + } + + @Override + public List getSelectionHighlights(int row) { + FieldLocation start = new FieldLocation(index, fieldNum, row, 0); + FieldLocation end = new FieldLocation(index, fieldNum, row + 1, 0); + FieldRange range = new FieldRange(start, end); + List highlights = new ArrayList<>(); + for (ColoredFieldSelection cfs : selections) { + FieldSelection intersect = cfs.selection.intersect(range); + for (int i = 0; i < intersect.getNumRanges(); i++) { + FieldRange rng = intersect.getFieldRange(i); + int min = rng.getStart().col; + int max = rng.getEnd().row == row ? range.getEnd().col : Integer.MAX_VALUE; + highlights.add(new Highlight(min, max, cfs.color)); + } + } + return highlights; + } + + @Override + public Color getPaddingColor(int padIndex) { + return layoutSelection.getPaddingColor(padIndex); + } + } + + private final BigInteger index; + private final List selections; + private final Color backgroundColor; + private final Color leftBorderColor; + private final Color rightBorderColor; + + public MultiSelectionBlendedLayoutBackgroundColorManager(BigInteger index, + List selections, Color backgroundColor, Color leftBorderColor, + Color rightBorderColor) { + this.index = index; + this.selections = selections; + this.backgroundColor = backgroundColor; + this.leftBorderColor = leftBorderColor; + this.rightBorderColor = rightBorderColor; + } + + @Override + public Color getBackgroundColor() { + return backgroundColor; + } + + protected Color dontPaintBg(Color color) { + return color == backgroundColor ? null : color; + } + + @Override + public Color getPaddingColor(int padIndex) { + if (padIndex == 0) { + return dontPaintBg(leftBorderColor); + } + if (padIndex == -1) { + return dontPaintBg(rightBorderColor); + } + return dontPaintBg(getPaddingColorBetweenFields(padIndex)); + } + + protected Color getPaddingColorBetweenFields(int padIndex) { + FieldLocation start = + new FieldLocation(index, padIndex - 1, Integer.MAX_VALUE, Integer.MAX_VALUE); + FieldLocation end = new FieldLocation(index, padIndex, 0, 0); + FieldRange range = new FieldRange(start, end); + + ColorBlender blender = new ColorBlender(); + for (ColoredFieldSelection cfs : selections) { + if (cfs.containsEntirely(range)) { + blender.add(cfs.color); + } + } + return blender.getColor(backgroundColor); + } + + protected boolean excludedByAll(FieldRange range) { + for (ColoredFieldSelection cfs : selections) { + if (!cfs.excludesEntirely(range)) { + return false; + } + } + return true; + } + + protected Color computeSolidColor(FieldRange range) { + ColorBlender blender = new ColorBlender(); + for (ColoredFieldSelection cfs : selections) { + if (cfs.containsEntirely(range)) { + blender.add(cfs.color); + continue; + } + if (cfs.excludesEntirely(range)) { + // good, but don't add color + continue; + } + // Field is not a solid color + return null; + } + return blender.getColor(backgroundColor); + } + + @Override + public FieldBackgroundColorManager getFieldBackgroundColorManager(int fieldNum) { + FieldLocation start = new FieldLocation(index, fieldNum, 0, 0); + FieldLocation end = new FieldLocation(index, fieldNum + 1, 0, 0); + FieldRange range = new FieldRange(start, end); + + if (excludedByAll(range)) { + return EmptyFieldBackgroundColorManager.EMPTY_INSTANCE; + } + + Color solidColor = computeSolidColor(range); + if (solidColor != null) { + return new FullySelectedFieldBackgroundColorManager(solidColor); + } + + // Could separate out solid colors, but at the expense of constructing a collection.... + // Leave fieldBackgroudColor the same as backgroundColor, and pass all selections in + return new MultiSelectionBlendedFieldBackgroundColorManager(index, fieldNum, this, + selections, backgroundColor); + } + + @Override + public Color getBackgroundColor(FieldLocation location) { + ColorBlender blender = new ColorBlender(); + for (ColoredFieldSelection cfs : selections) { + if (cfs.contains(location)) { + blender.add(cfs.color); + } + } + return blender.getColor(backgroundColor); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/colors/SelectionGenerator.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/colors/SelectionGenerator.java new file mode 100644 index 0000000000..80a1b38053 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/colors/SelectionGenerator.java @@ -0,0 +1,26 @@ +/* ### + * 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.colors; + +import java.math.BigInteger; +import java.util.List; + +import ghidra.app.plugin.core.debug.gui.colors.MultiSelectionBlendedLayoutBackgroundColorManager.ColoredFieldSelection; + +public interface SelectionGenerator { + void addSelections(BigInteger layoutIndex, SelectionTranslator translator, + List selections); +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/colors/SelectionTranslator.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/colors/SelectionTranslator.java new file mode 100644 index 0000000000..bdd27c904a --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/colors/SelectionTranslator.java @@ -0,0 +1,31 @@ +/* ### + * 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.colors; + +import docking.widgets.fieldpanel.support.FieldSelection; +import ghidra.program.model.address.*; + +public interface SelectionTranslator { + AddressSetView convertFieldToAddress(FieldSelection fieldSelection); + + FieldSelection convertAddressToField(AddressSetView addresses); + + FieldSelection convertAddressToField(AddressRange range); + + default FieldSelection convertAddressToField(Address address) { + return convertAddressToField(new AddressRangeImpl(address, address)); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java index 9f1a6184bf..b00daed407 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java @@ -28,7 +28,7 @@ import docking.ActionContext; import docking.action.MenuData; import ghidra.app.events.*; import ghidra.app.plugin.PluginCategoryNames; -import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; +import ghidra.app.plugin.core.codebrowser.AbstractCodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; @@ -46,7 +46,6 @@ import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.address.*; -import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.trace.model.program.TraceProgramView; @@ -89,7 +88,8 @@ import utilities.util.SuppressableCallback.Suppression; servicesProvided = { DebuggerListingService.class, }) -public class DebuggerListingPlugin extends CodeBrowserPlugin implements DebuggerListingService { +public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin + implements DebuggerListingService { private static final String KEY_CONNECTED_PROVIDER = "connectedProvider"; private static final String KEY_DISCONNECTED_COUNT = "disconnectedCount"; private static final String PREFIX_DISCONNECTED_PROVIDER = "disconnectedProvider"; @@ -124,14 +124,14 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger //private GoToService goToService; @AutoServiceConsumed private ProgramManager programManager; - // NOTE: ListingPlugin doesn't extend AbstractDebuggerPlugin + // NOTE: This plugin doesn't extend AbstractDebuggerPlugin @SuppressWarnings("unused") private AutoService.Wiring autoServiceWiring; - @AutoOptionDefined( // - name = OPTION_NAME_COLORS_STALE_MEMORY, // + @AutoOptionDefined( + name = OPTION_NAME_COLORS_STALE_MEMORY, description = "Color of memory addresses whose content is not known in the view's " + - "snap", // + "snap", help = @HelpInfo(anchor = "colors")) private Color staleMemoryColor = DEFAULT_COLOR_BACKGROUND_STALE; @AutoOptionDefined( // @@ -142,7 +142,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger private Color errorMemoryColor = DEFAULT_COLOR_BACKGROUND_ERROR; // NOTE: Static programs are marked via markerSet. Dynamic are marked via custom color model @AutoOptionDefined( // - name = OPTION_NAME_COLORS_REGISTER_MARKERS, // + name = OPTION_NAME_COLORS_TRACKING_MARKERS, // description = "Background color for locations referred to by a tracked register", // help = @HelpInfo(anchor = "colors")) private Color trackingColor = DEFAULT_COLOR_REGISTER_MARKERS; @@ -162,15 +162,6 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger createActions(); } - protected DebuggerListingProvider getConnectedProvider() { - return (DebuggerListingProvider) connectedProvider; - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - protected List getDisconnectedProviders() { - return (List) disconnectedProviders; - } - @Override protected DebuggerListingProvider createProvider(FormatManager formatManager, boolean isConnected) { @@ -184,7 +175,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger public DebuggerListingProvider createListingIfMissing(LocationTrackingSpec spec, boolean followsCurrentThread) { synchronized (disconnectedProviders) { - for (DebuggerListingProvider provider : getDisconnectedProviders()) { + for (DebuggerListingProvider provider : disconnectedProviders) { if (provider.getTrackingSpec() != spec) { continue; } @@ -201,11 +192,6 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger } } - @Override - public DebuggerListingProvider createNewDisconnectedProvider() { - return (DebuggerListingProvider) super.createNewDisconnectedProvider(); - } - @Override protected void viewChanged(AddressSetView addrSet) { TraceProgramView view = current.getView(); @@ -239,6 +225,12 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger firePluginEvent(new TraceSelectionPluginEvent(getName(), selection, view)); } + @Override + public void highlightChanged(CodeViewerProvider codeViewerProvider, + ProgramSelection highlight) { + // TODO Nothing, yet + } + protected boolean heedLocationEvent(ProgramLocationPluginEvent ev) { PluginEvent trigger = ev.getTriggerEvent(); /*Msg.debug(this, "Location event"); @@ -267,12 +259,11 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger @Override public void processEvent(PluginEvent event) { - // Do not call super here. I intend to prevent it from seeing events. if (event instanceof ProgramLocationPluginEvent) { cbProgramLocationEvents.invoke(() -> { ProgramLocationPluginEvent ev = (ProgramLocationPluginEvent) event; if (heedLocationEvent(ev)) { - getConnectedProvider().staticProgramLocationChanged(ev.getLocation()); + connectedProvider.staticProgramLocationChanged(ev.getLocation()); } }); } @@ -291,6 +282,9 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger } if (event instanceof TraceClosedPluginEvent) { TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event; + if (current.getTrace() == ev.getTrace()) { + current = DebuggerCoordinates.NOWHERE; + } allProviders(p -> p.traceClosed(ev.getTrace())); } // TODO: Sync selection and highlights? @@ -308,34 +302,29 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger } protected void allProviders(Consumer action) { - action.accept(getConnectedProvider()); - for (DebuggerListingProvider provider : getDisconnectedProviders()) { + action.accept(connectedProvider); + for (DebuggerListingProvider provider : disconnectedProviders) { action.accept(provider); } } - @Override - protected void programClosed(Program program) { - // Immaterial - } - @AutoServiceConsumed public void setTraceManager(DebuggerTraceManagerService traceManager) { - DebuggerListingProvider provider = getConnectedProvider(); + DebuggerListingProvider provider = connectedProvider; if (provider == null || traceManager == null) { return; } - provider.coordinatesActivated(traceManager.getCurrent()); + provider.coordinatesActivated(current = traceManager.getCurrent()); } @Override public void setTrackingSpec(LocationTrackingSpec spec) { - getConnectedProvider().setTrackingSpec(spec); + connectedProvider.setTrackingSpec(spec); } @Override public void setCurrentSelection(ProgramSelection selection) { - getConnectedProvider().setSelection(selection); + connectedProvider.setSelection(selection); } @Override @@ -345,7 +334,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger return false; } //cbGoTo.invoke(() -> { - DebuggerListingProvider provider = getConnectedProvider(); + DebuggerListingProvider provider = connectedProvider; provider.doSyncToStatic(location); provider.doCheckCurrentModuleMissing(); //}); @@ -354,7 +343,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger @Override public boolean goTo(Address address, boolean centerOnScreen) { - TraceProgramView view = getConnectedProvider().current.getView(); + TraceProgramView view = connectedProvider.current.getView(); if (view == null) { return false; } @@ -392,17 +381,17 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger @Override public void writeDataState(SaveState saveState) { SaveState connectedProviderState = new SaveState(); - getConnectedProvider().writeDataState(connectedProviderState); + connectedProvider.writeDataState(connectedProviderState); saveState.putXmlElement(KEY_CONNECTED_PROVIDER, connectedProviderState.saveToXml()); /** * Arrange the follows ones first, so that we reload them into corresponding providers * restored from config state */ - List disconnected = getDisconnectedProviders().stream() + List disconnected = disconnectedProviders.stream() .filter(p -> p.isFollowsCurrentThread()) .collect(Collectors.toList()); - for (DebuggerListingProvider p : getDisconnectedProviders()) { + for (DebuggerListingProvider p : disconnectedProviders) { if (!disconnected.contains(p)) { disconnected.add(p); } @@ -419,8 +408,8 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger } protected void ensureProviders(int count, boolean followCurrentThread, SaveState configState) { - while (getDisconnectedProviders().size() < count) { - int index = getDisconnectedProviders().size(); + while (disconnectedProviders.size() < count) { + int index = disconnectedProviders.size(); String stateName = PREFIX_DISCONNECTED_PROVIDER + index; DebuggerListingProvider provider = createNewDisconnectedProvider(); provider.setFollowsCurrentThread(false); @@ -442,13 +431,13 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger Element connectedProviderElement = saveState.getXmlElement(KEY_CONNECTED_PROVIDER); if (connectedProviderElement != null) { SaveState connectedProviderState = new SaveState(connectedProviderElement); - getConnectedProvider().readDataState(connectedProviderState); + connectedProvider.readDataState(connectedProviderState); } int disconnectedCount = saveState.getInt(KEY_DISCONNECTED_COUNT, 0); ensureProviders(disconnectedCount, false, saveState); - List disconnected = getDisconnectedProviders(); + List disconnected = disconnectedProviders; for (int index = 0; index < disconnectedCount; index++) { String stateName = PREFIX_DISCONNECTED_PROVIDER + index; Element providerElement = saveState.getXmlElement(stateName); @@ -463,10 +452,10 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger @Override public void writeConfigState(SaveState saveState) { SaveState connectedProviderState = new SaveState(); - getConnectedProvider().writeConfigState(connectedProviderState); + connectedProvider.writeConfigState(connectedProviderState); saveState.putXmlElement(KEY_CONNECTED_PROVIDER, connectedProviderState.saveToXml()); - List disconnected = getDisconnectedProviders().stream() + List disconnected = disconnectedProviders.stream() .filter(p -> p.isFollowsCurrentThread()) .collect(Collectors.toList()); int disconnectedCount = disconnected.size(); @@ -485,7 +474,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger Element connectedProviderElement = saveState.getXmlElement(KEY_CONNECTED_PROVIDER); if (connectedProviderElement != null) { SaveState connectedProviderState = new SaveState(connectedProviderElement); - getConnectedProvider().readConfigState(connectedProviderState); + connectedProvider.readConfigState(connectedProviderState); } int disconnectedCount = saveState.getInt(KEY_DISCONNECTED_COUNT, 0); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java index 88db498b61..e4dd775a17 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java @@ -16,49 +16,46 @@ package ghidra.app.plugin.core.debug.gui.listing; import static ghidra.app.plugin.core.debug.gui.DebuggerResources.ICON_REGISTER_MARKER; -import static ghidra.app.plugin.core.debug.gui.DebuggerResources.OPTION_NAME_COLORS_REGISTER_MARKERS; +import static ghidra.app.plugin.core.debug.gui.DebuggerResources.OPTION_NAME_COLORS_TRACKING_MARKERS; import java.awt.Color; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; import java.lang.invoke.MethodHandles; import java.util.*; -import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.swing.JLabel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import org.apache.commons.collections4.ComparatorUtils; import org.apache.commons.lang3.StringUtils; import org.jdom.Element; import docking.ActionContext; import docking.WindowPosition; -import docking.action.*; -import docking.menu.ActionState; +import docking.action.DockingAction; +import docking.action.MenuData; import docking.menu.MultiStateDockingAction; import docking.widgets.EventTrigger; import docking.widgets.fieldpanel.support.ViewerPosition; import ghidra.app.nav.ListingPanelContainer; +import ghidra.app.plugin.core.clipboard.CodeBrowserClipboardProvider; import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel; import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import ghidra.app.plugin.core.debug.gui.action.*; -import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec; -import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec.TrackingSpecConfigFieldCodec; import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext; -import ghidra.app.plugin.core.debug.utils.*; +import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils; +import ghidra.app.plugin.core.debug.utils.ProgramURLUtils; import ghidra.app.plugin.core.exporter.ExporterDialog; -import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.services.*; import ghidra.app.util.viewer.format.FormatManager; -import ghidra.app.util.viewer.listingpanel.ListingDisplayListener; import ghidra.app.util.viewer.listingpanel.ListingPanel; -import ghidra.async.AsyncDebouncer; -import ghidra.async.AsyncTimer; import ghidra.framework.model.DomainFile; import ghidra.framework.options.AutoOptions; import ghidra.framework.options.SaveState; @@ -67,31 +64,24 @@ import ghidra.framework.plugintool.AutoConfigState; import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.annotation.AutoConfigStateField; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; -import ghidra.pcode.exec.*; -import ghidra.pcode.utils.Utils; -import ghidra.program.model.address.*; -import ghidra.program.model.lang.Language; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; -import ghidra.trace.model.*; -import ghidra.trace.model.Trace.*; -import ghidra.trace.model.memory.TraceMemoryRegion; +import ghidra.trace.model.Trace; import ghidra.trace.model.modules.*; import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.program.TraceVariableSnapProgramView; -import ghidra.trace.model.stack.TraceStack; -import ghidra.trace.model.thread.TraceThread; -import ghidra.trace.model.time.TraceSnapshot; -import ghidra.trace.util.TraceAddressSpace; -import ghidra.util.*; +import ghidra.util.HTMLUtilities; +import ghidra.util.Swing; import ghidra.util.exception.CancelledException; import ghidra.util.exception.VersionException; import ghidra.util.task.*; import utilities.util.SuppressableCallback; import utilities.util.SuppressableCallback.Suppression; -public class DebuggerListingProvider extends CodeViewerProvider implements ListingDisplayListener { +public class DebuggerListingProvider extends CodeViewerProvider { private static final AutoConfigState.ClassHandler CONFIG_STATE_HANDLER = AutoConfigState.wireHandler(DebuggerListingProvider.class, MethodHandles.lookup()); @@ -116,50 +106,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi return true; } - protected class CaptureSelectedMemoryAction extends AbstractCaptureSelectedMemoryAction { - public static final String GROUP = DebuggerResources.GROUP_GENERAL; - - public CaptureSelectedMemoryAction() { - super(plugin); - setToolBarData(new ToolBarData(ICON, GROUP)); - addLocalAction(this); - setEnabled(false); - } - - @Override - public void actionPerformed(ActionContext context) { - if (!current.isAliveAndReadsPresent()) { - return; - } - Trace trace = current.getTrace(); - TraceRecorder recorder = current.getRecorder(); - BackgroundUtils.async(plugin.getTool(), trace, NAME, true, true, false, - (__, monitor) -> recorder.captureProcessMemory( - getListingPanel().getProgramSelection(), monitor, false)); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - if (!current.isAliveAndReadsPresent()) { - return false; - } - TraceRecorder recorder = current.getRecorder(); - ProgramSelection selection = getSelection(); - if (selection == null || selection.isEmpty()) { - return false; - } - // TODO: Either allow partial, or provide action to intersect with accessible - if (!recorder.getAccessibleProcessMemory().contains(selection)) { - return false; - } - return true; - } - - public void updateEnabled(ActionContext context) { - setEnabled(isEnabledForContext(context)); - } - } - protected class SyncToStaticListingAction extends AbstractSyncToStaticListingAction { public SyncToStaticListingAction() { super(plugin); @@ -190,19 +136,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi } } - protected class TrackedLocationBackgroundColorModel - extends DebuggerTrackedRegisterListingBackgroundColorModel { - public TrackedLocationBackgroundColorModel(DebuggerListingPlugin plugin, - ListingPanel listingPanel) { - super(plugin, listingPanel); - } - - @Override - protected ProgramLocation getTrackedLocation() { - return trackedLocation; - } - } - protected class MarkerSetChangeListener implements ChangeListener { @Override public void stateChanged(ChangeEvent e) { @@ -233,89 +166,41 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi } } - protected class ForTrackingAndLabelingTraceListener extends TraceDomainObjectListener { - private final AsyncDebouncer updateLabelDebouncer = - new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, 100); - - public ForTrackingAndLabelingTraceListener() { - updateLabelDebouncer - .addListener(__ -> Swing.runIfSwingOrRunLater(() -> doUpdateLabel())); - - listenFor(TraceSnapshotChangeType.ADDED, this::snapshotAdded); - listenFor(TraceMemoryBytesChangeType.CHANGED, this::registersChanged); - listenFor(TraceStackChangeType.CHANGED, this::stackChanged); - - listenFor(TraceMemoryRegionChangeType.ADDED, this::regionChanged); - listenFor(TraceMemoryRegionChangeType.CHANGED, this::regionChanged); - listenFor(TraceMemoryRegionChangeType.LIFESPAN_CHANGED, this::regionChanged); - listenFor(TraceMemoryRegionChangeType.DELETED, this::regionChanged); - - 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); + protected class ForListingGoToTrait extends DebuggerGoToTrait { + public ForListingGoToTrait() { + super(DebuggerListingProvider.this.tool, DebuggerListingProvider.this.plugin, + DebuggerListingProvider.this); } - private void snapshotAdded(TraceSnapshot snapshot) { - actionCaptureSelectedMemory.updateEnabled(null); - } - - private void registersChanged(TraceAddressSpace space, TraceAddressSnapRange range, - byte[] oldValue, byte[] newValue) { - if (current.getView() == null || trackingSpec == null) { - // Should only happen during transitional times, if at all. - return; - } - if (!trackingSpec.affectedByRegisterChange(space, range, current)) { - return; - } - doTrackSpec(); - } - - private void stackChanged(TraceStack stack) { - if (current.getView() == null || trackingSpec == null) { - // Should only happen during transitional times, if at all. - return; - } - if (!trackingSpec.affectedByStackChange(stack, current)) { - return; - } - doTrackSpec(); - } - - private void doUpdateLabel() { - updateLocationLabel(); - } - - private void regionChanged(TraceMemoryRegion region) { - updateLabelDebouncer.contact(null); - } - - private void moduleChanged(TraceModule module) { - updateLabelDebouncer.contact(null); - } - - private void sectionChanged(TraceSection section) { - updateLabelDebouncer.contact(null); - } - } - - protected class ForAccessRecorderListener implements TraceRecorderListener { @Override - public void processMemoryAccessibilityChanged(TraceRecorder recorder) { - Swing.runIfSwingOrRunLater(() -> { - actionCaptureSelectedMemory.updateEnabled(null); - }); + protected boolean goToAddress(Address address) { + return getListingPanel().goTo(address); } } - private final LocationTrackingSpec defaultTrackingSpec = - LocationTrackingSpec.fromConfigName(PCLocationTrackingSpec.CONFIG_NAME); - private final AutoReadMemorySpec defaultReadMemorySpec = - AutoReadMemorySpec.fromConfigName(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME); + protected class ForListingTrackingTrait extends DebuggerTrackLocationTrait { + public ForListingTrackingTrait() { + super(DebuggerListingProvider.this.tool, DebuggerListingProvider.this.plugin, + DebuggerListingProvider.this); + } + + @Override + protected void locationTracked() { + doGoToTracked(); + } + } + + protected class ForListingReadsMemoryTrait extends DebuggerReadsMemoryTrait { + public ForListingReadsMemoryTrait() { + super(DebuggerListingProvider.this.tool, DebuggerListingProvider.this.plugin, + DebuggerListingProvider.this); + } + + @Override + protected AddressSetView getSelection() { + return DebuggerListingProvider.this.getSelection(); + } + } private final DebuggerListingPlugin plugin; @@ -334,46 +219,37 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi @SuppressWarnings("unused") private final AutoService.Wiring autoServiceWiring; - @AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_REGISTER_MARKERS) + @AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_TRACKING_MARKERS) private Color trackingColor; @SuppressWarnings("unused") private final AutoOptions.Wiring autoOptionsWiring; DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; - protected AddressSetView visible; - protected TraceRecorder currentRecorder; - protected ProgramLocation trackedLocation; protected Program markedProgram; protected Address markedAddress; protected MarkerSet trackingMarker; - protected CaptureSelectedMemoryAction actionCaptureSelectedMemory; - protected MultiStateDockingAction actionTrackLocation; protected DockingAction actionGoTo; protected SyncToStaticListingAction actionSyncToStaticListing; protected FollowsCurrentThreadAction actionFollowsCurrentThread; protected MultiStateDockingAction actionAutoReadMemory; + protected DockingAction actionCaptureSelectedMemory; protected DockingAction actionExportView; protected DockingAction actionOpenProgram; + protected MultiStateDockingAction actionTrackLocation; - protected final DebuggerGoToDialog goToDialog; - - @AutoConfigStateField(codec = TrackingSpecConfigFieldCodec.class) - protected LocationTrackingSpec trackingSpec = defaultTrackingSpec; @AutoConfigStateField protected boolean syncToStaticListing; @AutoConfigStateField protected boolean followsCurrentThread = true; - @AutoConfigStateField(codec = AutoReadMemorySpecConfigFieldCodec.class) - protected AutoReadMemorySpec autoReadMemorySpec = defaultReadMemorySpec; - // TODO: followsCurrentSnap + // TODO: followsCurrentSnap? - protected ForTrackingAndLabelingTraceListener forTrackingTraceListener = - new ForTrackingAndLabelingTraceListener(); - protected ForAccessRecorderListener forAccessRecorderListener = new ForAccessRecorderListener(); + protected DebuggerGoToTrait goToTrait; + protected ForListingTrackingTrait trackingTrait; + protected ForListingReadsMemoryTrait readsMemTrait; - protected final JLabel locationLabel = new JLabel(); + protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel(); protected final MultiBlendedListingBackgroundColorModel colorModel; protected final MarkerSetChangeListener markerChangeListener = new MarkerSetChangeListener(); @@ -392,11 +268,13 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi this.plugin = plugin; this.isMainListing = isConnected; - goToDialog = new DebuggerGoToDialog(this); + goToTrait = new ForListingGoToTrait(); + trackingTrait = new ForListingTrackingTrait(); + readsMemTrait = new ForListingReadsMemoryTrait(); ListingPanel listingPanel = getListingPanel(); colorModel = new MultiBlendedListingBackgroundColorModel(); - colorModel.addModel(new TrackedLocationBackgroundColorModel(plugin, listingPanel)); + colorModel.addModel(trackingTrait.createListingBackgroundColorModel(listingPanel)); colorModel.addModel(new MemoryStateListingBackgroundColorModel(plugin, listingPanel)); colorModel.addModel(new CursorBackgroundColorModel(plugin, listingPanel)); listingPanel.setBackgroundColorModel(colorModel); @@ -408,12 +286,15 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi setVisible(true); createActions(); - doTrackSpec(); + goToTrait.goToCoordinates(current); + trackingTrait.goToCoordinates(current); + readsMemTrait.goToCoordinates(current); + locationLabel.goToCoordinates(current); // TODO: An icon to distinguish dynamic from static //getComponent().setBorder(BorderFactory.createEmptyBorder()); - addListingDisplayListener(this); + addDisplayListener(readsMemTrait.getDisplayListener()); this.setNorthComponent(locationLabel); if (isConnected) { @@ -454,6 +335,11 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi return isMainListing; } + @Override + public boolean isReadOnly() { + return current.isAliveAndPresent(); + } + @Override public String getWindowGroup() { //TODO: Overriding this to align disconnected providers @@ -486,6 +372,8 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi saveState.putXmlElement("formatManager", formatManagerState.saveToXml()); CONFIG_STATE_HANDLER.writeConfigState(this, saveState); + trackingTrait.writeConfigState(saveState); + readsMemTrait.writeConfigState(saveState); } void readConfigState(SaveState saveState) { @@ -498,8 +386,9 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi } CONFIG_STATE_HANDLER.readConfigState(this, saveState); + trackingTrait.readConfigState(saveState); + readsMemTrait.readConfigState(saveState); - actionTrackLocation.setCurrentActionStateByUserData(trackingSpec); if (isMainListing()) { actionSyncToStaticListing.setSelected(syncToStaticListing); followsCurrentThread = true; @@ -509,7 +398,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi actionFollowsCurrentThread.setSelected(followsCurrentThread); updateBorder(); } - actionAutoReadMemory.setCurrentActionStateByUserData(autoReadMemorySpec); } @Override @@ -522,6 +410,28 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi super.addToTool(); } + @Override + protected CodeBrowserClipboardProvider newClipboardProvider() { + /** + * TODO: Ensure memory writes via paste are properly directed to the target process. In the + * meantime, just prevent byte pastes altogether. I cannot disable the clipboard altogether, + * because there are still excellent cases for copying from the dynamic listing, and we + * should still permit pastes of annotations. + */ + return new CodeBrowserClipboardProvider(tool, this) { + @Override + protected boolean pasteBytes(Transferable pasteData) + throws UnsupportedFlavorException, IOException { + return false; + } + + @Override + protected boolean pasteByteString(String string) { + return false; + } + }; + } + protected void updateMarkerServiceColorModel() { colorModel.removeModel(markerServiceColorModel); if (markerService != null) { @@ -564,7 +474,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi } } - @AutoOptionConsumed(name = OPTION_NAME_COLORS_REGISTER_MARKERS) + @AutoOptionConsumed(name = OPTION_NAME_COLORS_TRACKING_MARKERS) private void setTrackingColor(Color trackingColor) { if (trackingMarker != null) { trackingMarker.setMarkerColor(trackingColor); @@ -638,20 +548,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi } } - protected void addNewListeners() { - Trace trace = current.getTrace(); - if (trace != null) { - trace.addListener(forTrackingTraceListener); - } - } - - protected void removeOldListeners() { - Trace trace = current.getTrace(); - if (trace != null) { - trace.removeListener(forTrackingTraceListener); - } - } - @Override protected void doSetProgram(Program newProgram) { if (newProgram != null && newProgram != current.getView()) { @@ -666,25 +562,13 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi setSelection(new ProgramSelection()); super.doSetProgram(newProgram); updateTitle(); - updateLocationLabel(); - } - - protected void doSetRecorder(TraceRecorder newRecorder) { - if (currentRecorder == newRecorder) { - return; - } - if (currentRecorder != null) { - currentRecorder.removeListener(forAccessRecorderListener); - } - currentRecorder = newRecorder; - if (currentRecorder != null) { - currentRecorder.addListener(forAccessRecorderListener); - } + locationLabel.updateLabel(); } protected String computeSubTitle() { TraceProgramView view = current.getView(); List parts = new ArrayList<>(); + LocationTrackingSpec trackingSpec = trackingTrait == null ? null : trackingTrait.getSpec(); if (trackingSpec != null) { String specTitle = trackingSpec.computeTitle(current); if (specTitle != null) { @@ -703,104 +587,18 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi setSubTitle(computeSubTitle()); } - protected TraceSection getNearestSectionContaining(Address address) { - if (current.getView() == null) { - return null; - } - Trace trace = current.getTrace(); - List sections = - new ArrayList<>(trace.getModuleManager().getSectionsAt(current.getSnap(), address)); - if (sections.isEmpty()) { - return null; - } - // TODO: DB's R-Tree could probably do this natively - sections.sort(ComparatorUtils.chainedComparator(List.of( - Comparator.comparing(s -> s.getRange().getMinAddress()), - Comparator.comparing(s -> -s.getRange().getLength())))); - return sections.get(sections.size() - 1); - } - - protected TraceModule getNearestModuleContaining(Address address) { - if (current.getView() == null) { - return null; - } - Trace trace = current.getTrace(); - List modules = - new ArrayList<>(trace.getModuleManager().getModulesAt(current.getSnap(), address)); - if (modules.isEmpty()) { - return null; - } - // TODO: DB's R-Tree could probably do this natively - modules.sort(ComparatorUtils.chainedComparator(List.of( - Comparator.comparing(m -> m.getRange().getMinAddress()), - Comparator.comparing(m -> -m.getRange().getLength())))); - return modules.get(modules.size() - 1); - } - - protected TraceMemoryRegion getRegionContaining(Address address) { - if (current.getView() == null) { - return null; - } - Trace trace = current.getTrace(); - return trace.getMemoryManager().getRegionContaining(current.getSnap(), address); - } - - protected String computeLocationString() { - TraceProgramView view = current.getView(); - if (view == null) { - return ""; - } - ProgramLocation location = getListingPanel().getProgramLocation(); - if (location == null) { - return "(nowhere)"; - } - Address address = location.getAddress(); - TraceSection section = getNearestSectionContaining(address); - if (section != null) { - return section.getModule().getName() + ":" + section.getName(); - } - TraceModule module = getNearestModuleContaining(address); - if (module != null) { - return module.getName(); - } - TraceMemoryRegion region = getRegionContaining(address); - if (region != null) { - return region.getName(); - } - return "(unknown)"; - } - - protected void updateLocationLabel() { - locationLabel.setText(computeLocationString()); - } - protected void createActions() { - // TODO: Add "other" option, and present most-recent in menu, too - // TODO: "other" as in arbitrary expression? - // Only those applicable to the current thread's registers, though. - actionTrackLocation = DebuggerTrackLocationAction.builder(plugin) - .onAction(this::activatedLocationTracking) - .onActionStateChanged(this::changedLocationTracking) - .buildAndInstallLocal(this); - actionTrackLocation.setCurrentActionStateByUserData(defaultTrackingSpec); - - actionGoTo = GoToAction.builder(plugin) - .enabledWhen(ctx -> current.getView() != null) - .onAction(this::activatedGoTo) - .buildAndInstallLocal(this); - if (isMainListing()) { actionSyncToStaticListing = new SyncToStaticListingAction(); } else { actionFollowsCurrentThread = new FollowsCurrentThreadAction(); } - actionCaptureSelectedMemory = new CaptureSelectedMemoryAction(); - actionAutoReadMemory = DebuggerAutoReadMemoryAction.builder(plugin) - .onAction(this::activatedAutoReadMemory) - .onActionStateChanged(this::changedAutoReadMemory) - .buildAndInstallLocal(this); - actionAutoReadMemory.setCurrentActionStateByUserData(defaultReadMemorySpec); + + actionGoTo = goToTrait.installAction(); + actionTrackLocation = trackingTrait.installAction(); + actionAutoReadMemory = readsMemTrait.installAutoReadAction(); + actionCaptureSelectedMemory = readsMemTrait.installCaptureSelectedAction(); actionExportView = ExportTraceViewAction.builder(plugin) .enabledWhen(ctx -> current.getView() != null) @@ -815,18 +613,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi contextChanged(); } - private void activatedGoTo(ActionContext context) { - TraceProgramView view = current.getView(); - if (view == null) { - return; - } - Language language = view.getLanguage(); - if (!(language instanceof SleighLanguage)) { - return; - } - goToDialog.show((SleighLanguage) language); - } - private void activatedExportView(ActionContext context) { if (current.getView() == null) { return; @@ -846,24 +632,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi ProgramManager.OPEN_CURRENT); } - protected void activatedLocationTracking(ActionContext ctx) { - doTrackSpec(); - } - - protected void changedLocationTracking(ActionState newState, - EventTrigger trigger) { - doSetTrackingSpec(newState.getUserData()); - } - - protected void activatedAutoReadMemory(ActionContext ctx) { - doAutoReadMemory(); - } - - protected void changedAutoReadMemory(ActionState newState, - EventTrigger trigger) { - doSetAutoReadMemory(newState.getUserData()); - } - protected boolean isEffectivelyDifferent(ProgramLocation cur, ProgramLocation dest) { if (Objects.equals(cur, dest)) { return false; @@ -897,6 +665,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi @Override public void stateChanged(ChangeEvent e) { super.stateChanged(e); + ProgramLocation trackedLocation = trackingTrait.getTrackedLocation(); if (trackedLocation != null && !isEffectivelyDifferent(getLocation(), trackedLocation)) { cbGoTo.invoke(() -> getListingPanel().goTo(trackedLocation, true)); } @@ -934,7 +703,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi @Override public void programLocationChanged(ProgramLocation location, EventTrigger trigger) { - updateLocationLabel(); + locationLabel.goToAddress(location.getAddress()); if (traceManager != null) { location = ProgramLocationUtils.fixLocation(location, false); } @@ -945,30 +714,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi } } - public CompletableFuture goToSleigh(String spaceName, String expression) { - Language language = current.getView().getLanguage(); - if (!(language instanceof SleighLanguage)) { - throw new IllegalStateException("Current trace does not use Sleigh"); - } - SleighLanguage slang = (SleighLanguage) language; - AddressSpace space = language.getAddressFactory().getAddressSpace(spaceName); - if (space == null) { - throw new IllegalArgumentException("No such address space: " + spaceName); - } - SleighExpression expr = SleighProgramCompiler.compileExpression(slang, expression); - return goToSleigh(space, expr); - } - - public CompletableFuture goToSleigh(AddressSpace space, SleighExpression expression) { - AsyncPcodeExecutor executor = TracePcodeUtils.executorForCoordinates(current); - CompletableFuture result = expression.evaluate(executor); - return result.thenApply(offset -> { - Address address = space.getAddress( - Utils.bytesToLong(offset, offset.length, expression.getLanguage().isBigEndian())); - return getListingPanel().goTo(address); - }); - } - protected void doSyncToStatic(ProgramLocation location) { if (isSyncToStaticListing() && location != null) { ProgramLocation staticLoc = mappingService.getStaticLocationFromDynamic(location); @@ -1102,19 +847,11 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi } public void setTrackingSpec(LocationTrackingSpec spec) { - actionTrackLocation.setCurrentActionStateByUserData(spec); - } - - protected void doSetTrackingSpec(LocationTrackingSpec spec) { - if (trackingSpec != spec) { - trackingSpec = spec; - updateTitle(); - } - doTrackSpec(); + trackingTrait.setSpec(spec); } public LocationTrackingSpec getTrackingSpec() { - return trackingSpec; + return trackingTrait.getSpec(); } public void setSyncToStaticListing(boolean sync) { @@ -1162,41 +899,16 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi return followsCurrentThread; } - protected void doSetAutoReadMemory(AutoReadMemorySpec spec) { - this.autoReadMemorySpec = spec; - if (visible != null) { - // HACK: Calling listener method directly - doAutoReadMemory(); - } - } - public void setAutoReadMemorySpec(AutoReadMemorySpec spec) { - actionAutoReadMemory.setCurrentActionStateByUserData(spec); + readsMemTrait.setAutoSpec(spec); } public AutoReadMemorySpec getAutoReadMemorySpec() { - return autoReadMemorySpec; - } - - protected ProgramLocation computeTrackedLocation() { - // Change of register values (for current frame) - // Change of stack pc (TODO) (for current frame) - // Change of current view (if not caused by goTo) - // Change of current thread - // Change of current snap (TODO) - // Change of current frame (TODO) - // Change of tracking settings - DebuggerCoordinates cur = current; - TraceThread thread = cur.getThread(); - if (thread == null || trackingSpec == null) { - return null; - } - // NB: view's snap may be forked for emulation - Address address = trackingSpec.computeTraceAddress(tool, cur, current.getView().getSnap()); - return address == null ? null : new ProgramLocation(current.getView(), address); + return readsMemTrait.getAutoSpec(); } protected ProgramLocation doMarkTrackedLocation() { + ProgramLocation trackedLocation = trackingTrait.getTrackedLocation(); if (trackedLocation == null) { markTrackedStaticLocation(null); return null; @@ -1207,8 +919,8 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi return trackedStatic; } - protected void doTrackSpec() { - ProgramLocation loc = trackedLocation = computeTrackedLocation(); + protected void doGoToTracked() { + ProgramLocation loc = trackingTrait.getTrackedLocation(); ProgramLocation trackedStatic = doMarkTrackedLocation(); if (loc == null) { return; @@ -1232,7 +944,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi @Override public void dispose() { super.dispose(); - removeOldListeners(); if (consoleService != null) { if (actionOpenProgram != null) { consoleService.removeResolutionAction(actionOpenProgram); @@ -1240,22 +951,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi } } - @Override - public void visibleAddressesChanged(AddressSetView visibleAddresses) { - if (Objects.equals(this.visible, visibleAddresses)) { - return; - } - this.visible = visibleAddresses; - doAutoReadMemory(); - } - - protected void doAutoReadMemory() { - autoReadMemorySpec.readMemory(tool, current, visible).exceptionally(ex -> { - Msg.error(this, "Could not auto-read memory: " + ex); - return null; - }); - } - public void staticProgramLocationChanged(ProgramLocation location) { TraceProgramView view = current.getView(); // NB. Used for snap (don't want emuSnap) if (!isSyncToStaticListing() || view == null || location == null) { @@ -1281,18 +976,12 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi current = coordinates; return; } - boolean doListeners = !Objects.equals(current.getTrace(), coordinates.getTrace()); - if (doListeners) { - removeOldListeners(); - } current = coordinates; - if (doListeners) { - addNewListeners(); - } doSetProgram(current.getView()); - doSetRecorder(current.getRecorder()); - doTrackSpec(); - doAutoReadMemory(); + goToTrait.goToCoordinates(coordinates); + trackingTrait.goToCoordinates(coordinates); + readsMemTrait.goToCoordinates(coordinates); + locationLabel.goToCoordinates(coordinates); contextChanged(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerTrackedRegisterListingBackgroundColorModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerTrackedRegisterListingBackgroundColorModel.java index 8fdfd9a2fe..26b2bd18c0 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerTrackedRegisterListingBackgroundColorModel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerTrackedRegisterListingBackgroundColorModel.java @@ -15,68 +15,23 @@ */ package ghidra.app.plugin.core.debug.gui.listing; -import java.awt.Color; -import java.math.BigInteger; - -import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.plugin.core.debug.gui.colors.DebuggerTrackedRegisterBackgroundColorModel; import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel; import ghidra.app.util.viewer.listingpanel.ListingPanel; -import ghidra.app.util.viewer.util.AddressIndexMap; -import ghidra.framework.options.AutoOptions; -import ghidra.framework.options.annotation.AutoOptionConsumed; -import ghidra.program.model.address.Address; -import ghidra.program.model.listing.Program; -import ghidra.program.util.ProgramLocation; +import ghidra.framework.plugintool.Plugin; public abstract class DebuggerTrackedRegisterListingBackgroundColorModel - implements ListingBackgroundColorModel { - private Color defaultBackgroundColor; - private Program program; - private AddressIndexMap addressIndexMap; + extends DebuggerTrackedRegisterBackgroundColorModel implements ListingBackgroundColorModel { - // TODO: Seems I should at least rename this option - @AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_REGISTER_MARKERS) - Color trackingColor; - @SuppressWarnings("unused") - private final AutoOptions.Wiring autoOptionsWiring; - - public DebuggerTrackedRegisterListingBackgroundColorModel(DebuggerListingPlugin plugin, + public DebuggerTrackedRegisterListingBackgroundColorModel(Plugin plugin, ListingPanel listingPanel) { - autoOptionsWiring = AutoOptions.wireOptions(plugin, this); + super(plugin); modelDataChanged(listingPanel); } - @Override - public Color getBackgroundColor(BigInteger index) { - if (program == null) { - return defaultBackgroundColor; - } - ProgramLocation loc = getTrackedLocation(); - if (loc == null) { - return defaultBackgroundColor; - } - Address address = addressIndexMap.getAddress(index); - if (!loc.getAddress().equals(address)) { - return defaultBackgroundColor; - } - return trackingColor; - } - - @Override - public Color getDefaultBackgroundColor() { - return defaultBackgroundColor; - } - - @Override - public void setDefaultBackgroundColor(Color c) { - defaultBackgroundColor = c; - } - @Override public void modelDataChanged(ListingPanel listingPanel) { this.program = listingPanel == null ? null : listingPanel.getProgram(); this.addressIndexMap = listingPanel == null ? null : listingPanel.getAddressIndexMap(); } - - protected abstract ProgramLocation getTrackedLocation(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/MultiBlendedListingBackgroundColorModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/MultiBlendedListingBackgroundColorModel.java index c3b6b41bdf..a9bc4a45e7 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/MultiBlendedListingBackgroundColorModel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/MultiBlendedListingBackgroundColorModel.java @@ -20,29 +20,31 @@ import java.math.BigInteger; import java.util.ArrayList; import java.util.List; +import docking.widgets.fieldpanel.support.BackgroundColorModel; import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel; import ghidra.app.util.viewer.listingpanel.ListingPanel; +import ghidra.util.ColorUtils.ColorBlender; public class MultiBlendedListingBackgroundColorModel implements ListingBackgroundColorModel { - private final List models = new ArrayList<>(); + private final List models = new ArrayList<>(); - private final List toBlend = new ArrayList<>(); + private final ColorBlender blender = new ColorBlender(); public MultiBlendedListingBackgroundColorModel() { } - public void addModel(ListingBackgroundColorModel m) { + public void addModel(BackgroundColorModel m) { models.add(m); } - public void removeModel(ListingBackgroundColorModel m) { + public void removeModel(BackgroundColorModel m) { models.remove(m); } @Override public Color getBackgroundColor(BigInteger index) { - toBlend.clear(); - for (ListingBackgroundColorModel m : models) { + blender.clear(); + for (BackgroundColorModel m : models) { Color c = m.getBackgroundColor(index); if (c == null) { continue; @@ -50,31 +52,9 @@ public class MultiBlendedListingBackgroundColorModel implements ListingBackgroun if (c.equals(m.getDefaultBackgroundColor())) { continue; } - toBlend.add(c); + blender.add(c); } - int size = toBlend.size(); - if (size == 0) { - return getDefaultBackgroundColor(); - } - if (size == 1) { - return toBlend.get(0); - } - return blend(); - } - - protected Color blend() { - int r = 0; - int g = 0; - int b = 0; - int ta = 0; - for (Color c : toBlend) { - int a = c.getAlpha(); - ta += a; - r += a * c.getRed(); - g += a * c.getGreen(); - b += a * c.getBlue(); - } - return ta == 0 ? getDefaultBackgroundColor() : new Color(r / ta, g / ta, b / ta); + return blender.getColor(getDefaultBackgroundColor()); } @Override @@ -87,15 +67,19 @@ public class MultiBlendedListingBackgroundColorModel implements ListingBackgroun @Override public void setDefaultBackgroundColor(Color c) { - for (ListingBackgroundColorModel m : models) { + for (BackgroundColorModel m : models) { m.setDefaultBackgroundColor(c); } } @Override public void modelDataChanged(ListingPanel listingPanel) { - for (ListingBackgroundColorModel m : models) { - m.modelDataChanged(listingPanel); + for (BackgroundColorModel m : models) { + if (!(m instanceof ListingBackgroundColorModel)) { + continue; + } + ListingBackgroundColorModel lm = (ListingBackgroundColorModel) m; + lm.modelDataChanged(listingPanel); } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryByteViewerComponent.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryByteViewerComponent.java new file mode 100644 index 0000000000..717a53cccf --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryByteViewerComponent.java @@ -0,0 +1,181 @@ +/* ### + * 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.memory; + +import java.awt.Color; +import java.awt.FontMetrics; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; + +import docking.widgets.fieldpanel.internal.LayoutBackgroundColorManager; +import docking.widgets.fieldpanel.support.FieldSelection; +import ghidra.app.plugin.core.byteviewer.*; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.plugin.core.debug.gui.colors.*; +import ghidra.app.plugin.core.debug.gui.colors.MultiSelectionBlendedLayoutBackgroundColorManager.ColoredFieldSelection; +import ghidra.app.plugin.core.format.DataFormatModel; +import ghidra.framework.options.AutoOptions; +import ghidra.framework.options.annotation.AutoOptionConsumed; +import ghidra.program.model.address.*; +import ghidra.trace.model.Trace; +import ghidra.trace.model.TraceAddressSnapRange; +import ghidra.trace.model.memory.TraceMemoryState; + +public class DebuggerMemoryByteViewerComponent extends ByteViewerComponent + implements SelectionTranslator { + + protected class SelectionHighlightSelectionGenerator implements SelectionGenerator { + @Override + public void addSelections(BigInteger layoutIndex, SelectionTranslator translator, + List selections) { + Color selectionColor = paintContext.getSelectionColor(); + Color highlightColor = paintContext.getHighlightColor(); + selections.add(new ColoredFieldSelection(getSelection(), selectionColor)); + selections.add(new ColoredFieldSelection(getHighlight(), highlightColor)); + } + } + + protected class TraceMemoryStateSelectionGenerator implements SelectionGenerator { + @Override + public void addSelections(BigInteger layoutIndex, SelectionTranslator translator, + List selections) { + FieldSelection lineFieldSel = new FieldSelection(); + lineFieldSel.addRange(layoutIndex, layoutIndex.add(BigInteger.ONE)); + + DebuggerMemoryBytesProvider provider = panel.getProvider(); + DebuggerCoordinates coordinates = provider.current; + if (coordinates.getView() == null) { + return; + } + Trace trace = coordinates.getTrace(); + // TODO: Mimic the listing's background, or factor into common + long snap = coordinates.getSnap(); + // TODO: Span out and cache? + AddressSetView lineAddresses = translator.convertFieldToAddress(lineFieldSel); + // Because UNKNOWN need not be explicitly recorded, compute it by subtracting others + AddressSet unknown = new AddressSet(lineAddresses); + for (AddressRange range : lineAddresses) { + for (Entry entry : trace.getMemoryManager() + .getStates(snap, range)) { + if (entry.getValue() != TraceMemoryState.UNKNOWN) { + unknown.delete(entry.getKey().getRange()); + } + Color color = colorForState(entry.getValue()); + if (color == null) { + continue; + } + // NOTE: Only TraceMemoryState.ERROR should reach here + FieldSelection resultFieldSel = + translator.convertAddressToField(entry.getKey().getRange()); + if (!resultFieldSel.isEmpty()) { + selections.add(new ColoredFieldSelection(resultFieldSel, color)); + } + } + } + if (unknownColor == null) { + return; + } + for (AddressRange unk : unknown) { + FieldSelection resultFieldSel = translator.convertAddressToField(unk); + if (!resultFieldSel.isEmpty()) { + selections.add(new ColoredFieldSelection(resultFieldSel, unknownColor)); + } + } + } + } + + private final DebuggerMemoryBytesPanel panel; + + @AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_ERROR_MEMORY) + private Color errorColor; + @AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_STALE_MEMORY) + private Color unknownColor; + @SuppressWarnings("unused") + private final AutoOptions.Wiring autoOptionsWiring; + + private final List selectionGenerators; + + public DebuggerMemoryByteViewerComponent(DebuggerMemoryBytesPanel vpanel, + ByteViewerLayoutModel layoutModel, DataFormatModel model, int bytesPerLine, + FontMetrics fm) { + super(vpanel, layoutModel, model, bytesPerLine, fm); + // TODO: I don't care much for this reverse path + this.panel = vpanel; + + autoOptionsWiring = AutoOptions.wireOptionsConsumed(vpanel.getProvider().getPlugin(), this); + + selectionGenerators = List.of( + new SelectionHighlightSelectionGenerator(), + new TraceMemoryStateSelectionGenerator(), + vpanel.getProvider().trackingTrait.getSelectionGenerator()); + // NOTE: Cursor, being line-by-line, is done via background color model in super + } + + protected Color colorForState(TraceMemoryState state) { + switch (state) { + case ERROR: + return errorColor; + case KNOWN: + return null; + case UNKNOWN: + return unknownColor; + } + throw new AssertionError(); + } + + @Override + protected LayoutBackgroundColorManager getLayoutSelectionMap(BigInteger layoutIndex) { + Color backgroundColor = backgroundColorModel.getBackgroundColor(layoutIndex); + boolean isBackgroundDefault = + backgroundColorModel.getDefaultBackgroundColor().equals(backgroundColor); + List selections = new ArrayList<>(3); + for (SelectionGenerator sg : selectionGenerators) { + sg.addSelections(layoutIndex, this, selections); + } + return MultiSelectionBlendedLayoutBackgroundColorManager.getLayoutColorMap( + layoutIndex, selections, backgroundColor, isBackgroundDefault); + } + + @Override + public AddressSetView convertFieldToAddress(FieldSelection fieldSelection) { + ProgramByteBlockSet blockSet = getBlockSet(); + if (blockSet == null) { + return new AddressSet(); + } + return blockSet.getAddressSet(processFieldSelection(fieldSelection)); + } + + @Override + public FieldSelection convertAddressToField(AddressSetView addresses) { + ProgramByteBlockSet blockSet = getBlockSet(); + if (blockSet == null) { + return new FieldSelection(); + } + return getFieldSelection(blockSet.getBlockSelection(addresses)); + } + + @Override + public FieldSelection convertAddressToField(AddressRange range) { + ProgramByteBlockSet blockSet = getBlockSet(); + if (blockSet == null) { + return new FieldSelection(); + } + return getFieldSelection(blockSet.getBlockSelection(range)); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesPanel.java new file mode 100644 index 0000000000..947a704760 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesPanel.java @@ -0,0 +1,42 @@ +/* ### + * 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.memory; + +import ghidra.app.plugin.core.byteviewer.*; +import ghidra.app.plugin.core.format.DataFormatModel; + +public class DebuggerMemoryBytesPanel extends ByteViewerPanel { + private final DebuggerMemoryBytesProvider provider; + + public DebuggerMemoryBytesPanel(DebuggerMemoryBytesProvider provider) { + super(provider); + // TODO: Would rather not provide this reverse path + this.provider = provider; + } + + /** + * TODO: I don't care for this + */ + public DebuggerMemoryBytesProvider getProvider() { + return provider; + } + + @Override + protected ByteViewerComponent newByteViewerComponent(DataFormatModel model) { + return new DebuggerMemoryByteViewerComponent(this, new ByteViewerLayoutModel(), model, + getBytesPerLine(), getFontMetrics()); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesPlugin.java index cad1843dcc..e4372833a8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesPlugin.java @@ -15,21 +15,285 @@ */ package ghidra.app.plugin.core.debug.gui.memory; -import static ghidra.lifecycle.Unfinished.TODO; +import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*; -import ghidra.app.plugin.core.byteviewer.ByteViewerPlugin; -import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; -import ghidra.framework.plugintool.PluginTool; +import java.awt.Color; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; -public class DebuggerMemoryBytesPlugin /*extends Plugin*/ { +import org.jdom.Element; - /** - * TODO: This will likely require a refactor of the existing {@link ByteViewerPlugin} to provide - * an abstract one that we can inherit from, in the same vein as the - * {@link DebuggerListingPlugin}. - */ - protected DebuggerMemoryBytesPlugin(PluginTool tool) { - //super(tool); - TODO(); +import ghidra.app.events.ProgramLocationPluginEvent; +import ghidra.app.events.ProgramSelectionPluginEvent; +import ghidra.app.plugin.PluginCategoryNames; +import ghidra.app.plugin.core.byteviewer.*; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.DebuggerPluginPackage; +import ghidra.app.plugin.core.debug.event.*; +import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec; +import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec; +import ghidra.app.services.*; +import ghidra.framework.options.AutoOptions; +import ghidra.framework.options.SaveState; +import ghidra.framework.options.annotation.AutoOptionConsumed; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.annotation.AutoServiceConsumed; +import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramSelection; + +@PluginInfo( + shortDescription = "View bytes of trace (possibly live) memory", + description = "Provides the memory bytes display window. Functions similarly to " + + "the main program bytes display window, but for traces. If the trace is the " + + "destination of a live recording, the view(s) retrieve live memory on demand.", + category = PluginCategoryNames.DEBUGGER, + packageName = DebuggerPluginPackage.NAME, + status = PluginStatus.RELEASED, + eventsConsumed = { + // ProgramSelectionPluginEvent.class, // TODO: Later or remove + // ProgramHighlightPluginEvent.class, // TODO: Later or remove + TraceActivatedPluginEvent.class, // Trace/thread activation and register tracking + TraceClosedPluginEvent.class, + }, + eventsProduced = { + TraceLocationPluginEvent.class, + TraceSelectionPluginEvent.class, + }, + servicesRequired = { + DebuggerModelService.class, // For memory capture + ClipboardService.class, + }) +public class DebuggerMemoryBytesPlugin + extends AbstractByteViewerPlugin { + private static final String KEY_CONNECTED_PROVIDER = "connectedProvider"; + private static final String KEY_DISCONNECTED_COUNT = "disconnectedCount"; + private static final String PREFIX_DISCONNECTED_PROVIDER = "disconnectedProvider"; + + @AutoServiceConsumed + private ProgramManager programManager; + // NOTE: This plugin doesn't extend AbstractDebuggerPlugin + @SuppressWarnings("unused") + private AutoService.Wiring autoServiceWiring; + + @AutoOptionConsumed(name = OPTION_NAME_COLORS_STALE_MEMORY) + private Color staleMemoryColor; + @AutoOptionConsumed(name = OPTION_NAME_COLORS_ERROR_MEMORY) + private Color errorMemoryColor; + @AutoOptionConsumed(name = OPTION_NAME_COLORS_TRACKING_MARKERS) + private Color trackingColor; + @SuppressWarnings("unused") + private AutoOptions.Wiring autoOptionsWiring; + + private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; + + public DebuggerMemoryBytesPlugin(PluginTool tool) { + super(tool); + autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this); + autoOptionsWiring = AutoOptions.wireOptions(this); + + createActions(); + } + + @Override + protected DebuggerMemoryBytesProvider createProvider(boolean isConnected) { + return new DebuggerMemoryBytesProvider(tool, this, isConnected); + } + + private void createActions() { + // TODO + } + + public DebuggerMemoryBytesProvider createViewerIfMissing(LocationTrackingSpec spec, + boolean followsCurrentThread) { + synchronized (disconnectedProviders) { + for (DebuggerMemoryBytesProvider provider : disconnectedProviders) { + if (provider.getTrackingSpec() != spec) { + continue; + } + if (provider.isFollowsCurrentThread() != followsCurrentThread) { + continue; + } + return provider; + } + DebuggerMemoryBytesProvider provider = createNewDisconnectedProvider(); + provider.setTrackingSpec(spec); + provider.setFollowsCurrentThread(followsCurrentThread); + provider.goToCoordinates(current); + return provider; + } + } + + @Override + protected void updateLocation( + ProgramByteViewerComponentProvider programByteViewerComponentProvider, + ProgramLocationPluginEvent event, boolean export) { + // TODO + } + + @Override + protected void fireProgramLocationPluginEvent( + ProgramByteViewerComponentProvider programByteViewerComponentProvider, + ProgramLocationPluginEvent pluginEvent) { + // TODO + } + + @Override + public void updateSelection(ByteViewerComponentProvider provider, + ProgramSelectionPluginEvent event, Program program) { + // TODO + } + + @Override + public void highlightChanged(ByteViewerComponentProvider provider, ProgramSelection highlight) { + // TODO + } + + protected void allProviders(Consumer action) { + action.accept(connectedProvider); + for (DebuggerMemoryBytesProvider provider : disconnectedProviders) { + action.accept(provider); + } + } + + @Override + public void processEvent(PluginEvent event) { + if (event instanceof TraceActivatedPluginEvent) { + TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event; + current = ev.getActiveCoordinates(); + allProviders(p -> p.coordinatesActivated(current)); + } + if (event instanceof TraceClosedPluginEvent) { + TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event; + if (current.getTrace() == ev.getTrace()) { + current = DebuggerCoordinates.NOWHERE; + } + allProviders(p -> p.traceClosed(ev.getTrace())); + } + // TODO: Sync among dynamic providers? + } + + @AutoServiceConsumed + public void setTraceManager(DebuggerTraceManagerService traceManager) { + DebuggerMemoryBytesProvider provider = connectedProvider; + if (provider == null || traceManager == null) { + return; + } + provider.coordinatesActivated(current = traceManager.getCurrent()); + } + + @Override + public Object getTransientState() { + // Not needed, since I'm not coordinated with ProgramManager + return new Object[] {}; + } + + @Override + public void restoreTransientState(Object objectState) { + // Not needed, since I'm not coordinated with ProgramManager + } + + @Override + public void writeDataState(SaveState saveState) { + SaveState connectedProviderState = new SaveState(); + connectedProvider.writeDataState(connectedProviderState); + saveState.putXmlElement(KEY_CONNECTED_PROVIDER, connectedProviderState.saveToXml()); + + /** + * Arrange the follows ones first, so that we reload them into corresponding providers + * restored from config state + */ + List disconnected = disconnectedProviders.stream() + .filter(p -> p.isFollowsCurrentThread()) + .collect(Collectors.toList()); + for (DebuggerMemoryBytesProvider p : disconnectedProviders) { + if (!disconnected.contains(p)) { + disconnected.add(p); + } + } + int disconnectedCount = disconnected.size(); + saveState.putInt(KEY_DISCONNECTED_COUNT, disconnectedCount); + for (int index = 0; index < disconnectedCount; index++) { + DebuggerMemoryBytesProvider provider = disconnected.get(index); + String stateName = PREFIX_DISCONNECTED_PROVIDER + index; + SaveState providerState = new SaveState(); + provider.writeDataState(providerState); + saveState.putXmlElement(stateName, providerState.saveToXml()); + } + } + + protected void ensureProviders(int count, boolean followCurrentThread, SaveState configState) { + while (disconnectedProviders.size() < count) { + int index = disconnectedProviders.size(); + String stateName = PREFIX_DISCONNECTED_PROVIDER + index; + DebuggerMemoryBytesProvider provider = createNewDisconnectedProvider(); + provider.setFollowsCurrentThread(false); + Element providerElement = configState.getXmlElement(stateName); + // Read transient configs, which are not saved in tool + if (providerElement != null) { + SaveState providerState = new SaveState(providerElement); + provider.readConfigState(providerState); // Yes, config + } + else { + provider.setTrackingSpec( + LocationTrackingSpec.fromConfigName(NoneLocationTrackingSpec.CONFIG_NAME)); + } + } + } + + @Override + public void readDataState(SaveState saveState) { + Element connectedProviderElement = saveState.getXmlElement(KEY_CONNECTED_PROVIDER); + if (connectedProviderElement != null) { + SaveState connectedProviderState = new SaveState(connectedProviderElement); + connectedProvider.readDataState(connectedProviderState); + } + + int disconnectedCount = saveState.getInt(KEY_DISCONNECTED_COUNT, 0); + ensureProviders(disconnectedCount, false, saveState); + + List disconnected = disconnectedProviders; + for (int index = 0; index < disconnectedCount; index++) { + String stateName = PREFIX_DISCONNECTED_PROVIDER + index; + Element providerElement = saveState.getXmlElement(stateName); + if (providerElement != null) { + SaveState providerState = new SaveState(providerElement); + DebuggerMemoryBytesProvider provider = disconnected.get(index); + provider.readDataState(providerState); + } + } + } + + @Override + public void writeConfigState(SaveState saveState) { + SaveState connectedProviderState = new SaveState(); + connectedProvider.writeConfigState(connectedProviderState); + saveState.putXmlElement(KEY_CONNECTED_PROVIDER, connectedProviderState.saveToXml()); + + List disconnected = disconnectedProviders.stream() + .filter(p -> p.isFollowsCurrentThread()) + .collect(Collectors.toList()); + int disconnectedCount = disconnected.size(); + saveState.putInt(KEY_DISCONNECTED_COUNT, disconnectedCount); + for (int index = 0; index < disconnectedCount; index++) { + DebuggerMemoryBytesProvider provider = disconnected.get(index); + String stateName = PREFIX_DISCONNECTED_PROVIDER + index; + SaveState providerState = new SaveState(); + provider.writeConfigState(providerState); + saveState.putXmlElement(stateName, providerState.saveToXml()); + } + } + + @Override + public void readConfigState(SaveState saveState) { + Element connectedProviderElement = saveState.getXmlElement(KEY_CONNECTED_PROVIDER); + if (connectedProviderElement != null) { + SaveState connectedProviderState = new SaveState(connectedProviderElement); + connectedProvider.readConfigState(connectedProviderState); + } + + int disconnectedCount = saveState.getInt(KEY_DISCONNECTED_COUNT, 0); + ensureProviders(disconnectedCount, true, saveState); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java new file mode 100644 index 0000000000..fe8e49b679 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java @@ -0,0 +1,439 @@ +/* ### + * 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.memory; + +import java.awt.BorderLayout; +import java.lang.invoke.MethodHandles; +import java.math.BigInteger; +import java.util.*; + +import org.apache.commons.lang3.StringUtils; + +import docking.ActionContext; +import docking.action.*; +import docking.menu.MultiStateDockingAction; +import ghidra.app.plugin.core.byteviewer.*; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction; +import ghidra.app.plugin.core.debug.gui.action.*; +import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec; +import ghidra.app.plugin.core.format.ByteBlock; +import ghidra.app.services.DebuggerTraceManagerService; +import ghidra.framework.options.SaveState; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.annotation.AutoConfigStateField; +import ghidra.framework.plugintool.annotation.AutoServiceConsumed; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; +import ghidra.trace.model.Trace; +import ghidra.trace.model.program.TraceProgramView; +import ghidra.util.Swing; + +public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvider { + private static final AutoConfigState.ClassHandler CONFIG_STATE_HANDLER = + AutoConfigState.wireHandler(ProgramByteViewerComponentProvider.class, + MethodHandles.lookup()); + private static final String KEY_DEBUGGER_COORDINATES = "DebuggerCoordinates"; + + protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) { + if (!Objects.equals(a.getView(), b.getView())) { + return false; // Subsumes trace + } + if (!Objects.equals(a.getRecorder(), b.getRecorder())) { + return false; // For capture memory action + } + if (!Objects.equals(a.getTime(), b.getTime())) { + return false; + } + if (!Objects.equals(a.getThread(), b.getThread())) { + return false; // for reg/pc tracking + } + if (!Objects.equals(a.getFrame(), b.getFrame())) { + return false; // for reg/pc tracking + } + return true; + } + + protected class FollowsCurrentThreadAction extends AbstractFollowsCurrentThreadAction { + public FollowsCurrentThreadAction() { + super(plugin); + setMenuBarData(new MenuData(new String[] { NAME })); + setSelected(true); + addLocalAction(this); + setEnabled(true); + } + + @Override + public void actionPerformed(ActionContext context) { + doSetFollowsCurrentThread(isSelected()); + } + } + + protected class ForMemoryBytesGoToTrait extends DebuggerGoToTrait { + public ForMemoryBytesGoToTrait() { + super(DebuggerMemoryBytesProvider.this.tool, DebuggerMemoryBytesProvider.this.plugin, + DebuggerMemoryBytesProvider.this); + } + + @Override + protected boolean goToAddress(Address address) { + TraceProgramView view = current.getView(); + if (view == null) { + return false; + } + return goTo(view, new ProgramLocation(view, address)); + } + } + + protected class ForMemoryBytesTrackingTrait extends DebuggerTrackLocationTrait { + public ForMemoryBytesTrackingTrait() { + super(DebuggerMemoryBytesProvider.this.tool, DebuggerMemoryBytesProvider.this.plugin, + DebuggerMemoryBytesProvider.this); + } + + @Override + protected void locationTracked() { + doGoToTracked(); + } + } + + protected class ForMemoryBytesReadsMemoryTrait extends DebuggerReadsMemoryTrait { + public ForMemoryBytesReadsMemoryTrait() { + super(DebuggerMemoryBytesProvider.this.tool, DebuggerMemoryBytesProvider.this.plugin, + DebuggerMemoryBytesProvider.this); + } + + @Override + protected AddressSetView getSelection() { + return DebuggerMemoryBytesProvider.this.getSelection(); + } + } + + private final AutoReadMemorySpec defaultReadMemorySpec = + AutoReadMemorySpec.fromConfigName(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME); + + private final DebuggerMemoryBytesPlugin myPlugin; + + @AutoServiceConsumed + private DebuggerTraceManagerService traceManager; + @SuppressWarnings("unused") + private final AutoService.Wiring autoServiceWiring; + + protected DockingAction actionGoTo; + protected FollowsCurrentThreadAction actionFollowsCurrentThread; + protected MultiStateDockingAction actionAutoReadMemory; + protected DockingAction actionCaptureSelectedMemory; + protected MultiStateDockingAction actionTrackLocation; + + protected ForMemoryBytesGoToTrait goToTrait; + protected ForMemoryBytesTrackingTrait trackingTrait; + protected ForMemoryBytesReadsMemoryTrait readsMemTrait; + + protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel(); + + @AutoConfigStateField + protected boolean followsCurrentThread = true; + @AutoConfigStateField(codec = AutoReadMemorySpecConfigFieldCodec.class) + protected AutoReadMemorySpec autoReadMemorySpec = defaultReadMemorySpec; + // TODO: followsCurrentSnap? + + DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; + + protected final boolean isMainViewer; + + protected DebuggerMemoryBytesProvider(PluginTool tool, DebuggerMemoryBytesPlugin plugin, + boolean isConnected) { + super(tool, plugin, "Memory", isConnected); + this.myPlugin = plugin; + this.isMainViewer = isConnected; + + autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); + createActions(); + addDisplayListener(readsMemTrait.getDisplayListener()); + decorationComponent.add(locationLabel, BorderLayout.NORTH); + + goToTrait.goToCoordinates(current); + trackingTrait.goToCoordinates(current); + readsMemTrait.goToCoordinates(current); + locationLabel.goToCoordinates(current); + } + + /** + * TODO: I'd rather this not be here + */ + protected Plugin getPlugin() { + return plugin; + } + + protected void initTraits() { + if (goToTrait == null) { + goToTrait = new ForMemoryBytesGoToTrait(); + } + if (trackingTrait == null) { + trackingTrait = new ForMemoryBytesTrackingTrait(); + } + if (readsMemTrait == null) { + readsMemTrait = new ForMemoryBytesReadsMemoryTrait(); + } + } + + @Override + protected ByteViewerPanel newByteViewerPanel() { + initTraits(); + return new DebuggerMemoryBytesPanel(this); + } + + // For testing access + @Override + protected ByteViewerPanel getByteViewerPanel() { + return super.getByteViewerPanel(); + } + + /** + * Deal with the fact that initialization order is hard to control + */ + protected DebuggerCoordinates getCurrent() { + return current == null ? DebuggerCoordinates.NOWHERE : current; + } + + protected String computeSubTitle() { + // TODO: This should be factored in a common place + DebuggerCoordinates current = getCurrent(); + TraceProgramView view = current == null ? null : current.getView(); + List parts = new ArrayList<>(); + LocationTrackingSpec trackingSpec = trackingTrait == null ? null : trackingTrait.getSpec(); + if (trackingSpec != null) { + String specTitle = trackingSpec.computeTitle(current); + if (specTitle != null) { + parts.add(specTitle); + } + } + if (view != null) { + parts.add(current.getTrace().getDomainFile().getName()); + } + return StringUtils.join(parts, ", "); + } + + @Override + protected void updateTitle() { + setSubTitle(computeSubTitle()); + } + + protected void createActions() { + initTraits(); + + if (!isMainViewer()) { + actionFollowsCurrentThread = new FollowsCurrentThreadAction(); + } + + actionGoTo = goToTrait.installAction(); + actionTrackLocation = trackingTrait.installAction(); + actionAutoReadMemory = readsMemTrait.installAutoReadAction(); + actionCaptureSelectedMemory = readsMemTrait.installCaptureSelectedAction(); + } + + @Override + protected void doSetProgram(Program newProgram) { + if (newProgram != null && newProgram != current.getView()) { + throw new AssertionError(); + } + if (getProgram() == newProgram) { + return; + } + if (newProgram != null && !(newProgram instanceof TraceProgramView)) { + throw new IllegalArgumentException("Dynamic Listings require trace views"); + } + super.doSetProgram(newProgram); + if (newProgram != null) { + setSelection(new ProgramSelection()); + } + updateTitle(); + locationLabel.updateLabel(); + } + + protected DebuggerCoordinates adjustCoordinates(DebuggerCoordinates coordinates) { + if (followsCurrentThread) { + return coordinates; + } + // Because the view's snap is changing with or without us.... So go with. + return current.withTime(coordinates.getTime()); + } + + public void goToCoordinates(DebuggerCoordinates coordinates) { + if (sameCoordinates(current, coordinates)) { + current = coordinates; + return; + } + current = coordinates; + doSetProgram(current.getView()); + goToTrait.goToCoordinates(coordinates); + trackingTrait.goToCoordinates(coordinates); + readsMemTrait.goToCoordinates(coordinates); + locationLabel.goToCoordinates(coordinates); + contextChanged(); + } + + public void coordinatesActivated(DebuggerCoordinates coordinates) { + DebuggerCoordinates adjusted = adjustCoordinates(coordinates); + goToCoordinates(adjusted); + } + + public void traceClosed(Trace trace) { + if (current.getTrace() == trace) { + goToCoordinates(DebuggerCoordinates.NOWHERE); + } + } + + public void setFollowsCurrentThread(boolean follows) { + if (isMainViewer()) { + throw new IllegalStateException( + "The main memory bytes viewer always follows the current trace and thread"); + } + actionFollowsCurrentThread.setSelected(follows); + doSetFollowsCurrentThread(follows); + } + + protected void doSetFollowsCurrentThread(boolean follows) { + this.followsCurrentThread = follows; + updateBorder(); + updateTitle(); + coordinatesActivated(traceManager.getCurrent()); + } + + protected void updateBorder() { + decorationComponent.setConnected(followsCurrentThread); + } + + public boolean isFollowsCurrentThread() { + return followsCurrentThread; + } + + public void setAutoReadMemorySpec(AutoReadMemorySpec spec) { + readsMemTrait.setAutoSpec(spec); + } + + public AutoReadMemorySpec getAutoReadMemorySpec() { + return readsMemTrait.getAutoSpec(); + } + + protected void doGoToTracked() { + ProgramLocation loc = trackingTrait.getTrackedLocation(); + if (loc == null) { + return; + } + TraceProgramView curView = current.getView(); + Swing.runIfSwingOrRunLater(() -> { + goTo(curView, loc); + }); + } + + public void setTrackingSpec(LocationTrackingSpec spec) { + trackingTrait.setSpec(spec); + } + + public LocationTrackingSpec getTrackingSpec() { + return trackingTrait.getSpec(); + } + + public boolean isMainViewer() { + return isMainViewer; + } + + @Override + protected void writeConfigState(SaveState saveState) { + super.writeConfigState(saveState); + } + + @Override + protected void readConfigState(SaveState saveState) { + super.readConfigState(saveState); + + CONFIG_STATE_HANDLER.readConfigState(this, saveState); + trackingTrait.readConfigState(saveState); + + if (isMainViewer()) { + followsCurrentThread = true; + } + else { + actionFollowsCurrentThread.setSelected(followsCurrentThread); + updateBorder(); + } + // TODO: actionAutoReadMemory + } + + @Override + protected void writeDataState(SaveState saveState) { + if (!isMainViewer()) { + current.writeDataState(tool, saveState, KEY_DEBUGGER_COORDINATES); + } + super.writeDataState(saveState); + } + + @Override + protected void readDataState(SaveState saveState) { + if (!isMainViewer()) { + DebuggerCoordinates coordinates = + DebuggerCoordinates.readDataState(tool, saveState, KEY_DEBUGGER_COORDINATES, true); + coordinatesActivated(coordinates); + } + super.readDataState(saveState); + } + + @Override + protected void updateLocation(ByteBlock block, BigInteger blockOffset, int column, + boolean export) { + super.updateLocation(block, blockOffset, column, export); + locationLabel.goToAddress(currentLocation == null ? null : currentLocation.getAddress()); + } + + @Override + public void cloneWindow() { + final DebuggerMemoryBytesProvider newProvider = myPlugin.createNewDisconnectedProvider(); + SaveState saveState = new SaveState(); + writeConfigState(saveState); + newProvider.readConfigState(saveState); + + newProvider.goToCoordinates(current); + newProvider.setLocation(currentLocation); + newProvider.panel.setViewerPosition(panel.getViewerPosition()); + } + + @Override + protected ProgramByteBlockSet newByteBlockSet(ByteBlockChangeManager changeManager) { + if (program == null) { + return null; + } + return new WritesTargetProgramByteBlockSet(this, program, changeManager); + } + + @Override + public void addLocalAction(DockingActionIf action) { + /** + * TODO This is a terrible hack, but it's temporary. We do not yet support writing target + * memory from the bytes provider. Once we do, we should obviously take this hack out. I + * don't think we'll forget, because the only way to get the write toggle button back is to + * delete this override. + */ + if (action == editModeAction) { + return; + } + super.addLocalAction(action); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/WritesTargetMemoryByteBlock.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/WritesTargetMemoryByteBlock.java new file mode 100644 index 0000000000..5251d1a4b6 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/WritesTargetMemoryByteBlock.java @@ -0,0 +1,179 @@ +/* ### + * 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.memory; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import ghidra.app.plugin.core.byteviewer.MemoryByteBlock; +import ghidra.app.plugin.core.format.ByteBlockAccessException; +import ghidra.app.services.TraceRecorder; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryBlock; + +/** + * An extension of MemoryByteBlock that redirects applicable writes to a debug target + */ +public class WritesTargetMemoryByteBlock extends MemoryByteBlock { + protected final WritesTargetProgramByteBlockSet blockSet; + + /** + * Constructor. + * + * @param blockSet the containing block set + * @param program the trace program view for the block + * @param memory the view's memory + * @param block the view's memory block + */ + public WritesTargetMemoryByteBlock(WritesTargetProgramByteBlockSet blockSet, Program program, + Memory memory, MemoryBlock block) { + super(program, memory, block); + this.blockSet = blockSet; + } + + /** + * Check writes should be redirected, based on the provider's coordinates. + * + *

+ * Note that redirecting the write prevents the edit from being written (dirtectly) into the + * trace. If the edit is successful, the trace recorder will record it to the trace. + * + * @return true to redirect + */ + protected boolean shouldWriteTarget() { + return blockSet.provider.current.isAliveAndPresent(); + } + + /** + * Get the recorder. + * + *

+ * This should only be used for redirected writes. If we're not live, this will return null. + * + * @return the recorder + */ + protected TraceRecorder getTraceRecorder() { + return blockSet.provider.current.getRecorder(); + } + + @Override + public void setByte(BigInteger index, byte value) + throws ByteBlockAccessException { + if (!shouldWriteTarget()) { + super.setByte(index, value); + return; + } + Address addr = getAddress(index); + writeTargetByte(addr, value); + } + + @Override + public void setInt(BigInteger index, int value) + throws ByteBlockAccessException { + if (!shouldWriteTarget()) { + super.setInt(index, value); + return; + } + Address addr = getAddress(index); + writeTargetInt(addr, value, isBigEndian()); + } + + @Override + public void setLong(BigInteger index, long value) + throws ByteBlockAccessException { + if (!shouldWriteTarget()) { + super.setLong(index, value); + return; + } + Address addr = getAddress(index); + writeTargetLong(addr, value, isBigEndian()); + } + + /** + * Write an array of bytes to the target's memory. + * + * @param addr the starting address + * @param data the data to write, prepared in correct endianness + */ + public void writeTarget(Address addr, byte[] data) { + TraceRecorder recorder = getTraceRecorder(); + recorder.writeProcessMemory(addr, data); + } + + /** + * Allocate a buffer for encoding values into bytes. + * + * @param size the number of bytes to allocate + * @param bigEndian true to order the buffer in {@link ByteOrder#BIG_ENDIAN}. + * @return the buffer, allocated and configured. + */ + protected ByteBuffer newBuffer(int size, boolean bigEndian) { + ByteBuffer buf = ByteBuffer.allocate(size); + buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN); + return buf; + } + + /** + * Write a single byte to the target. + * + *

+ * Endianness is meaningless + * + * @param addr the address + * @param value the byte + */ + public void writeTargetByte(Address addr, byte value) { + writeTarget(addr, new byte[] { value }); + } + + /** + * Write a single int to the target + * + * @param addr the minimum address to modify + * @param value the integer + * @param bigEndian true for big endian, false for little + */ + public void writeTargetInt(Address addr, int value, boolean bigEndian) { + ByteBuffer buf = newBuffer(Integer.BYTES, bigEndian); + buf.putInt(value); + writeTarget(addr, buf.array()); + } + + /** + * Write a single long to the target + * + * @param addr the minimum address to modify + * @param value the long + * @param bigEndian true for big endian, false for little + */ + public void writeTargetLong(Address addr, long value, boolean bigEndian) { + ByteBuffer buf = newBuffer(Long.BYTES, bigEndian); + buf.putLong(value); + writeTarget(addr, buf.array()); + } + + @Override + protected boolean editAllowed(Address addr, long length) { + /** + * Traces are much more permissive when it comes to writes. The instruction will just get + * clobbered, from this time forward. + */ + return true; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/WritesTargetProgramByteBlockSet.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/WritesTargetProgramByteBlockSet.java new file mode 100644 index 0000000000..9c717cd1de --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/WritesTargetProgramByteBlockSet.java @@ -0,0 +1,36 @@ +/* ### + * 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.memory; + +import ghidra.app.plugin.core.byteviewer.*; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryBlock; + +public class WritesTargetProgramByteBlockSet extends ProgramByteBlockSet { + protected final DebuggerMemoryBytesProvider provider; + + public WritesTargetProgramByteBlockSet(DebuggerMemoryBytesProvider provider, + Program program, ByteBlockChangeManager bbcm) { + super(provider, program, bbcm); + this.provider = provider; + } + + @Override + protected MemoryByteBlock newMemoryByteBlock(Memory memory, MemoryBlock memBlock) { + return new WritesTargetMemoryByteBlock(this, program, memory, memBlock); + } +} diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPluginScreenShots.java index 91a77409ab..7a38cc5eb7 100644 --- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPluginScreenShots.java +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPluginScreenShots.java @@ -23,6 +23,7 @@ import com.google.common.collect.Range; import ghidra.app.plugin.assembler.Assembler; import ghidra.app.plugin.assembler.Assemblers; +import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; import ghidra.app.services.DebuggerTraceManagerService; import ghidra.program.model.lang.RegisterValue; @@ -135,7 +136,7 @@ public class DebuggerListingPluginScreenShots extends GhidraScreenShotGenerator performAction(listingProvider.actionGoTo, false); DebuggerGoToDialog dialog = waitForDialogComponent(DebuggerGoToDialog.class); - dialog.textExpression.setText("RAX"); + dialog.setExpression("RAX"); captureDialog(dialog); } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java index ed502a991e..04fca7415d 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java @@ -69,7 +69,7 @@ import ghidra.util.task.TaskMonitor; public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUITest { static LocationTrackingSpec getLocationTrackingSpec(String name) { - return /*waitForValue(() ->*/ LocationTrackingSpec.fromConfigName(name)/*)*/; + return LocationTrackingSpec.fromConfigName(name); } static AutoReadMemorySpec getAutoReadMemorySpec(String name) { @@ -261,7 +261,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI createAndOpenTrace(); TraceThread thread1; TraceThread thread2; - DebuggerListingProvider extraListing = SwingExecutorService.INSTANCE.submit( + DebuggerListingProvider extraProvider = SwingExecutorService.INSTANCE.submit( () -> listingPlugin.createListingIfMissing(trackPc, true)).get(); try (UndoableTransaction tid = tb.startTransaction()) { DBTraceMemoryManager memory = tb.trace.getMemoryManager(); @@ -283,15 +283,15 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI traceManager.activateThread(thread1); waitForSwing(); - loc = extraListing.getLocation(); + loc = extraProvider.getLocation(); assertEquals(tb.trace.getProgramView(), loc.getProgram()); assertEquals(tb.addr(0x00401234), loc.getAddress()); - extraListing.setFollowsCurrentThread(false); + extraProvider.setFollowsCurrentThread(false); traceManager.activateThread(thread2); waitForSwing(); - loc = extraListing.getLocation(); + loc = extraProvider.getLocation(); assertEquals(tb.trace.getProgramView(), loc.getProgram()); assertEquals(tb.addr(0x00401234), loc.getAddress()); } @@ -432,6 +432,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI loc = listingProvider.getLocation(); assertEquals(b1.trace.getProgramView(), loc.getProgram()); assertEquals(b1.addr(0x00400000), loc.getAddress()); + // TODO: Assert thread? traceManager.activateThread(t2); waitForSwing(); @@ -820,7 +821,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI performAction(listingProvider.actionGoTo, false); DebuggerGoToDialog dialog = waitForDialogComponent(DebuggerGoToDialog.class); - dialog.textExpression.setText("r0"); + dialog.setExpression("r0"); dialog.okCallback(); waitForPass( @@ -828,7 +829,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI performAction(listingProvider.actionGoTo, false); dialog = waitForDialogComponent(DebuggerGoToDialog.class); - dialog.textExpression.setText("*:4 r0"); + dialog.setExpression("*:4 r0"); dialog.okCallback(); waitForPass( diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java new file mode 100644 index 0000000000..e83b94f2ec --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java @@ -0,0 +1,1187 @@ +/* ### + * 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.memory; + +import static ghidra.lifecycle.Unfinished.TODO; +import static org.junit.Assert.*; + +import java.awt.*; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.awt.event.KeyEvent; +import java.awt.image.BufferedImage; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Arrays; + +import org.junit.*; + +import com.google.common.collect.Range; + +import docking.action.DockingActionIf; +import docking.menu.ActionState; +import docking.menu.MultiStateDockingAction; +import docking.widgets.EventTrigger; +import ghidra.GhidraOptions; +import ghidra.app.plugin.core.byteviewer.ByteViewerComponent; +import ghidra.app.plugin.core.byteviewer.ByteViewerPanel; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; +import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction; +import ghidra.app.plugin.core.debug.gui.action.*; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; +import ghidra.app.services.TraceRecorder; +import ghidra.async.SwingExecutorService; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.RegisterValue; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; +import ghidra.trace.database.ToyDBTraceBuilder; +import ghidra.trace.database.memory.DBTraceMemoryManager; +import ghidra.trace.database.stack.DBTraceStack; +import ghidra.trace.database.stack.DBTraceStackManager; +import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.*; +import ghidra.trace.model.modules.TraceModule; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.time.TraceSnapshot; +import ghidra.util.database.UndoableTransaction; + +public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebuggerGUITest { + static LocationTrackingSpec getLocationTrackingSpec(String name) { + return LocationTrackingSpec.fromConfigName(name); + } + + static AutoReadMemorySpec getAutoReadMemorySpec(String name) { + return AutoReadMemorySpec.fromConfigName(name); + } + + final LocationTrackingSpec trackNone = + getLocationTrackingSpec(NoneLocationTrackingSpec.CONFIG_NAME); + final LocationTrackingSpec trackPc = + getLocationTrackingSpec(PCLocationTrackingSpec.CONFIG_NAME); + final LocationTrackingSpec trackSp = + getLocationTrackingSpec(SPLocationTrackingSpec.CONFIG_NAME); + + final AutoReadMemorySpec readNone = + getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME); + final AutoReadMemorySpec readVisible = + getAutoReadMemorySpec(VisibleAutoReadMemorySpec.CONFIG_NAME); + final AutoReadMemorySpec readVisROOnce = + getAutoReadMemorySpec(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME); + + protected DebuggerMemoryBytesPlugin memBytesPlugin; + protected DebuggerMemoryBytesProvider memBytesProvider; + + @Before + public void setUpMemoryBytesProviderTest() throws Exception { + memBytesPlugin = addPlugin(tool, DebuggerMemoryBytesPlugin.class); + memBytesProvider = waitForComponentProvider(DebuggerMemoryBytesProvider.class); + memBytesProvider.setVisible(true); + } + + protected void goToDyn(Address address) { + goToDyn(new ProgramLocation(traceManager.getCurrentView(), address)); + } + + protected void goToDyn(ProgramLocation location) { + waitForPass(() -> { + runSwing(() -> memBytesProvider.goTo(location.getProgram(), location)); + ProgramLocation confirm = memBytesProvider.getLocation(); + assertNotNull(confirm); + assertEquals(location.getAddress(), confirm.getAddress()); + }); + } + + protected static byte[] incBlock() { + byte[] data = new byte[4096]; + for (int i = 0; i < data.length; i++) { + data[i] = (byte) i; + } + return data; + } + + @Test + public void testBytesViewIsRegionsActivateThenAdd() throws Exception { + createAndOpenTrace(); + traceManager.activateTrace(tb.trace); + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + } + waitForDomainObject(tb.trace); + + waitForPass(() -> { + assertEquals(tb.set(tb.range(0x00400000, 0x0040ffff)), + memBytesProvider.getByteViewerPanel().getCurrentComponent().getView()); + }); + } + + @Test + public void testBytesViewIsRegionsAddThenActivate() throws Exception { + createAndOpenTrace(); + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + } + waitForDomainObject(tb.trace); + traceManager.activateTrace(tb.trace); + waitForSwing(); + + assertEquals(tb.set(tb.range(0x00400000, 0x0040ffff)), + new AddressSet(memBytesProvider.getByteViewerPanel().getCurrentComponent().getView())); + } + + @Test + public void testRegisterTrackingOnRegisterChange() throws Exception { + createAndOpenTrace(); + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + TraceThread thread = tb.getOrAddThread("Thread1", 0); + waitForDomainObject(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); // Ensure the open/activate events are processed first + + assertEquals(tb.trace.getProgramView(), memBytesProvider.getProgram()); + + // NOTE: PC-tracking should be the default for the main bytes viewer + Register pc = tb.trace.getBaseLanguage().getProgramCounter(); + TraceMemoryRegisterSpace regs = memory.getMemoryRegisterSpace(thread, true); + regs.setValue(0, new RegisterValue(pc, BigInteger.valueOf(0x00401234))); + } + waitForDomainObject(tb.trace); + + ProgramLocation loc = memBytesProvider.getLocation(); + assertEquals(tb.trace.getProgramView(), loc.getProgram()); + assertEquals(tb.addr(0x00401234), loc.getAddress()); + } + + @Test + public void testRegisterTrackingOnSnapChange() throws Exception { + createAndOpenTrace(); + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + TraceThread thread = tb.getOrAddThread("Thread1", 0); + waitForDomainObject(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); // Ensure the open/activate events are processed first + + assertEquals(tb.trace.getProgramView(), memBytesProvider.getProgram()); + + // NOTE: PC-tracking should be the default for the main bytes viewer + Register pc = tb.trace.getBaseLanguage().getProgramCounter(); + TraceMemoryRegisterSpace regs = memory.getMemoryRegisterSpace(thread, true); + regs.setValue(1, new RegisterValue(pc, BigInteger.valueOf(0x00401234))); + } + waitForDomainObject(tb.trace); + //Pre-check. NOTE: PC not set at 0, so tracking is not performed. + // Because BytesProvider is wierd, this means it's currentLocation is null :/ + assertNull(memBytesProvider.getLocation()); + + traceManager.activateSnap(1); + waitForSwing(); + + ProgramLocation loc = memBytesProvider.getLocation(); + assertEquals(tb.trace.getProgramView(), loc.getProgram()); + assertEquals(tb.addr(0x00401234), loc.getAddress()); + } + + @Test + public void testRegisterTrackingOnThreadChangeWithFollowsCurrentThread() throws Exception { + createAndOpenTrace(); + TraceThread thread1; + TraceThread thread2; + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + thread1 = tb.getOrAddThread("Thread1", 0); + thread2 = tb.getOrAddThread("Thread2", 0); + + // NOTE: PC-tracking should be the default for the main bytes viewer + Register pc = tb.trace.getBaseLanguage().getProgramCounter(); + TraceMemoryRegisterSpace regs1 = memory.getMemoryRegisterSpace(thread1, true); + regs1.setValue(0, new RegisterValue(pc, BigInteger.valueOf(0x00401234))); + TraceMemoryRegisterSpace regs2 = memory.getMemoryRegisterSpace(thread2, true); + regs2.setValue(0, new RegisterValue(pc, BigInteger.valueOf(0x00405678))); + } + waitForDomainObject(tb.trace); + ProgramLocation loc; + + traceManager.activateThread(thread1); + waitForSwing(); + + loc = memBytesProvider.getLocation(); + assertEquals(tb.trace.getProgramView(), loc.getProgram()); + assertEquals(tb.addr(0x00401234), loc.getAddress()); + + traceManager.activateThread(thread2); + waitForSwing(); + + loc = memBytesProvider.getLocation(); + assertEquals(tb.trace.getProgramView(), loc.getProgram()); + assertEquals(tb.addr(0x00405678), loc.getAddress()); + } + + @Test(expected = IllegalStateException.class) + public void testMainViewerMustFollowCurrentThread() { + memBytesProvider.setFollowsCurrentThread(false); + } + + @Test + public void testRegisterTrackingOnThreadChangeWithoutFollowsCurrentThread() throws Exception { + createAndOpenTrace(); + TraceThread thread1; + TraceThread thread2; + DebuggerMemoryBytesProvider extraProvider = SwingExecutorService.INSTANCE.submit( + () -> memBytesPlugin.createViewerIfMissing(trackPc, true)).get(); + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + thread1 = tb.getOrAddThread("Thread1", 0); + thread2 = tb.getOrAddThread("Thread2", 0); + + // NOTE: PC-tracking should be the default for the main bytes viewer + Register pc = tb.trace.getBaseLanguage().getProgramCounter(); + TraceMemoryRegisterSpace regs1 = memory.getMemoryRegisterSpace(thread1, true); + regs1.setValue(0, new RegisterValue(pc, BigInteger.valueOf(0x00401234))); + TraceMemoryRegisterSpace regs2 = memory.getMemoryRegisterSpace(thread2, true); + regs2.setValue(0, new RegisterValue(pc, BigInteger.valueOf(0x00405678))); + } + waitForDomainObject(tb.trace); + ProgramLocation loc; + + traceManager.activateThread(thread1); + waitForSwing(); + + loc = extraProvider.getLocation(); + assertEquals(tb.trace.getProgramView(), loc.getProgram()); + assertEquals(tb.addr(0x00401234), loc.getAddress()); + + extraProvider.setFollowsCurrentThread(false); + traceManager.activateThread(thread2); + waitForSwing(); + + loc = extraProvider.getLocation(); + assertEquals(tb.trace.getProgramView(), loc.getProgram()); + assertEquals(tb.addr(0x00401234), loc.getAddress()); + } + + @Test + public void testRegisterTrackingOnTrackingSpecChange() throws Exception { + createAndOpenTrace(); + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + memory.addRegion("[stack]", Range.atLeast(0L), tb.range(0x01000000, 0x01ffffff), + TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); + TraceThread thread = tb.getOrAddThread("Thread1", 0); + waitForDomainObject(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); // Ensure the open/activate events are processed first + + assertEquals(tb.trace.getProgramView(), memBytesProvider.getProgram()); + + TraceMemoryRegisterSpace regs = memory.getMemoryRegisterSpace(thread, true); + Register sp = tb.trace.getBaseCompilerSpec().getStackPointer(); + regs.setValue(0, new RegisterValue(sp, BigInteger.valueOf(0x01fff800))); + } + waitForDomainObject(tb.trace); + //Pre-check + assertNull(memBytesProvider.getLocation()); + + memBytesProvider.setTrackingSpec(trackSp); + waitForSwing(); + + ProgramLocation loc = memBytesProvider.getLocation(); + assertEquals(tb.trace.getProgramView(), loc.getProgram()); + assertEquals(tb.addr(0x01fff800), loc.getAddress()); + } + + @Test + public void testFollowsCurrentTraceOnTraceChangeWithoutRegisterTracking() + throws Exception { + memBytesProvider.setTrackingSpec(trackNone); + try ( // + ToyDBTraceBuilder b1 = + new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); // + ToyDBTraceBuilder b2 = + new ToyDBTraceBuilder(name.getMethodName() + "_2", LANGID_TOYBE64)) { + TraceThread t1, t2; + + try (UndoableTransaction tid = b1.startTransaction()) { + b1.trace.getTimeManager().createSnapshot("First snap"); + DBTraceMemoryManager memory = b1.trace.getMemoryManager(); + memory.addRegion("exe:.text", Range.atLeast(0L), b1.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + t1 = b1.getOrAddThread("Thread1", 0); + + Register pc = b1.trace.getBaseLanguage().getProgramCounter(); + TraceMemoryRegisterSpace regs = memory.getMemoryRegisterSpace(t1, true); + regs.setValue(0, new RegisterValue(pc, BigInteger.valueOf(0x00401234))); + } + waitForDomainObject(b1.trace); + try (UndoableTransaction tid = b2.startTransaction()) { + b2.trace.getTimeManager().createSnapshot("First snap"); + DBTraceMemoryManager memory = b2.trace.getMemoryManager(); + memory.addRegion("exe:.text", Range.atLeast(0L), b2.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + t2 = b2.getOrAddThread("Thread2", 0); + + Register pc = b2.trace.getBaseLanguage().getProgramCounter(); + TraceMemoryRegisterSpace regs = memory.getMemoryRegisterSpace(t2, true); + regs.setValue(0, new RegisterValue(pc, BigInteger.valueOf(0x00405678))); + } + waitForDomainObject(b1.trace); + waitForDomainObject(b2.trace); + traceManager.openTrace(b1.trace); + traceManager.openTrace(b2.trace); + waitForSwing(); + + traceManager.activateTrace(b1.trace); + waitForSwing(); + + assertEquals(b1.trace.getProgramView(), memBytesProvider.getProgram()); + + traceManager.activateTrace(b2.trace); + waitForSwing(); + + assertEquals(b2.trace.getProgramView(), memBytesProvider.getProgram()); + } + } + + @Test + public void testFollowsCurrentThreadOnThreadChangeWithoutRegisterTracking() + throws Exception { + memBytesProvider.setTrackingSpec(trackNone); + try ( // + ToyDBTraceBuilder b1 = + new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); // + ToyDBTraceBuilder b2 = + new ToyDBTraceBuilder(name.getMethodName() + "_2", LANGID_TOYBE64)) { + TraceThread t1, t2; + + try (UndoableTransaction tid = b1.startTransaction()) { + b1.trace.getTimeManager().createSnapshot("First snap"); + DBTraceMemoryManager memory = b1.trace.getMemoryManager(); + memory.addRegion("exe:.text", Range.atLeast(0L), b1.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + t1 = b1.getOrAddThread("Thread1", 0); + + Register pc = b1.trace.getBaseLanguage().getProgramCounter(); + TraceMemoryRegisterSpace regs = memory.getMemoryRegisterSpace(t1, true); + regs.setValue(0, new RegisterValue(pc, BigInteger.valueOf(0x00401234))); + } + waitForDomainObject(b1.trace); + try (UndoableTransaction tid = b2.startTransaction()) { + b2.trace.getTimeManager().createSnapshot("First snap"); + DBTraceMemoryManager memory = b2.trace.getMemoryManager(); + memory.addRegion("exe:.text", Range.atLeast(0L), b2.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + t2 = b2.getOrAddThread("Thread2", 0); + + Register pc = b2.trace.getBaseLanguage().getProgramCounter(); + TraceMemoryRegisterSpace regs = memory.getMemoryRegisterSpace(t2, true); + regs.setValue(0, new RegisterValue(pc, BigInteger.valueOf(0x00405678))); + } + waitForDomainObject(b1.trace); + waitForDomainObject(b2.trace); + traceManager.openTrace(b1.trace); + traceManager.openTrace(b2.trace); + waitForSwing(); + + traceManager.activateThread(t1); + waitForSwing(); + + assertEquals(b1.trace.getProgramView(), memBytesProvider.getProgram()); + // TODO: Assert thread? + + traceManager.activateThread(t2); + waitForSwing(); + + assertEquals(b2.trace.getProgramView(), memBytesProvider.getProgram()); + } + } + + protected void assertViewerBackgroundAt(Color expected, ByteViewerPanel panel, + Address addr) throws AWTException, InterruptedException { + goToDyn(addr); + waitForPass(() -> { + Rectangle r = panel.getBounds(); + // Capture off screen, so that focus/stacking doesn't matter + BufferedImage image = new BufferedImage(r.width, r.height, BufferedImage.TYPE_INT_ARGB); + Graphics g = image.getGraphics(); + ByteViewerComponent component = panel.getCurrentComponent(); + try { + runSwing(() -> component.paint(g)); + } + finally { + g.dispose(); + } + Rectangle cursor = component.getCursorBounds(); + Color actual = new Color(image.getRGB(cursor.x + 8, cursor.y)); + assertEquals(expected, actual); + }); + } + + @Test + public void testDynamicBytesViewerMarksTrackedRegister() throws Exception { + // TODO: This shouldn't be a dependency, but it is for option definitions.... + addPlugin(tool, DebuggerListingPlugin.class); + + createAndOpenTrace(); + waitForSwing(); + + TraceThread thread; + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + // To keep gray out of the color equation + memory.setState(0, tb.range(0x00401233, 0x00401235), TraceMemoryState.KNOWN); + + thread = tb.getOrAddThread("Thread1", 0); + Register pc = tb.trace.getBaseLanguage().getProgramCounter(); + TraceMemoryRegisterSpace regs = memory.getMemoryRegisterSpace(thread, true); + regs.setValue(0, new RegisterValue(pc, BigInteger.valueOf(0x00401234))); + } + waitForDomainObject(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + assertViewerBackgroundAt(DebuggerResources.DEFAULT_COLOR_REGISTER_MARKERS, + memBytesProvider.getByteViewerPanel(), tb.addr(0x00401234)); + } + + @Test + public void testAutoReadMemoryReads() throws Exception { + byte[] data = incBlock(); + byte[] zero = new byte[data.length]; + ByteBuffer buf = ByteBuffer.allocate(data.length); + assertEquals(readVisROOnce, memBytesProvider.getAutoReadMemorySpec()); + memBytesProvider.setAutoReadMemorySpec(readNone); + + createTestModel(); + mb.createTestProcessesAndThreads(); + + TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, + new TestDebuggerTargetTraceMapper(mb.testProcess1)); + Trace trace = recorder.getTrace(); + + mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx"); + waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty()); + + mb.testProcess1.memory.setMemory(mb.addr(0x55550000), data); + waitForDomainObject(trace); + buf.clear(); + assertEquals(data.length, + trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf)); + assertArrayEquals(zero, buf.array()); + + goToDyn(addr(trace, 0x55550800)); + waitForDomainObject(trace); + buf.clear(); + assertEquals(data.length, + trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf)); + assertArrayEquals(zero, buf.array()); + + goToDyn(addr(trace, 0x55551800)); + waitForDomainObject(trace); + buf.clear(); + assertEquals(data.length, + trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf)); + assertArrayEquals(zero, buf.array()); + + /** + * NOTE: Should read immediately upon setting auto-read, but we're not focused on the + * written block + */ + memBytesProvider.setAutoReadMemorySpec(readVisROOnce); + waitForDomainObject(trace); + buf.clear(); + assertEquals(data.length, + trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf)); + assertArrayEquals(zero, buf.array()); + + /** + * We're now moving to the written block + */ + goToDyn(addr(trace, 0x55550800)); + waitForSwing(); + waitForDomainObject(trace); + // NB. Recorder can delay writing in a thread / queue + waitForPass(() -> { + buf.clear(); + assertEquals(data.length, trace.getMemoryManager() + .getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf)); + assertArrayEquals(data, buf.array()); + }); + } + + @Test + public void testMemoryStateBackgroundColors() throws Exception { + // TODO: This shouldn't be a dependency, but it is for option definitions.... + addPlugin(tool, DebuggerListingPlugin.class); + + createAndOpenTrace(); + waitForSwing(); + + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + memory.setState(0, tb.addr(0x00401234), TraceMemoryState.KNOWN); + memory.setState(0, tb.addr(0x00401235), TraceMemoryState.ERROR); + } + waitForDomainObject(tb.trace); + traceManager.activateTrace(tb.trace); + waitForSwing(); + + // TODO: Colors should be blended with cursor color.... + assertViewerBackgroundAt(DebuggerResources.DEFAULT_COLOR_BACKGROUND_STALE, + memBytesProvider.getByteViewerPanel(), tb.addr(0x00401233)); + assertViewerBackgroundAt(GhidraOptions.DEFAULT_CURSOR_LINE_COLOR, + memBytesProvider.getByteViewerPanel(), tb.addr(0x00401234)); + assertViewerBackgroundAt(DebuggerResources.DEFAULT_COLOR_BACKGROUND_ERROR, + memBytesProvider.getByteViewerPanel(), tb.addr(0x00401235)); + } + + @Test + public void testCloseCurrentTraceBlanksViewers() throws Exception { + createAndOpenTrace(); + traceManager.activateTrace(tb.trace); + waitForSwing(); + assertEquals(traceManager.getCurrentView(), memBytesProvider.getProgram()); + assertEquals("(nowhere)", memBytesProvider.locationLabel.getText()); + + DebuggerMemoryBytesProvider extraProvider = runSwing( + () -> memBytesPlugin.createViewerIfMissing(trackNone, false)); + waitForSwing(); + assertEquals(traceManager.getCurrentView(), extraProvider.getProgram()); + assertEquals("(nowhere)", extraProvider.locationLabel.getText()); + + traceManager.closeTrace(tb.trace); + waitForSwing(); + assertNull(memBytesProvider.getProgram()); + assertNull(extraProvider.getProgram()); + + assertEquals("", memBytesProvider.locationLabel.getText()); + assertEquals("", extraProvider.locationLabel.getText()); + } + + public static void setActionStateWithTrigger(MultiStateDockingAction action, T userData, + EventTrigger trigger) { + for (ActionState actionState : action.getAllActionStates()) { + if (actionState.getUserData() == userData) { + action.setCurrentActionStateWithTrigger(actionState, trigger); + return; + } + } + fail("Invalid action state user data"); + } + + @Test + public void testActionGoTo() throws Exception { + assertNull(memBytesProvider.current.getView()); + assertFalse(memBytesProvider.actionGoTo.isEnabled()); + createAndOpenTrace(); + TraceThread thread; + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager mm = tb.trace.getMemoryManager(); + mm.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + thread = tb.getOrAddThread("Thread 1", 0); + TraceMemoryRegisterSpace regs = mm.getMemoryRegisterSpace(thread, true); + Register r0 = tb.language.getRegister("r0"); + regs.setValue(0, new RegisterValue(r0, new BigInteger("00401234", 16))); + mm.putBytes(0, tb.addr(0x00401234), tb.buf(0x00, 0x40, 0x43, 0x21)); + } + waitForDomainObject(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + assertTrue(memBytesProvider.actionGoTo.isEnabled()); + performAction(memBytesProvider.actionGoTo, false); + DebuggerGoToDialog dialog = waitForDialogComponent(DebuggerGoToDialog.class); + + dialog.setExpression("r0"); + dialog.okCallback(); + + waitForPass( + () -> assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress())); + + performAction(memBytesProvider.actionGoTo, false); + dialog = waitForDialogComponent(DebuggerGoToDialog.class); + dialog.setExpression("*:4 r0"); + dialog.okCallback(); + + waitForPass( + () -> assertEquals(tb.addr(0x00404321), memBytesProvider.getLocation().getAddress())); + } + + @Test + public void testActionTrackLocation() throws Exception { + assertTrue(memBytesProvider.actionTrackLocation.isEnabled()); + createAndOpenTrace(); + TraceThread thread; + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager mm = tb.trace.getMemoryManager(); + mm.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + mm.addRegion("[stack]", Range.atLeast(0L), tb.range(0x1f000000, 0x1fffffff), + TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); + thread = tb.getOrAddThread("Thread 1", 0); + TraceMemoryRegisterSpace regs = mm.getMemoryRegisterSpace(thread, true); + Register pc = tb.language.getProgramCounter(); + regs.setValue(0, new RegisterValue(pc, new BigInteger("00401234", 16))); + Register sp = tb.trace.getBaseCompilerSpec().getStackPointer(); + regs.setValue(0, new RegisterValue(sp, new BigInteger("1fff8765", 16))); + } + waitForDomainObject(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + // Check the default is track pc + assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData()); + assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress()); + + goToDyn(tb.addr(0x00400000)); + // Ensure it's changed so we know the action is effective + waitForSwing(); + assertEquals(tb.addr(0x00400000), memBytesProvider.getLocation().getAddress()); + + performAction(memBytesProvider.actionTrackLocation); + assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress()); + + setActionStateWithTrigger(memBytesProvider.actionTrackLocation, trackSp, + EventTrigger.GUI_ACTION); + waitForSwing(); + assertEquals(tb.addr(0x1fff8765), memBytesProvider.getLocation().getAddress()); + + memBytesProvider.setTrackingSpec(trackNone); + waitForSwing(); + assertEquals(trackNone, memBytesProvider.actionTrackLocation.getCurrentUserData()); + } + + @Test + @Ignore("Haven't specified this action, yet") + public void testActionTrackOtherRegister() { + // TODO: Actually, can we make this an arbitrary (pcode/sleigh?) expression. + TODO(); + } + + @Test + public void testActionFollowsCurrentThread() throws Exception { + createAndOpenTrace(); + TraceThread thread1; + TraceThread thread2; + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + thread1 = tb.getOrAddThread("Thread1", 0); + thread2 = tb.getOrAddThread("Thread2", 0); + + // NOTE: PC-tracking should be the default for the main dynamic listing + Register pc = tb.trace.getBaseLanguage().getProgramCounter(); + TraceMemoryRegisterSpace regs1 = memory.getMemoryRegisterSpace(thread1, true); + regs1.setValue(0, new RegisterValue(pc, BigInteger.valueOf(0x00401234))); + TraceMemoryRegisterSpace regs2 = memory.getMemoryRegisterSpace(thread2, true); + regs2.setValue(0, new RegisterValue(pc, BigInteger.valueOf(0x00405678))); + } + waitForDomainObject(tb.trace); + traceManager.activateThread(thread1); + + // NOTE: Action does not exist for main dynamic listing + DebuggerMemoryBytesProvider extraProvider = runSwing( + () -> memBytesPlugin.createViewerIfMissing(trackNone, true)); + waitForSwing(); + assertTrue(extraProvider.actionFollowsCurrentThread.isEnabled()); + assertTrue(extraProvider.actionFollowsCurrentThread.isSelected()); + // Verify it has immediately tracked on creation + assertEquals(tb.trace.getProgramView(), extraProvider.getProgram()); + assertEquals(thread1, extraProvider.current.getThread()); + assertNull(getLocalAction(memBytesProvider, AbstractFollowsCurrentThreadAction.NAME)); + assertNotNull(getLocalAction(extraProvider, AbstractFollowsCurrentThreadAction.NAME)); + + performAction(extraProvider.actionFollowsCurrentThread); + traceManager.activateThread(thread2); + assertEquals(thread1, extraProvider.current.getThread()); + + performAction(extraProvider.actionFollowsCurrentThread); + assertEquals(thread2, extraProvider.current.getThread()); + + extraProvider.setFollowsCurrentThread(false); + assertFalse(extraProvider.actionFollowsCurrentThread.isSelected()); + } + + @Test + @Ignore("TODO") // Needs attention, but low priority + // Accessibility listener does not seem to work + public void testActionCaptureSelectedMemory() throws Exception { + byte[] data = incBlock(); + byte[] zero = new byte[data.length]; + ByteBuffer buf = ByteBuffer.allocate(data.length); + assertFalse(memBytesProvider.actionCaptureSelectedMemory.isEnabled()); + memBytesProvider.setAutoReadMemorySpec(readNone); + + // To verify enabled requires live target + createAndOpenTrace(); + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getMemoryManager() + .addRegion("exe:.text", Range.atLeast(0L), tb.range(0x55550000, 0x555500ff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + } + ProgramSelection sel = new ProgramSelection(tb.set(tb.range(0x55550040, 0x5555004f))); + waitForDomainObject(tb.trace); + traceManager.activateTrace(tb.trace); + waitForSwing(); + // Still + assertFalse(memBytesProvider.actionCaptureSelectedMemory.isEnabled()); + + memBytesProvider.setSelection(sel); + waitForSwing(); + // Still + assertFalse(memBytesProvider.actionCaptureSelectedMemory.isEnabled()); + + // Now, simulate the sequence that typically enables the action + createTestModel(); + mb.createTestProcessesAndThreads(); + + TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, + new TestDebuggerTargetTraceMapper(mb.testProcess1)); + Trace trace = recorder.getTrace(); + + mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x555500ff), "rx"); + mb.testProcess1.memory.setMemory(mb.addr(0x55550000), data); // No effect yet + waitForDomainObject(trace); + waitFor(() -> trace.getMemoryManager().getAllRegions().size() == 1); + + // NOTE: recordTargetContainerAndOpenTrace has already activated the trace + // Action is still disabled, because it requires a selection + assertFalse(memBytesProvider.actionCaptureSelectedMemory.isEnabled()); + + memBytesProvider.setSelection(sel); + waitForSwing(); + // Now, it should be enabled + assertTrue(memBytesProvider.actionCaptureSelectedMemory.isEnabled()); + + // First check nothing captured yet + buf.clear(); + assertEquals(data.length, + trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf)); + assertArrayEquals(zero, buf.array()); + + // Verify that the action performs the expected task + performAction(memBytesProvider.actionCaptureSelectedMemory); + waitForBusyTool(tool); + waitForDomainObject(trace); + + waitForPass(() -> { + buf.clear(); + assertEquals(data.length, trace.getMemoryManager() + .getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf)); + // NOTE: The region is only 256 bytes long + // TODO: This fails unpredictably, and I'm not sure why. + assertArrayEquals(Arrays.copyOf(data, 256), Arrays.copyOf(buf.array(), 256)); + }); + + // Verify that setting the memory inaccessible disables the action + mb.testProcess1.memory.setAccessible(false); + waitForPass(() -> assertFalse(memBytesProvider.actionCaptureSelectedMemory.isEnabled())); + + // Verify that setting it accessible re-enables it (assuming we still have selection) + mb.testProcess1.memory.setAccessible(true); + waitForPass(() -> assertTrue(memBytesProvider.actionCaptureSelectedMemory.isEnabled())); + + // Verify that moving into the past disables the action + TraceSnapshot forced = recorder.forceSnapshot(); + waitForSwing(); // UI Wants to sync with new snap. Wait.... + traceManager.activateSnap(forced.getKey() - 1); + waitForSwing(); + assertFalse(memBytesProvider.actionCaptureSelectedMemory.isEnabled()); + + // Verify that advancing to the present enables the action (assuming a selection) + traceManager.activateSnap(forced.getKey()); + waitForSwing(); + assertTrue(memBytesProvider.actionCaptureSelectedMemory.isEnabled()); + + // Verify that stopping the recording disables the action + recorder.stopRecording(); + waitForSwing(); + assertFalse(memBytesProvider.actionCaptureSelectedMemory.isEnabled()); + + // TODO: When resume recording is implemented, verify action is enabled with selection + } + + @Test + public void testActionAutoReadMemory() { + assertTrue(memBytesProvider.actionAutoReadMemory.isEnabled()); + + assertEquals(readVisROOnce, memBytesProvider.getAutoReadMemorySpec()); + assertEquals(readVisROOnce, memBytesProvider.actionAutoReadMemory.getCurrentUserData()); + + memBytesProvider.actionAutoReadMemory + .setCurrentActionStateByUserData(readNone); + waitForSwing(); + assertEquals(readNone, memBytesProvider.getAutoReadMemorySpec()); + assertEquals(readNone, memBytesProvider.actionAutoReadMemory.getCurrentUserData()); + + memBytesProvider.setAutoReadMemorySpec(readVisROOnce); + waitForSwing(); + assertEquals(readVisROOnce, memBytesProvider.getAutoReadMemorySpec()); + assertEquals(readVisROOnce, memBytesProvider.actionAutoReadMemory.getCurrentUserData()); + + memBytesProvider.setAutoReadMemorySpec(readNone); + waitForSwing(); + assertEquals(readNone, memBytesProvider.getAutoReadMemorySpec()); + assertEquals(readNone, memBytesProvider.actionAutoReadMemory.getCurrentUserData()); + } + + @Test + public void testLocationLabel() throws Exception { + assertEquals("", memBytesProvider.locationLabel.getText()); + + createAndOpenTrace(); + waitForSwing(); + assertEquals("", memBytesProvider.locationLabel.getText()); + + traceManager.activateTrace(tb.trace); + waitForSwing(); + assertEquals("(nowhere)", memBytesProvider.locationLabel.getText()); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getMemoryManager() + .addRegion("test_region", Range.atLeast(0L), tb.range(0x55550000, 0x555502ff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + } + waitForDomainObject(tb.trace); + // TODO: This initial goTo should not really be needed + goToDyn(tb.addr(0x55550000)); + waitForPass(() -> assertEquals("test_region", memBytesProvider.locationLabel.getText())); + + TraceModule modExe; + try (UndoableTransaction tid = tb.startTransaction()) { + modExe = tb.trace.getModuleManager() + .addModule("modExe", "modExe", + tb.range(0x55550000, 0x555501ff), Range.atLeast(0L)); + } + waitForDomainObject(tb.trace); + waitForPass(() -> assertEquals("modExe", memBytesProvider.locationLabel.getText())); + + try (UndoableTransaction tid = tb.startTransaction()) { + modExe.addSection(".text", tb.range(0x55550000, 0x555500ff)); + } + waitForDomainObject(tb.trace); + waitForPass(() -> assertEquals("modExe:.text", memBytesProvider.locationLabel.getText())); + } + + @Test + public void testActivateThreadTracks() throws Exception { + assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData()); + + createAndOpenTrace(); + Register pc = tb.language.getProgramCounter(); + TraceThread thread1; + TraceThread thread2; + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager mm = tb.trace.getMemoryManager(); + mm.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + thread1 = tb.getOrAddThread("Thread 1", 0); + thread2 = tb.getOrAddThread("Thread 2", 0); + TraceMemoryRegisterSpace regs1 = mm.getMemoryRegisterSpace(thread1, true); + regs1.setValue(0, new RegisterValue(pc, new BigInteger("00401234", 16))); + TraceMemoryRegisterSpace regs2 = mm.getMemoryRegisterSpace(thread2, true); + regs2.setValue(0, new RegisterValue(pc, new BigInteger("00404321", 16))); + } + waitForDomainObject(tb.trace); + traceManager.activateThread(thread1); + waitForSwing(); + + assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress()); + + traceManager.activateThread(thread2); + waitForSwing(); + + assertEquals(tb.addr(0x00404321), memBytesProvider.getLocation().getAddress()); + } + + @Test + public void testActivateSnapTracks() throws Exception { + assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData()); + + createAndOpenTrace(); + Register pc = tb.language.getProgramCounter(); + TraceThread thread; + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager mm = tb.trace.getMemoryManager(); + mm.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + thread = tb.getOrAddThread("Thread 1", 0); + TraceMemoryRegisterSpace regs = mm.getMemoryRegisterSpace(thread, true); + regs.setValue(0, new RegisterValue(pc, new BigInteger("00401234", 16))); + regs.setValue(1, new RegisterValue(pc, new BigInteger("00404321", 16))); + } + waitForDomainObject(tb.trace); + traceManager.activate(DebuggerCoordinates.threadSnap(thread, 0)); + waitForSwing(); + + assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress()); + + traceManager.activateSnap(1); + waitForSwing(); + + assertEquals(tb.addr(0x00404321), memBytesProvider.getLocation().getAddress()); + } + + @Test + public void testActivateFrameTracks() throws Exception { + assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData()); + + createAndOpenTrace(); + TraceThread thread; + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager mm = tb.trace.getMemoryManager(); + mm.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + thread = tb.getOrAddThread("Thread 1", 0); + DBTraceStackManager sm = tb.trace.getStackManager(); + DBTraceStack stack = sm.getStack(thread, 0, true); + stack.getFrame(0, true).setProgramCounter(tb.addr(0x00401234)); + stack.getFrame(1, true).setProgramCounter(tb.addr(0x00404321)); + } + waitForDomainObject(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress()); + + traceManager.activateFrame(1); + waitForSwing(); + + assertEquals(tb.addr(0x00404321), memBytesProvider.getLocation().getAddress()); + } + + @Test + public void testRegsPCChangedTracks() throws Exception { + assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData()); + + createAndOpenTrace(); + DBTraceMemoryManager mm = tb.trace.getMemoryManager(); + Register pc = tb.language.getProgramCounter(); + TraceThread thread; + try (UndoableTransaction tid = tb.startTransaction()) { + mm.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + thread = tb.getOrAddThread("Thread 1", 0); + TraceMemoryRegisterSpace regs = mm.getMemoryRegisterSpace(thread, true); + regs.setValue(0, new RegisterValue(pc, new BigInteger("00401234", 16))); + } + waitForDomainObject(tb.trace); + traceManager.activate(DebuggerCoordinates.threadSnap(thread, 0)); + waitForSwing(); + + assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress()); + + try (UndoableTransaction tid = tb.startTransaction()) { + TraceMemoryRegisterSpace regs = mm.getMemoryRegisterSpace(thread, true); + regs.setValue(0, new RegisterValue(pc, new BigInteger("00404321", 16))); + } + waitForDomainObject(tb.trace); + + assertEquals(tb.addr(0x00404321), memBytesProvider.getLocation().getAddress()); + } + + @Test + public void testRegsPCChangedTracksDespiteStackWithNoPC() throws Exception { + assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData()); + + createAndOpenTrace(); + DBTraceMemoryManager mm = tb.trace.getMemoryManager(); + Register pc = tb.language.getProgramCounter(); + TraceThread thread; + try (UndoableTransaction tid = tb.startTransaction()) { + mm.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + thread = tb.getOrAddThread("Thread 1", 0); + TraceMemoryRegisterSpace regs = mm.getMemoryRegisterSpace(thread, true); + regs.setValue(0, new RegisterValue(pc, new BigInteger("00401234", 16))); + + DBTraceStackManager sm = tb.trace.getStackManager(); + DBTraceStack stack = sm.getStack(thread, 0, true); + stack.getFrame(0, true); + } + waitForDomainObject(tb.trace); + traceManager.activate(DebuggerCoordinates.threadSnap(thread, 0)); + waitForSwing(); + + assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress()); + + try (UndoableTransaction tid = tb.startTransaction()) { + TraceMemoryRegisterSpace regs = mm.getMemoryRegisterSpace(thread, true); + regs.setValue(0, new RegisterValue(pc, new BigInteger("00404321", 16))); + } + waitForDomainObject(tb.trace); + + assertEquals(tb.addr(0x00404321), memBytesProvider.getLocation().getAddress()); + } + + @Test + public void testStackPCChangedTracks() throws Exception { + assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData()); + + createAndOpenTrace(); + DBTraceStackManager sm = tb.trace.getStackManager(); + TraceThread thread; + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager mm = tb.trace.getMemoryManager(); + mm.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + thread = tb.getOrAddThread("Thread 1", 0); + DBTraceStack stack = sm.getStack(thread, 0, true); + stack.getFrame(0, true).setProgramCounter(tb.addr(0x00401234)); + } + waitForDomainObject(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress()); + + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceStack stack = sm.getStack(thread, 0, true); + stack.getFrame(0, true).setProgramCounter(tb.addr(0x00404321)); + } + waitForDomainObject(tb.trace); + + assertEquals(tb.addr(0x00404321), memBytesProvider.getLocation().getAddress()); + } + + @Test + public void testEditLiveBytesWritesTarget() throws Exception { + createTestModel(); + mb.createTestProcessesAndThreads(); + + TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, + new TestDebuggerTargetTraceMapper(mb.testProcess1)); + Trace trace = recorder.getTrace(); + + mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx"); + waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty()); + + goToDyn(addr(trace, 0x55550800)); + DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing"); + performAction(actionEdit); + + Robot robot = new Robot(); + robot.keyPress(KeyEvent.VK_4); + robot.keyRelease(KeyEvent.VK_4); + robot.keyPress(KeyEvent.VK_2); + robot.keyRelease(KeyEvent.VK_2); + + performAction(actionEdit); + + byte[] data = new byte[4]; + waitForPass(() -> { + mb.testProcess1.memory.getMemory(mb.addr(0x55550800), data); + assertArrayEquals(mb.arr(0x42, 0, 0, 0), data); + }); + } + + @Test + @Ignore + public void testEditPastBytesWritesNotTarget() throws Exception { + createTestModel(); + mb.createTestProcessesAndThreads(); + + TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, + new TestDebuggerTargetTraceMapper(mb.testProcess1)); + Trace trace = recorder.getTrace(); + + mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx"); + waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty()); + + TraceSnapshot present = recorder.forceSnapshot(); + + goToDyn(addr(trace, 0x55550800)); + long snap = present.getKey() - 1; + traceManager.activateSnap(snap); + waitForSwing(); + + DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing"); + performAction(actionEdit); + + Robot robot = new Robot(); + robot.keyPress(KeyEvent.VK_4); + robot.keyRelease(KeyEvent.VK_4); + robot.keyPress(KeyEvent.VK_2); + robot.keyRelease(KeyEvent.VK_2); + + performAction(actionEdit); + + byte[] data = new byte[4]; + AddressSpace space = trace.getBaseAddressFactory().getDefaultAddressSpace(); + trace.getMemoryManager() + .getBytes(snap, space.getAddress(0x55550800), ByteBuffer.wrap(data)); + assertArrayEquals(mb.arr(0x42, 0, 0, 0), data); + // Verify the target was not touched + Arrays.fill(data, (byte) 0); // test model uses semisparse array + waitForPass(() -> { + mb.testProcess1.memory.getMemory(mb.addr(0x55550800), data); + assertArrayEquals(mb.arr(0, 0, 0, 0), data); + }); + } + + @Test + @Ignore + public void testPasteLiveBytesWritesTarget() throws Exception { + createTestModel(); + mb.createTestProcessesAndThreads(); + + TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, + new TestDebuggerTargetTraceMapper(mb.testProcess1)); + Trace trace = recorder.getTrace(); + + mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx"); + waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty()); + + goToDyn(addr(trace, 0x55550800)); + DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing"); + performAction(actionEdit); + + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(new StringSelection("42 53 64 75"), null); + DockingActionIf actionPaste = getAction(memBytesPlugin, "Paste"); + performAction(actionPaste); + + performAction(actionEdit); + byte[] data = new byte[4]; + waitForPass(() -> { + mb.testProcess1.memory.getMemory(mb.addr(0x55550800), data); + assertArrayEquals(mb.arr(0x42, 0x53, 0x64, 0x75), data); + }); + } +} diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerModelBuilder.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerModelBuilder.java index cf3ad8bc3e..26df74bb01 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerModelBuilder.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerModelBuilder.java @@ -15,6 +15,7 @@ */ package ghidra.dbg.model; +import java.nio.ByteBuffer; import java.util.function.Function; import ghidra.program.model.address.Address; @@ -55,6 +56,18 @@ public class TestDebuggerModelBuilder { return testModel.range(min, max); } + public byte[] arr(int... e) { + byte[] result = new byte[e.length]; + for (int i = 0; i < e.length; i++) { + result[i] = (byte) e[i]; + } + return result; + } + + public ByteBuffer buf(int... e) { + return ByteBuffer.wrap(arr(e)); + } + public void createTestProcessesAndThreads() { testProcess1 = testModel.addProcess(1); testThread1 = testProcess1.addThread(1); diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetMemory.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetMemory.java index 25d9d3577a..8105d9de87 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetMemory.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetMemory.java @@ -40,11 +40,15 @@ public class TestTargetMemory ), "Initialized"); } + public void getMemory(Address address, byte[] data) { + assertEquals(getModel().ram, address.getAddressSpace()); + memory.getData(address.getOffset(), data); + } + @Override public CompletableFuture readMemory(Address address, int length) { - assertEquals(getModel().ram, address.getAddressSpace()); byte[] data = new byte[length]; - memory.getData(address.getOffset(), data); + getMemory(address, data); CompletableFuture future = getModel().future(data); future.thenAccept(__ -> { listeners.fire.memoryUpdated(this, address, data); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemory.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemory.java index 9c49319bd5..e36d55e580 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemory.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemory.java @@ -86,6 +86,7 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory { public MemoryBlock[] getBlocks() { List result = new ArrayList<>(); forVisibleRegions(reg -> result.add(getBlock(reg))); + Collections.sort(result, Comparator.comparing(b -> b.getStart())); return result.toArray(new MemoryBlock[result.size()]); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/nav/DecoratorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/nav/DecoratorPanel.java index bc7e1c7992..5cdf223f49 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/nav/DecoratorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/nav/DecoratorPanel.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,10 +25,10 @@ public class DecoratorPanel extends JPanel { public DecoratorPanel(JComponent component, boolean isConnected) { setLayout(new BorderLayout()); add(component); - setConnnected( isConnected ); + setConnected( isConnected ); } - public void setConnnected( boolean isConnected ) { + public void setConnected( boolean isConnected ) { if ( !isConnected ) { setBorder( BorderFactory.createLineBorder( Color.ORANGE, 2 ) ); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssembleDockingAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssembleDockingAction.java index 21287276c3..81e2cd58f5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssembleDockingAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssembleDockingAction.java @@ -275,6 +275,9 @@ public class AssembleDockingAction extends DockingAction { return; } prepareLayout(context); + if (cv.isReadOnly()) { + return; + } ListingActionContext lac = (ListingActionContext) context; ProgramLocation cur = lac.getLocation(); @@ -371,6 +374,13 @@ public class AssembleDockingAction extends DockingAction { } ListingActionContext lac = (ListingActionContext) context; + ComponentProvider cp = lac.getComponentProvider(); + if (cp instanceof CodeViewerProvider) { + CodeViewerProvider cv = (CodeViewerProvider) cp; + if (cv.isReadOnly()) { + return false; + } + } Program program = lac.getProgram(); if (program == null) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java new file mode 100644 index 0000000000..61ff961d46 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java @@ -0,0 +1,1105 @@ +/* ### + * 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.codebrowser; + +import java.awt.Color; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.math.BigInteger; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.action.MenuData; +import docking.action.builder.ActionBuilder; +import docking.tool.ToolConstants; +import docking.widgets.fieldpanel.*; +import docking.widgets.fieldpanel.field.Field; +import docking.widgets.fieldpanel.support.FieldLocation; +import docking.widgets.fieldpanel.support.FieldSelection; +import ghidra.GhidraOptions; +import ghidra.app.context.ListingActionContext; +import ghidra.app.events.ProgramHighlightPluginEvent; +import ghidra.app.events.ProgramSelectionPluginEvent; +import ghidra.app.nav.Navigatable; +import ghidra.app.plugin.core.codebrowser.hover.ListingHoverService; +import ghidra.app.plugin.core.table.TableComponentProvider; +import ghidra.app.services.*; +import ghidra.app.util.*; +import ghidra.app.util.query.TableService; +import ghidra.app.util.viewer.field.ListingField; +import ghidra.app.util.viewer.field.ListingTextField; +import ghidra.app.util.viewer.format.*; +import ghidra.app.util.viewer.listingpanel.*; +import ghidra.app.util.viewer.options.ListingDisplayOptionsEditor; +import ghidra.app.util.viewer.options.OptionsGui; +import ghidra.app.util.viewer.util.AddressIndexMap; +import ghidra.framework.model.*; +import ghidra.framework.options.*; +import ghidra.framework.plugintool.Plugin; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.Reference; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; +import ghidra.util.*; +import ghidra.util.datastruct.Accumulator; +import ghidra.util.exception.CancelledException; +import ghidra.util.table.*; +import ghidra.util.task.TaskMonitor; +import resources.ResourceManager; + +public abstract class AbstractCodeBrowserPlugin

extends Plugin + implements CodeViewerService, CodeFormatService, OptionsChangeListener, + FormatModelListener, DomainObjectListener, CodeBrowserPluginInterface { + + private static final Color CURSOR_LINE_COLOR = GhidraOptions.DEFAULT_CURSOR_LINE_COLOR; + private static final String CURSOR_COLOR = "Cursor.Cursor Color - Focused"; + private static final String UNFOCUSED_CURSOR_COLOR = "Cursor.Cursor Color - Unfocused"; + private static final String BLINK_CURSOR = "Cursor.Blink Cursor"; + private static final String MOUSE_WHEEL_HORIZONTAL_SCROLLING = "Mouse.Horizontal Scrolling"; + + // - Icon - + private ImageIcon CURSOR_LOC_ICON = + ResourceManager.loadImage("images/cursor_arrow_flipped.gif"); + protected final P connectedProvider; + protected List

disconnectedProviders = new ArrayList<>(); + protected FormatManager formatMgr; + protected ViewManagerService viewManager; + private MarkerService markerService; + protected AddressSetView currentView; + protected Program currentProgram; + private boolean selectionChanging; + private MarkerSet currentSelectionMarkers; + private MarkerSet currentHighlightMarkers; + private MarkerSet currentCursorMarkers; + private ChangeListener markerChangeListener; + private FocusingMouseListener focusingMouseListener = new FocusingMouseListener(); + + private DockingAction tableFromSelectionAction; + private DockingAction showXrefsAction; + + private Color cursorHighlightColor; + private boolean isHighlightCursorLine; + private ProgramDropProvider dndProvider; + + public AbstractCodeBrowserPlugin(PluginTool tool) { + super(tool); + + ToolOptions displayOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_DISPLAY); + ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); + displayOptions.registerOptionsEditor(new ListingDisplayOptionsEditor(displayOptions)); + displayOptions.setOptionsHelpLocation( + new HelpLocation(getName(), GhidraOptions.CATEGORY_BROWSER_DISPLAY)); + fieldOptions.setOptionsHelpLocation( + new HelpLocation(getName(), GhidraOptions.CATEGORY_BROWSER_DISPLAY)); + + formatMgr = new FormatManager(displayOptions, fieldOptions); + formatMgr.addFormatModelListener(this); + formatMgr.setServiceProvider(tool); + connectedProvider = createProvider(formatMgr, true); + tool.showComponentProvider(connectedProvider, true); + initOptions(fieldOptions); + initDisplayOptions(displayOptions); + initMiscellaneousOptions(); + initActions(); + displayOptions.addOptionsChangeListener(this); + fieldOptions.addOptionsChangeListener(this); + tool.setDefaultComponent(connectedProvider); + markerChangeListener = new MarkerChangeListener(connectedProvider); + createActions(); + } + + protected abstract P createProvider(FormatManager formatManager, boolean isConnected); + + private void createActions() { + new ActionBuilder("Select All", getName()) + .menuPath(ToolConstants.MENU_SELECTION, "&All in View") + .menuGroup("Select Group", "a") + .keyBinding("ctrl A") + .supportsDefaultToolContext(true) + .helpLocation(new HelpLocation(HelpTopics.SELECTION, "Select All")) + .withContext(CodeViewerActionContext.class) + .inWindow(ActionBuilder.When.CONTEXT_MATCHES) + .onAction(c -> ((CodeViewerProvider) c.getComponentProvider()).selectAll()) + .buildAndInstall(tool); + + new ActionBuilder("Clear Selection", getName()) + .menuPath(ToolConstants.MENU_SELECTION, "&Clear Selection") + .menuGroup("Select Group", "b") + .supportsDefaultToolContext(true) + .helpLocation(new HelpLocation(HelpTopics.SELECTION, "Clear Selection")) + .withContext(CodeViewerActionContext.class) + .inWindow(ActionBuilder.When.CONTEXT_MATCHES) + .onAction(c -> ((CodeViewerProvider) c.getComponentProvider()) + .setSelection(new ProgramSelection())) + .buildAndInstall(tool); + + new ActionBuilder("Select Complement", getName()) + .menuPath(ToolConstants.MENU_SELECTION, "&Complement") + .menuGroup("Select Group", "c") + .supportsDefaultToolContext(true) + .helpLocation(new HelpLocation(HelpTopics.SELECTION, "Select Complement")) + .withContext(CodeViewerActionContext.class) + .inWindow(ActionBuilder.When.CONTEXT_MATCHES) + .onAction(c -> ((CodeViewerProvider) c.getComponentProvider()).selectComplement()) + .buildAndInstall(tool); + + } + + protected void viewChanged(AddressSetView addrSet) { + ProgramLocation currLoc = getCurrentLocation(); + currentView = addrSet; + if (addrSet != null && !addrSet.isEmpty()) { + connectedProvider.setView(addrSet); + if (currLoc != null && addrSet.contains(currLoc.getAddress())) { + goTo(currLoc, true); + } + } + else { + connectedProvider.setView(new AddressSet()); + } + updateBackgroundColorModel(); + + setHighlight(connectedProvider.getHighlight()); + setSelection(connectedProvider.getSelection()); + } + + @Override + protected void init() { + markerService = tool.getService(MarkerService.class); + if (markerService != null) { + markerService.addChangeListener(markerChangeListener); + } + updateBackgroundColorModel(); + + if (viewManager == null) { + viewManager = tool.getService(ViewManagerService.class); + } + + ClipboardService clipboardService = tool.getService(ClipboardService.class); + if (clipboardService != null) { + connectedProvider.setClipboardService(clipboardService); + for (CodeViewerProvider provider : disconnectedProviders) { + provider.setClipboardService(clipboardService); + } + } + } + + protected void updateBackgroundColorModel() { + ListingPanel listingPanel = connectedProvider.getListingPanel(); + if (markerService != null) { + AddressIndexMap indexMap = connectedProvider.getListingPanel().getAddressIndexMap(); + listingPanel.setBackgroundColorModel( + new MarkerServiceBackgroundColorModel(markerService, indexMap)); + } + else { + listingPanel.setBackgroundColorModel(null); + } + + // TODO: update all providers, not just the connected provider + } + + @Override + public P createNewDisconnectedProvider() { + P newProvider = createProvider(formatMgr.createClone(), false); + newProvider.setClipboardService(tool.getService(ClipboardService.class)); + disconnectedProviders.add(newProvider); + if (dndProvider != null) { + newProvider.addProgramDropProvider(dndProvider); + } + tool.showComponentProvider(newProvider, true); + ListingHoverService[] hoverServices = tool.getServices(ListingHoverService.class); + for (ListingHoverService hoverService : hoverServices) { + newProvider.getListingPanel().addHoverService(hoverService); + } + return newProvider; + } + + protected void setHighlight(FieldSelection highlight) { + MarkerSet highlightMarkers = getHighlightMarkers(currentProgram); + + if (highlight != null && !highlight.isEmpty()) { + ListingPanel listingPanel = connectedProvider.getListingPanel(); + ProgramSelection programHighlight = listingPanel.getProgramSelection(highlight); + connectedProvider.setHighlight(programHighlight); + + firePluginEvent( + new ProgramHighlightPluginEvent(this.getName(), programHighlight, currentProgram)); + + if (highlightMarkers != null) { + highlightMarkers.clearAll(); + highlightMarkers.add(programHighlight); + } + } + else { + connectedProvider.setHighlight(new ProgramSelection()); + if (highlightMarkers != null) { + highlightMarkers.clearAll(); + } + } + } + + protected void removeProvider(CodeViewerProvider provider) { + tool.removeComponentProvider(provider); + provider.dispose(); + } + + @Override + public void serviceAdded(Class interfaceClass, Object service) { + if (interfaceClass == TableService.class) { + tool.addAction(tableFromSelectionAction); + tool.addAction(showXrefsAction); + } + if (interfaceClass == ViewManagerService.class && viewManager == null) { + viewManager = (ViewManagerService) service; + viewChanged(viewManager.getCurrentView()); + } + if (interfaceClass == MarkerService.class && markerService == null) { + markerService = tool.getService(MarkerService.class); + markerService.addChangeListener(markerChangeListener); + updateBackgroundColorModel(); + if (viewManager != null) { + viewChanged(viewManager.getCurrentView()); + } + } + if (interfaceClass == ListingHoverService.class) { + ListingHoverService hoverService = (ListingHoverService) service; + connectedProvider.getListingPanel().addHoverService(hoverService); + for (CodeViewerProvider provider : disconnectedProviders) { + provider.getListingPanel().addHoverService(hoverService); + } + ListingPanel otherPanel = connectedProvider.getOtherPanel(); + if (otherPanel != null) { + otherPanel.addHoverService(hoverService); + } + } + } + + @Override + public void serviceRemoved(Class interfaceClass, Object service) { + if (interfaceClass == TableService.class) { + if (tool != null) { + tool.removeAction(tableFromSelectionAction); + tool.removeAction(showXrefsAction); + } + } + if ((service == viewManager) && (currentProgram != null)) { + viewManager = null; + viewChanged(currentProgram.getMemory()); + } + if (service == markerService) { + markerService.removeChangeListener(markerChangeListener); + clearMarkers(currentProgram); + markerService = null; + updateBackgroundColorModel(); + } + if (interfaceClass == ListingHoverService.class) { + ListingHoverService hoverService = (ListingHoverService) service; + connectedProvider.getListingPanel().removeHoverService(hoverService); + for (CodeViewerProvider provider : disconnectedProviders) { + provider.getListingPanel().removeHoverService(hoverService); + } + ListingPanel otherPanel = connectedProvider.getOtherPanel(); + if (otherPanel != null) { + otherPanel.removeHoverService(hoverService); + } + } + } + + @Override + public void addOverviewProvider(OverviewProvider overviewProvider) { + JComponent component = overviewProvider.getComponent(); + + // just in case we get repeated calls + component.removeMouseListener(focusingMouseListener); + component.addMouseListener(focusingMouseListener); + connectedProvider.getListingPanel().addOverviewProvider(overviewProvider); + } + + @Override + public void addMarginProvider(MarginProvider marginProvider) { + JComponent component = marginProvider.getComponent(); + + // just in case we get repeated calls + component.removeMouseListener(focusingMouseListener); + component.addMouseListener(focusingMouseListener); + connectedProvider.getListingPanel().addMarginProvider(marginProvider); + } + + @Override + public void removeOverviewProvider(OverviewProvider overviewProvider) { + JComponent component = overviewProvider.getComponent(); + component.removeMouseListener(focusingMouseListener); + connectedProvider.getListingPanel().removeOverviewProvider(overviewProvider); + } + + @Override + public void removeMarginProvider(MarginProvider marginProvider) { + JComponent component = marginProvider.getComponent(); + component.removeMouseListener(focusingMouseListener); + connectedProvider.getListingPanel().removeMarginProvider(marginProvider); + } + + @Override + public void addLocalAction(DockingAction action) { + tool.addLocalAction(connectedProvider, action); + } + + @Override + public void removeLocalAction(DockingAction action) { + if (tool != null) { + tool.removeLocalAction(connectedProvider, action); + } + } + + @Override + public void addProgramDropProvider(ProgramDropProvider dnd) { + this.dndProvider = dnd; + connectedProvider.addProgramDropProvider(dnd); + for (CodeViewerProvider provider : disconnectedProviders) { + provider.addProgramDropProvider(dnd); + } + } + + @Override + public void addButtonPressedListener(ButtonPressedListener listener) { + connectedProvider.getListingPanel().addButtonPressedListener(listener); + } + + @Override + public void removeButtonPressedListener(ButtonPressedListener listener) { + connectedProvider.getListingPanel().removeButtonPressedListener(listener); + } + + @Override + public void removeHighlightProvider(HighlightProvider highlightProvider, + Program highlightProgram) { + connectedProvider.removeHighlightProvider(highlightProvider, highlightProgram); + } + + @Override + public void setHighlightProvider(HighlightProvider highlightProvider, + Program highlightProgram) { + connectedProvider.setHighlightProvider(highlightProvider, highlightProgram); + } + + protected void updateHighlightProvider() { + connectedProvider.updateHighlightProvider(); + } + + @Override + public void setListingPanel(ListingPanel lp) { + connectedProvider.setPanel(lp); + viewChanged(currentView); + } + + @Override + public void setCoordinatedListingPanelListener(CoordinatedListingPanelListener listener) { + connectedProvider.setCoordinatedListingPanelListener(listener); + } + + @Override + public void setNorthComponent(JComponent comp) { + connectedProvider.setNorthComponent(comp); + + } + + @Override + public void removeListingPanel(ListingPanel lp) { + if (isDisposed()) { + return; + } + if (connectedProvider.getOtherPanel() == lp) { + connectedProvider.clearPanel(); + viewChanged(currentView); + } + } + + @Override + protected void dispose() { + if (currentProgram != null) { + currentProgram.removeListener(this); + } + clearMarkers(currentProgram); + formatMgr.dispose(); + removeProvider(connectedProvider); + for (CodeViewerProvider provider : disconnectedProviders) { + removeProvider(provider); + } + } + + @Override + public void optionsChanged(ToolOptions options, String optionName, Object oldValue, + Object newValue) { + + ListingPanel listingPanel = connectedProvider.getListingPanel(); + if (options.getName().equals(GhidraOptions.CATEGORY_BROWSER_DISPLAY)) { + if (optionName.equals(OptionsGui.BACKGROUND.getColorOptionName())) { + Color c = (Color) newValue; + listingPanel.setTextBackgroundColor(c); + } + } + else if (options.getName().equals(GhidraOptions.CATEGORY_BROWSER_FIELDS)) { + + FieldPanel fieldPanel = listingPanel.getFieldPanel(); + if (optionName.equals(GhidraOptions.OPTION_SELECTION_COLOR)) { + Color color = ((Color) newValue); + fieldPanel.setSelectionColor(color); + MarkerSet selectionMarkers = getSelectionMarkers(currentProgram); + if (selectionMarkers != null) { + selectionMarkers.setMarkerColor(color); + } + ListingPanel otherPanel = connectedProvider.getOtherPanel(); + if (otherPanel != null) { + otherPanel.getFieldPanel().setSelectionColor(color); + } + } + else if (optionName.equals(GhidraOptions.OPTION_HIGHLIGHT_COLOR)) { + Color color = ((Color) newValue); + fieldPanel.setHighlightColor(color); + MarkerSet highlightMarkers = getHighlightMarkers(currentProgram); + if (highlightMarkers != null) { + highlightMarkers.setMarkerColor(color); + } + } + else if (optionName.equals(CURSOR_COLOR)) { + Color color = ((Color) newValue); + fieldPanel.setFocusedCursorColor(color); + } + else if (optionName.equals(UNFOCUSED_CURSOR_COLOR)) { + Color color = ((Color) newValue); + fieldPanel.setNonFocusCursorColor(color); + } + else if (optionName.equals(BLINK_CURSOR)) { + Boolean isBlinkCursor = ((Boolean) newValue); + fieldPanel.setBlinkCursor(isBlinkCursor); + } + else if (optionName.equals(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR)) { + cursorHighlightColor = (Color) newValue; + if (currentCursorMarkers != null) { + currentCursorMarkers.setMarkerColor(cursorHighlightColor); + } + } + else if (optionName.equals(GhidraOptions.HIGHLIGHT_CURSOR_LINE)) { + isHighlightCursorLine = (Boolean) newValue; + if (currentCursorMarkers != null) { + currentCursorMarkers.setColoringBackground(isHighlightCursorLine); + } + } + else if (optionName.equals(MOUSE_WHEEL_HORIZONTAL_SCROLLING)) { + fieldPanel.setHorizontalScrollingEnabled((Boolean) newValue); + } + + connectedProvider.fieldOptionChanged(optionName, newValue); + } + + } + + @Override + public void selectionChanged(CodeViewerProvider provider, ProgramSelection selection) { + if (provider == connectedProvider) { + MarkerSet selectionMarkers = getSelectionMarkers(currentProgram); + if (selectionMarkers != null) { + selectionMarkers.clearAll(); + } + if (selection != null) { + if (selectionMarkers != null) { + selectionMarkers.add(selection); + } + } + if (!selectionChanging) { + tool.firePluginEvent(new ProgramSelectionPluginEvent(getName(), selection, + connectedProvider.getProgram())); + } + } + } + + protected void setHighlight(ProgramSelection highlight) { + connectedProvider.setHighlight(highlight); + } + + protected void setSelection(ProgramSelection sel) { + selectionChanging = true; + connectedProvider.setSelection(sel); + selectionChanging = false; + } + + protected void clearMarkers(Program program) { + if (markerService == null) { + return; + } + + if (program == null) { + return; // can happen during dispose after a programDeactivated() + } + + if (currentSelectionMarkers != null) { + markerService.removeMarker(currentSelectionMarkers, program); + currentSelectionMarkers = null; + } + + if (currentHighlightMarkers != null) { + markerService.removeMarker(currentHighlightMarkers, program); + currentHighlightMarkers = null; + } + + if (currentCursorMarkers != null) { + markerService.removeMarker(currentCursorMarkers, program); + currentCursorMarkers = null; + } + } + + private MarkerSet getSelectionMarkers(Program program) { + if (markerService == null || program == null) { + return null; + } + + // already created + if (currentSelectionMarkers != null) { + return currentSelectionMarkers; + } + + FieldPanel fp = connectedProvider.getListingPanel().getFieldPanel(); + currentSelectionMarkers = markerService.createAreaMarker("Selection", "Selection Display", + program, MarkerService.SELECTION_PRIORITY, false, true, false, fp.getSelectionColor()); + return currentSelectionMarkers; + } + + protected MarkerSet getHighlightMarkers(Program program) { + if (markerService == null || program == null) { + return null; + } + + // already created + if (currentHighlightMarkers != null) { + return currentHighlightMarkers; + } + + FieldPanel fp = connectedProvider.getListingPanel().getFieldPanel(); + currentHighlightMarkers = markerService.createAreaMarker("Highlight", "Highlight Display ", + program, MarkerService.HIGHLIGHT_PRIORITY, false, true, false, fp.getHighlightColor()); + return currentHighlightMarkers; + } + + protected MarkerSet getCursorMarkers(Program program) { + if (markerService == null || program == null) { + return null; + } + + // already created + if (currentCursorMarkers != null) { + return currentCursorMarkers; + } + + currentCursorMarkers = markerService.createPointMarker("Cursor", "Cursor Location", program, + MarkerService.CURSOR_PRIORITY, true, true, isHighlightCursorLine, cursorHighlightColor, + CURSOR_LOC_ICON); + + return currentCursorMarkers; + } + + private void initOptions(Options fieldOptions) { + + HelpLocation helpLocation = new HelpLocation(getName(), "Selection Colors"); + fieldOptions.getOptions("Selection Colors").setOptionsHelpLocation(helpLocation); + + fieldOptions.registerOption(GhidraOptions.OPTION_SELECTION_COLOR, + GhidraOptions.DEFAULT_SELECTION_COLOR, helpLocation, + "The selection color in the browser."); + fieldOptions.registerOption(GhidraOptions.OPTION_HIGHLIGHT_COLOR, + GhidraOptions.DEFAULT_HIGHLIGHT_COLOR, helpLocation, + "The highlight color in the browser."); + + fieldOptions.registerOption(CURSOR_COLOR, Color.RED, helpLocation, + "The color of the cursor in the browser."); + fieldOptions.registerOption(UNFOCUSED_CURSOR_COLOR, Color.PINK, helpLocation, + "The color of the cursor in the browser when the browser does not have focus."); + fieldOptions.registerOption(BLINK_CURSOR, true, helpLocation, + "When selected, the cursor will blink when the containing window is focused."); + fieldOptions.registerOption(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR, CURSOR_LINE_COLOR, + helpLocation, "The background color of the line where the cursor is located"); + fieldOptions.registerOption(GhidraOptions.HIGHLIGHT_CURSOR_LINE, true, helpLocation, + "Toggles highlighting background color of line containing the cursor"); + + helpLocation = new HelpLocation(getName(), "Keyboard_Controls_Shift"); + fieldOptions.registerOption(MOUSE_WHEEL_HORIZONTAL_SCROLLING, true, helpLocation, + "Enables horizontal scrolling by holding the Shift key while " + + "using the mouse scroll wheel"); + + Color color = fieldOptions.getColor(GhidraOptions.OPTION_SELECTION_COLOR, + GhidraOptions.DEFAULT_SELECTION_COLOR); + + FieldPanel fieldPanel = connectedProvider.getListingPanel().getFieldPanel(); + fieldPanel.setSelectionColor(color); + MarkerSet selectionMarkers = getSelectionMarkers(currentProgram); + if (selectionMarkers != null) { + selectionMarkers.setMarkerColor(color); + } + + color = + fieldOptions.getColor(GhidraOptions.OPTION_HIGHLIGHT_COLOR, new Color(255, 255, 180)); + MarkerSet highlightMarkers = getHighlightMarkers(currentProgram); + fieldPanel.setHighlightColor(color); + if (highlightMarkers != null) { + highlightMarkers.setMarkerColor(color); + } + + color = fieldOptions.getColor(CURSOR_COLOR, Color.RED); + fieldPanel.setFocusedCursorColor(color); + + color = fieldOptions.getColor(UNFOCUSED_CURSOR_COLOR, Color.PINK); + fieldPanel.setNonFocusCursorColor(color); + + Boolean isBlinkCursor = fieldOptions.getBoolean(BLINK_CURSOR, true); + fieldPanel.setBlinkCursor(isBlinkCursor); + + boolean horizontalScrollingEnabled = + fieldOptions.getBoolean(MOUSE_WHEEL_HORIZONTAL_SCROLLING, true); + fieldPanel.setHorizontalScrollingEnabled(horizontalScrollingEnabled); + + cursorHighlightColor = + fieldOptions.getColor(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR, CURSOR_LINE_COLOR); + + isHighlightCursorLine = fieldOptions.getBoolean(GhidraOptions.HIGHLIGHT_CURSOR_LINE, true); + } + + private void initDisplayOptions(Options displayOptions) { + Color color = displayOptions.getColor(OptionsGui.BACKGROUND.getColorOptionName(), + OptionsGui.BACKGROUND.getDefaultColor()); + connectedProvider.getListingPanel().setTextBackgroundColor(color); + } + + private void initMiscellaneousOptions() { + // make sure the following options are registered + HelpLocation helpLocation = + new HelpLocation("ShowInstructionInfoPlugin", "Processor_Manual_Options"); + Options options = tool.getOptions(ManualViewerCommandWrappedOption.OPTIONS_CATEGORY_NAME); + options.registerOption(ManualViewerCommandWrappedOption.MANUAL_VIEWER_OPTIONS, + OptionType.CUSTOM_TYPE, + ManualViewerCommandWrappedOption.getDefaultBrowserLoaderOptions(), helpLocation, + "Options for running manual viewer", new ManualViewerCommandEditor()); + + } + + public void initActions() { + + // note: these actions gets added later when the TableService is added + + tableFromSelectionAction = new DockingAction("Create Table From Selection", getName()) { + ImageIcon markerIcon = ResourceManager.loadImage("images/searchm_obj.gif"); + + @Override + public void actionPerformed(ActionContext context) { + Listing listing = currentProgram.getListing(); + ProgramSelection selection = connectedProvider.getSelection(); + CodeUnitIterator codeUnits = listing.getCodeUnits(selection, true); + TableService tableService = tool.getService(TableService.class); + if (!codeUnits.hasNext()) { + tool.setStatusInfo( + "Unable to create table from selection: no " + "code units in selection"); + return; + } + + GhidraProgramTableModel

model = createTableModel(codeUnits, selection); + String title = "Selection Table"; + TableComponentProvider
tableProvider = + tableService.showTableWithMarkers(title + " " + model.getName(), "Selection", + model, PluginConstants.SEARCH_HIGHLIGHT_COLOR, markerIcon, title, null); + tableProvider.installRemoveItemsAction(); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + ProgramSelection programSelection = connectedProvider.getSelection(); + return programSelection != null && !programSelection.isEmpty(); + } + }; + + tableFromSelectionAction.setEnabled(false); + tableFromSelectionAction.setMenuBarData(new MenuData( + new String[] { ToolConstants.MENU_SELECTION, "Create Table From Selection" }, null, + "SelectUtils")); + tableFromSelectionAction + .setHelpLocation(new HelpLocation("CodeBrowserPlugin", "Selection_Table")); + + showXrefsAction = new ActionBuilder("Show Xrefs", getName()) + .description("Show the Xrefs to the code unit containing the cursor") + .validContextWhen(context -> context instanceof ListingActionContext) + .onAction(context -> showXrefs(context)) + .build(); + } + + private void showXrefs(ActionContext context) { + + TableService service = tool.getService(TableService.class); + if (service == null) { + return; + } + + ListingActionContext lac = (ListingActionContext) context; + ProgramLocation location = lac.getLocation(); + if (location == null) { + return; // not sure if this can happen + } + + Set refs = XReferenceUtil.getAllXrefs(location); + XReferenceUtil.showAllXrefs(connectedProvider, tool, service, location, refs); + } + + private GhidraProgramTableModel
createTableModel(CodeUnitIterator iterator, + ProgramSelection selection) { + + CodeUnitFromSelectionTableModelLoader loader = + new CodeUnitFromSelectionTableModelLoader(iterator, selection); + + return new CustomLoadingAddressTableModel(" - from " + selection.getMinAddress(), tool, + currentProgram, loader, null, true); + } + + @Override + public void updateDisplay() { + connectedProvider.getListingPanel().updateDisplay(false); + } + + @Override + public FieldPanel getFieldPanel() { + return connectedProvider.getListingPanel().getFieldPanel(); + } + + @Override + public Navigatable getNavigatable() { + return connectedProvider; + } + +//================================================================================================== +// Testing Methods +//================================================================================================== + + public void updateNow() { + SystemUtilities.runSwingNow(() -> connectedProvider.getListingPanel().updateDisplay(true)); + } + + /** + * Positions the cursor to the given location + * + * @param address the address to goto + * @param fieldName the name of the field to + * @param row the row within the given field + * @param col the col within the given row + * @return true if the specified location was found, false otherwise + */ + public boolean goToField(Address address, String fieldName, int row, int col) { + return goToField(address, fieldName, 0, row, col, true); + } + + /** + * Positions the cursor to the given location + * + * @param addr the address to goto + * @param fieldName the name of the field to + * @param occurrence specifies the which occurrence for multiple fields of same type + * @param row the row within the given field + * @param col the col within the given row + * @return true if the specified location was found, false otherwise + */ + public boolean goToField(Address addr, String fieldName, int occurrence, int row, int col) { + return goToField(addr, fieldName, occurrence, row, col, true); + } + + /** + * Positions the cursor to the given location + * + * @param a the address to goto + * @param fieldName the name of the field to + * @param occurrence specifies the which occurrence for multiple fields of same type + * @param row the row within the given field + * @param col the col within the given row + * @param scroll specifies if the field panel to scroll the position to the center of the screen + * @return true if the specified location was found, false otherwise + */ + public boolean goToField(Address a, String fieldName, int occurrence, int row, int col, + boolean scroll) { + + boolean result = SystemUtilities + .runSwingNow(() -> doGoToField(a, fieldName, occurrence, row, col, scroll)); + return result; + } + + private boolean doGoToField(Address a, String fieldName, int occurrence, int row, int col, + boolean scroll) { + + Swing.assertSwingThread("'Go To' must be performed on the Swing thread"); + + // make sure that the code browser is ready to go--sometimes it is not, due to timing + // during the testing process, like when the tool is first loaded. + updateNow(); + + ListingPanel panel = connectedProvider.getListingPanel(); + if (a == null) { + a = getCurrentAddress(); + } + + BigInteger index = panel.getAddressIndexMap().getIndex(a); + FieldPanel fieldPanel = panel.getFieldPanel(); + int fieldNum = getFieldNumber(fieldName, occurrence, index, fieldPanel); + if (fieldNum < 0) { + return false; + } + + if (scroll) { + fieldPanel.goTo(index, fieldNum, row, col, true); + } + else { + fieldPanel.setCursorPosition(index, fieldNum, row, col); + } + + return true; + } + + private int getFieldNumber(String fieldName, int occurrence, final BigInteger index, + FieldPanel fieldPanel) { + + if (fieldName == null) { + return -1; + } + + int fieldNum = -1; + LayoutModel model = fieldPanel.getLayoutModel(); + Layout layout = model.getLayout(index); + if (layout == null) { + return -1; + } + + int instanceNum = 0; + for (int i = 0; i < layout.getNumFields(); i++) { + ListingField bf = (ListingField) layout.getField(i); + if (bf.getFieldFactory().getFieldName().equals(fieldName)) { + if (instanceNum++ == occurrence) { + fieldNum = i; + break; + } + } + } + return fieldNum; + } + + public Address getCurrentAddress() { + ProgramLocation loc = getCurrentLocation(); + if (loc == null) { + return null; + } + return getCurrentLocation().getAddress(); + } + + @Override + public ProgramSelection getCurrentSelection() { + return connectedProvider.getListingPanel().getProgramSelection(); + } + + Program getCurrentProgram() { + return currentProgram; + } + + public CodeViewerProvider getProvider() { + return connectedProvider; + } + + public boolean goTo(ProgramLocation location) { + return goTo(location, true); + } + + @Override + public boolean goTo(ProgramLocation location, boolean centerOnScreen) { + + AtomicBoolean didGoTo = new AtomicBoolean(); + SystemUtilities.runSwingNow(() -> { + boolean success = connectedProvider.getListingPanel().goTo(location, centerOnScreen); + didGoTo.set(success); + }); + return didGoTo.get(); + } + + @Override + public ProgramLocation getCurrentLocation() { + return connectedProvider.getListingPanel().getProgramLocation(); + } + + public FieldLocation getCurrentFieldLoction() { + return getFieldPanel().getCursorLocation(); + } + + @Override + public String getCurrentFieldTextSelection() { + return connectedProvider.getStringSelection(); + } + + @Override + public ListingField getCurrentField() { + Field f = getFieldPanel().getCurrentField(); + if (f instanceof ListingField) { + return (ListingField) f; + } + return null; + } + + @Override + public void addListingDisplayListener(AddressSetDisplayListener listener) { + connectedProvider.addDisplayListener(listener); + } + + @Override + public void removeListingDisplayListener(AddressSetDisplayListener listener) { + connectedProvider.removeDisplayListener(listener); + } + + public String getCurrentFieldText() { + ListingField lf = getCurrentField(); + if (lf instanceof ListingTextField) { + return ((ListingTextField) lf).getText(); + } + return ""; + } + + @Override + public AddressSetView getView() { + return currentView; + } + + @Override + public FormatManager getFormatManager() { + return formatMgr; + } + + public void toggleOpen(Data data) { + connectedProvider.getListingPanel().getListingModel().toggleOpen(data); + } + + @Override + public AddressIndexMap getAddressIndexMap() { + return getListingPanel().getAddressIndexMap(); + } + + @Override + public ListingPanel getListingPanel() { + return connectedProvider.getListingPanel(); + } + + Address getAddressTopOfScreen() { + BigInteger index = getFieldPanel().getViewerPosition().getIndex(); + return getAddressIndexMap().getAddress(index); + } + + @Override + public void formatModelAdded(FieldFormatModel model) { + // uninterested + } + + @Override + public void formatModelRemoved(FieldFormatModel model) { + // uninterested + } + + @Override + public void formatModelChanged(FieldFormatModel model) { + tool.setConfigChanged(true); + } + + @Override + public ListingModel getListingModel() { + return connectedProvider.getListingPanel().getListingModel().copy(); + } + + @Override + public void domainObjectChanged(DomainObjectChangedEvent ev) { + if (ev.containsEvent(DomainObject.DO_DOMAIN_FILE_CHANGED)) { + connectedProvider.updateTitle(); + } + + if (viewManager != null) { + return; + } + if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED)) { + viewChanged(currentProgram.getMemory()); + } + } + + @Override + public void providerClosed(CodeViewerProvider codeViewerProvider) { + removeProvider(codeViewerProvider); + if (!codeViewerProvider.isConnected()) { + disconnectedProviders.remove(codeViewerProvider); + } + } + +//================================================================================================== +// Inner Classes +//================================================================================================== + + static class MarkerChangeListener implements ChangeListener { + private FieldPanel fieldPanel; + + MarkerChangeListener(CodeViewerProvider provider) { + this.fieldPanel = provider.getListingPanel().getFieldPanel(); + } + + @Override + public void stateChanged(ChangeEvent e) { + fieldPanel.repaint(); + } + } + + private class FocusingMouseListener extends MouseAdapter { + @Override + public void mousePressed(MouseEvent e) { + connectedProvider.getListingPanel().getFieldPanel().requestFocus(); + } + } + + private class CodeUnitFromSelectionTableModelLoader implements TableModelLoader
{ + + private CodeUnitIterator iterator; + private ProgramSelection selection; + + CodeUnitFromSelectionTableModelLoader(CodeUnitIterator iterator, + ProgramSelection selection) { + this.iterator = iterator; + this.selection = selection; + } + + @Override + public void load(Accumulator
accumulator, TaskMonitor monitor) + throws CancelledException { + + long size = selection.getNumAddresses(); + monitor.initialize(size); + + while (iterator.hasNext()) { + monitor.checkCanceled(); + CodeUnit cu = iterator.next(); + accumulator.add(cu.getMinAddress()); + monitor.incrementProgress(cu.getLength()); + } + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java index 6c800cf20f..f719eba2cc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java @@ -15,374 +15,81 @@ */ package ghidra.app.plugin.core.codebrowser; -import java.awt.Color; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.math.BigInteger; -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.swing.ImageIcon; -import javax.swing.JComponent; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; +import java.util.Iterator; import org.jdom.Element; -import docking.ActionContext; -import docking.action.DockingAction; -import docking.action.MenuData; -import docking.action.builder.ActionBuilder; -import docking.tool.ToolConstants; -import docking.widgets.fieldpanel.*; -import docking.widgets.fieldpanel.field.Field; -import docking.widgets.fieldpanel.support.*; -import ghidra.GhidraOptions; +import docking.widgets.fieldpanel.FieldPanel; +import docking.widgets.fieldpanel.support.FieldSelection; +import docking.widgets.fieldpanel.support.ViewerPosition; import ghidra.app.CorePluginPackage; -import ghidra.app.context.ListingActionContext; import ghidra.app.events.*; -import ghidra.app.nav.Navigatable; import ghidra.app.plugin.PluginCategoryNames; -import ghidra.app.plugin.core.codebrowser.hover.ListingHoverService; -import ghidra.app.plugin.core.table.TableComponentProvider; import ghidra.app.services.*; -import ghidra.app.util.*; -import ghidra.app.util.query.TableService; -import ghidra.app.util.viewer.field.ListingField; -import ghidra.app.util.viewer.field.ListingTextField; -import ghidra.app.util.viewer.format.*; -import ghidra.app.util.viewer.listingpanel.*; -import ghidra.app.util.viewer.options.ListingDisplayOptionsEditor; -import ghidra.app.util.viewer.options.OptionsGui; -import ghidra.app.util.viewer.util.AddressIndexMap; -import ghidra.framework.model.*; -import ghidra.framework.options.*; +import ghidra.app.util.viewer.format.FormatManager; +import ghidra.app.util.viewer.listingpanel.ListingPanel; +import ghidra.framework.model.DomainFile; +import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.address.*; -import ghidra.program.model.listing.*; -import ghidra.program.model.symbol.Reference; +import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; -import ghidra.util.*; -import ghidra.util.datastruct.Accumulator; -import ghidra.util.exception.CancelledException; -import ghidra.util.table.*; -import ghidra.util.task.TaskMonitor; -import resources.ResourceManager; -//@formatter:off @PluginInfo( status = PluginStatus.RELEASED, packageName = CorePluginPackage.NAME, category = PluginCategoryNames.CODE_VIEWER, shortDescription = "Code Viewer", description = "This plugin provides the main program listing display window. " + - "It also includes the header component which allows the various " + - "program fields to be arranged as desired. In addition, this plugin " + - "provides the \"CodeViewerService\" which allows other plugins to extend " + - "the basic functionality to include such features as flow arrows, " + - "margin markers and difference " + - "tracking. The listing component created by this plugin generates " + - "ProgramLocation events and ProgramSelection events as the user moves " + - "the cursor and makes selections respectively.", - servicesRequired = { ProgramManager.class, GoToService.class, ClipboardService.class /*, TableService.class */ }, - servicesProvided = { CodeViewerService.class, CodeFormatService.class, FieldMouseHandlerService.class }, + "It also includes the header component which allows the various " + + "program fields to be arranged as desired. In addition, this plugin " + + "provides the \"CodeViewerService\" which allows other plugins to extend " + + "the basic functionality to include such features as flow arrows, " + + "margin markers and difference " + + "tracking. The listing component created by this plugin generates " + + "ProgramLocation events and ProgramSelection events as the user moves " + + "the cursor and makes selections respectively.", + servicesRequired = { ProgramManager.class, GoToService.class, + ClipboardService.class /*, TableService.class */ }, + servicesProvided = { CodeViewerService.class, CodeFormatService.class, + FieldMouseHandlerService.class }, eventsConsumed = { ProgramSelectionPluginEvent.class, ProgramActivatedPluginEvent.class, ProgramClosedPluginEvent.class, ProgramLocationPluginEvent.class, ViewChangedPluginEvent.class, ProgramHighlightPluginEvent.class }, - eventsProduced = { ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class } - -) -//@formatter:on -public class CodeBrowserPlugin extends Plugin - implements CodeViewerService, CodeFormatService, OptionsChangeListener, FormatModelListener, - DomainObjectListener, CodeBrowserPluginInterface { // - - private static final Color CURSOR_LINE_COLOR = GhidraOptions.DEFAULT_CURSOR_LINE_COLOR; - private static final String CURSOR_COLOR = "Cursor.Cursor Color - Focused"; - private static final String UNFOCUSED_CURSOR_COLOR = "Cursor.Cursor Color - Unfocused"; - private static final String BLINK_CURSOR = "Cursor.Blink Cursor"; - private static final String MOUSE_WHEEL_HORIZONTAL_SCROLLING = "Mouse.Horizontal Scrolling"; - - // - Icon - - private ImageIcon CURSOR_LOC_ICON = - ResourceManager.loadImage("images/cursor_arrow_flipped.gif"); - protected final CodeViewerProvider connectedProvider; - protected List disconnectedProviders = new ArrayList<>(); - private FormatManager formatMgr; - private ViewManagerService viewManager; - private MarkerService markerService; - private AddressSetView currentView; - private Program currentProgram; - private boolean selectionChanging; - private MarkerSet currentSelectionMarkers; - private MarkerSet currentHighlightMarkers; - private MarkerSet currentCursorMarkers; - private ChangeListener markerChangeListener; - private FocusingMouseListener focusingMouseListener = new FocusingMouseListener(); - - private DockingAction tableFromSelectionAction; - private DockingAction showXrefsAction; - - private Color cursorHighlightColor; - private boolean isHighlightCursorLine; - private ProgramDropProvider dndProvider; + eventsProduced = { ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class }) +public class CodeBrowserPlugin extends AbstractCodeBrowserPlugin { public CodeBrowserPlugin(PluginTool tool) { super(tool); - ToolOptions displayOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_DISPLAY); - ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); - displayOptions.registerOptionsEditor(new ListingDisplayOptionsEditor(displayOptions)); - displayOptions.setOptionsHelpLocation( - new HelpLocation(getName(), GhidraOptions.CATEGORY_BROWSER_DISPLAY)); - fieldOptions.setOptionsHelpLocation( - new HelpLocation(getName(), GhidraOptions.CATEGORY_BROWSER_DISPLAY)); - - formatMgr = new FormatManager(displayOptions, fieldOptions); - formatMgr.addFormatModelListener(this); - formatMgr.setServiceProvider(tool); - connectedProvider = createProvider(formatMgr, true); - tool.showComponentProvider(connectedProvider, true); - initOptions(fieldOptions); - initDisplayOptions(displayOptions); - initMiscellaneousOptions(); - initActions(); - displayOptions.addOptionsChangeListener(this); - fieldOptions.addOptionsChangeListener(this); - tool.setDefaultComponent(connectedProvider); - markerChangeListener = new MarkerChangeListener(connectedProvider); registerServiceProvided(FieldMouseHandlerService.class, connectedProvider.getFieldNavigator()); - createActions(); } + @Override protected CodeViewerProvider createProvider(FormatManager formatManager, boolean isConnected) { return new CodeViewerProvider(this, formatManager, isConnected); } - private void createActions() { - new ActionBuilder("Select All", getName()) - .menuPath(ToolConstants.MENU_SELECTION, "&All in View") - .menuGroup("Select Group", "a") - .keyBinding("ctrl A") - .supportsDefaultToolContext(true) - .helpLocation(new HelpLocation(HelpTopics.SELECTION, "Select All")) - .withContext(CodeViewerActionContext.class) - .inWindow(ActionBuilder.When.CONTEXT_MATCHES) - .onAction(c -> ((CodeViewerProvider) c.getComponentProvider()).selectAll()) - .buildAndInstall(tool); - - new ActionBuilder("Clear Selection", getName()) - .menuPath(ToolConstants.MENU_SELECTION, "&Clear Selection") - .menuGroup("Select Group", "b") - .supportsDefaultToolContext(true) - .helpLocation(new HelpLocation(HelpTopics.SELECTION, "Clear Selection")) - .withContext(CodeViewerActionContext.class) - .inWindow(ActionBuilder.When.CONTEXT_MATCHES) - .onAction(c -> ((CodeViewerProvider) c.getComponentProvider()) - .setSelection(new ProgramSelection())) - .buildAndInstall(tool); - - new ActionBuilder("Select Complement", getName()) - .menuPath(ToolConstants.MENU_SELECTION, "&Complement") - .menuGroup("Select Group", "c") - .supportsDefaultToolContext(true) - .helpLocation(new HelpLocation(HelpTopics.SELECTION, "Select Complement")) - .withContext(CodeViewerActionContext.class) - .inWindow(ActionBuilder.When.CONTEXT_MATCHES) - .onAction(c -> ((CodeViewerProvider) c.getComponentProvider()).selectComplement()) - .buildAndInstall(tool); - - } - - protected void viewChanged(AddressSetView addrSet) { - ProgramLocation currLoc = getCurrentLocation(); - currentView = addrSet; - if (addrSet != null && !addrSet.isEmpty()) { - connectedProvider.setView(addrSet); - if (currLoc != null && addrSet.contains(currLoc.getAddress())) { - goTo(currLoc, true); - } - } - else { - connectedProvider.setView(new AddressSet()); - } - updateBackgroundColorModel(); - - setHighlight(connectedProvider.getHighlight()); - setSelection(connectedProvider.getSelection()); - } - @Override - protected void init() { - markerService = tool.getService(MarkerService.class); - if (markerService != null) { - markerService.addChangeListener(markerChangeListener); - } - updateBackgroundColorModel(); - - if (viewManager == null) { - viewManager = tool.getService(ViewManagerService.class); - } - - ClipboardService clipboardService = tool.getService(ClipboardService.class); - if (clipboardService != null) { - connectedProvider.setClipboardService(clipboardService); - for (CodeViewerProvider provider : disconnectedProviders) { - provider.setClipboardService(clipboardService); - } - } - } - - protected void updateBackgroundColorModel() { - ListingPanel listingPanel = connectedProvider.getListingPanel(); - if (markerService != null) { - AddressIndexMap indexMap = connectedProvider.getListingPanel().getAddressIndexMap(); - listingPanel.setBackgroundColorModel( - new MarkerServiceBackgroundColorModel(markerService, indexMap)); - } - else { - listingPanel.setBackgroundColorModel(null); - } - - // TODO: update all providers, not just the connected provider - } - - @Override - public CodeViewerProvider createNewDisconnectedProvider() { - CodeViewerProvider newProvider = - createProvider(formatMgr.createClone(), false); - newProvider.setClipboardService(tool.getService(ClipboardService.class)); - disconnectedProviders.add(newProvider); - if (dndProvider != null) { - newProvider.addProgramDropProvider(dndProvider); - } - tool.showComponentProvider(newProvider, true); - ListingHoverService[] hoverServices = tool.getServices(ListingHoverService.class); - for (ListingHoverService hoverService : hoverServices) { - newProvider.getListingPanel().addHoverService(hoverService); - } - return newProvider; - } - - @Override - public void readDataState(SaveState saveState) { - ProgramManager programManagerService = tool.getService(ProgramManager.class); - - if (connectedProvider != null) { - connectedProvider.readDataState(saveState); - } - int numDisconnected = saveState.getInt("Num Disconnected", 0); - for (int i = 0; i < numDisconnected; i++) { - Element xmlElement = saveState.getXmlElement("Provider" + i); - SaveState providerSaveState = new SaveState(xmlElement); - String programPath = providerSaveState.getString("Program Path", ""); - DomainFile file = tool.getProject().getProjectData().getFile(programPath); - if (file == null) { - continue; - } - Program program = programManagerService.openProgram(file); - if (program != null) { - CodeViewerProvider provider = createNewDisconnectedProvider(); - provider.doSetProgram(program); - provider.readDataState(providerSaveState); - } - } - - FieldSelection highlight = new FieldSelection(); - highlight.load(saveState); - if (!highlight.isEmpty()) { - setHighlight(highlight); - } - } - - private void setHighlight(FieldSelection highlight) { + public void highlightChanged(CodeViewerProvider provider, ProgramSelection highlight) { MarkerSet highlightMarkers = getHighlightMarkers(currentProgram); - - if (highlight != null && !highlight.isEmpty()) { - ListingPanel listingPanel = connectedProvider.getListingPanel(); - ProgramSelection programHighlight = listingPanel.getProgramSelection(highlight); - connectedProvider.setHighlight(programHighlight); - - firePluginEvent( - new ProgramHighlightPluginEvent(this.getName(), programHighlight, currentProgram)); - + if (highlightMarkers != null) { + highlightMarkers.clearAll(); + } + if (highlight != null && currentProgram != null) { if (highlightMarkers != null) { - highlightMarkers.clearAll(); - highlightMarkers.add(programHighlight); + highlightMarkers.add(highlight); } } - else { - connectedProvider.setHighlight(new ProgramSelection()); - if (highlightMarkers != null) { - highlightMarkers.clearAll(); - } + if (provider == connectedProvider) { + tool.firePluginEvent(new ProgramHighlightPluginEvent(getName(), highlight, + connectedProvider.getProgram())); } } - @Override - public void writeDataState(SaveState saveState) { - if (connectedProvider != null) { - connectedProvider.writeDataState(saveState); - } - saveState.putInt("Num Disconnected", disconnectedProviders.size()); - int i = 0; - for (CodeViewerProvider provider : disconnectedProviders) { - SaveState providerSaveState = new SaveState(); - DomainFile df = provider.getProgram().getDomainFile(); - if (df.getParent() == null) { - continue; // not contained within project - } - String programPathname = df.getPathname(); - providerSaveState.putString("Program Path", programPathname); - provider.writeDataState(providerSaveState); - String elementName = "Provider" + i; - saveState.putXmlElement(elementName, providerSaveState.saveToXml()); - i++; - } - FieldSelection highlight = - connectedProvider.getListingPanel().getFieldPanel().getHighlight(); - highlight.save(saveState); - } - - @Override - public Object getTransientState() { - Object[] state = new Object[5]; - FieldPanel fieldPanel = connectedProvider.getListingPanel().getFieldPanel(); - state[0] = fieldPanel.getViewerPosition(); - state[1] = connectedProvider.getLocation(); - state[2] = connectedProvider.getHighlight(); - state[3] = connectedProvider.getSelection(); - state[4] = currentView; - return state; - } - - @Override - public void restoreTransientState(final Object objectState) { - Object[] state = (Object[]) objectState; - ViewerPosition vp = (ViewerPosition) state[0]; - ProgramLocation location = (ProgramLocation) state[1]; - ProgramSelection highlight = (ProgramSelection) state[2]; - ProgramSelection selection = (ProgramSelection) state[3]; - - viewChanged((AddressSetView) state[4]); - - if (location != null) { - connectedProvider.setLocation(location); - } - setHighlight(highlight); - if (selection != null) { - connectedProvider.setSelection(selection); - } - if (vp != null) { - FieldPanel fieldPanel = connectedProvider.getListingPanel().getFieldPanel(); - fieldPanel.setViewerPosition(vp.getIndex(), vp.getXOffset(), vp.getYOffset()); - } - - } - /** * Interface method called to process a plugin event. */ @@ -455,279 +162,107 @@ public class CodeBrowserPlugin extends Plugin } } - void removeProvider(CodeViewerProvider provider) { - tool.removeComponentProvider(provider); - provider.dispose(); + @Override + public Object getTransientState() { + Object[] state = new Object[5]; + FieldPanel fieldPanel = connectedProvider.getListingPanel().getFieldPanel(); + state[0] = fieldPanel.getViewerPosition(); + state[1] = connectedProvider.getLocation(); + state[2] = connectedProvider.getHighlight(); + state[3] = connectedProvider.getSelection(); + state[4] = currentView; + return state; } @Override - public void serviceAdded(Class interfaceClass, Object service) { - if (interfaceClass == TableService.class) { - tool.addAction(tableFromSelectionAction); - tool.addAction(showXrefsAction); + public void restoreTransientState(final Object objectState) { + Object[] state = (Object[]) objectState; + ViewerPosition vp = (ViewerPosition) state[0]; + ProgramLocation location = (ProgramLocation) state[1]; + ProgramSelection highlight = (ProgramSelection) state[2]; + ProgramSelection selection = (ProgramSelection) state[3]; + + viewChanged((AddressSetView) state[4]); + + if (location != null) { + connectedProvider.setLocation(location); } - if (interfaceClass == ViewManagerService.class && viewManager == null) { - viewManager = (ViewManagerService) service; - viewChanged(viewManager.getCurrentView()); + setHighlight(highlight); + if (selection != null) { + connectedProvider.setSelection(selection); } - if (interfaceClass == MarkerService.class && markerService == null) { - markerService = tool.getService(MarkerService.class); - markerService.addChangeListener(markerChangeListener); - updateBackgroundColorModel(); - if (viewManager != null) { - viewChanged(viewManager.getCurrentView()); - } - } - if (interfaceClass == ListingHoverService.class) { - ListingHoverService hoverService = (ListingHoverService) service; - connectedProvider.getListingPanel().addHoverService(hoverService); - for (CodeViewerProvider provider : disconnectedProviders) { - provider.getListingPanel().addHoverService(hoverService); - } - ListingPanel otherPanel = connectedProvider.getOtherPanel(); - if (otherPanel != null) { - otherPanel.addHoverService(hoverService); - } + if (vp != null) { + FieldPanel fieldPanel = connectedProvider.getListingPanel().getFieldPanel(); + fieldPanel.setViewerPosition(vp.getIndex(), vp.getXOffset(), vp.getYOffset()); } } @Override - public void serviceRemoved(Class interfaceClass, Object service) { - if (interfaceClass == TableService.class) { - if (tool != null) { - tool.removeAction(tableFromSelectionAction); - tool.removeAction(showXrefsAction); - } + public void writeDataState(SaveState saveState) { + if (connectedProvider != null) { + connectedProvider.writeDataState(saveState); } - if ((service == viewManager) && (currentProgram != null)) { - viewManager = null; - viewChanged(currentProgram.getMemory()); - } - if (service == markerService) { - markerService.removeChangeListener(markerChangeListener); - clearMarkers(currentProgram); - markerService = null; - updateBackgroundColorModel(); - } - if (interfaceClass == ListingHoverService.class) { - ListingHoverService hoverService = (ListingHoverService) service; - connectedProvider.getListingPanel().removeHoverService(hoverService); - for (CodeViewerProvider provider : disconnectedProviders) { - provider.getListingPanel().removeHoverService(hoverService); - } - ListingPanel otherPanel = connectedProvider.getOtherPanel(); - if (otherPanel != null) { - otherPanel.removeHoverService(hoverService); - } - } - } - - @Override - public void addOverviewProvider(OverviewProvider overviewProvider) { - JComponent component = overviewProvider.getComponent(); - - // just in case we get repeated calls - component.removeMouseListener(focusingMouseListener); - component.addMouseListener(focusingMouseListener); - connectedProvider.getListingPanel().addOverviewProvider(overviewProvider); - } - - @Override - public void addMarginProvider(MarginProvider marginProvider) { - JComponent component = marginProvider.getComponent(); - - // just in case we get repeated calls - component.removeMouseListener(focusingMouseListener); - component.addMouseListener(focusingMouseListener); - connectedProvider.getListingPanel().addMarginProvider(marginProvider); - } - - @Override - public void removeOverviewProvider(OverviewProvider overviewProvider) { - JComponent component = overviewProvider.getComponent(); - component.removeMouseListener(focusingMouseListener); - connectedProvider.getListingPanel().removeOverviewProvider(overviewProvider); - } - - @Override - public void removeMarginProvider(MarginProvider marginProvider) { - JComponent component = marginProvider.getComponent(); - component.removeMouseListener(focusingMouseListener); - connectedProvider.getListingPanel().removeMarginProvider(marginProvider); - } - - @Override - public void addLocalAction(DockingAction action) { - tool.addLocalAction(connectedProvider, action); - } - - @Override - public void removeLocalAction(DockingAction action) { - if (tool != null) { - tool.removeLocalAction(connectedProvider, action); - } - } - - @Override - public void addProgramDropProvider(ProgramDropProvider dnd) { - this.dndProvider = dnd; - connectedProvider.addProgramDropProvider(dnd); + saveState.putInt("Num Disconnected", disconnectedProviders.size()); + int i = 0; for (CodeViewerProvider provider : disconnectedProviders) { - provider.addProgramDropProvider(dnd); + SaveState providerSaveState = new SaveState(); + DomainFile df = provider.getProgram().getDomainFile(); + if (df.getParent() == null) { + continue; // not contained within project + } + String programPathname = df.getPathname(); + providerSaveState.putString("Program Path", programPathname); + provider.writeDataState(providerSaveState); + String elementName = "Provider" + i; + saveState.putXmlElement(elementName, providerSaveState.saveToXml()); + i++; + } + FieldSelection highlight = + connectedProvider.getListingPanel().getFieldPanel().getHighlight(); + highlight.save(saveState); + } + + @Override + public void readDataState(SaveState saveState) { + ProgramManager programManagerService = tool.getService(ProgramManager.class); + + if (connectedProvider != null) { + connectedProvider.readDataState(saveState); + } + int numDisconnected = saveState.getInt("Num Disconnected", 0); + for (int i = 0; i < numDisconnected; i++) { + Element xmlElement = saveState.getXmlElement("Provider" + i); + SaveState providerSaveState = new SaveState(xmlElement); + String programPath = providerSaveState.getString("Program Path", ""); + DomainFile file = tool.getProject().getProjectData().getFile(programPath); + if (file == null) { + continue; + } + Program program = programManagerService.openProgram(file); + if (program != null) { + CodeViewerProvider provider = createNewDisconnectedProvider(); + provider.doSetProgram(program); + provider.readDataState(providerSaveState); + } + } + + FieldSelection highlight = new FieldSelection(); + highlight.load(saveState); + if (!highlight.isEmpty()) { + setHighlight(highlight); } } @Override - public void addButtonPressedListener(ButtonPressedListener listener) { - connectedProvider.getListingPanel().addButtonPressedListener(listener); + public void writeConfigState(SaveState saveState) { + formatMgr.saveState(saveState); + connectedProvider.saveState(saveState); } @Override - public void removeButtonPressedListener(ButtonPressedListener listener) { - connectedProvider.getListingPanel().removeButtonPressedListener(listener); - } - - @Override - public void removeHighlightProvider(HighlightProvider highlightProvider, - Program highlightProgram) { - connectedProvider.removeHighlightProvider(highlightProvider, highlightProgram); - } - - @Override - public void setHighlightProvider(HighlightProvider highlightProvider, - Program highlightProgram) { - connectedProvider.setHighlightProvider(highlightProvider, highlightProgram); - } - - private void updateHighlightProvider() { - connectedProvider.updateHighlightProvider(); - } - - @Override - public void setListingPanel(ListingPanel lp) { - connectedProvider.setPanel(lp); - viewChanged(currentView); - } - - @Override - public void setCoordinatedListingPanelListener(CoordinatedListingPanelListener listener) { - connectedProvider.setCoordinatedListingPanelListener(listener); - } - - @Override - public void setNorthComponent(JComponent comp) { - connectedProvider.setNorthComponent(comp); - - } - - @Override - public void removeListingPanel(ListingPanel lp) { - if (isDisposed()) { - return; - } - if (connectedProvider.getOtherPanel() == lp) { - connectedProvider.clearPanel(); - viewChanged(currentView); - } - } - - @Override - protected void dispose() { - if (currentProgram != null) { - currentProgram.removeListener(this); - } - clearMarkers(currentProgram); - formatMgr.dispose(); - removeProvider(connectedProvider); - for (CodeViewerProvider provider : disconnectedProviders) { - removeProvider(provider); - } - } - - @Override - public void optionsChanged(ToolOptions options, String optionName, Object oldValue, - Object newValue) { - - ListingPanel listingPanel = connectedProvider.getListingPanel(); - if (options.getName().equals(GhidraOptions.CATEGORY_BROWSER_DISPLAY)) { - if (optionName.equals(OptionsGui.BACKGROUND.getColorOptionName())) { - Color c = (Color) newValue; - listingPanel.setTextBackgroundColor(c); - } - } - else if (options.getName().equals(GhidraOptions.CATEGORY_BROWSER_FIELDS)) { - - FieldPanel fieldPanel = listingPanel.getFieldPanel(); - if (optionName.equals(GhidraOptions.OPTION_SELECTION_COLOR)) { - Color color = ((Color) newValue); - fieldPanel.setSelectionColor(color); - MarkerSet selectionMarkers = getSelectionMarkers(currentProgram); - if (selectionMarkers != null) { - selectionMarkers.setMarkerColor(color); - } - ListingPanel otherPanel = connectedProvider.getOtherPanel(); - if (otherPanel != null) { - otherPanel.getFieldPanel().setSelectionColor(color); - } - } - else if (optionName.equals(GhidraOptions.OPTION_HIGHLIGHT_COLOR)) { - Color color = ((Color) newValue); - fieldPanel.setHighlightColor(color); - MarkerSet highlightMarkers = getHighlightMarkers(currentProgram); - if (highlightMarkers != null) { - highlightMarkers.setMarkerColor(color); - } - } - else if (optionName.equals(CURSOR_COLOR)) { - Color color = ((Color) newValue); - fieldPanel.setFocusedCursorColor(color); - } - else if (optionName.equals(UNFOCUSED_CURSOR_COLOR)) { - Color color = ((Color) newValue); - fieldPanel.setNonFocusCursorColor(color); - } - else if (optionName.equals(BLINK_CURSOR)) { - Boolean isBlinkCursor = ((Boolean) newValue); - fieldPanel.setBlinkCursor(isBlinkCursor); - } - else if (optionName.equals(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR)) { - cursorHighlightColor = (Color) newValue; - if (currentCursorMarkers != null) { - currentCursorMarkers.setMarkerColor(cursorHighlightColor); - } - } - else if (optionName.equals(GhidraOptions.HIGHLIGHT_CURSOR_LINE)) { - isHighlightCursorLine = (Boolean) newValue; - if (currentCursorMarkers != null) { - currentCursorMarkers.setColoringBackground(isHighlightCursorLine); - } - } - else if (optionName.equals(MOUSE_WHEEL_HORIZONTAL_SCROLLING)) { - fieldPanel.setHorizontalScrollingEnabled((Boolean) newValue); - } - - connectedProvider.fieldOptionChanged(optionName, newValue); - } - - } - - @Override - public void selectionChanged(CodeViewerProvider provider, ProgramSelection selection) { - if (provider == connectedProvider) { - MarkerSet selectionMarkers = getSelectionMarkers(currentProgram); - if (selectionMarkers != null) { - selectionMarkers.clearAll(); - } - if (selection != null) { - if (selectionMarkers != null) { - selectionMarkers.add(selection); - } - } - if (!selectionChanging) { - tool.firePluginEvent(new ProgramSelectionPluginEvent(getName(), selection, - connectedProvider.getProgram())); - } - } + public void readConfigState(SaveState saveState) { + formatMgr.readState(saveState); + connectedProvider.readState(saveState); } @Override @@ -743,549 +278,6 @@ public class CodeBrowserPlugin extends Plugin } } - @Override - public void highlightChanged(CodeViewerProvider provider, ProgramSelection highlight) { - MarkerSet highlightMarkers = getHighlightMarkers(currentProgram); - if (highlightMarkers != null) { - highlightMarkers.clearAll(); - } - if (highlight != null && currentProgram != null) { - if (highlightMarkers != null) { - highlightMarkers.add(highlight); - } - } - if (provider == connectedProvider) { - tool.firePluginEvent(new ProgramHighlightPluginEvent(getName(), highlight, - connectedProvider.getProgram())); - } - } - - private void setHighlight(ProgramSelection highlight) { - connectedProvider.setHighlight(highlight); - } - - void setSelection(ProgramSelection sel) { - selectionChanging = true; - connectedProvider.setSelection(sel); - selectionChanging = false; - } - - private void clearMarkers(Program program) { - if (markerService == null) { - return; - } - - if (program == null) { - return; // can happen during dispose after a programDeactivated() - } - - if (currentSelectionMarkers != null) { - markerService.removeMarker(currentSelectionMarkers, program); - currentSelectionMarkers = null; - } - - if (currentHighlightMarkers != null) { - markerService.removeMarker(currentHighlightMarkers, program); - currentHighlightMarkers = null; - } - - if (currentCursorMarkers != null) { - markerService.removeMarker(currentCursorMarkers, program); - currentCursorMarkers = null; - } - } - - private MarkerSet getSelectionMarkers(Program program) { - if (markerService == null || program == null) { - return null; - } - - // already created - if (currentSelectionMarkers != null) { - return currentSelectionMarkers; - } - - FieldPanel fp = connectedProvider.getListingPanel().getFieldPanel(); - currentSelectionMarkers = markerService.createAreaMarker("Selection", "Selection Display", - program, MarkerService.SELECTION_PRIORITY, false, true, false, fp.getSelectionColor()); - return currentSelectionMarkers; - } - - private MarkerSet getHighlightMarkers(Program program) { - if (markerService == null || program == null) { - return null; - } - - // already created - if (currentHighlightMarkers != null) { - return currentHighlightMarkers; - } - - FieldPanel fp = connectedProvider.getListingPanel().getFieldPanel(); - currentHighlightMarkers = markerService.createAreaMarker("Highlight", "Highlight Display ", - program, MarkerService.HIGHLIGHT_PRIORITY, false, true, false, fp.getHighlightColor()); - return currentHighlightMarkers; - } - - private MarkerSet getCursorMarkers(Program program) { - if (markerService == null || program == null) { - return null; - } - - // already created - if (currentCursorMarkers != null) { - return currentCursorMarkers; - } - - currentCursorMarkers = markerService.createPointMarker("Cursor", "Cursor Location", program, - MarkerService.CURSOR_PRIORITY, true, true, isHighlightCursorLine, cursorHighlightColor, - CURSOR_LOC_ICON); - - return currentCursorMarkers; - } - - private void initOptions(Options fieldOptions) { - - HelpLocation helpLocation = new HelpLocation(getName(), "Selection Colors"); - fieldOptions.getOptions("Selection Colors").setOptionsHelpLocation(helpLocation); - - fieldOptions.registerOption(GhidraOptions.OPTION_SELECTION_COLOR, - GhidraOptions.DEFAULT_SELECTION_COLOR, helpLocation, - "The selection color in the browser."); - fieldOptions.registerOption(GhidraOptions.OPTION_HIGHLIGHT_COLOR, - GhidraOptions.DEFAULT_HIGHLIGHT_COLOR, helpLocation, - "The highlight color in the browser."); - - fieldOptions.registerOption(CURSOR_COLOR, Color.RED, helpLocation, - "The color of the cursor in the browser."); - fieldOptions.registerOption(UNFOCUSED_CURSOR_COLOR, Color.PINK, helpLocation, - "The color of the cursor in the browser when the browser does not have focus."); - fieldOptions.registerOption(BLINK_CURSOR, true, helpLocation, - "When selected, the cursor will blink when the containing window is focused."); - fieldOptions.registerOption(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR, CURSOR_LINE_COLOR, - helpLocation, "The background color of the line where the cursor is located"); - fieldOptions.registerOption(GhidraOptions.HIGHLIGHT_CURSOR_LINE, true, helpLocation, - "Toggles highlighting background color of line containing the cursor"); - - helpLocation = new HelpLocation(getName(), "Keyboard_Controls_Shift"); - fieldOptions.registerOption(MOUSE_WHEEL_HORIZONTAL_SCROLLING, true, helpLocation, - "Enables horizontal scrolling by holding the Shift key while " + - "using the mouse scroll wheel"); - - Color color = fieldOptions.getColor(GhidraOptions.OPTION_SELECTION_COLOR, - GhidraOptions.DEFAULT_SELECTION_COLOR); - - FieldPanel fieldPanel = connectedProvider.getListingPanel().getFieldPanel(); - fieldPanel.setSelectionColor(color); - MarkerSet selectionMarkers = getSelectionMarkers(currentProgram); - if (selectionMarkers != null) { - selectionMarkers.setMarkerColor(color); - } - - color = - fieldOptions.getColor(GhidraOptions.OPTION_HIGHLIGHT_COLOR, new Color(255, 255, 180)); - MarkerSet highlightMarkers = getHighlightMarkers(currentProgram); - fieldPanel.setHighlightColor(color); - if (highlightMarkers != null) { - highlightMarkers.setMarkerColor(color); - } - - color = fieldOptions.getColor(CURSOR_COLOR, Color.RED); - fieldPanel.setFocusedCursorColor(color); - - color = fieldOptions.getColor(UNFOCUSED_CURSOR_COLOR, Color.PINK); - fieldPanel.setNonFocusCursorColor(color); - - Boolean isBlinkCursor = fieldOptions.getBoolean(BLINK_CURSOR, true); - fieldPanel.setBlinkCursor(isBlinkCursor); - - boolean horizontalScrollingEnabled = - fieldOptions.getBoolean(MOUSE_WHEEL_HORIZONTAL_SCROLLING, true); - fieldPanel.setHorizontalScrollingEnabled(horizontalScrollingEnabled); - - cursorHighlightColor = - fieldOptions.getColor(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR, CURSOR_LINE_COLOR); - - isHighlightCursorLine = fieldOptions.getBoolean(GhidraOptions.HIGHLIGHT_CURSOR_LINE, true); - } - - private void initDisplayOptions(Options displayOptions) { - Color color = displayOptions.getColor(OptionsGui.BACKGROUND.getColorOptionName(), - OptionsGui.BACKGROUND.getDefaultColor()); - connectedProvider.getListingPanel().setTextBackgroundColor(color); - } - - private void initMiscellaneousOptions() { - // make sure the following options are registered - HelpLocation helpLocation = - new HelpLocation("ShowInstructionInfoPlugin", "Processor_Manual_Options"); - Options options = tool.getOptions(ManualViewerCommandWrappedOption.OPTIONS_CATEGORY_NAME); - options.registerOption(ManualViewerCommandWrappedOption.MANUAL_VIEWER_OPTIONS, - OptionType.CUSTOM_TYPE, - ManualViewerCommandWrappedOption.getDefaultBrowserLoaderOptions(), helpLocation, - "Options for running manual viewer", new ManualViewerCommandEditor()); - - } - - public void initActions() { - - // note: these actions gets added later when the TableService is added - - tableFromSelectionAction = new DockingAction("Create Table From Selection", getName()) { - ImageIcon markerIcon = ResourceManager.loadImage("images/searchm_obj.gif"); - - @Override - public void actionPerformed(ActionContext context) { - Listing listing = currentProgram.getListing(); - ProgramSelection selection = connectedProvider.getSelection(); - CodeUnitIterator codeUnits = listing.getCodeUnits(selection, true); - TableService tableService = tool.getService(TableService.class); - if (!codeUnits.hasNext()) { - tool.setStatusInfo( - "Unable to create table from selection: no " + "code units in selection"); - return; - } - - GhidraProgramTableModel
model = createTableModel(codeUnits, selection); - String title = "Selection Table"; - TableComponentProvider
tableProvider = - tableService.showTableWithMarkers(title + " " + model.getName(), "Selection", - model, PluginConstants.SEARCH_HIGHLIGHT_COLOR, markerIcon, title, null); - tableProvider.installRemoveItemsAction(); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - ProgramSelection programSelection = connectedProvider.getSelection(); - return programSelection != null && !programSelection.isEmpty(); - } - }; - - tableFromSelectionAction.setEnabled(false); - tableFromSelectionAction.setMenuBarData(new MenuData( - new String[] { ToolConstants.MENU_SELECTION, "Create Table From Selection" }, null, - "SelectUtils")); - tableFromSelectionAction - .setHelpLocation(new HelpLocation("CodeBrowserPlugin", "Selection_Table")); - - showXrefsAction = new ActionBuilder("Show Xrefs", getName()) - .description("Show the Xrefs to the code unit containing the cursor") - .validContextWhen(context -> context instanceof ListingActionContext) - .onAction(context -> showXrefs(context)) - .build(); - } - - private void showXrefs(ActionContext context) { - - TableService service = tool.getService(TableService.class); - if (service == null) { - return; - } - - ListingActionContext lac = (ListingActionContext) context; - ProgramLocation location = lac.getLocation(); - if (location == null) { - return; // not sure if this can happen - } - - Set refs = XReferenceUtils.getAllXrefs(location); - XReferenceUtils.showXrefs(connectedProvider, tool, service, location, refs); - } - - private GhidraProgramTableModel
createTableModel(CodeUnitIterator iterator, - ProgramSelection selection) { - - CodeUnitFromSelectionTableModelLoader loader = - new CodeUnitFromSelectionTableModelLoader(iterator, selection); - - return new CustomLoadingAddressTableModel(" - from " + selection.getMinAddress(), tool, - currentProgram, loader, null, true); - } - - @Override - public void updateDisplay() { - connectedProvider.getListingPanel().updateDisplay(false); - } - - @Override - public FieldPanel getFieldPanel() { - return connectedProvider.getListingPanel().getFieldPanel(); - } - - @Override - public Navigatable getNavigatable() { - return connectedProvider; - } - -//================================================================================================== -// Testing Methods -//================================================================================================== - - public void updateNow() { - SystemUtilities.runSwingNow(() -> connectedProvider.getListingPanel().updateDisplay(true)); - } - - /** - * Positions the cursor to the given location - * - * @param address the address to goto - * @param fieldName the name of the field to - * @param row the row within the given field - * @param col the col within the given row - * @return true if the specified location was found, false otherwise - */ - public boolean goToField(Address address, String fieldName, int row, int col) { - return goToField(address, fieldName, 0, row, col, true); - } - - /** - * Positions the cursor to the given location - * - * @param addr the address to goto - * @param fieldName the name of the field to - * @param occurrence specifies the which occurrence for multiple fields of same type - * @param row the row within the given field - * @param col the col within the given row - * @return true if the specified location was found, false otherwise - */ - public boolean goToField(Address addr, String fieldName, int occurrence, int row, int col) { - return goToField(addr, fieldName, occurrence, row, col, true); - } - - /** - * Positions the cursor to the given location - * - * @param a the address to goto - * @param fieldName the name of the field to - * @param occurrence specifies the which occurrence for multiple fields of same type - * @param row the row within the given field - * @param col the col within the given row - * @param scroll specifies if the field panel to scroll the position to the center of the screen - * @return true if the specified location was found, false otherwise - */ - public boolean goToField(Address a, String fieldName, int occurrence, int row, int col, - boolean scroll) { - return Swing.runNow(() -> doGoToField(a, fieldName, occurrence, row, col, scroll)); - } - - private boolean doGoToField(Address a, String fieldName, int occurrence, int row, int col, - boolean scroll) { - - Swing.assertSwingThread("'Go To' must be performed on the Swing thread"); - - // make sure that the code browser is ready to go--sometimes it is not, due to timing - // during the testing process, like when the tool is first loaded. - updateNow(); - - ListingPanel panel = connectedProvider.getListingPanel(); - if (a == null) { - a = getCurrentAddress(); - } - - BigInteger index = panel.getAddressIndexMap().getIndex(a); - FieldPanel fieldPanel = panel.getFieldPanel(); - int fieldNum = getFieldNumber(fieldName, occurrence, index, fieldPanel); - if (fieldNum < 0) { - return false; - } - - if (scroll) { - fieldPanel.goTo(index, fieldNum, row, col, true); - } - else { - fieldPanel.setCursorPosition(index, fieldNum, row, col); - } - - return true; - } - - private int getFieldNumber(String fieldName, int occurrence, final BigInteger index, - FieldPanel fieldPanel) { - - if (fieldName == null) { - return -1; - } - - int fieldNum = -1; - LayoutModel model = fieldPanel.getLayoutModel(); - Layout layout = model.getLayout(index); - if (layout == null) { - return -1; - } - - int instanceNum = 0; - for (int i = 0; i < layout.getNumFields(); i++) { - ListingField bf = (ListingField) layout.getField(i); - if (bf.getFieldFactory().getFieldName().equals(fieldName)) { - if (instanceNum++ == occurrence) { - fieldNum = i; - break; - } - } - } - return fieldNum; - } - - public Address getCurrentAddress() { - ProgramLocation loc = getCurrentLocation(); - if (loc == null) { - return null; - } - return getCurrentLocation().getAddress(); - } - - @Override - public ProgramSelection getCurrentSelection() { - return connectedProvider.getListingPanel().getProgramSelection(); - } - - Program getCurrentProgram() { - return currentProgram; - } - - public CodeViewerProvider getProvider() { - return connectedProvider; - } - - public boolean goTo(ProgramLocation location) { - return goTo(location, true); - } - - @Override - public boolean goTo(ProgramLocation location, boolean centerOnScreen) { - - AtomicBoolean didGoTo = new AtomicBoolean(); - SystemUtilities.runSwingNow(() -> { - boolean success = connectedProvider.getListingPanel().goTo(location, centerOnScreen); - didGoTo.set(success); - }); - return didGoTo.get(); - } - - @Override - public ProgramLocation getCurrentLocation() { - return connectedProvider.getListingPanel().getProgramLocation(); - } - - public FieldLocation getCurrentFieldLoction() { - return getFieldPanel().getCursorLocation(); - } - - @Override - public String getCurrentFieldTextSelection() { - return connectedProvider.getStringSelection(); - } - - @Override - public ListingField getCurrentField() { - Field f = getFieldPanel().getCurrentField(); - if (f instanceof ListingField) { - return (ListingField) f; - } - return null; - } - - @Override - public void addListingDisplayListener(ListingDisplayListener listener) { - connectedProvider.addListingDisplayListener(listener); - } - - @Override - public void removeListingDisplayListener(ListingDisplayListener listener) { - connectedProvider.removeListingDisplayListener(listener); - } - - public String getCurrentFieldText() { - ListingField lf = getCurrentField(); - if (lf instanceof ListingTextField) { - return ((ListingTextField) lf).getText(); - } - return ""; - } - - @Override - public AddressSetView getView() { - return currentView; - } - - @Override - public FormatManager getFormatManager() { - return formatMgr; - } - - public void toggleOpen(Data data) { - connectedProvider.getListingPanel().getListingModel().toggleOpen(data); - } - - @Override - public AddressIndexMap getAddressIndexMap() { - return getListingPanel().getAddressIndexMap(); - } - - @Override - public ListingPanel getListingPanel() { - return connectedProvider.getListingPanel(); - } - - Address getAddressTopOfScreen() { - BigInteger index = getFieldPanel().getViewerPosition().getIndex(); - return getAddressIndexMap().getAddress(index); - } - - @Override - public void readConfigState(SaveState saveState) { - formatMgr.readState(saveState); - connectedProvider.readState(saveState); - } - - @Override - public void writeConfigState(SaveState saveState) { - formatMgr.saveState(saveState); - connectedProvider.saveState(saveState); - } - - @Override - public void formatModelAdded(FieldFormatModel model) { - // uninterested - } - - @Override - public void formatModelRemoved(FieldFormatModel model) { - // uninterested - } - - @Override - public void formatModelChanged(FieldFormatModel model) { - tool.setConfigChanged(true); - } - - @Override - public ListingModel getListingModel() { - return connectedProvider.getListingPanel().getListingModel().copy(); - } - - @Override - public void domainObjectChanged(DomainObjectChangedEvent ev) { - if (ev.containsEvent(DomainObject.DO_DOMAIN_FILE_CHANGED)) { - connectedProvider.updateTitle(); - } - - if (viewManager != null) { - return; - } - if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED)) { - viewChanged(currentProgram.getMemory()); - } - } - - @Override - public void providerClosed(CodeViewerProvider codeViewerProvider) { - removeProvider(codeViewerProvider); - if (!codeViewerProvider.isConnected()) { - disconnectedProviders.remove(codeViewerProvider); - } - } - @Override public ViewManagerService getViewManager(CodeViewerProvider codeViewerProvider) { if (codeViewerProvider == connectedProvider) { @@ -1293,55 +285,4 @@ public class CodeBrowserPlugin extends Plugin } return null; } - -//================================================================================================== -// Inner Classes -//================================================================================================== - - static class MarkerChangeListener implements ChangeListener { - private FieldPanel fieldPanel; - - MarkerChangeListener(CodeViewerProvider provider) { - this.fieldPanel = provider.getListingPanel().getFieldPanel(); - } - - @Override - public void stateChanged(ChangeEvent e) { - fieldPanel.repaint(); - } - } - - private class FocusingMouseListener extends MouseAdapter { - @Override - public void mousePressed(MouseEvent e) { - connectedProvider.getListingPanel().getFieldPanel().requestFocus(); - } - } - - private class CodeUnitFromSelectionTableModelLoader implements TableModelLoader
{ - - private CodeUnitIterator iterator; - private ProgramSelection selection; - - CodeUnitFromSelectionTableModelLoader(CodeUnitIterator iterator, - ProgramSelection selection) { - this.iterator = iterator; - this.selection = selection; - } - - @Override - public void load(Accumulator
accumulator, TaskMonitor monitor) - throws CancelledException { - - long size = selection.getNumAddresses(); - monitor.initialize(size); - - while (iterator.hasNext()) { - monitor.checkCanceled(); - CodeUnit cu = iterator.next(); - accumulator.add(cu.getMinAddress()); - monitor.incrementProgress(cu.getLength()); - } - } - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPluginInterface.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPluginInterface.java index 5b8685ddb9..8641be4a73 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPluginInterface.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPluginInterface.java @@ -24,16 +24,21 @@ import ghidra.program.util.ProgramSelection; public interface CodeBrowserPluginInterface { PluginTool getTool(); + String getName(); void providerClosed(CodeViewerProvider codeViewerProvider); + boolean isDisposed(); void locationChanged(CodeViewerProvider codeViewerProvider, ProgramLocation loc); + void selectionChanged(CodeViewerProvider codeViewerProvider, ProgramSelection currentSelection); + void highlightChanged(CodeViewerProvider codeViewerProvider, ProgramSelection highlight); ViewManagerService getViewManager(CodeViewerProvider codeViewerProvider); + CodeViewerProvider createNewDisconnectedProvider(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java index 305063eb2f..b9b6c9ded9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java @@ -158,10 +158,14 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter listingPanel.setStringSelectionListener(this); listingPanel.addIndexMapChangeListener(this); - codeViewerClipboardProvider = new CodeBrowserClipboardProvider(tool, this); + codeViewerClipboardProvider = newClipboardProvider(); tool.addPopupActionProvider(this); } + protected CodeBrowserClipboardProvider newClipboardProvider() { + return new CodeBrowserClipboardProvider(tool, this); + } + @Override public boolean isSnapshot() { // we are a snapshot when we are 'disconnected' @@ -175,6 +179,17 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter return false; } + /** + * TODO: Remove or rename this to something that accommodates redirecting writes, e.g., to a + * debug target process, particularly for assembly, which may involve code unit modification + * after a successful write, reported asynchronously :/ . + * + * @return true if this listing represents a read-only view + */ + public boolean isReadOnly() { + return false; + } + private ListingHighlightProvider createListingHighlighter(ListingPanel panel, PluginTool pluginTool, Component repaintComponent) { ListingHighlightProvider listingHighlighter = @@ -1051,20 +1066,20 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter } /** - * Add the ListingDisplayListener to the listing panel + * Add the {@link AddressSetDisplayListener} to the listing panel * * @param listener the listener to add */ - public void addListingDisplayListener(ListingDisplayListener listener) { - listingPanel.addListingDisplayListener(listener); + public void addDisplayListener(AddressSetDisplayListener listener) { + listingPanel.addDisplayListener(listener); } /** - * Remove the ListingDisplayListener from the listing panel + * Remove the {@link AddressSetDisplayListener} from the listing panel * * @param listener the listener to remove */ - public void removeListingDisplayListener(ListingDisplayListener listener) { - listingPanel.removeListingDisplayListener(listener); + public void removeDisplayListener(AddressSetDisplayListener listener) { + listingPanel.removeDisplayListener(listener); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/CodeViewerService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/CodeViewerService.java index f60ea1d43e..afca51f92f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/CodeViewerService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/CodeViewerService.java @@ -219,11 +219,11 @@ public interface CodeViewerService { * Adds a listener to be notified when the set of visible addresses change. * @param listener the listener to be notified; */ - public void addListingDisplayListener(ListingDisplayListener listener); + public void addListingDisplayListener(AddressSetDisplayListener listener); /** * Removes listener from being notified when the set of visible addresses change. * @param listener the listener to be notified; */ - public void removeListingDisplayListener(ListingDisplayListener listener); + public void removeListingDisplayListener(AddressSetDisplayListener listener); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDisplayListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/AddressSetDisplayListener.java similarity index 96% rename from Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDisplayListener.java rename to Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/AddressSetDisplayListener.java index 4bcf58003c..3c8e23b84d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDisplayListener.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/AddressSetDisplayListener.java @@ -20,7 +20,7 @@ import ghidra.program.model.address.AddressSetView; /** * Interface for being notified whenever the set of visible addresses change in the listing. */ -public interface ListingDisplayListener { +public interface AddressSetDisplayListener { /** * Callback whenever the set of visible addresses change in the listing. * @param visibleAddresses the current set of visible addresses in the listing. If no diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java index dad3ae52e6..0d54661b21 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java @@ -89,7 +89,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc // don't care } }; - private List displayListeners = new ArrayList<>(); + private List displayListeners = new ArrayList<>(); private String currentTextSelection; @@ -479,12 +479,12 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc element.setPixelMap(pixmap); } - for (ListingDisplayListener listener : displayListeners) { + for (AddressSetDisplayListener listener : displayListeners) { notifyDisplayListener(listener); } } - private void notifyDisplayListener(ListingDisplayListener listener) { + private void notifyDisplayListener(AddressSetDisplayListener listener) { AddressSetView displayAddresses = pixmap.getAddressSet(); try { listener.visibleAddressesChanged(displayAddresses); @@ -1160,11 +1160,11 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc layoutModel.dataChanged(true); } - public void addListingDisplayListener(ListingDisplayListener listener) { + public void addDisplayListener(AddressSetDisplayListener listener) { displayListeners.add(listener); } - public void removeListingDisplayListener(ListingDisplayListener listener) { + public void removeDisplayListener(AddressSetDisplayListener listener) { displayListeners.remove(listener); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/listingpanel/ListingPanelTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/listingpanel/ListingPanelTest.java index bc4656ab20..aecaec11a8 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/listingpanel/ListingPanelTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/listingpanel/ListingPanelTest.java @@ -307,7 +307,7 @@ public class ListingPanelTest extends AbstractGhidraHeadedIntegrationTest { AtomicReference addresses = new AtomicReference<>(); CodeViewerService cvs = tool.getService(CodeViewerService.class); - cvs.addListingDisplayListener(new ListingDisplayListener() { + cvs.addListingDisplayListener(new AddressSetDisplayListener() { @Override public void visibleAddressesChanged(AddressSetView visibleAddresses) { addresses.set(visibleAddresses); diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/AbstractByteViewerPlugin.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/AbstractByteViewerPlugin.java new file mode 100644 index 0000000000..24eb2615b6 --- /dev/null +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/AbstractByteViewerPlugin.java @@ -0,0 +1,308 @@ +/* ### + * 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.byteviewer; + +import java.util.*; + +import org.jdom.Element; + +import ghidra.app.events.ProgramLocationPluginEvent; +import ghidra.app.events.ProgramSelectionPluginEvent; +import ghidra.app.services.*; +import ghidra.framework.model.DomainFile; +import ghidra.framework.model.DomainObject; +import ghidra.framework.options.SaveState; +import ghidra.framework.plugintool.Plugin; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; +import utility.function.Callback; + +public abstract class AbstractByteViewerPlugin

+ extends Plugin { + + protected Program currentProgram; + private boolean areEventsDisabled; + protected ProgramLocation currentLocation; + + protected P connectedProvider; + + protected List

disconnectedProviders = new ArrayList<>(); + + public AbstractByteViewerPlugin(PluginTool tool) { + super(tool); + + connectedProvider = createProvider(true); + } + + protected abstract P createProvider(boolean isConnected); + + protected void showConnectedProvider() { + tool.showComponentProvider(connectedProvider, true); + } + + public P createNewDisconnectedProvider() { + P newProvider = createProvider(false); + disconnectedProviders.add(newProvider); + tool.showComponentProvider(newProvider, true); + return newProvider; + } + + @Override + protected void init() { + ClipboardService clipboardService = tool.getService(ClipboardService.class); + if (clipboardService != null) { + connectedProvider.setClipboardService(clipboardService); + for (P provider : disconnectedProviders) { + provider.setClipboardService(clipboardService); + } + } + } + + /** + * Tells a plugin that it is no longer needed. The plugin should remove itself from anything + * that it is registered to and release any resources. + */ + @Override + public void dispose() { + removeProvider(connectedProvider); + for (P provider : disconnectedProviders) { + removeProvider(provider); + } + disconnectedProviders.clear(); + } + + /** + * Process the plugin event; delegates the processing to the byte block. + */ + + /** + * Tells a Plugin to write any data-independent (preferences) properties to the output stream. + */ + @Override + public void writeConfigState(SaveState saveState) { + connectedProvider.writeConfigState(saveState); + } + + /** + * Tells the Plugin to read its data-independent (preferences) properties from the input stream. + */ + @Override + public void readConfigState(SaveState saveState) { + connectedProvider.readConfigState(saveState); + } + + /** + * Read data state; called after readConfigState(). Events generated by plugins we depend on + * should have been already been thrown by the time this method is called. + */ + @Override + public void readDataState(SaveState saveState) { + + doWithEventsDisabled(() -> { + + ProgramManager programManagerService = tool.getService(ProgramManager.class); + + connectedProvider.readDataState(saveState); + + int numDisconnected = saveState.getInt("Num Disconnected", 0); + for (int i = 0; i < numDisconnected; i++) { + Element xmlElement = saveState.getXmlElement("Provider" + i); + SaveState providerSaveState = new SaveState(xmlElement); + String programPath = providerSaveState.getString("Program Path", ""); + DomainFile file = tool.getProject().getProjectData().getFile(programPath); + if (file == null) { + continue; + } + Program program = programManagerService.openProgram(file); + if (program != null) { + P provider = createProvider(false); + provider.doSetProgram(program); + provider.readConfigState(providerSaveState); + provider.readDataState(providerSaveState); + tool.showComponentProvider(provider, true); + addProvider(provider); + } + } + + }); + } + + /** + * Tells the Plugin to write any data-dependent state to the output stream. + */ + @Override + public void writeDataState(SaveState saveState) { + connectedProvider.writeDataState(saveState); + saveState.putInt("Num Disconnected", disconnectedProviders.size()); + int i = 0; + for (P provider : disconnectedProviders) { + SaveState providerSaveState = new SaveState(); + DomainFile df = provider.getProgram().getDomainFile(); + if (df.getParent() == null) { + continue; // not contained within project + } + String programPathname = df.getPathname(); + providerSaveState.putString("Program Path", programPathname); + provider.writeConfigState(providerSaveState); + provider.writeDataState(providerSaveState); + String elementName = "Provider" + i; + saveState.putXmlElement(elementName, providerSaveState.saveToXml()); + i++; + } + } + + @Override + public Object getUndoRedoState(DomainObject domainObject) { + Map stateMap = new HashMap<>(); + + addUndoRedoState(stateMap, domainObject, connectedProvider); + + for (P provider : disconnectedProviders) { + addUndoRedoState(stateMap, domainObject, provider); + } + + if (stateMap.isEmpty()) { + return null; + } + return stateMap; + } + + private void addUndoRedoState(Map stateMap, DomainObject domainObject, + P provider) { + if (provider == null) { + return; + } + Object state = provider.getUndoRedoState(domainObject); + if (state != null) { + stateMap.put(provider.getInstanceID(), state); + } + } + + @SuppressWarnings("unchecked") + @Override + public void restoreUndoRedoState(DomainObject domainObject, Object state) { + Map stateMap = (Map) state; + restoreUndoRedoState(stateMap, domainObject, connectedProvider); + for (P provider : disconnectedProviders) { + restoreUndoRedoState(stateMap, domainObject, provider); + } + + } + + private void restoreUndoRedoState(Map stateMap, DomainObject domainObject, + P provider) { + if (provider == null) { + return; + } + Object state = stateMap.get(provider.getInstanceID()); + if (state != null) { + provider.restoreUndoRedoState(domainObject, state); + } + } + + @Override + public Object getTransientState() { + Object[] state = new Object[2]; + + SaveState ss = new SaveState(); + connectedProvider.writeDataState(ss); + + state[0] = ss; + state[1] = connectedProvider.getCurrentSelection(); + + return state; + } + + @Override + public void restoreTransientState(Object objectState) { + + doWithEventsDisabled(() -> { + Object[] state = (Object[]) objectState; + connectedProvider.restoreLocation((SaveState) state[0]); + connectedProvider.setSelection((ProgramSelection) state[1]); + }); + } + + private void doWithEventsDisabled(Callback callback) { + areEventsDisabled = true; + try { + callback.call(); + } + finally { + areEventsDisabled = false; + } + } + + protected boolean eventsDisabled() { + return areEventsDisabled; + } + + void setStatusMessage(String msg) { + tool.setStatusInfo(msg); + } + + void addProvider(P provider) { + disconnectedProviders.add(provider); + provider.setClipboardService(tool.getService(ClipboardService.class)); + } + + Program getProgram() { + return currentProgram; + } + + // Silly Junits - only public until we move to the new multi-view system + public P getProvider() { + return connectedProvider; + } + + public abstract void updateSelection(ByteViewerComponentProvider provider, + ProgramSelectionPluginEvent event, Program program); + + public abstract void highlightChanged(ByteViewerComponentProvider provider, + ProgramSelection highlight); + + public void closeProvider(ByteViewerComponentProvider provider) { + if (provider == connectedProvider) { + tool.showComponentProvider(provider, false); + } + else { + disconnectedProviders.remove(provider); + removeProvider(provider); + } + } + + protected void exportLocation(Program program, ProgramLocation location) { + GoToService service = tool.getService(GoToService.class); + if (service != null) { + service.goTo(location, program); + } + } + + protected void removeProvider(ByteViewerComponentProvider provider) { + tool.removeComponentProvider(provider); + provider.dispose(); + } + + protected abstract void updateLocation( + ProgramByteViewerComponentProvider programByteViewerComponentProvider, + ProgramLocationPluginEvent event, boolean export); + + protected abstract void fireProgramLocationPluginEvent( + ProgramByteViewerComponentProvider programByteViewerComponentProvider, + ProgramLocationPluginEvent pluginEvent); +} diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteBlockChangeManager.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteBlockChangeManager.java index 3a250605e9..57c41ccc13 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteBlockChangeManager.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteBlockChangeManager.java @@ -25,10 +25,10 @@ import ghidra.framework.options.SaveState; import ghidra.program.model.address.Address; /** - * Helper class to manage changes within byte blocks; determines what offsets - * have changed so the changes can be rendered properly in the Byte Viewer. + * Helper class to manage changes within byte blocks; determines what offsets have changed so the + * changes can be rendered properly in the Byte Viewer. */ -class ByteBlockChangeManager { +public class ByteBlockChangeManager { private ProgramByteBlockSet blockSet; private List changeList; // list of changes for this tool @@ -57,6 +57,7 @@ class ByteBlockChangeManager { /** * Add a change to the change list. + * * @param edit edit object that has the old value and new value * */ @@ -117,12 +118,12 @@ class ByteBlockChangeManager { } /** - * Return true if any offset in the range offset to offset+unitByteSize-1 - * is in either of the change lists. + * Return true if any offset in the range offset to offset+unitByteSize-1 is in either of the + * change lists. + * * @param block block in question * @param offset offset into the block - * @param unitByteSize number of bytes in the unit (dictated by the - * data format model) + * @param unitByteSize number of bytes in the unit (dictated by the data format model) * * @return boolean true if an offset in the range was found */ @@ -140,6 +141,7 @@ class ByteBlockChangeManager { ////////////////////////////////////////////////////////////////////// /** * Return true if the block and offset are in the list. + * * @param list either the local change list or the external change list * @param block block in question * @param offset offset into the block diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponent.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponent.java index 125554a2d6..8c340a29df 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponent.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponent.java @@ -32,13 +32,12 @@ import docking.widgets.fieldpanel.field.Field; import docking.widgets.fieldpanel.listener.*; import docking.widgets.fieldpanel.support.*; import ghidra.app.plugin.core.format.*; -import ghidra.program.model.address.AddressOutOfBoundsException; +import ghidra.program.model.address.*; import ghidra.util.Msg; /** - * FieldViewer to show data formatted according to the DataFormatModel that - * is passed in to the constructor. The source of the data is an array - * of ByteBlocks that is managed by an IndexMap. + * FieldViewer to show data formatted according to the DataFormatModel that is passed in to the + * constructor. The source of the data is an array of ByteBlocks that is managed by an IndexMap. */ public class ByteViewerComponent extends FieldPanel implements FieldMouseListener, FieldLocationListener, FieldSelectionListener, FieldInputListener { @@ -70,14 +69,14 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene /** * Constructor - * @param vpanel the byte viewer panel that this component lives in + * + * @param vpanel the byte viewer panel that this component lives in * @param layoutModel the layout model for this component - * @param model data format model that knows how the data should be - * displayed + * @param model data format model that knows how the data should be displayed * @param bytesPerLine number of bytes displayed in a row * @param fm the font metrics used for drawing */ - ByteViewerComponent(ByteViewerPanel vpanel, ByteViewerLayoutModel layoutModel, + protected ByteViewerComponent(ByteViewerPanel vpanel, ByteViewerLayoutModel layoutModel, DataFormatModel model, int bytesPerLine, FontMetrics fm) { super(layoutModel); @@ -357,6 +356,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene /** * Set the color for the component that has focus. + * * @param c the color to set */ void setCurrentCursorColor(Color c) { @@ -366,6 +366,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene /** * Set the background color for the line containing the cursor. + * * @param c the color to set */ void setCurrentCursorLineColor(Color c) { @@ -374,6 +375,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene /** * Set the color for showing gaps in indexes. + * * @param c the color to set */ void setSeparatorColor(Color c) { @@ -415,8 +417,17 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene updatingIndexMap = false; } + protected IndexMap getIndexMap() { + return indexMap; + } + + protected ProgramByteBlockSet getBlockSet() { + return blockSet; + } + /** * Set the new group size + * * @param groupSize the group size * @throws UnsupportedOperationException if model for this view does not support groups */ @@ -442,7 +453,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene } } - private FieldSelection getFieldSelection(ByteBlockSelection selection) { + protected FieldSelection getFieldSelection(ByteBlockSelection selection) { FieldSelection fsel = new FieldSelection(); for (int i = 0; i < selection.getNumberOfRanges(); i++) { ByteBlockRange r = selection.getRange(i); @@ -487,8 +498,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene * @param block the block * @param index the index * @param characterOffset the offset into the UI field - * @return index of the location; return -1 if there was an error - * setting the cursor location + * @return index of the location; return -1 if there was an error setting the cursor location */ int setViewerCursorLocation(ByteBlock block, BigInteger index, int characterOffset) { if (indexMap == null) { @@ -649,10 +659,9 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene } /** - * Set the edit mode according to the given param if the model - * for this view supports editing. - * @param editMode true means to enable editing, and change the cursor - * color. + * Set the edit mode according to the given param if the model for this view supports editing. + * + * @param editMode true means to enable editing, and change the cursor color. */ void setEditMode(boolean editMode) { consumeKeyStrokes = editMode; @@ -742,9 +751,8 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene enableHelp(); } - /** - * Enable help for this component; used the model name as part of - * the help ID. + /** + * Enable help for this component; used the model name as part of the help ID. */ private void enableHelp() { HelpService helpService = Help.getHelpService(); @@ -814,7 +822,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene /** * Create a byte block selection from the field selection. */ - private ByteBlockSelection processFieldSelection(FieldSelection selection) { + protected ByteBlockSelection processFieldSelection(FieldSelection selection) { ByteBlockSelection sel = new ByteBlockSelection(); int count = selection.getNumRanges(); @@ -865,6 +873,17 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene return null; } + public AddressSetView getView() { + AddressSet result = new AddressSet(); + if (blockSet != null) { + for (ByteBlock block : blockSet.getBlocks()) { + Address start = blockSet.getBlockStart(block); + result.add(start, start.add(block.getLength().longValue() - 1)); + } + } + return result; + } + private class ByteViewerBackgroundColorModel implements BackgroundColorModel { private Color defaultBackgroundColor = Color.WHITE; diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponentProvider.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponentProvider.java index 35d93c367a..447e566201 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponentProvider.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponentProvider.java @@ -28,6 +28,7 @@ import ghidra.GhidraOptions; import ghidra.GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES; import ghidra.app.plugin.core.format.*; import ghidra.app.services.MarkerService; +import ghidra.app.util.viewer.listingpanel.AddressSetDisplayListener; import ghidra.framework.options.*; import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.PluginTool; @@ -87,25 +88,26 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt protected Map viewMap = new HashMap<>(); - private ToggleEditAction editModeAction; + protected ToggleEditAction editModeAction; protected OptionsAction setOptionsAction; protected ProgramByteBlockSet blockSet; - protected final ByteViewerPlugin plugin; + protected final AbstractByteViewerPlugin plugin; protected SwingUpdateManager updateManager; private Map> dataFormatModelClassMap; - protected ByteViewerComponentProvider(PluginTool tool, ByteViewerPlugin plugin, String name, + protected ByteViewerComponentProvider(PluginTool tool, AbstractByteViewerPlugin plugin, + String name, Class contextType) { super(tool, name, plugin.getName(), contextType); this.plugin = plugin; initializedDataFormatModelClassMap(); - panel = new ByteViewerPanel(this); + panel = newByteViewerPanel(); bytesPerLine = DEFAULT_BYTES_PER_LINE; setIcon(ResourceManager.loadImage("images/binaryData.gif")); setOptions(); @@ -118,6 +120,10 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt setWindowMenuGroup("Byte Viewer"); } + protected ByteViewerPanel newByteViewerPanel() { + return new ByteViewerPanel(this); + } + private void initializedDataFormatModelClassMap() { dataFormatModelClassMap = new HashMap<>(); Set models = getDataFormatModels(); @@ -150,6 +156,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt /** * Notification that an option changed. + * * @param options options object containing the property that changed * @param group * @param optionName name of option that changed @@ -338,7 +345,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt } } - void writeConfigState(SaveState saveState) { + protected void writeConfigState(SaveState saveState) { DataModelInfo info = panel.getDataModelInfo(); saveState.putStrings(VIEW_NAMES, info.getNames()); saveState.putInt(HEX_VIEW_GROUPSIZE, hexGroupSize); @@ -346,7 +353,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt saveState.putInt(OFFSET_NAME, offset); } - void readConfigState(SaveState saveState) { + protected void readConfigState(SaveState saveState) { String[] names = saveState.getStrings(VIEW_NAMES, new String[0]); hexGroupSize = saveState.getInt(HEX_VIEW_GROUPSIZE, 1); restoreViews(names, false); @@ -412,10 +419,10 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt } - abstract void updateLocation(ByteBlock block, BigInteger blockOffset, int column, + protected abstract void updateLocation(ByteBlock block, BigInteger blockOffset, int column, boolean export); - abstract void updateSelection(ByteBlockSelection selection); + protected abstract void updateSelection(ByteBlockSelection selection); void dispose() { updateManager.dispose(); @@ -445,7 +452,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt } - ByteViewerPanel getByteViewerPanel() { + protected ByteViewerPanel getByteViewerPanel() { return panel; } @@ -481,7 +488,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt return null; } try { - return classy.newInstance(); + return classy.getConstructor().newInstance(); } catch (Exception e) { // cannot happen, since we only get the value from valid class that we put into the map @@ -493,4 +500,22 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt public MarkerService getMarkerService() { return tool.getService(MarkerService.class); } + + /** + * Add the {@link AddressSetDisplayListener} to the byte viewer panel + * + * @param listener the listener to add + */ + public void addDisplayListener(AddressSetDisplayListener listener) { + panel.addDisplayListener(listener); + } + + /** + * Remove the {@link AddressSetDisplayListener} from the byte viewer panel + * + * @param listener the listener to remove + */ + public void removeDisplayListener(AddressSetDisplayListener listener) { + panel.removeDisplayListener(listener); + } } diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerLayoutModel.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerLayoutModel.java index 0ddb5e8dd0..8fd968ee69 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerLayoutModel.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerLayoutModel.java @@ -33,15 +33,14 @@ import ghidra.app.plugin.core.format.DataFormatModel; /** * Implements the LayoutModel for ByteViewer Components. */ - -class ByteViewerLayoutModel implements LayoutModel { +public class ByteViewerLayoutModel implements LayoutModel { private int width; private IndexMap indexMap; private List listeners; private FieldFactory[] factorys; private BigInteger numIndexes; - ByteViewerLayoutModel() { + public ByteViewerLayoutModel() { factorys = new FieldFactory[0]; listeners = new ArrayList(1); numIndexes = BigInteger.ZERO; diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPanel.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPanel.java index bbc94f4ea0..c1af73f722 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPanel.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPanel.java @@ -28,25 +28,27 @@ import docking.help.HelpService; import docking.widgets.fieldpanel.*; import docking.widgets.fieldpanel.field.EmptyTextField; import docking.widgets.fieldpanel.field.Field; -import docking.widgets.fieldpanel.listener.IndexMapper; -import docking.widgets.fieldpanel.listener.LayoutModelListener; -import docking.widgets.fieldpanel.support.SingleRowLayout; -import docking.widgets.fieldpanel.support.ViewerPosition; +import docking.widgets.fieldpanel.listener.*; +import docking.widgets.fieldpanel.support.*; import docking.widgets.indexedscrollpane.*; import docking.widgets.label.GDLabel; import docking.widgets.label.GLabel; import ghidra.app.plugin.core.format.*; +import ghidra.app.util.viewer.listingpanel.AddressSetDisplayListener; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.address.AddressSetView; import ghidra.util.HelpLocation; +import ghidra.util.Msg; import ghidra.util.exception.InvalidInputException; import ghidra.util.layout.HorizontalLayout; import ghidra.util.layout.PairLayout; /** - * Top level component that contains has a scrolled pane for the panel of - * components that show the view for each format. + * Top level component that contains has a scrolled pane for the panel of components that show the + * view for each format. */ -public class ByteViewerPanel extends JPanel implements TableColumnModelListener, LayoutModel { - +public class ByteViewerPanel extends JPanel + implements TableColumnModelListener, LayoutModel, LayoutListener { // private ByteViewerPlugin plugin; private List viewList; // list of field viewers private FieldPanel indexPanel; // panel for showing indexes @@ -79,10 +81,12 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, // changes while this flag is true private final ByteViewerComponentProvider provider; + private List displayListeners = new ArrayList<>(); + /** * Constructor */ - ByteViewerPanel(ByteViewerComponentProvider provider) { + protected ByteViewerPanel(ByteViewerComponentProvider provider) { super(); this.provider = provider; bytesPerLine = ByteViewerComponentProvider.DEFAULT_BYTES_PER_LINE; @@ -203,7 +207,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, } void setMouseButtonHighlightColor(Color color) { - this.highlightColor = color; + this.highlightColor = color; for (int i = 0; i < viewList.size(); i++) { ByteViewerComponent comp = viewList.get(i); comp.setMouseButtonHighlightColor(color); @@ -240,8 +244,8 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, } /** - * Set the byte blocks and create an new IndexMap object that will be - * passed to the index panel and to each component that shows a format. + * Set the byte blocks and create an new IndexMap object that will be passed to the index panel + * and to each component that shows a format. */ void setByteBlocks(ByteBlockSet blockSet) { this.blockSet = blockSet; @@ -323,6 +327,15 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, } } + /** + * Set the background color model for all the views. + */ + public void setViewerBackgroundColorModel(BackgroundColorModel colorModel) { + for (ByteViewerComponent c : viewList) { + c.setBackgroundColorModel(colorModel); + } + } + /** * Get the current highlight. * @@ -363,8 +376,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, /** * Get the data format model of the view that is in focus. * - * @return DataFormatModel model of the view in focus; return null - * if no views are shown + * @return DataFormatModel model of the view in focus; return null if no views are shown */ DataFormatModel getCurrentModel() { if (currentView == null) { @@ -376,17 +388,21 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, /** * Returns the currently focused view. */ - ByteViewerComponent getCurrentComponent() { + public ByteViewerComponent getCurrentComponent() { return currentView; } + protected ByteViewerComponent newByteViewerComponent(DataFormatModel model) { + return new ByteViewerComponent(this, new ByteViewerLayoutModel(), model, bytesPerLine, fm); + } + /** * Add a view to the panel. + * * @param viewName name of the format, e.g., Hex, Ascii, etc. * @param model model that understands the format * @param editMode true if edit mode is on - * @param updateViewPosition true if the view position should be - * set + * @param updateViewPosition true if the view position should be set */ ByteViewerComponent addView(String viewName, DataFormatModel model, boolean editMode, boolean updateViewPosition) { @@ -398,8 +414,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, // create new ByteViewerComponent - ByteViewerComponent c = - new ByteViewerComponent(this, new ByteViewerLayoutModel(), model, bytesPerLine, fm); + ByteViewerComponent c = newByteViewerComponent(model); c.setEditColor(editColor); c.setNonFocusCursorColor(cursorColor); c.setCurrentCursorColor(currentCursorColor); @@ -471,9 +486,8 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, } /** - * Set the given component to be the current view; called by the - * mouse listener in the ByteViewerComponent when the user clicks in the - * panel. + * Set the given component to be the current view; called by the mouse listener in the + * ByteViewerComponent when the user clicks in the panel. */ void setCurrentView(ByteViewerComponent c) { if (currentView != null && currentView != c) { @@ -483,8 +497,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, } /** - * Set the cursor color on the current view to show that it is in - * edit mode. + * Set the cursor color on the current view to show that it is in edit mode. */ void setEditMode(boolean editMode) { for (int i = 0; i < viewList.size(); i++) { @@ -537,8 +550,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, } /** - * Set the bytes per line. Bytes per line dictates the number of fields - * displayed in a row. + * Set the bytes per line. Bytes per line dictates the number of fields displayed in a row. */ void setBytesPerLine(int bytesPerLine) { @@ -555,10 +567,9 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, } /** - * Check that each model for the views can support the given - * bytes per line value. - * @throws InvalidInputException if a model cannot support the - * bytesPerLine value + * Check that each model for the views can support the given bytes per line value. + * + * @throws InvalidInputException if a model cannot support the bytesPerLine value */ void checkBytesPerLine(int numBytesPerLine) throws InvalidInputException { for (int i = 0; i < viewList.size(); i++) { @@ -576,6 +587,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, /** * Set the group size on the current view. + * * @param groupSize new group size */ void setCurrentGroupSize(int groupSize) { @@ -596,9 +608,9 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, } /** - * Set the insertion field and tell other views to change location; - * called when the ByteViewerComponent receives a notification that - * the cursor location has changed. + * Set the insertion field and tell other views to change location; called when the + * ByteViewerComponent receives a notification that the cursor location has changed. + * * @param source source of the change * @param block block for the new location * @param offset offset into the block @@ -632,8 +644,9 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, } /** - * Called from the ByteViewerComponent when it received a notification - * that the selection has changed. + * Called from the ByteViewerComponent when it received a notification that the selection has + * changed. + * * @param source source of the change * @param selection selection */ @@ -657,8 +670,8 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, } /** - * Return array of names of views in the order that they appear in the - * panel. The name array includes an entry for the index panel. + * Return array of names of views in the order that they appear in the panel. The name array + * includes an entry for the index panel. */ DataModelInfo getDataModelInfo() { @@ -689,11 +702,11 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, * * @return ViewerPosition top viewer position */ - ViewerPosition getViewerPosition() { + public ViewerPosition getViewerPosition() { return indexPanel.getViewerPosition(); } - void setViewerPosition(ViewerPosition pos) { + public void setViewerPosition(ViewerPosition pos) { indexPanel.setViewerPosition(pos.getIndex(), pos.getXOffset(), pos.getYOffset()); } @@ -718,6 +731,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, /** * Restore the configuration of the plugin. + * * @param fontMetrics font metrics * @param newEditColor color for showing edits */ @@ -781,10 +795,14 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, /** * Get the font metrics that the panel is using. */ - FontMetrics getFontMetrics() { + protected FontMetrics getFontMetrics() { return fm; } + protected int getBytesPerLine() { + return bytesPerLine; + } + /** * Create the components for this top level panel. */ @@ -804,6 +822,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, indexPanel.enableSelection(false); indexPanel.setCursorOn(false); indexPanel.setFocusable(false); + indexPanel.addLayoutListener(this); compPanel = new CompositePanel(indexPanel); @@ -885,8 +904,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, } /** - * Create a new index map and update the map in the index field adapter - * and all the views. + * Create a new index map and update the map in the index field adapter and all the views. */ private void updateIndexMap() { if (blockSet == null) { @@ -1027,6 +1045,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, /*** * Getter for the list of ByteViewer Components + * * @return viewList the list of ByteViewerComponents */ public List getViewList() { @@ -1046,6 +1065,43 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, public void flushChanges() { // nothing to do } + + protected AddressSetView computeVisibleAddresses(List layouts) { + // Kind of gross, but current component will do + ByteViewerComponent component = getCurrentComponent(); + if (component == null || blockSet == null) { + return new AddressSet(); + } + + BigInteger startIndex = layouts.get(0).getIndex(); + BigInteger endIndex = layouts.get(layouts.size() - 1).getIndex(); + FieldSelection fieldSel = new FieldSelection(); + fieldSel.addRange(startIndex, endIndex.add(BigInteger.ONE)); + ByteBlockSelection blockSel = component.processFieldSelection(fieldSel); + return blockSet.getAddressSet(blockSel); + } + + @Override + public void layoutsChanged(List layouts) { + AddressSetView visible = computeVisibleAddresses(layouts); + for (AddressSetDisplayListener listener : displayListeners) { + try { + listener.visibleAddressesChanged(visible); + } + catch (Throwable t) { + Msg.showError(this, indexPanel, "Error in Display Listener", + "Exception encountered when notifying listeners of change in display", t); + } + } + } + + public void addDisplayListener(AddressSetDisplayListener listener) { + displayListeners.add(listener); + } + + public void removeDisplayListener(AddressSetDisplayListener listener) { + displayListeners.add(listener); + } } class CompositePanel extends JPanel implements IndexedScrollable, IndexScrollListener { @@ -1211,4 +1267,5 @@ class CompositePanel extends JPanel implements IndexedScrollable, IndexScrollLis processingIndexRangeChanged = false; } } + } diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin.java index 57cf9009a3..b348305783 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin.java @@ -15,94 +15,54 @@ */ package ghidra.app.plugin.core.byteviewer; -import java.util.*; - -import org.jdom.Element; +import java.util.Iterator; import ghidra.app.CorePluginPackage; import ghidra.app.events.*; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.services.*; -import ghidra.framework.model.DomainFile; -import ghidra.framework.model.DomainObject; -import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.listing.Program; -import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.util.SystemUtilities; -import utility.function.Callback; /** * Visible Plugin to show ByteBlock data in various formats. */ -//@formatter:off @PluginInfo( status = PluginStatus.RELEASED, packageName = CorePluginPackage.NAME, category = PluginCategoryNames.BYTE_VIEWER, shortDescription = "Displays bytes in memory", description = "Provides a component for showing the bytes in memory. " + - "Additional plugins provide capabilites for this plugin" + - " to show the bytes in various formats (e.g., hex, octal, decimal)." + - " The hex format plugin is loaded by default when this " + "plugin is loaded.", - servicesRequired = { ProgramManager.class, GoToService.class, NavigationHistoryService.class, ClipboardService.class }, + "Additional plugins provide capabilites for this plugin" + + " to show the bytes in various formats (e.g., hex, octal, decimal)." + + " The hex format plugin is loaded by default when this plugin is loaded.", + servicesRequired = { + ProgramManager.class, GoToService.class, NavigationHistoryService.class, + ClipboardService.class, + }, eventsConsumed = { ProgramLocationPluginEvent.class, ProgramActivatedPluginEvent.class, - ProgramSelectionPluginEvent.class, ProgramHighlightPluginEvent.class, ProgramClosedPluginEvent.class, - ByteBlockChangePluginEvent.class }, - eventsProduced = { ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class, ByteBlockChangePluginEvent.class } -) -//@formatter:on -public class ByteViewerPlugin extends Plugin { - - private Program currentProgram; - private boolean areEventsDisabled; - private ProgramLocation currentLocation; - - private ProgramByteViewerComponentProvider connectedProvider; - - private List disconnectedProviders = new ArrayList<>(); + ProgramSelectionPluginEvent.class, ProgramHighlightPluginEvent.class, + ProgramClosedPluginEvent.class, ByteBlockChangePluginEvent.class, + }, + eventsProduced = { + ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class, + ByteBlockChangePluginEvent.class, + }) +public class ByteViewerPlugin extends AbstractByteViewerPlugin { public ByteViewerPlugin(PluginTool tool) { super(tool); - - connectedProvider = new ProgramByteViewerComponentProvider(tool, this, true); - } - - protected void showConnectedProvider() { - tool.showComponentProvider(connectedProvider, true); } @Override - protected void init() { - ClipboardService clipboardService = tool.getService(ClipboardService.class); - if (clipboardService != null) { - connectedProvider.setClipboardService(clipboardService); - for (ProgramByteViewerComponentProvider provider : disconnectedProviders) { - provider.setClipboardService(clipboardService); - } - } + protected ProgramByteViewerComponentProvider createProvider(boolean isConnected) { + return new ProgramByteViewerComponentProvider(tool, this, isConnected); } - /** - * Tells a plugin that it is no longer needed. The plugin should remove - * itself from anything that it is registered to and release any resources. - */ - @Override - public void dispose() { - removeProvider(connectedProvider); - for (ProgramByteViewerComponentProvider provider : disconnectedProviders) { - removeProvider(provider); - } - disconnectedProviders.clear(); - } - - /** - * Process the plugin event; delegates the processing to the - * byte block. - */ @Override public void processEvent(PluginEvent event) { if (event instanceof ProgramClosedPluginEvent) { @@ -132,229 +92,7 @@ public class ByteViewerPlugin extends Plugin { } } - public void fireProgramLocationPluginEvent(ProgramByteViewerComponentProvider provider, - ProgramLocationPluginEvent event) { - - if (SystemUtilities.isEqual(event.getLocation(), currentLocation)) { - return; - } - - currentLocation = event.getLocation(); - if (provider == connectedProvider) { - firePluginEvent(event); - } - } - - /** - * Tells a Plugin to write any data-independent (preferences) - * properties to the output stream. - */ @Override - public void writeConfigState(SaveState saveState) { - connectedProvider.writeConfigState(saveState); - } - - /** - * Tells the Plugin to read its data-independent (preferences) - * properties from the input stream. - */ - @Override - public void readConfigState(SaveState saveState) { - connectedProvider.readConfigState(saveState); - } - - /** - * Read data state; called after readConfigState(). Events generated - * by plugins we depend on should have been already been thrown by the - * time this method is called. - */ - @Override - public void readDataState(SaveState saveState) { - - doWithEventsDisabled(() -> { - - ProgramManager programManagerService = tool.getService(ProgramManager.class); - - connectedProvider.readDataState(saveState); - - int numDisconnected = saveState.getInt("Num Disconnected", 0); - for (int i = 0; i < numDisconnected; i++) { - Element xmlElement = saveState.getXmlElement("Provider" + i); - SaveState providerSaveState = new SaveState(xmlElement); - String programPath = providerSaveState.getString("Program Path", ""); - DomainFile file = tool.getProject().getProjectData().getFile(programPath); - if (file == null) { - continue; - } - Program program = programManagerService.openProgram(file); - if (program != null) { - ProgramByteViewerComponentProvider provider = - new ProgramByteViewerComponentProvider(tool, this, false); - provider.doSetProgram(program); - provider.readConfigState(providerSaveState); - provider.readDataState(providerSaveState); - tool.showComponentProvider(provider, true); - addProvider(provider); - } - } - - }); - } - - /** - * Tells the Plugin to write any data-dependent state to the - * output stream. - */ - @Override - public void writeDataState(SaveState saveState) { - connectedProvider.writeDataState(saveState); - saveState.putInt("Num Disconnected", disconnectedProviders.size()); - int i = 0; - for (ProgramByteViewerComponentProvider provider : disconnectedProviders) { - SaveState providerSaveState = new SaveState(); - DomainFile df = provider.getProgram().getDomainFile(); - if (df.getParent() == null) { - continue; // not contained within project - } - String programPathname = df.getPathname(); - providerSaveState.putString("Program Path", programPathname); - provider.writeConfigState(providerSaveState); - provider.writeDataState(providerSaveState); - String elementName = "Provider" + i; - saveState.putXmlElement(elementName, providerSaveState.saveToXml()); - i++; - } - } - - @Override - public Object getUndoRedoState(DomainObject domainObject) { - Map stateMap = new HashMap<>(); - - addUndoRedoState(stateMap, domainObject, connectedProvider); - - for (ProgramByteViewerComponentProvider provider : disconnectedProviders) { - addUndoRedoState(stateMap, domainObject, provider); - } - - if (stateMap.isEmpty()) { - return null; - } - return stateMap; - } - - private void addUndoRedoState(Map stateMap, DomainObject domainObject, - ProgramByteViewerComponentProvider provider) { - if (provider == null) { - return; - } - Object state = provider.getUndoRedoState(domainObject); - if (state != null) { - stateMap.put(provider.getInstanceID(), state); - } - } - - @SuppressWarnings("unchecked") - @Override - public void restoreUndoRedoState(DomainObject domainObject, Object state) { - Map stateMap = (Map) state; - restoreUndoRedoState(stateMap, domainObject, connectedProvider); - for (ProgramByteViewerComponentProvider provider : disconnectedProviders) { - restoreUndoRedoState(stateMap, domainObject, provider); - } - - } - - private void restoreUndoRedoState(Map stateMap, DomainObject domainObject, - ProgramByteViewerComponentProvider provider) { - if (provider == null) { - return; - } - Object state = stateMap.get(provider.getInstanceID()); - if (state != null) { - provider.restoreUndoRedoState(domainObject, state); - } - } - - @Override - public Object getTransientState() { - Object[] state = new Object[2]; - - SaveState ss = new SaveState(); - connectedProvider.writeDataState(ss); - - state[0] = ss; - state[1] = connectedProvider.getCurrentSelection(); - - return state; - } - - @Override - public void restoreTransientState(Object objectState) { - - doWithEventsDisabled(() -> { - Object[] state = (Object[]) objectState; - connectedProvider.restoreLocation((SaveState) state[0]); - connectedProvider.setSelection((ProgramSelection) state[1]); - }); - } - - private void doWithEventsDisabled(Callback callback) { - areEventsDisabled = true; - try { - callback.call(); - } - finally { - areEventsDisabled = false; - } - } - - private boolean eventsDisabled() { - return areEventsDisabled; - } - - void setStatusMessage(String msg) { - tool.setStatusInfo(msg); - } - - void addProvider(ProgramByteViewerComponentProvider provider) { - disconnectedProviders.add(provider); - provider.setClipboardService(tool.getService(ClipboardService.class)); - } - - Program getProgram() { - return currentProgram; - } - - // Silly Junits - only public until we move to the new multi-view system - public ProgramByteViewerComponentProvider getProvider() { - return connectedProvider; - } - - public void updateSelection(ProgramByteViewerComponentProvider provider, - ProgramSelectionPluginEvent event, Program program) { - if (provider == connectedProvider) { - firePluginEvent(event); - } - } - - public void highlightChanged(ProgramByteViewerComponentProvider provider, - ProgramSelection highlight) { - if (provider == connectedProvider) { - tool.firePluginEvent(new ProgramHighlightPluginEvent(getName(), highlight, - connectedProvider.getProgram())); - } - } - - public void closeProvider(ProgramByteViewerComponentProvider provider) { - if (provider == connectedProvider) { - tool.showComponentProvider(provider, false); - } - else { - disconnectedProviders.remove(provider); - removeProvider(provider); - } - } - public void updateLocation(ProgramByteViewerComponentProvider provider, ProgramLocationPluginEvent event, boolean export) { @@ -370,16 +108,34 @@ public class ByteViewerPlugin extends Plugin { } } - private void exportLocation(Program program, ProgramLocation location) { - GoToService service = tool.getService(GoToService.class); - if (service != null) { - service.goTo(location, program); + @Override + public void fireProgramLocationPluginEvent(ProgramByteViewerComponentProvider provider, + ProgramLocationPluginEvent event) { + + if (SystemUtilities.isEqual(event.getLocation(), currentLocation)) { + return; + } + + currentLocation = event.getLocation(); + if (provider == connectedProvider) { + firePluginEvent(event); } } - private void removeProvider(ProgramByteViewerComponentProvider provider) { - tool.removeComponentProvider(provider); - provider.dispose(); + @Override + public void updateSelection(ByteViewerComponentProvider provider, + ProgramSelectionPluginEvent event, Program program) { + if (provider == connectedProvider) { + firePluginEvent(event); + } } + @Override + public void highlightChanged(ByteViewerComponentProvider provider, + ProgramSelection highlight) { + if (provider == connectedProvider) { + tool.firePluginEvent(new ProgramHighlightPluginEvent(getName(), highlight, + connectedProvider.getProgram())); + } + } } diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/EmptyByteBlockSet.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/EmptyByteBlockSet.java index 890f906c1c..376d4b22ba 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/EmptyByteBlockSet.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/EmptyByteBlockSet.java @@ -20,6 +20,7 @@ import java.math.BigInteger; import ghidra.app.events.ProgramLocationPluginEvent; import ghidra.app.events.ProgramSelectionPluginEvent; import ghidra.app.plugin.core.format.*; +import ghidra.program.model.address.AddressSet; public class EmptyByteBlockSet implements ByteBlockSet { @@ -53,4 +54,8 @@ public class EmptyByteBlockSet implements ByteBlockSet { byte[] newValue) { } + @Override + public AddressSet getAddressSet(ByteBlockSelection selection) { + return null; + } } diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/FileByteBlockSet.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/FileByteBlockSet.java index 7ebfead2b6..e1fb522799 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/FileByteBlockSet.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/FileByteBlockSet.java @@ -23,6 +23,7 @@ import java.util.List; import ghidra.app.events.ProgramLocationPluginEvent; import ghidra.app.events.ProgramSelectionPluginEvent; import ghidra.app.plugin.core.format.*; +import ghidra.program.model.address.AddressSet; /** * ByteBlockSet for a File object. @@ -148,4 +149,10 @@ class FileByteBlockSet implements ByteBlockSet { this.index = index; } } + + @Override + public AddressSet getAddressSet(ByteBlockSelection selection) { + // Not applicable + return null; + } } diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/MemoryByteBlock.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/MemoryByteBlock.java index 6080754741..af767cc338 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/MemoryByteBlock.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/MemoryByteBlock.java @@ -38,11 +38,12 @@ public class MemoryByteBlock implements ByteBlock { /** * Constructor + * * @param program program used in generating plugin events - * @param memory memory from a program + * @param memory memory from a program * @param block block from memory */ - MemoryByteBlock(Program program, Memory memory, MemoryBlock block) { + protected MemoryByteBlock(Program program, Memory memory, MemoryBlock block) { this.program = program; this.memory = memory; this.block = block; @@ -53,6 +54,7 @@ public class MemoryByteBlock implements ByteBlock { /** * Get the location representation for the given index. + * * @param index byte index into this block * @throws IndexOutOfBoundsException if index in not in this block. */ @@ -77,8 +79,7 @@ public class MemoryByteBlock implements ByteBlock { } /** - * Return the name to be used for describing the indexes into the - * byte block. + * Return the name to be used for describing the indexes into the byte block. */ @Override public String getIndexName() { @@ -93,19 +94,20 @@ public class MemoryByteBlock implements ByteBlock { long size = block.getSize(); if (size < 0) { - return BigInteger.valueOf(size + 0x8000000000000000L).subtract( - BigInteger.valueOf(0x8000000000000000L)); + return BigInteger.valueOf(size + 0x8000000000000000L) + .subtract( + BigInteger.valueOf(0x8000000000000000L)); } return BigInteger.valueOf(size); } /** * Set the byte at the given index. + * * @param index byte index * @param value value to set * @throws ByteBlockAccessException if the block cannot be updated - * @throws IndexOutOfBoundsException if the given index is not in this - * block. + * @throws IndexOutOfBoundsException if the given index is not in this block. */ @Override public void setByte(BigInteger index, byte value) throws ByteBlockAccessException { @@ -121,10 +123,10 @@ public class MemoryByteBlock implements ByteBlock { /** * Get the byte at the given index. + * * @param index byte index * @throws ByteBlockAccessException if the block cannot be read - * @throws IndexOutOfBoundsException if the given index is not in this - * block. + * @throws IndexOutOfBoundsException if the given index is not in this block. */ @Override public byte getByte(BigInteger index) throws ByteBlockAccessException { @@ -145,11 +147,11 @@ public class MemoryByteBlock implements ByteBlock { /** * Set the long at the given index. + * * @param index byte index * @param value value to set * @throws ByteBlockAccessException if the block cannot be updated - * @throws IndexOutOfBoundsException if the given index is not in this - * block. + * @throws IndexOutOfBoundsException if the given index is not in this block. */ @Override public void setLong(BigInteger index, long value) throws ByteBlockAccessException { @@ -165,10 +167,10 @@ public class MemoryByteBlock implements ByteBlock { /** * Get the long at the given index. + * * @param index byte index * @throws ByteBlockAccessException if the block cannot be read - * @throws IndexOutOfBoundsException if the given index is not in this - * block. + * @throws IndexOutOfBoundsException if the given index is not in this block. */ @Override public long getLong(BigInteger index) throws ByteBlockAccessException { @@ -183,6 +185,7 @@ public class MemoryByteBlock implements ByteBlock { /** * Set the block according to the bigEndian parameter. + * * @param bigEndian true means big endian; false means little endian */ @Override @@ -193,10 +196,10 @@ public class MemoryByteBlock implements ByteBlock { /** * Get the int value at the given index. + * * @param index byte index * @throws ByteBlockAccessException if the block cannot be read - * @throws IndexOutOfBoundsException if the given index is not in this - * block. + * @throws IndexOutOfBoundsException if the given index is not in this block. */ @Override public int getInt(BigInteger index) throws ByteBlockAccessException { @@ -211,11 +214,11 @@ public class MemoryByteBlock implements ByteBlock { /** * Set the int at the given index. + * * @param index byte index * @param value value to set * @throws ByteBlockAccessException if the block cannot be updated - * @throws IndexOutOfBoundsException if the given index is not in this - * block. + * @throws IndexOutOfBoundsException if the given index is not in this block. */ @Override public void setInt(BigInteger index, int value) throws ByteBlockAccessException { @@ -239,6 +242,7 @@ public class MemoryByteBlock implements ByteBlock { /** * Return true if the block is big endian. + * * @return false if the block is little endian */ @Override @@ -247,12 +251,11 @@ public class MemoryByteBlock implements ByteBlock { } /** - * Returns the natural alignment (offset) for the given radix. If there is - * no natural alignment, it should return 0. A natural alignment only exists if - * there is some underlying indexing structure that isn't based at 0. For example, - * if the underlying structure is address based and the starting address is not 0, - * then the natural alignment is the address offset mod the radix (if the starting - * address is 10 and the radix is 4, then then the alignment is 2)). + * Returns the natural alignment (offset) for the given radix. If there is no natural alignment, + * it should return 0. A natural alignment only exists if there is some underlying indexing + * structure that isn't based at 0. For example, if the underlying structure is address based + * and the starting address is not 0, then the natural alignment is the address offset mod the + * radix (if the starting address is 10 and the radix is 4, then then the alignment is 2)). */ @Override public int getAlignment(int radix) { @@ -309,11 +312,10 @@ public class MemoryByteBlock implements ByteBlock { ///////////////////////////////////////////////////////////////////// /** - * Check for whether edits are allowed at the given address; edits are - * not allowed if a code unit (other than undefined data) exists at the - * given address. + * Check for whether edits are allowed at the given address; edits are not allowed if a code + * unit (other than undefined data) exists at the given address. */ - private void checkEditsAllowed(Address addr, long length) throws ByteBlockAccessException { + protected void checkEditsAllowed(Address addr, long length) throws ByteBlockAccessException { if (!editAllowed(addr, length)) { String msg = "Instruction exists at address " + addr; if (length > 1) { @@ -330,10 +332,10 @@ public class MemoryByteBlock implements ByteBlock { } /** - * Return true if undefined data exists at the given address; return - * false if code unit exists, thus editing is not allowed. + * Return true if undefined data exists at the given address; return false if code unit exists, + * thus editing is not allowed. */ - private boolean editAllowed(Address addr, long length) { + protected boolean editAllowed(Address addr, long length) { Listing listing = program.getListing(); Address a = addr; for (int i = 0; i < length; i++) { diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteBlockSet.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteBlockSet.java index 762e11ee0a..057c85315a 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteBlockSet.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteBlockSet.java @@ -33,15 +33,15 @@ import ghidra.program.util.ProgramSelection; /** * ByteBlockSet implementation for a Program object. */ -class ProgramByteBlockSet implements ByteBlockSet { +public class ProgramByteBlockSet implements ByteBlockSet { private MemoryBlock[] memBlocks; - private Program program; + protected final Program program; private ByteBlockChangeManager bbcm; private ByteBlock[] blocks; private final ProgramByteViewerComponentProvider provider; - ProgramByteBlockSet(ProgramByteViewerComponentProvider provider, Program program, + protected ProgramByteBlockSet(ProgramByteViewerComponentProvider provider, Program program, ByteBlockChangeManager bbcm) { this.provider = provider; @@ -53,7 +53,7 @@ class ProgramByteBlockSet implements ByteBlockSet { this.bbcm = new ByteBlockChangeManager(this, bbcm); } - getMemoryBlocks(); + newMemoryBlocks(); } /** @@ -66,6 +66,7 @@ class ProgramByteBlockSet implements ByteBlockSet { /** * Get the appropriate plugin event for the given block selection. + * * @param source source to use in the event * @param selection selection to use to generate the event */ @@ -86,6 +87,7 @@ class ProgramByteBlockSet implements ByteBlockSet { /** * Get a plugin event for the given block and offset. + * * @param source source to use in the event * @param block block to use to generate the event * @param offset offset into the block @@ -105,38 +107,54 @@ class ProgramByteBlockSet implements ByteBlockSet { } } - ByteBlockSelection getBlockSelection(ProgramSelection selection) { - - AddressRangeIterator iter = selection.getAddressRanges(); - List list = new ArrayList(3); - - while (iter.hasNext()) { - AddressRange range = iter.next(); - - for (int i = 0; i < blocks.length; i++) { - Address blockStart = memBlocks[i].getStart(); - Address blockEnd = memBlocks[i].getEnd(); - AddressRange intersection = - range.intersect(new AddressRangeImpl(blockStart, blockEnd)); - if (intersection != null) { - ByteBlockInfo startInfo = getByteBlockInfo(intersection.getMinAddress()); - ByteBlockInfo endInfo = getByteBlockInfo(intersection.getMaxAddress()); - ByteBlockRange br = new ByteBlockRange(startInfo.getBlock(), - startInfo.getOffset(), endInfo.getOffset()); - list.add(br); - } + protected void collectBlockSelection(AddressRange range, List result) { + // TODO: These blocks really ought to be indexed by start address + // Use a NavigableMap + // I'll wait to assess speed before worrying about tree indexing + // Use entries that groups the relevant objects instead of co-indexed arrays + // Though a nicety, it becomes necessary if indexing/sorting by start address + for (int i = 0; i < blocks.length; i++) { + Address blockStart = memBlocks[i].getStart(); + Address blockEnd = memBlocks[i].getEnd(); + AddressRange intersection = + range.intersect(new AddressRangeImpl(blockStart, blockEnd)); + if (intersection != null) { + ByteBlockInfo startInfo = getByteBlockInfo(intersection.getMinAddress()); + ByteBlockInfo endInfo = getByteBlockInfo(intersection.getMaxAddress()); + ByteBlockRange br = new ByteBlockRange(startInfo.getBlock(), + startInfo.getOffset(), endInfo.getOffset()); + result.add(br); } } - ByteBlockRange[] bRange = new ByteBlockRange[list.size()]; - bRange = list.toArray(bRange); + } - return new ByteBlockSelection(bRange); + public ByteBlockSelection getBlockSelection(AddressRange range) { + List list = new ArrayList<>(); + collectBlockSelection(range, list); + return new ByteBlockSelection(list); + } + + public ByteBlockSelection getBlockSelection(AddressRangeIterator iter) { + List list = new ArrayList<>(); + while (iter.hasNext()) { + collectBlockSelection(iter.next(), list); + } + return new ByteBlockSelection(list); + } + + public ByteBlockSelection getBlockSelection(AddressSetView addresses) { + return getBlockSelection(addresses.getAddressRanges()); + } + + public ByteBlockSelection getBlockSelection(ProgramSelection selection) { + return getBlockSelection(selection.getAddressRanges()); } /** * Return true if the block has been changed at the given index. - * @param block byte block - * @param index offset into the block + * + * @param block byte block + * @param index offset into the block * @param length number of bytes in question */ @Override @@ -150,6 +168,7 @@ class ProgramByteBlockSet implements ByteBlockSet { /** * Send a notification that a byte block edit occurred. + * * @param block block being edited * @param index offset into the block * @param oldValue old byte values @@ -171,21 +190,21 @@ class ProgramByteBlockSet implements ByteBlockSet { return bbcm.getUndoRedoState(); } - void restoreUndoReoState(SaveState saveState) { + void restoreUndoRedoState(SaveState saveState) { bbcm.restoreUndoRedoState(saveState); } /** * Get the byte block change manager */ - ByteBlockChangeManager getByteBlockChangeManager() { + protected ByteBlockChangeManager getByteBlockChangeManager() { return bbcm; } /** * Get the address for the given block and offset. */ - Address getAddress(ByteBlock block, BigInteger offset) { + protected Address getAddress(ByteBlock block, BigInteger offset) { for (int i = 0; i < blocks.length; i++) { if (blocks[i] != block) { @@ -207,7 +226,7 @@ class ProgramByteBlockSet implements ByteBlockSet { /** * Given an address, get the byte block info. */ - ByteBlockInfo getByteBlockInfo(Address address) { + protected ByteBlockInfo getByteBlockInfo(Address address) { if (!program.getMemory().contains(address)) { // this block set is out of date...eventually a new @@ -224,8 +243,9 @@ class ProgramByteBlockSet implements ByteBlockSet { long off = address.subtract(memBlocks[i].getStart()); BigInteger offset = (off < 0) - ? BigInteger.valueOf(off + 0x8000000000000000L).subtract( - BigInteger.valueOf(0x8000000000000000L)) + ? BigInteger.valueOf(off + 0x8000000000000000L) + .subtract( + BigInteger.valueOf(0x8000000000000000L)) : BigInteger.valueOf(off); return new ByteBlockInfo(blocks[i], offset); } @@ -236,15 +256,15 @@ class ProgramByteBlockSet implements ByteBlockSet { return null; } - Address getBlockStart(ByteBlock block) { + protected Address getBlockStart(ByteBlock block) { return getAddress(block, BigInteger.ZERO); } - Address getBlockStart(int blockNumber) { + protected Address getBlockStart(int blockNumber) { return memBlocks[blockNumber].getStart(); } - int getByteBlockNumber(Address blockStartAddr) { + protected int getByteBlockNumber(Address blockStartAddr) { for (int i = 0; i < memBlocks.length; i++) { if (memBlocks[i].getStart().compareTo(blockStartAddr) == 0) { return i; @@ -253,8 +273,8 @@ class ProgramByteBlockSet implements ByteBlockSet { return -1; } - AddressSet getAddressSet(ByteBlockSelection selection) { - + @Override + public AddressSet getAddressSet(ByteBlockSelection selection) { AddressSet addrSet = new AddressSet(); for (int i = 0; i < selection.getNumberOfRanges(); i++) { @@ -267,15 +287,19 @@ class ProgramByteBlockSet implements ByteBlockSet { return addrSet; } - private void getMemoryBlocks() { + protected void newMemoryBlocks() { Memory memory = program.getMemory(); - memBlocks = program.getMemory().getBlocks(); + memBlocks = memory.getBlocks(); blocks = new ByteBlock[memBlocks.length]; for (int i = 0; i < memBlocks.length; i++) { - blocks[i] = new MemoryByteBlock(program, memory, memBlocks[i]); + blocks[i] = newMemoryByteBlock(memory, memBlocks[i]); } } + protected MemoryByteBlock newMemoryByteBlock(Memory memory, MemoryBlock memBlock) { + return new MemoryByteBlock(program, memory, memBlock); + } + @Override public void dispose() { // nothing to do?!?!? diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteViewerComponentProvider.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteViewerComponentProvider.java index 6fcdfaa71e..8fa3ebbd70 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteViewerComponentProvider.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteViewerComponentProvider.java @@ -68,10 +68,14 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi private boolean disposed; - public ProgramByteViewerComponentProvider(PluginTool tool, ByteViewerPlugin plugin, + public ProgramByteViewerComponentProvider(PluginTool tool, AbstractByteViewerPlugin plugin, boolean isConnected) { - super(tool, plugin, "Bytes", ByteViewerActionContext.class); + this(tool, plugin, "Bytes", isConnected); + } + protected ProgramByteViewerComponentProvider(PluginTool tool, + AbstractByteViewerPlugin plugin, String name, boolean isConnected) { + super(tool, plugin, name, ByteViewerActionContext.class); this.isConnected = isConnected; setIcon(ResourceManager.loadImage("images/binaryData.gif")); if (!isConnected) { @@ -215,7 +219,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi clipboardProvider.setPasteEnabled(enabled); } - void doSetProgram(Program newProgram) { + protected void doSetProgram(Program newProgram) { setOptionsAction.setEnabled(newProgram != null); cloneByteViewerAction.setEnabled(newProgram != null); @@ -239,7 +243,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi updateTitle(); } - private void updateTitle() { + protected void updateTitle() { String title = "Bytes: " + (program == null ? "No Program" : program.getDomainFile().getName()); if (!isConnected()) { @@ -428,7 +432,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi return loc; } - void setLocation(ProgramLocation location) { + protected void setLocation(ProgramLocation location) { setLocation(location, false); } @@ -559,7 +563,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi } } - private ProgramByteBlockSet getByteBlockSet(ByteBlockChangeManager changeManager) { + protected ProgramByteBlockSet newByteBlockSet(ByteBlockChangeManager changeManager) { if (program == null) { return null; } @@ -572,12 +576,12 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi blockSet.dispose(); } - blockSet = getByteBlockSet(changeManager); + blockSet = newByteBlockSet(changeManager); panel.setByteBlocks(blockSet); } @Override - void updateSelection(ByteBlockSelection selection) { + protected void updateSelection(ByteBlockSelection selection) { ProgramSelectionPluginEvent event = blockSet.getPluginEvent(plugin.getName(), selection); currentSelection = event.getSelection(); plugin.updateSelection(this, event, program); @@ -586,7 +590,8 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi } @Override - void updateLocation(ByteBlock block, BigInteger blockOffset, int column, boolean export) { + protected void updateLocation(ByteBlock block, BigInteger blockOffset, int column, + boolean export) { ProgramLocationPluginEvent event = blockSet.getPluginEvent(plugin.getName(), block, blockOffset, column); currentLocation = event.getLocation(); @@ -595,7 +600,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi contextChanged(); } - void readDataState(SaveState saveState) { + protected void readDataState(SaveState saveState) { unRegisterNavigatable(); initializeInstanceID(saveState.getLong("NAV_ID", getInstanceID())); registerNavigatable(); @@ -632,10 +637,10 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi return; } SaveState saveState = (SaveState) state; - blockSet.restoreUndoReoState(saveState); + blockSet.restoreUndoRedoState(saveState); } - void writeDataState(SaveState saveState) { + protected void writeDataState(SaveState saveState) { saveState.putLong("NAV_ID", getInstanceID()); ByteBlockInfo info = panel.getCursorLocation(); int blockNumber = -1; @@ -708,6 +713,22 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi return dataFormatModels; } + public void cloneWindow() { + ProgramByteViewerComponentProvider newProvider = plugin.createNewDisconnectedProvider(); + + SaveState saveState = new SaveState(); + writeConfigState(saveState); + newProvider.readConfigState(saveState); + + newProvider.doSetProgram(program); + + newProvider.setLocation(currentLocation); + newProvider.setSelection(currentSelection, false); + newProvider.setHighlight(currentHighlight); + ViewerPosition viewerPosition = panel.getViewerPosition(); + newProvider.panel.setViewerPosition(viewerPosition); + } + //================================================================================================== // Inner Classes //================================================================================================== @@ -727,23 +748,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi @Override public void actionPerformed(ActionContext context) { - ProgramByteViewerComponentProvider newProvider = - new ProgramByteViewerComponentProvider(tool, plugin, false); - - plugin.addProvider(newProvider); - SaveState saveState = new SaveState(); - writeConfigState(saveState); - newProvider.readConfigState(saveState); - - tool.showComponentProvider(newProvider, true); - - newProvider.doSetProgram(program); - - newProvider.setLocation(currentLocation); - newProvider.setSelection(currentSelection, false); - newProvider.setHighlight(currentHighlight); - ViewerPosition viewerPosition = panel.getViewerPosition(); - newProvider.panel.setViewerPosition(viewerPosition); + cloneWindow(); } } diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ToggleEditAction.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ToggleEditAction.java index a675a14728..c900cae83e 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ToggleEditAction.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ToggleEditAction.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,32 +15,33 @@ */ package ghidra.app.plugin.core.byteviewer; -import ghidra.framework.plugintool.Plugin; - import java.awt.event.InputEvent; import java.awt.event.KeyEvent; -import resources.ResourceManager; import docking.ActionContext; import docking.action.*; +import ghidra.framework.plugintool.Plugin; +import resources.ResourceManager; - class ToggleEditAction extends ToggleDockingAction { - private final ByteViewerComponentProvider provider; - public ToggleEditAction(ByteViewerComponentProvider provider, Plugin plugin) { - super("Enable/Disable Byteviewer Editing", plugin.getName()); - this.provider = provider; - setToolBarData( new ToolBarData( - ResourceManager.loadImage( "images/editbytes.gif" ), "Byteviewer" ) ); - setKeyBindingData( new KeyBindingData( - KeyEvent.VK_E, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK ) ); +class ToggleEditAction extends ToggleDockingAction { + private final ByteViewerComponentProvider provider; - setDescription("Enable/Disable editing of bytes in Byte Viewer panels."); - setSelected(false); - setEnabled(true); - } - @Override - public void actionPerformed(ActionContext context) { - boolean isSelected = isSelected(); - provider.setEditMode(isSelected); - } - } + public ToggleEditAction(ByteViewerComponentProvider provider, Plugin plugin) { + super("Enable/Disable Byteviewer Editing", plugin.getName()); + this.provider = provider; + setToolBarData(new ToolBarData( + ResourceManager.loadImage("images/editbytes.gif"), "Byteviewer")); + setKeyBindingData(new KeyBindingData( + KeyEvent.VK_E, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); + + setDescription("Enable/Disable editing of bytes in Byte Viewer panels."); + setSelected(false); + setEnabled(true); + } + + @Override + public void actionPerformed(ActionContext context) { + boolean isSelected = isSelected(); + provider.setEditMode(isSelected); + } +} diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/format/ByteBlockSelection.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/format/ByteBlockSelection.java index f5af73ad0b..7eadbb7662 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/format/ByteBlockSelection.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/format/ByteBlockSelection.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,46 +19,63 @@ import java.util.*; /** * Defines a selection for byte blocks. + * + * The selection is a list of disjoint ranges. */ public class ByteBlockSelection { + private final List list; - private List list; + /** + * Construct a selection from a list of ranges + * + * @param ranges the ranges + */ + public ByteBlockSelection(List ranges) { + list = new ArrayList<>(ranges); + } - /** - * Construct an empty selection. - */ - public ByteBlockSelection() { - list = new ArrayList(); - } - /** - * Constructor - */ - public ByteBlockSelection(ByteBlockRange[] ranges) { - List l = Arrays.asList(ranges); - list = new ArrayList(l); - } + /** + * Construct an empty selection. + */ + public ByteBlockSelection() { + this(new ArrayList<>()); + } - /** - * Add a range to the selection. - */ - public void add(ByteBlockRange range) { - list.add(range); - } + /** + * Construct a selection from an array of ranges + * + * @param ranges the ranges + */ + public ByteBlockSelection(ByteBlockRange[] ranges) { + this(Arrays.asList(ranges)); + } - /** - * Get the number of byte block ranges in this selection. - * - * @return int - */ - public int getNumberOfRanges() { - return list.size(); - } + /** + * Add a range to the selection. + * + * @param range the range to add + */ + public void add(ByteBlockRange range) { + list.add(range); + } - /** - * Get the byte block range at the given index. - */ - public ByteBlockRange getRange(int index) { - return list.get(index); - } + /** + * Get the number of byte block ranges in this selection. + * + * @return the number of (disjoint) ranges + */ + public int getNumberOfRanges() { + return list.size(); + } + + /** + * Get the byte block range at the given index. + * + * @param index the index of the range to get + * @return the requested range + */ + public ByteBlockRange getRange(int index) { + return list.get(index); + } } diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/format/ByteBlockSet.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/format/ByteBlockSet.java index 1881165f3f..b56bece64f 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/format/ByteBlockSet.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/format/ByteBlockSet.java @@ -19,32 +19,35 @@ import java.math.BigInteger; import ghidra.app.events.ProgramLocationPluginEvent; import ghidra.app.events.ProgramSelectionPluginEvent; +import ghidra.program.model.address.AddressSet; /** - * Interface to define methods for getting byte blocks and translating - * events. + * Interface to define methods for getting byte blocks and translating events. */ public interface ByteBlockSet { /** * Get the blocks in this set. + * * @return the blocks */ public ByteBlock[] getBlocks(); /** * Get a plugin event for the given block and offset. + * * @param source source to use in the event * @param block block to use to generate the event * @param offset offset into the block * @param column the column within the UI byte field - * @return the event + * @return the event */ public ProgramLocationPluginEvent getPluginEvent(String source, ByteBlock block, BigInteger offset, int column); /** * Get the appropriate plugin event for the given block selection. + * * @param source source to use in the event * @param selection selection to use to generate the event * @return the event @@ -53,8 +56,9 @@ public interface ByteBlockSet { /** * Return true if the block has been changed at the given index. - * @param block byte block - * @param index offset into the block + * + * @param block byte block + * @param index offset into the block * @param length number of bytes in question * @return true if changed */ @@ -62,6 +66,7 @@ public interface ByteBlockSet { /** * Send a notification that a byte block edit occurred. + * * @param block block being edited * @param index offset into the block * @param oldValue old byte values @@ -72,7 +77,14 @@ public interface ByteBlockSet { /** * Release resources that this object may be using. - * */ public void dispose(); + + /** + * Convert the byte block selection to the address set it covers + * + * @param selection the selection from the byte block perspective + * @return the selection from the address perspective + */ + public AddressSet getAddressSet(ByteBlockSelection selection); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java index eb76cfcc18..ee942c2994 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java @@ -50,9 +50,9 @@ public class FieldPanel extends JPanel private boolean repaintPosted; private boolean inFocus; - private BackgroundColorModel backgroundColorModel = + protected BackgroundColorModel backgroundColorModel = new DefaultBackgroundColorModel(Color.WHITE); - private PaintContext paintContext = new PaintContext(); + protected PaintContext paintContext = new PaintContext(); private AnchoredLayoutHandler layoutHandler; private CursorHandler cursorHandler = new CursorHandler(); @@ -1079,7 +1079,7 @@ public class FieldPanel extends JPanel } } - private LayoutBackgroundColorManager getLayoutSelectionMap(BigInteger layoutIndex) { + protected LayoutBackgroundColorManager getLayoutSelectionMap(BigInteger layoutIndex) { Color backgroundColor = backgroundColorModel.getBackgroundColor(layoutIndex); Color defaultBackColor = backgroundColorModel.getDefaultBackgroundColor(); boolean isDefault = backgroundColor.equals(defaultBackColor); diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/ColorUtils.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/ColorUtils.java index da370f41de..2158781a1b 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/ColorUtils.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/ColorUtils.java @@ -129,8 +129,8 @@ public class ColorUtils { } /** - * A method to produce a color (either black or white) that contrasts with the given color. - * This is useful for finding a readable foreground color for a given background. + * A method to produce a color (either black or white) that contrasts with the given color. This + * is useful for finding a readable foreground color for a given background. * * @param color the color for which to find a contrast. * @return the contrasting color. @@ -158,9 +158,9 @@ public class ColorUtils { } /** - * Takes the first color, blending into it the second color, using the given ratio. A - * lower ratio (say .1f) signals to use very little of the first color; a larger ratio - * signals to use more of the first color. + * Takes the first color, blending into it the second color, using the given ratio. A lower + * ratio (say .1f) signals to use very little of the first color; a larger ratio signals to use + * more of the first color. * * @param c1 the first color * @param c2 the second color @@ -185,4 +185,50 @@ public class ColorUtils { return color; } + + /** + * Blender of colors + */ + public static class ColorBlender { + int r = 0; + int g = 0; + int b = 0; + int a = 0; + + /** + * Add a color into the mixture, in a quantity proportional to its alpha value + * + * @param color the color to mix + */ + public void add(Color color) { + int ca = color.getAlpha(); + a += ca; + r += ca * color.getRed(); + g += ca * color.getGreen(); + b += ca * color.getBlue(); + } + + /** + * Reset the mixture + */ + public void clear() { + r = 0; + g = 0; + b = 0; + a = 0; + } + + /** + * Get the color of the current mixture + * + * @param defaultColor the default (background) color, if the mixture has no color + * @return the resulting color + */ + public Color getColor(Color defaultColor) { + if (a == 0) { + return defaultColor; + } + return new Color(r / a, g / a, b / a); + } + } }