mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-80: Add Dynamic bytes (raw memory) viewer
This commit is contained in:
parent
a1dba97a10
commit
48ba18306e
54 changed files with 5937 additions and 2330 deletions
|
@ -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<Void> 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<TraceSection> 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<TraceModule> 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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -257,9 +257,7 @@ public interface DebuggerResources {
|
||||||
Color DEFAULT_COLOR_BACKGROUND_ERROR = new Color(1.0f, 0.75f, 0.75f);
|
Color DEFAULT_COLOR_BACKGROUND_ERROR = new Color(1.0f, 0.75f, 0.75f);
|
||||||
|
|
||||||
int PRIORITY_REGISTER_MARKER = 10;
|
int PRIORITY_REGISTER_MARKER = 10;
|
||||||
// TODO: Is this the right name? Used by Location Tracking, which could be anything
|
String OPTION_NAME_COLORS_TRACKING_MARKERS = "Colors.Tracking Markers";
|
||||||
// Close enough for now
|
|
||||||
String OPTION_NAME_COLORS_REGISTER_MARKERS = "Colors.Register Markers";
|
|
||||||
Color DEFAULT_COLOR_REGISTER_MARKERS = new Color(0.75f, 0.875f, 0.75f);
|
Color DEFAULT_COLOR_REGISTER_MARKERS = new Color(0.75f, 0.875f, 0.75f);
|
||||||
ImageIcon ICON_REGISTER_MARKER = ResourceManager.loadImage("images/register-marker.png");
|
ImageIcon ICON_REGISTER_MARKER = ResourceManager.loadImage("images/register-marker.png");
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.awt.BorderLayout;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -34,14 +34,15 @@ import ghidra.util.Msg;
|
||||||
|
|
||||||
public class DebuggerGoToDialog extends DialogComponentProvider {
|
public class DebuggerGoToDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
private final DebuggerListingProvider provider;
|
private final DebuggerGoToTrait trait;
|
||||||
final JTextField textExpression;
|
|
||||||
final JComboBox<String> comboSpaces;
|
|
||||||
private final DefaultComboBoxModel<String> modelSpaces;
|
private final DefaultComboBoxModel<String> modelSpaces;
|
||||||
|
|
||||||
protected DebuggerGoToDialog(DebuggerListingProvider provider) {
|
final JTextField textExpression;
|
||||||
|
final JComboBox<String> comboSpaces;
|
||||||
|
|
||||||
|
public DebuggerGoToDialog(DebuggerGoToTrait trait) {
|
||||||
super("Go To", true, true, true, false);
|
super("Go To", true, true, true, false);
|
||||||
this.provider = provider;
|
this.trait = trait;
|
||||||
|
|
||||||
textExpression = new JTextField();
|
textExpression = new JTextField();
|
||||||
modelSpaces = new DefaultComboBoxModel<>();
|
modelSpaces = new DefaultComboBoxModel<>();
|
||||||
|
@ -91,11 +92,11 @@ public class DebuggerGoToDialog extends DialogComponentProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override // public for tests
|
||||||
protected void okCallback() {
|
public void okCallback() {
|
||||||
CompletableFuture<Boolean> future;
|
CompletableFuture<Boolean> future;
|
||||||
try {
|
try {
|
||||||
future = provider.goToSleigh((String) comboSpaces.getSelectedItem(),
|
future = trait.goToSleigh((String) comboSpaces.getSelectedItem(),
|
||||||
textExpression.getText());
|
textExpression.getText());
|
||||||
}
|
}
|
||||||
catch (Throwable t) {
|
catch (Throwable t) {
|
||||||
|
@ -117,12 +118,16 @@ public class DebuggerGoToDialog extends DialogComponentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void cancelCallback() {
|
public void cancelCallback() {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void show(SleighLanguage language) {
|
public void show(SleighLanguage language) {
|
||||||
populateSpaces(language);
|
populateSpaces(language);
|
||||||
provider.getTool().showDialog(this);
|
trait.tool.showDialog(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpression(String expression) {
|
||||||
|
textExpression.setText(expression);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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<Boolean> 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<Boolean> goToSleigh(AddressSpace space, SleighExpression expression) {
|
||||||
|
AsyncPcodeExecutor<byte[]> executor = TracePcodeUtils.executorForCoordinates(current);
|
||||||
|
CompletableFuture<byte[]> result = expression.evaluate(executor);
|
||||||
|
return result.thenApply(offset -> {
|
||||||
|
Address address = space.getAddress(
|
||||||
|
Utils.bytesToLong(offset, offset.length, expression.getLanguage().isBigEndian()));
|
||||||
|
return goToAddress(address);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<DebuggerReadsMemoryTrait> 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<AutoReadMemorySpec> 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<AutoReadMemorySpec> 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<AutoReadMemorySpec> 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();
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
|
|
||||||
public interface DebuggerTrackLocationAction extends TrackLocationAction {
|
public interface DebuggerTrackLocationAction extends TrackLocationAction {
|
||||||
|
|
||||||
// TODO: Update the action when new specs enter the class path?
|
// TODO: Update the action when new specs enter the class path?
|
||||||
static MultiStateActionBuilder<LocationTrackingSpec> builder(Plugin owner) {
|
static MultiStateActionBuilder<LocationTrackingSpec> builder(Plugin owner) {
|
||||||
MultiStateActionBuilder<LocationTrackingSpec> builder = TrackLocationAction.builder(owner);
|
MultiStateActionBuilder<LocationTrackingSpec> builder = TrackLocationAction.builder(owner);
|
||||||
|
|
|
@ -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<DebuggerTrackLocationTrait> 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<ColoredFieldSelection> selections) {
|
||||||
|
if (trackedLocation == null || trackingColor == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FieldSelection fieldSel =
|
||||||
|
translator.convertAddressToField(trackedLocation.getAddress());
|
||||||
|
selections.add(new ColoredFieldSelection(fieldSel, trackingColor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MultiStateDockingAction<LocationTrackingSpec> 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<LocationTrackingSpec> 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<LocationTrackingSpec> 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<ColoredFieldSelection> selections, Color backgroundColor,
|
||||||
|
boolean isBackgroundDefault) {
|
||||||
|
List<ColoredFieldSelection> intersections =
|
||||||
|
selections.stream().map(cfs -> cfs.intersect(index)).collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<ColoredFieldSelection> 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<ColoredFieldSelection> 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<ColoredFieldSelection> selections;
|
||||||
|
private final Color backgroundColor;
|
||||||
|
|
||||||
|
public MultiSelectionBlendedFieldBackgroundColorManager(BigInteger index, int fieldNum,
|
||||||
|
MultiSelectionBlendedLayoutBackgroundColorManager layoutSelection,
|
||||||
|
List<ColoredFieldSelection> 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<Highlight> 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<Highlight> 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<ColoredFieldSelection> selections;
|
||||||
|
private final Color backgroundColor;
|
||||||
|
private final Color leftBorderColor;
|
||||||
|
private final Color rightBorderColor;
|
||||||
|
|
||||||
|
public MultiSelectionBlendedLayoutBackgroundColorManager(BigInteger index,
|
||||||
|
List<ColoredFieldSelection> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<ColoredFieldSelection> selections);
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ import docking.ActionContext;
|
||||||
import docking.action.MenuData;
|
import docking.action.MenuData;
|
||||||
import ghidra.app.events.*;
|
import ghidra.app.events.*;
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
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.codebrowser.CodeViewerProvider;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
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.annotation.AutoServiceConsumed;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.listing.Program;
|
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.program.util.ProgramSelection;
|
import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
|
@ -89,7 +88,8 @@ import utilities.util.SuppressableCallback.Suppression;
|
||||||
servicesProvided = {
|
servicesProvided = {
|
||||||
DebuggerListingService.class,
|
DebuggerListingService.class,
|
||||||
})
|
})
|
||||||
public class DebuggerListingPlugin extends CodeBrowserPlugin implements DebuggerListingService {
|
public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerListingProvider>
|
||||||
|
implements DebuggerListingService {
|
||||||
private static final String KEY_CONNECTED_PROVIDER = "connectedProvider";
|
private static final String KEY_CONNECTED_PROVIDER = "connectedProvider";
|
||||||
private static final String KEY_DISCONNECTED_COUNT = "disconnectedCount";
|
private static final String KEY_DISCONNECTED_COUNT = "disconnectedCount";
|
||||||
private static final String PREFIX_DISCONNECTED_PROVIDER = "disconnectedProvider";
|
private static final String PREFIX_DISCONNECTED_PROVIDER = "disconnectedProvider";
|
||||||
|
@ -124,14 +124,14 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||||
//private GoToService goToService;
|
//private GoToService goToService;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private ProgramManager programManager;
|
private ProgramManager programManager;
|
||||||
// NOTE: ListingPlugin doesn't extend AbstractDebuggerPlugin
|
// NOTE: This plugin doesn't extend AbstractDebuggerPlugin
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private AutoService.Wiring autoServiceWiring;
|
private AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
@AutoOptionDefined( //
|
@AutoOptionDefined(
|
||||||
name = OPTION_NAME_COLORS_STALE_MEMORY, //
|
name = OPTION_NAME_COLORS_STALE_MEMORY,
|
||||||
description = "Color of memory addresses whose content is not known in the view's " +
|
description = "Color of memory addresses whose content is not known in the view's " +
|
||||||
"snap", //
|
"snap",
|
||||||
help = @HelpInfo(anchor = "colors"))
|
help = @HelpInfo(anchor = "colors"))
|
||||||
private Color staleMemoryColor = DEFAULT_COLOR_BACKGROUND_STALE;
|
private Color staleMemoryColor = DEFAULT_COLOR_BACKGROUND_STALE;
|
||||||
@AutoOptionDefined( //
|
@AutoOptionDefined( //
|
||||||
|
@ -142,7 +142,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||||
private Color errorMemoryColor = DEFAULT_COLOR_BACKGROUND_ERROR;
|
private Color errorMemoryColor = DEFAULT_COLOR_BACKGROUND_ERROR;
|
||||||
// NOTE: Static programs are marked via markerSet. Dynamic are marked via custom color model
|
// NOTE: Static programs are marked via markerSet. Dynamic are marked via custom color model
|
||||||
@AutoOptionDefined( //
|
@AutoOptionDefined( //
|
||||||
name = OPTION_NAME_COLORS_REGISTER_MARKERS, //
|
name = OPTION_NAME_COLORS_TRACKING_MARKERS, //
|
||||||
description = "Background color for locations referred to by a tracked register", //
|
description = "Background color for locations referred to by a tracked register", //
|
||||||
help = @HelpInfo(anchor = "colors"))
|
help = @HelpInfo(anchor = "colors"))
|
||||||
private Color trackingColor = DEFAULT_COLOR_REGISTER_MARKERS;
|
private Color trackingColor = DEFAULT_COLOR_REGISTER_MARKERS;
|
||||||
|
@ -162,15 +162,6 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||||
createActions();
|
createActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DebuggerListingProvider getConnectedProvider() {
|
|
||||||
return (DebuggerListingProvider) connectedProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
|
||||||
protected List<DebuggerListingProvider> getDisconnectedProviders() {
|
|
||||||
return (List) disconnectedProviders;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DebuggerListingProvider createProvider(FormatManager formatManager,
|
protected DebuggerListingProvider createProvider(FormatManager formatManager,
|
||||||
boolean isConnected) {
|
boolean isConnected) {
|
||||||
|
@ -184,7 +175,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||||
public DebuggerListingProvider createListingIfMissing(LocationTrackingSpec spec,
|
public DebuggerListingProvider createListingIfMissing(LocationTrackingSpec spec,
|
||||||
boolean followsCurrentThread) {
|
boolean followsCurrentThread) {
|
||||||
synchronized (disconnectedProviders) {
|
synchronized (disconnectedProviders) {
|
||||||
for (DebuggerListingProvider provider : getDisconnectedProviders()) {
|
for (DebuggerListingProvider provider : disconnectedProviders) {
|
||||||
if (provider.getTrackingSpec() != spec) {
|
if (provider.getTrackingSpec() != spec) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -201,11 +192,6 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public DebuggerListingProvider createNewDisconnectedProvider() {
|
|
||||||
return (DebuggerListingProvider) super.createNewDisconnectedProvider();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void viewChanged(AddressSetView addrSet) {
|
protected void viewChanged(AddressSetView addrSet) {
|
||||||
TraceProgramView view = current.getView();
|
TraceProgramView view = current.getView();
|
||||||
|
@ -239,6 +225,12 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||||
firePluginEvent(new TraceSelectionPluginEvent(getName(), selection, view));
|
firePluginEvent(new TraceSelectionPluginEvent(getName(), selection, view));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void highlightChanged(CodeViewerProvider codeViewerProvider,
|
||||||
|
ProgramSelection highlight) {
|
||||||
|
// TODO Nothing, yet
|
||||||
|
}
|
||||||
|
|
||||||
protected boolean heedLocationEvent(ProgramLocationPluginEvent ev) {
|
protected boolean heedLocationEvent(ProgramLocationPluginEvent ev) {
|
||||||
PluginEvent trigger = ev.getTriggerEvent();
|
PluginEvent trigger = ev.getTriggerEvent();
|
||||||
/*Msg.debug(this, "Location event");
|
/*Msg.debug(this, "Location event");
|
||||||
|
@ -267,12 +259,11 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processEvent(PluginEvent event) {
|
public void processEvent(PluginEvent event) {
|
||||||
// Do not call super here. I intend to prevent it from seeing events.
|
|
||||||
if (event instanceof ProgramLocationPluginEvent) {
|
if (event instanceof ProgramLocationPluginEvent) {
|
||||||
cbProgramLocationEvents.invoke(() -> {
|
cbProgramLocationEvents.invoke(() -> {
|
||||||
ProgramLocationPluginEvent ev = (ProgramLocationPluginEvent) event;
|
ProgramLocationPluginEvent ev = (ProgramLocationPluginEvent) event;
|
||||||
if (heedLocationEvent(ev)) {
|
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) {
|
if (event instanceof TraceClosedPluginEvent) {
|
||||||
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
|
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
|
||||||
|
if (current.getTrace() == ev.getTrace()) {
|
||||||
|
current = DebuggerCoordinates.NOWHERE;
|
||||||
|
}
|
||||||
allProviders(p -> p.traceClosed(ev.getTrace()));
|
allProviders(p -> p.traceClosed(ev.getTrace()));
|
||||||
}
|
}
|
||||||
// TODO: Sync selection and highlights?
|
// TODO: Sync selection and highlights?
|
||||||
|
@ -308,34 +302,29 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void allProviders(Consumer<DebuggerListingProvider> action) {
|
protected void allProviders(Consumer<DebuggerListingProvider> action) {
|
||||||
action.accept(getConnectedProvider());
|
action.accept(connectedProvider);
|
||||||
for (DebuggerListingProvider provider : getDisconnectedProviders()) {
|
for (DebuggerListingProvider provider : disconnectedProviders) {
|
||||||
action.accept(provider);
|
action.accept(provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void programClosed(Program program) {
|
|
||||||
// Immaterial
|
|
||||||
}
|
|
||||||
|
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
public void setTraceManager(DebuggerTraceManagerService traceManager) {
|
public void setTraceManager(DebuggerTraceManagerService traceManager) {
|
||||||
DebuggerListingProvider provider = getConnectedProvider();
|
DebuggerListingProvider provider = connectedProvider;
|
||||||
if (provider == null || traceManager == null) {
|
if (provider == null || traceManager == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
provider.coordinatesActivated(traceManager.getCurrent());
|
provider.coordinatesActivated(current = traceManager.getCurrent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setTrackingSpec(LocationTrackingSpec spec) {
|
public void setTrackingSpec(LocationTrackingSpec spec) {
|
||||||
getConnectedProvider().setTrackingSpec(spec);
|
connectedProvider.setTrackingSpec(spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCurrentSelection(ProgramSelection selection) {
|
public void setCurrentSelection(ProgramSelection selection) {
|
||||||
getConnectedProvider().setSelection(selection);
|
connectedProvider.setSelection(selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -345,7 +334,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//cbGoTo.invoke(() -> {
|
//cbGoTo.invoke(() -> {
|
||||||
DebuggerListingProvider provider = getConnectedProvider();
|
DebuggerListingProvider provider = connectedProvider;
|
||||||
provider.doSyncToStatic(location);
|
provider.doSyncToStatic(location);
|
||||||
provider.doCheckCurrentModuleMissing();
|
provider.doCheckCurrentModuleMissing();
|
||||||
//});
|
//});
|
||||||
|
@ -354,7 +343,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean goTo(Address address, boolean centerOnScreen) {
|
public boolean goTo(Address address, boolean centerOnScreen) {
|
||||||
TraceProgramView view = getConnectedProvider().current.getView();
|
TraceProgramView view = connectedProvider.current.getView();
|
||||||
if (view == null) {
|
if (view == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -392,17 +381,17 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||||
@Override
|
@Override
|
||||||
public void writeDataState(SaveState saveState) {
|
public void writeDataState(SaveState saveState) {
|
||||||
SaveState connectedProviderState = new SaveState();
|
SaveState connectedProviderState = new SaveState();
|
||||||
getConnectedProvider().writeDataState(connectedProviderState);
|
connectedProvider.writeDataState(connectedProviderState);
|
||||||
saveState.putXmlElement(KEY_CONNECTED_PROVIDER, connectedProviderState.saveToXml());
|
saveState.putXmlElement(KEY_CONNECTED_PROVIDER, connectedProviderState.saveToXml());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Arrange the follows ones first, so that we reload them into corresponding providers
|
* Arrange the follows ones first, so that we reload them into corresponding providers
|
||||||
* restored from config state
|
* restored from config state
|
||||||
*/
|
*/
|
||||||
List<DebuggerListingProvider> disconnected = getDisconnectedProviders().stream()
|
List<DebuggerListingProvider> disconnected = disconnectedProviders.stream()
|
||||||
.filter(p -> p.isFollowsCurrentThread())
|
.filter(p -> p.isFollowsCurrentThread())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
for (DebuggerListingProvider p : getDisconnectedProviders()) {
|
for (DebuggerListingProvider p : disconnectedProviders) {
|
||||||
if (!disconnected.contains(p)) {
|
if (!disconnected.contains(p)) {
|
||||||
disconnected.add(p);
|
disconnected.add(p);
|
||||||
}
|
}
|
||||||
|
@ -419,8 +408,8 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void ensureProviders(int count, boolean followCurrentThread, SaveState configState) {
|
protected void ensureProviders(int count, boolean followCurrentThread, SaveState configState) {
|
||||||
while (getDisconnectedProviders().size() < count) {
|
while (disconnectedProviders.size() < count) {
|
||||||
int index = getDisconnectedProviders().size();
|
int index = disconnectedProviders.size();
|
||||||
String stateName = PREFIX_DISCONNECTED_PROVIDER + index;
|
String stateName = PREFIX_DISCONNECTED_PROVIDER + index;
|
||||||
DebuggerListingProvider provider = createNewDisconnectedProvider();
|
DebuggerListingProvider provider = createNewDisconnectedProvider();
|
||||||
provider.setFollowsCurrentThread(false);
|
provider.setFollowsCurrentThread(false);
|
||||||
|
@ -442,13 +431,13 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||||
Element connectedProviderElement = saveState.getXmlElement(KEY_CONNECTED_PROVIDER);
|
Element connectedProviderElement = saveState.getXmlElement(KEY_CONNECTED_PROVIDER);
|
||||||
if (connectedProviderElement != null) {
|
if (connectedProviderElement != null) {
|
||||||
SaveState connectedProviderState = new SaveState(connectedProviderElement);
|
SaveState connectedProviderState = new SaveState(connectedProviderElement);
|
||||||
getConnectedProvider().readDataState(connectedProviderState);
|
connectedProvider.readDataState(connectedProviderState);
|
||||||
}
|
}
|
||||||
|
|
||||||
int disconnectedCount = saveState.getInt(KEY_DISCONNECTED_COUNT, 0);
|
int disconnectedCount = saveState.getInt(KEY_DISCONNECTED_COUNT, 0);
|
||||||
ensureProviders(disconnectedCount, false, saveState);
|
ensureProviders(disconnectedCount, false, saveState);
|
||||||
|
|
||||||
List<DebuggerListingProvider> disconnected = getDisconnectedProviders();
|
List<DebuggerListingProvider> disconnected = disconnectedProviders;
|
||||||
for (int index = 0; index < disconnectedCount; index++) {
|
for (int index = 0; index < disconnectedCount; index++) {
|
||||||
String stateName = PREFIX_DISCONNECTED_PROVIDER + index;
|
String stateName = PREFIX_DISCONNECTED_PROVIDER + index;
|
||||||
Element providerElement = saveState.getXmlElement(stateName);
|
Element providerElement = saveState.getXmlElement(stateName);
|
||||||
|
@ -463,10 +452,10 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||||
@Override
|
@Override
|
||||||
public void writeConfigState(SaveState saveState) {
|
public void writeConfigState(SaveState saveState) {
|
||||||
SaveState connectedProviderState = new SaveState();
|
SaveState connectedProviderState = new SaveState();
|
||||||
getConnectedProvider().writeConfigState(connectedProviderState);
|
connectedProvider.writeConfigState(connectedProviderState);
|
||||||
saveState.putXmlElement(KEY_CONNECTED_PROVIDER, connectedProviderState.saveToXml());
|
saveState.putXmlElement(KEY_CONNECTED_PROVIDER, connectedProviderState.saveToXml());
|
||||||
|
|
||||||
List<DebuggerListingProvider> disconnected = getDisconnectedProviders().stream()
|
List<DebuggerListingProvider> disconnected = disconnectedProviders.stream()
|
||||||
.filter(p -> p.isFollowsCurrentThread())
|
.filter(p -> p.isFollowsCurrentThread())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
int disconnectedCount = disconnected.size();
|
int disconnectedCount = disconnected.size();
|
||||||
|
@ -485,7 +474,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||||
Element connectedProviderElement = saveState.getXmlElement(KEY_CONNECTED_PROVIDER);
|
Element connectedProviderElement = saveState.getXmlElement(KEY_CONNECTED_PROVIDER);
|
||||||
if (connectedProviderElement != null) {
|
if (connectedProviderElement != null) {
|
||||||
SaveState connectedProviderState = new SaveState(connectedProviderElement);
|
SaveState connectedProviderState = new SaveState(connectedProviderElement);
|
||||||
getConnectedProvider().readConfigState(connectedProviderState);
|
connectedProvider.readConfigState(connectedProviderState);
|
||||||
}
|
}
|
||||||
|
|
||||||
int disconnectedCount = saveState.getInt(KEY_DISCONNECTED_COUNT, 0);
|
int disconnectedCount = saveState.getInt(KEY_DISCONNECTED_COUNT, 0);
|
||||||
|
|
|
@ -16,49 +16,46 @@
|
||||||
package ghidra.app.plugin.core.debug.gui.listing;
|
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.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.Color;
|
||||||
|
import java.awt.datatransfer.Transferable;
|
||||||
|
import java.awt.datatransfer.UnsupportedFlavorException;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import javax.swing.JLabel;
|
|
||||||
import javax.swing.event.ChangeEvent;
|
import javax.swing.event.ChangeEvent;
|
||||||
import javax.swing.event.ChangeListener;
|
import javax.swing.event.ChangeListener;
|
||||||
|
|
||||||
import org.apache.commons.collections4.ComparatorUtils;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.jdom.Element;
|
import org.jdom.Element;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.WindowPosition;
|
import docking.WindowPosition;
|
||||||
import docking.action.*;
|
import docking.action.DockingAction;
|
||||||
import docking.menu.ActionState;
|
import docking.action.MenuData;
|
||||||
import docking.menu.MultiStateDockingAction;
|
import docking.menu.MultiStateDockingAction;
|
||||||
import docking.widgets.EventTrigger;
|
import docking.widgets.EventTrigger;
|
||||||
import docking.widgets.fieldpanel.support.ViewerPosition;
|
import docking.widgets.fieldpanel.support.ViewerPosition;
|
||||||
import ghidra.app.nav.ListingPanelContainer;
|
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.CodeViewerProvider;
|
||||||
import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
|
import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
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.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.*;
|
||||||
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.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.core.exporter.ExporterDialog;
|
||||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.util.viewer.format.FormatManager;
|
import ghidra.app.util.viewer.format.FormatManager;
|
||||||
import ghidra.app.util.viewer.listingpanel.ListingDisplayListener;
|
|
||||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
import ghidra.async.AsyncDebouncer;
|
|
||||||
import ghidra.async.AsyncTimer;
|
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
import ghidra.framework.options.AutoOptions;
|
import ghidra.framework.options.AutoOptions;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
|
@ -67,31 +64,24 @@ import ghidra.framework.plugintool.AutoConfigState;
|
||||||
import ghidra.framework.plugintool.AutoService;
|
import ghidra.framework.plugintool.AutoService;
|
||||||
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.pcode.exec.*;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.pcode.utils.Utils;
|
import ghidra.program.model.address.AddressSetView;
|
||||||
import ghidra.program.model.address.*;
|
|
||||||
import ghidra.program.model.lang.Language;
|
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.program.util.ProgramSelection;
|
import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.Trace.*;
|
|
||||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
|
||||||
import ghidra.trace.model.modules.*;
|
import ghidra.trace.model.modules.*;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.trace.model.program.TraceVariableSnapProgramView;
|
import ghidra.trace.model.program.TraceVariableSnapProgramView;
|
||||||
import ghidra.trace.model.stack.TraceStack;
|
import ghidra.util.HTMLUtilities;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.util.Swing;
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
|
||||||
import ghidra.trace.util.TraceAddressSpace;
|
|
||||||
import ghidra.util.*;
|
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.exception.VersionException;
|
import ghidra.util.exception.VersionException;
|
||||||
import ghidra.util.task.*;
|
import ghidra.util.task.*;
|
||||||
import utilities.util.SuppressableCallback;
|
import utilities.util.SuppressableCallback;
|
||||||
import utilities.util.SuppressableCallback.Suppression;
|
import utilities.util.SuppressableCallback.Suppression;
|
||||||
|
|
||||||
public class DebuggerListingProvider extends CodeViewerProvider implements ListingDisplayListener {
|
public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
|
|
||||||
private static final AutoConfigState.ClassHandler<DebuggerListingProvider> CONFIG_STATE_HANDLER =
|
private static final AutoConfigState.ClassHandler<DebuggerListingProvider> CONFIG_STATE_HANDLER =
|
||||||
AutoConfigState.wireHandler(DebuggerListingProvider.class, MethodHandles.lookup());
|
AutoConfigState.wireHandler(DebuggerListingProvider.class, MethodHandles.lookup());
|
||||||
|
@ -116,50 +106,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
return true;
|
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 {
|
protected class SyncToStaticListingAction extends AbstractSyncToStaticListingAction {
|
||||||
public SyncToStaticListingAction() {
|
public SyncToStaticListingAction() {
|
||||||
super(plugin);
|
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 {
|
protected class MarkerSetChangeListener implements ChangeListener {
|
||||||
@Override
|
@Override
|
||||||
public void stateChanged(ChangeEvent e) {
|
public void stateChanged(ChangeEvent e) {
|
||||||
|
@ -233,89 +166,41 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class ForTrackingAndLabelingTraceListener extends TraceDomainObjectListener {
|
protected class ForListingGoToTrait extends DebuggerGoToTrait {
|
||||||
private final AsyncDebouncer<Void> updateLabelDebouncer =
|
public ForListingGoToTrait() {
|
||||||
new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, 100);
|
super(DebuggerListingProvider.this.tool, DebuggerListingProvider.this.plugin,
|
||||||
|
DebuggerListingProvider.this);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
@Override
|
||||||
public void processMemoryAccessibilityChanged(TraceRecorder recorder) {
|
protected boolean goToAddress(Address address) {
|
||||||
Swing.runIfSwingOrRunLater(() -> {
|
return getListingPanel().goTo(address);
|
||||||
actionCaptureSelectedMemory.updateEnabled(null);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final LocationTrackingSpec defaultTrackingSpec =
|
protected class ForListingTrackingTrait extends DebuggerTrackLocationTrait {
|
||||||
LocationTrackingSpec.fromConfigName(PCLocationTrackingSpec.CONFIG_NAME);
|
public ForListingTrackingTrait() {
|
||||||
private final AutoReadMemorySpec defaultReadMemorySpec =
|
super(DebuggerListingProvider.this.tool, DebuggerListingProvider.this.plugin,
|
||||||
AutoReadMemorySpec.fromConfigName(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME);
|
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;
|
private final DebuggerListingPlugin plugin;
|
||||||
|
|
||||||
|
@ -334,46 +219,37 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final AutoService.Wiring autoServiceWiring;
|
private final AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
@AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_REGISTER_MARKERS)
|
@AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_TRACKING_MARKERS)
|
||||||
private Color trackingColor;
|
private Color trackingColor;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final AutoOptions.Wiring autoOptionsWiring;
|
private final AutoOptions.Wiring autoOptionsWiring;
|
||||||
|
|
||||||
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||||
protected AddressSetView visible;
|
|
||||||
protected TraceRecorder currentRecorder;
|
|
||||||
protected ProgramLocation trackedLocation;
|
|
||||||
|
|
||||||
protected Program markedProgram;
|
protected Program markedProgram;
|
||||||
protected Address markedAddress;
|
protected Address markedAddress;
|
||||||
protected MarkerSet trackingMarker;
|
protected MarkerSet trackingMarker;
|
||||||
|
|
||||||
protected CaptureSelectedMemoryAction actionCaptureSelectedMemory;
|
|
||||||
protected MultiStateDockingAction<LocationTrackingSpec> actionTrackLocation;
|
|
||||||
protected DockingAction actionGoTo;
|
protected DockingAction actionGoTo;
|
||||||
protected SyncToStaticListingAction actionSyncToStaticListing;
|
protected SyncToStaticListingAction actionSyncToStaticListing;
|
||||||
protected FollowsCurrentThreadAction actionFollowsCurrentThread;
|
protected FollowsCurrentThreadAction actionFollowsCurrentThread;
|
||||||
protected MultiStateDockingAction<AutoReadMemorySpec> actionAutoReadMemory;
|
protected MultiStateDockingAction<AutoReadMemorySpec> actionAutoReadMemory;
|
||||||
|
protected DockingAction actionCaptureSelectedMemory;
|
||||||
protected DockingAction actionExportView;
|
protected DockingAction actionExportView;
|
||||||
protected DockingAction actionOpenProgram;
|
protected DockingAction actionOpenProgram;
|
||||||
|
protected MultiStateDockingAction<LocationTrackingSpec> actionTrackLocation;
|
||||||
|
|
||||||
protected final DebuggerGoToDialog goToDialog;
|
|
||||||
|
|
||||||
@AutoConfigStateField(codec = TrackingSpecConfigFieldCodec.class)
|
|
||||||
protected LocationTrackingSpec trackingSpec = defaultTrackingSpec;
|
|
||||||
@AutoConfigStateField
|
@AutoConfigStateField
|
||||||
protected boolean syncToStaticListing;
|
protected boolean syncToStaticListing;
|
||||||
@AutoConfigStateField
|
@AutoConfigStateField
|
||||||
protected boolean followsCurrentThread = true;
|
protected boolean followsCurrentThread = true;
|
||||||
@AutoConfigStateField(codec = AutoReadMemorySpecConfigFieldCodec.class)
|
// TODO: followsCurrentSnap?
|
||||||
protected AutoReadMemorySpec autoReadMemorySpec = defaultReadMemorySpec;
|
|
||||||
// TODO: followsCurrentSnap
|
|
||||||
|
|
||||||
protected ForTrackingAndLabelingTraceListener forTrackingTraceListener =
|
protected DebuggerGoToTrait goToTrait;
|
||||||
new ForTrackingAndLabelingTraceListener();
|
protected ForListingTrackingTrait trackingTrait;
|
||||||
protected ForAccessRecorderListener forAccessRecorderListener = new ForAccessRecorderListener();
|
protected ForListingReadsMemoryTrait readsMemTrait;
|
||||||
|
|
||||||
protected final JLabel locationLabel = new JLabel();
|
protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel();
|
||||||
|
|
||||||
protected final MultiBlendedListingBackgroundColorModel colorModel;
|
protected final MultiBlendedListingBackgroundColorModel colorModel;
|
||||||
protected final MarkerSetChangeListener markerChangeListener = new MarkerSetChangeListener();
|
protected final MarkerSetChangeListener markerChangeListener = new MarkerSetChangeListener();
|
||||||
|
@ -392,11 +268,13 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.isMainListing = isConnected;
|
this.isMainListing = isConnected;
|
||||||
|
|
||||||
goToDialog = new DebuggerGoToDialog(this);
|
goToTrait = new ForListingGoToTrait();
|
||||||
|
trackingTrait = new ForListingTrackingTrait();
|
||||||
|
readsMemTrait = new ForListingReadsMemoryTrait();
|
||||||
|
|
||||||
ListingPanel listingPanel = getListingPanel();
|
ListingPanel listingPanel = getListingPanel();
|
||||||
colorModel = new MultiBlendedListingBackgroundColorModel();
|
colorModel = new MultiBlendedListingBackgroundColorModel();
|
||||||
colorModel.addModel(new TrackedLocationBackgroundColorModel(plugin, listingPanel));
|
colorModel.addModel(trackingTrait.createListingBackgroundColorModel(listingPanel));
|
||||||
colorModel.addModel(new MemoryStateListingBackgroundColorModel(plugin, listingPanel));
|
colorModel.addModel(new MemoryStateListingBackgroundColorModel(plugin, listingPanel));
|
||||||
colorModel.addModel(new CursorBackgroundColorModel(plugin, listingPanel));
|
colorModel.addModel(new CursorBackgroundColorModel(plugin, listingPanel));
|
||||||
listingPanel.setBackgroundColorModel(colorModel);
|
listingPanel.setBackgroundColorModel(colorModel);
|
||||||
|
@ -408,12 +286,15 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
createActions();
|
createActions();
|
||||||
|
|
||||||
doTrackSpec();
|
goToTrait.goToCoordinates(current);
|
||||||
|
trackingTrait.goToCoordinates(current);
|
||||||
|
readsMemTrait.goToCoordinates(current);
|
||||||
|
locationLabel.goToCoordinates(current);
|
||||||
|
|
||||||
// TODO: An icon to distinguish dynamic from static
|
// TODO: An icon to distinguish dynamic from static
|
||||||
|
|
||||||
//getComponent().setBorder(BorderFactory.createEmptyBorder());
|
//getComponent().setBorder(BorderFactory.createEmptyBorder());
|
||||||
addListingDisplayListener(this);
|
addDisplayListener(readsMemTrait.getDisplayListener());
|
||||||
|
|
||||||
this.setNorthComponent(locationLabel);
|
this.setNorthComponent(locationLabel);
|
||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
|
@ -454,6 +335,11 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
return isMainListing;
|
return isMainListing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReadOnly() {
|
||||||
|
return current.isAliveAndPresent();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getWindowGroup() {
|
public String getWindowGroup() {
|
||||||
//TODO: Overriding this to align disconnected providers
|
//TODO: Overriding this to align disconnected providers
|
||||||
|
@ -486,6 +372,8 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
saveState.putXmlElement("formatManager", formatManagerState.saveToXml());
|
saveState.putXmlElement("formatManager", formatManagerState.saveToXml());
|
||||||
|
|
||||||
CONFIG_STATE_HANDLER.writeConfigState(this, saveState);
|
CONFIG_STATE_HANDLER.writeConfigState(this, saveState);
|
||||||
|
trackingTrait.writeConfigState(saveState);
|
||||||
|
readsMemTrait.writeConfigState(saveState);
|
||||||
}
|
}
|
||||||
|
|
||||||
void readConfigState(SaveState saveState) {
|
void readConfigState(SaveState saveState) {
|
||||||
|
@ -498,8 +386,9 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_STATE_HANDLER.readConfigState(this, saveState);
|
CONFIG_STATE_HANDLER.readConfigState(this, saveState);
|
||||||
|
trackingTrait.readConfigState(saveState);
|
||||||
|
readsMemTrait.readConfigState(saveState);
|
||||||
|
|
||||||
actionTrackLocation.setCurrentActionStateByUserData(trackingSpec);
|
|
||||||
if (isMainListing()) {
|
if (isMainListing()) {
|
||||||
actionSyncToStaticListing.setSelected(syncToStaticListing);
|
actionSyncToStaticListing.setSelected(syncToStaticListing);
|
||||||
followsCurrentThread = true;
|
followsCurrentThread = true;
|
||||||
|
@ -509,7 +398,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
actionFollowsCurrentThread.setSelected(followsCurrentThread);
|
actionFollowsCurrentThread.setSelected(followsCurrentThread);
|
||||||
updateBorder();
|
updateBorder();
|
||||||
}
|
}
|
||||||
actionAutoReadMemory.setCurrentActionStateByUserData(autoReadMemorySpec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -522,6 +410,28 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
super.addToTool();
|
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() {
|
protected void updateMarkerServiceColorModel() {
|
||||||
colorModel.removeModel(markerServiceColorModel);
|
colorModel.removeModel(markerServiceColorModel);
|
||||||
if (markerService != null) {
|
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) {
|
private void setTrackingColor(Color trackingColor) {
|
||||||
if (trackingMarker != null) {
|
if (trackingMarker != null) {
|
||||||
trackingMarker.setMarkerColor(trackingColor);
|
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
|
@Override
|
||||||
protected void doSetProgram(Program newProgram) {
|
protected void doSetProgram(Program newProgram) {
|
||||||
if (newProgram != null && newProgram != current.getView()) {
|
if (newProgram != null && newProgram != current.getView()) {
|
||||||
|
@ -666,25 +562,13 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
setSelection(new ProgramSelection());
|
setSelection(new ProgramSelection());
|
||||||
super.doSetProgram(newProgram);
|
super.doSetProgram(newProgram);
|
||||||
updateTitle();
|
updateTitle();
|
||||||
updateLocationLabel();
|
locationLabel.updateLabel();
|
||||||
}
|
|
||||||
|
|
||||||
protected void doSetRecorder(TraceRecorder newRecorder) {
|
|
||||||
if (currentRecorder == newRecorder) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (currentRecorder != null) {
|
|
||||||
currentRecorder.removeListener(forAccessRecorderListener);
|
|
||||||
}
|
|
||||||
currentRecorder = newRecorder;
|
|
||||||
if (currentRecorder != null) {
|
|
||||||
currentRecorder.addListener(forAccessRecorderListener);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String computeSubTitle() {
|
protected String computeSubTitle() {
|
||||||
TraceProgramView view = current.getView();
|
TraceProgramView view = current.getView();
|
||||||
List<String> parts = new ArrayList<>();
|
List<String> parts = new ArrayList<>();
|
||||||
|
LocationTrackingSpec trackingSpec = trackingTrait == null ? null : trackingTrait.getSpec();
|
||||||
if (trackingSpec != null) {
|
if (trackingSpec != null) {
|
||||||
String specTitle = trackingSpec.computeTitle(current);
|
String specTitle = trackingSpec.computeTitle(current);
|
||||||
if (specTitle != null) {
|
if (specTitle != null) {
|
||||||
|
@ -703,104 +587,18 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
setSubTitle(computeSubTitle());
|
setSubTitle(computeSubTitle());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TraceSection getNearestSectionContaining(Address address) {
|
|
||||||
if (current.getView() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Trace trace = current.getTrace();
|
|
||||||
List<TraceSection> 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<TraceModule> 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() {
|
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()) {
|
if (isMainListing()) {
|
||||||
actionSyncToStaticListing = new SyncToStaticListingAction();
|
actionSyncToStaticListing = new SyncToStaticListingAction();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
actionFollowsCurrentThread = new FollowsCurrentThreadAction();
|
actionFollowsCurrentThread = new FollowsCurrentThreadAction();
|
||||||
}
|
}
|
||||||
actionCaptureSelectedMemory = new CaptureSelectedMemoryAction();
|
|
||||||
actionAutoReadMemory = DebuggerAutoReadMemoryAction.builder(plugin)
|
actionGoTo = goToTrait.installAction();
|
||||||
.onAction(this::activatedAutoReadMemory)
|
actionTrackLocation = trackingTrait.installAction();
|
||||||
.onActionStateChanged(this::changedAutoReadMemory)
|
actionAutoReadMemory = readsMemTrait.installAutoReadAction();
|
||||||
.buildAndInstallLocal(this);
|
actionCaptureSelectedMemory = readsMemTrait.installCaptureSelectedAction();
|
||||||
actionAutoReadMemory.setCurrentActionStateByUserData(defaultReadMemorySpec);
|
|
||||||
|
|
||||||
actionExportView = ExportTraceViewAction.builder(plugin)
|
actionExportView = ExportTraceViewAction.builder(plugin)
|
||||||
.enabledWhen(ctx -> current.getView() != null)
|
.enabledWhen(ctx -> current.getView() != null)
|
||||||
|
@ -815,18 +613,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
contextChanged();
|
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) {
|
private void activatedExportView(ActionContext context) {
|
||||||
if (current.getView() == null) {
|
if (current.getView() == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -846,24 +632,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
ProgramManager.OPEN_CURRENT);
|
ProgramManager.OPEN_CURRENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void activatedLocationTracking(ActionContext ctx) {
|
|
||||||
doTrackSpec();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void changedLocationTracking(ActionState<LocationTrackingSpec> newState,
|
|
||||||
EventTrigger trigger) {
|
|
||||||
doSetTrackingSpec(newState.getUserData());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void activatedAutoReadMemory(ActionContext ctx) {
|
|
||||||
doAutoReadMemory();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void changedAutoReadMemory(ActionState<AutoReadMemorySpec> newState,
|
|
||||||
EventTrigger trigger) {
|
|
||||||
doSetAutoReadMemory(newState.getUserData());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isEffectivelyDifferent(ProgramLocation cur, ProgramLocation dest) {
|
protected boolean isEffectivelyDifferent(ProgramLocation cur, ProgramLocation dest) {
|
||||||
if (Objects.equals(cur, dest)) {
|
if (Objects.equals(cur, dest)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -897,6 +665,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
@Override
|
@Override
|
||||||
public void stateChanged(ChangeEvent e) {
|
public void stateChanged(ChangeEvent e) {
|
||||||
super.stateChanged(e);
|
super.stateChanged(e);
|
||||||
|
ProgramLocation trackedLocation = trackingTrait.getTrackedLocation();
|
||||||
if (trackedLocation != null && !isEffectivelyDifferent(getLocation(), trackedLocation)) {
|
if (trackedLocation != null && !isEffectivelyDifferent(getLocation(), trackedLocation)) {
|
||||||
cbGoTo.invoke(() -> getListingPanel().goTo(trackedLocation, true));
|
cbGoTo.invoke(() -> getListingPanel().goTo(trackedLocation, true));
|
||||||
}
|
}
|
||||||
|
@ -934,7 +703,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void programLocationChanged(ProgramLocation location, EventTrigger trigger) {
|
public void programLocationChanged(ProgramLocation location, EventTrigger trigger) {
|
||||||
updateLocationLabel();
|
locationLabel.goToAddress(location.getAddress());
|
||||||
if (traceManager != null) {
|
if (traceManager != null) {
|
||||||
location = ProgramLocationUtils.fixLocation(location, false);
|
location = ProgramLocationUtils.fixLocation(location, false);
|
||||||
}
|
}
|
||||||
|
@ -945,30 +714,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Boolean> 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<Boolean> goToSleigh(AddressSpace space, SleighExpression expression) {
|
|
||||||
AsyncPcodeExecutor<byte[]> executor = TracePcodeUtils.executorForCoordinates(current);
|
|
||||||
CompletableFuture<byte[]> 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) {
|
protected void doSyncToStatic(ProgramLocation location) {
|
||||||
if (isSyncToStaticListing() && location != null) {
|
if (isSyncToStaticListing() && location != null) {
|
||||||
ProgramLocation staticLoc = mappingService.getStaticLocationFromDynamic(location);
|
ProgramLocation staticLoc = mappingService.getStaticLocationFromDynamic(location);
|
||||||
|
@ -1102,19 +847,11 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTrackingSpec(LocationTrackingSpec spec) {
|
public void setTrackingSpec(LocationTrackingSpec spec) {
|
||||||
actionTrackLocation.setCurrentActionStateByUserData(spec);
|
trackingTrait.setSpec(spec);
|
||||||
}
|
|
||||||
|
|
||||||
protected void doSetTrackingSpec(LocationTrackingSpec spec) {
|
|
||||||
if (trackingSpec != spec) {
|
|
||||||
trackingSpec = spec;
|
|
||||||
updateTitle();
|
|
||||||
}
|
|
||||||
doTrackSpec();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocationTrackingSpec getTrackingSpec() {
|
public LocationTrackingSpec getTrackingSpec() {
|
||||||
return trackingSpec;
|
return trackingTrait.getSpec();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSyncToStaticListing(boolean sync) {
|
public void setSyncToStaticListing(boolean sync) {
|
||||||
|
@ -1162,41 +899,16 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
return followsCurrentThread;
|
return followsCurrentThread;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doSetAutoReadMemory(AutoReadMemorySpec spec) {
|
|
||||||
this.autoReadMemorySpec = spec;
|
|
||||||
if (visible != null) {
|
|
||||||
// HACK: Calling listener method directly
|
|
||||||
doAutoReadMemory();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAutoReadMemorySpec(AutoReadMemorySpec spec) {
|
public void setAutoReadMemorySpec(AutoReadMemorySpec spec) {
|
||||||
actionAutoReadMemory.setCurrentActionStateByUserData(spec);
|
readsMemTrait.setAutoSpec(spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AutoReadMemorySpec getAutoReadMemorySpec() {
|
public AutoReadMemorySpec getAutoReadMemorySpec() {
|
||||||
return autoReadMemorySpec;
|
return readsMemTrait.getAutoSpec();
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ProgramLocation doMarkTrackedLocation() {
|
protected ProgramLocation doMarkTrackedLocation() {
|
||||||
|
ProgramLocation trackedLocation = trackingTrait.getTrackedLocation();
|
||||||
if (trackedLocation == null) {
|
if (trackedLocation == null) {
|
||||||
markTrackedStaticLocation(null);
|
markTrackedStaticLocation(null);
|
||||||
return null;
|
return null;
|
||||||
|
@ -1207,8 +919,8 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
return trackedStatic;
|
return trackedStatic;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doTrackSpec() {
|
protected void doGoToTracked() {
|
||||||
ProgramLocation loc = trackedLocation = computeTrackedLocation();
|
ProgramLocation loc = trackingTrait.getTrackedLocation();
|
||||||
ProgramLocation trackedStatic = doMarkTrackedLocation();
|
ProgramLocation trackedStatic = doMarkTrackedLocation();
|
||||||
if (loc == null) {
|
if (loc == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -1232,7 +944,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
removeOldListeners();
|
|
||||||
if (consoleService != null) {
|
if (consoleService != null) {
|
||||||
if (actionOpenProgram != null) {
|
if (actionOpenProgram != null) {
|
||||||
consoleService.removeResolutionAction(actionOpenProgram);
|
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) {
|
public void staticProgramLocationChanged(ProgramLocation location) {
|
||||||
TraceProgramView view = current.getView(); // NB. Used for snap (don't want emuSnap)
|
TraceProgramView view = current.getView(); // NB. Used for snap (don't want emuSnap)
|
||||||
if (!isSyncToStaticListing() || view == null || location == null) {
|
if (!isSyncToStaticListing() || view == null || location == null) {
|
||||||
|
@ -1281,18 +976,12 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
current = coordinates;
|
current = coordinates;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
boolean doListeners = !Objects.equals(current.getTrace(), coordinates.getTrace());
|
|
||||||
if (doListeners) {
|
|
||||||
removeOldListeners();
|
|
||||||
}
|
|
||||||
current = coordinates;
|
current = coordinates;
|
||||||
if (doListeners) {
|
|
||||||
addNewListeners();
|
|
||||||
}
|
|
||||||
doSetProgram(current.getView());
|
doSetProgram(current.getView());
|
||||||
doSetRecorder(current.getRecorder());
|
goToTrait.goToCoordinates(coordinates);
|
||||||
doTrackSpec();
|
trackingTrait.goToCoordinates(coordinates);
|
||||||
doAutoReadMemory();
|
readsMemTrait.goToCoordinates(coordinates);
|
||||||
|
locationLabel.goToCoordinates(coordinates);
|
||||||
contextChanged();
|
contextChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,68 +15,23 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.listing;
|
package ghidra.app.plugin.core.debug.gui.listing;
|
||||||
|
|
||||||
import java.awt.Color;
|
import ghidra.app.plugin.core.debug.gui.colors.DebuggerTrackedRegisterBackgroundColorModel;
|
||||||
import java.math.BigInteger;
|
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
|
||||||
import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel;
|
import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel;
|
||||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
import ghidra.app.util.viewer.util.AddressIndexMap;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
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;
|
|
||||||
|
|
||||||
public abstract class DebuggerTrackedRegisterListingBackgroundColorModel
|
public abstract class DebuggerTrackedRegisterListingBackgroundColorModel
|
||||||
implements ListingBackgroundColorModel {
|
extends DebuggerTrackedRegisterBackgroundColorModel implements ListingBackgroundColorModel {
|
||||||
private Color defaultBackgroundColor;
|
|
||||||
private Program program;
|
|
||||||
private AddressIndexMap addressIndexMap;
|
|
||||||
|
|
||||||
// TODO: Seems I should at least rename this option
|
public DebuggerTrackedRegisterListingBackgroundColorModel(Plugin plugin,
|
||||||
@AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_REGISTER_MARKERS)
|
|
||||||
Color trackingColor;
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private final AutoOptions.Wiring autoOptionsWiring;
|
|
||||||
|
|
||||||
public DebuggerTrackedRegisterListingBackgroundColorModel(DebuggerListingPlugin plugin,
|
|
||||||
ListingPanel listingPanel) {
|
ListingPanel listingPanel) {
|
||||||
autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
|
super(plugin);
|
||||||
modelDataChanged(listingPanel);
|
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
|
@Override
|
||||||
public void modelDataChanged(ListingPanel listingPanel) {
|
public void modelDataChanged(ListingPanel listingPanel) {
|
||||||
this.program = listingPanel == null ? null : listingPanel.getProgram();
|
this.program = listingPanel == null ? null : listingPanel.getProgram();
|
||||||
this.addressIndexMap = listingPanel == null ? null : listingPanel.getAddressIndexMap();
|
this.addressIndexMap = listingPanel == null ? null : listingPanel.getAddressIndexMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract ProgramLocation getTrackedLocation();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,29 +20,31 @@ import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import docking.widgets.fieldpanel.support.BackgroundColorModel;
|
||||||
import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel;
|
import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel;
|
||||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
|
import ghidra.util.ColorUtils.ColorBlender;
|
||||||
|
|
||||||
public class MultiBlendedListingBackgroundColorModel implements ListingBackgroundColorModel {
|
public class MultiBlendedListingBackgroundColorModel implements ListingBackgroundColorModel {
|
||||||
private final List<ListingBackgroundColorModel> models = new ArrayList<>();
|
private final List<BackgroundColorModel> models = new ArrayList<>();
|
||||||
|
|
||||||
private final List<Color> toBlend = new ArrayList<>();
|
private final ColorBlender blender = new ColorBlender();
|
||||||
|
|
||||||
public MultiBlendedListingBackgroundColorModel() {
|
public MultiBlendedListingBackgroundColorModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addModel(ListingBackgroundColorModel m) {
|
public void addModel(BackgroundColorModel m) {
|
||||||
models.add(m);
|
models.add(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeModel(ListingBackgroundColorModel m) {
|
public void removeModel(BackgroundColorModel m) {
|
||||||
models.remove(m);
|
models.remove(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Color getBackgroundColor(BigInteger index) {
|
public Color getBackgroundColor(BigInteger index) {
|
||||||
toBlend.clear();
|
blender.clear();
|
||||||
for (ListingBackgroundColorModel m : models) {
|
for (BackgroundColorModel m : models) {
|
||||||
Color c = m.getBackgroundColor(index);
|
Color c = m.getBackgroundColor(index);
|
||||||
if (c == null) {
|
if (c == null) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -50,31 +52,9 @@ public class MultiBlendedListingBackgroundColorModel implements ListingBackgroun
|
||||||
if (c.equals(m.getDefaultBackgroundColor())) {
|
if (c.equals(m.getDefaultBackgroundColor())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
toBlend.add(c);
|
blender.add(c);
|
||||||
}
|
}
|
||||||
int size = toBlend.size();
|
return blender.getColor(getDefaultBackgroundColor());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -87,15 +67,19 @@ public class MultiBlendedListingBackgroundColorModel implements ListingBackgroun
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDefaultBackgroundColor(Color c) {
|
public void setDefaultBackgroundColor(Color c) {
|
||||||
for (ListingBackgroundColorModel m : models) {
|
for (BackgroundColorModel m : models) {
|
||||||
m.setDefaultBackgroundColor(c);
|
m.setDefaultBackgroundColor(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void modelDataChanged(ListingPanel listingPanel) {
|
public void modelDataChanged(ListingPanel listingPanel) {
|
||||||
for (ListingBackgroundColorModel m : models) {
|
for (BackgroundColorModel m : models) {
|
||||||
m.modelDataChanged(listingPanel);
|
if (!(m instanceof ListingBackgroundColorModel)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ListingBackgroundColorModel lm = (ListingBackgroundColorModel) m;
|
||||||
|
lm.modelDataChanged(listingPanel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<ColoredFieldSelection> 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<ColoredFieldSelection> 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<TraceAddressSnapRange, TraceMemoryState> 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<SelectionGenerator> 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<ColoredFieldSelection> 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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,21 +15,285 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.memory;
|
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 java.awt.Color;
|
||||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
import java.util.List;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class DebuggerMemoryBytesPlugin /*extends Plugin*/ {
|
import org.jdom.Element;
|
||||||
|
|
||||||
/**
|
import ghidra.app.events.ProgramLocationPluginEvent;
|
||||||
* TODO: This will likely require a refactor of the existing {@link ByteViewerPlugin} to provide
|
import ghidra.app.events.ProgramSelectionPluginEvent;
|
||||||
* an abstract one that we can inherit from, in the same vein as the
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
* {@link DebuggerListingPlugin}.
|
import ghidra.app.plugin.core.byteviewer.*;
|
||||||
*/
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
protected DebuggerMemoryBytesPlugin(PluginTool tool) {
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
//super(tool);
|
import ghidra.app.plugin.core.debug.event.*;
|
||||||
TODO();
|
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<DebuggerMemoryBytesProvider> {
|
||||||
|
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<DebuggerMemoryBytesProvider> 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<DebuggerMemoryBytesProvider> 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<DebuggerMemoryBytesProvider> 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<DebuggerMemoryBytesProvider> 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<ProgramByteViewerComponentProvider> 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<AutoReadMemorySpec> actionAutoReadMemory;
|
||||||
|
protected DockingAction actionCaptureSelectedMemory;
|
||||||
|
protected MultiStateDockingAction<LocationTrackingSpec> 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<String> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import com.google.common.collect.Range;
|
||||||
|
|
||||||
import ghidra.app.plugin.assembler.Assembler;
|
import ghidra.app.plugin.assembler.Assembler;
|
||||||
import ghidra.app.plugin.assembler.Assemblers;
|
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.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService;
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
import ghidra.program.model.lang.RegisterValue;
|
import ghidra.program.model.lang.RegisterValue;
|
||||||
|
@ -135,7 +136,7 @@ public class DebuggerListingPluginScreenShots extends GhidraScreenShotGenerator
|
||||||
|
|
||||||
performAction(listingProvider.actionGoTo, false);
|
performAction(listingProvider.actionGoTo, false);
|
||||||
DebuggerGoToDialog dialog = waitForDialogComponent(DebuggerGoToDialog.class);
|
DebuggerGoToDialog dialog = waitForDialogComponent(DebuggerGoToDialog.class);
|
||||||
dialog.textExpression.setText("RAX");
|
dialog.setExpression("RAX");
|
||||||
|
|
||||||
captureDialog(dialog);
|
captureDialog(dialog);
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
static LocationTrackingSpec getLocationTrackingSpec(String name) {
|
static LocationTrackingSpec getLocationTrackingSpec(String name) {
|
||||||
return /*waitForValue(() ->*/ LocationTrackingSpec.fromConfigName(name)/*)*/;
|
return LocationTrackingSpec.fromConfigName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static AutoReadMemorySpec getAutoReadMemorySpec(String name) {
|
static AutoReadMemorySpec getAutoReadMemorySpec(String name) {
|
||||||
|
@ -261,7 +261,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
TraceThread thread1;
|
TraceThread thread1;
|
||||||
TraceThread thread2;
|
TraceThread thread2;
|
||||||
DebuggerListingProvider extraListing = SwingExecutorService.INSTANCE.submit(
|
DebuggerListingProvider extraProvider = SwingExecutorService.INSTANCE.submit(
|
||||||
() -> listingPlugin.createListingIfMissing(trackPc, true)).get();
|
() -> listingPlugin.createListingIfMissing(trackPc, true)).get();
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
DBTraceMemoryManager memory = tb.trace.getMemoryManager();
|
DBTraceMemoryManager memory = tb.trace.getMemoryManager();
|
||||||
|
@ -283,15 +283,15 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
traceManager.activateThread(thread1);
|
traceManager.activateThread(thread1);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
loc = extraListing.getLocation();
|
loc = extraProvider.getLocation();
|
||||||
assertEquals(tb.trace.getProgramView(), loc.getProgram());
|
assertEquals(tb.trace.getProgramView(), loc.getProgram());
|
||||||
assertEquals(tb.addr(0x00401234), loc.getAddress());
|
assertEquals(tb.addr(0x00401234), loc.getAddress());
|
||||||
|
|
||||||
extraListing.setFollowsCurrentThread(false);
|
extraProvider.setFollowsCurrentThread(false);
|
||||||
traceManager.activateThread(thread2);
|
traceManager.activateThread(thread2);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
loc = extraListing.getLocation();
|
loc = extraProvider.getLocation();
|
||||||
assertEquals(tb.trace.getProgramView(), loc.getProgram());
|
assertEquals(tb.trace.getProgramView(), loc.getProgram());
|
||||||
assertEquals(tb.addr(0x00401234), loc.getAddress());
|
assertEquals(tb.addr(0x00401234), loc.getAddress());
|
||||||
}
|
}
|
||||||
|
@ -432,6 +432,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
loc = listingProvider.getLocation();
|
loc = listingProvider.getLocation();
|
||||||
assertEquals(b1.trace.getProgramView(), loc.getProgram());
|
assertEquals(b1.trace.getProgramView(), loc.getProgram());
|
||||||
assertEquals(b1.addr(0x00400000), loc.getAddress());
|
assertEquals(b1.addr(0x00400000), loc.getAddress());
|
||||||
|
// TODO: Assert thread?
|
||||||
|
|
||||||
traceManager.activateThread(t2);
|
traceManager.activateThread(t2);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
@ -820,7 +821,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
performAction(listingProvider.actionGoTo, false);
|
performAction(listingProvider.actionGoTo, false);
|
||||||
DebuggerGoToDialog dialog = waitForDialogComponent(DebuggerGoToDialog.class);
|
DebuggerGoToDialog dialog = waitForDialogComponent(DebuggerGoToDialog.class);
|
||||||
|
|
||||||
dialog.textExpression.setText("r0");
|
dialog.setExpression("r0");
|
||||||
dialog.okCallback();
|
dialog.okCallback();
|
||||||
|
|
||||||
waitForPass(
|
waitForPass(
|
||||||
|
@ -828,7 +829,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
performAction(listingProvider.actionGoTo, false);
|
performAction(listingProvider.actionGoTo, false);
|
||||||
dialog = waitForDialogComponent(DebuggerGoToDialog.class);
|
dialog = waitForDialogComponent(DebuggerGoToDialog.class);
|
||||||
dialog.textExpression.setText("*:4 r0");
|
dialog.setExpression("*:4 r0");
|
||||||
dialog.okCallback();
|
dialog.okCallback();
|
||||||
|
|
||||||
waitForPass(
|
waitForPass(
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.dbg.model;
|
package ghidra.dbg.model;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
|
@ -55,6 +56,18 @@ public class TestDebuggerModelBuilder {
|
||||||
return testModel.range(min, max);
|
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() {
|
public void createTestProcessesAndThreads() {
|
||||||
testProcess1 = testModel.addProcess(1);
|
testProcess1 = testModel.addProcess(1);
|
||||||
testThread1 = testProcess1.addThread(1);
|
testThread1 = testProcess1.addThread(1);
|
||||||
|
|
|
@ -40,11 +40,15 @@ public class TestTargetMemory
|
||||||
), "Initialized");
|
), "Initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void getMemory(Address address, byte[] data) {
|
||||||
|
assertEquals(getModel().ram, address.getAddressSpace());
|
||||||
|
memory.getData(address.getOffset(), data);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<byte[]> readMemory(Address address, int length) {
|
public CompletableFuture<byte[]> readMemory(Address address, int length) {
|
||||||
assertEquals(getModel().ram, address.getAddressSpace());
|
|
||||||
byte[] data = new byte[length];
|
byte[] data = new byte[length];
|
||||||
memory.getData(address.getOffset(), data);
|
getMemory(address, data);
|
||||||
CompletableFuture<byte[]> future = getModel().future(data);
|
CompletableFuture<byte[]> future = getModel().future(data);
|
||||||
future.thenAccept(__ -> {
|
future.thenAccept(__ -> {
|
||||||
listeners.fire.memoryUpdated(this, address, data);
|
listeners.fire.memoryUpdated(this, address, data);
|
||||||
|
|
|
@ -86,6 +86,7 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
||||||
public MemoryBlock[] getBlocks() {
|
public MemoryBlock[] getBlocks() {
|
||||||
List<MemoryBlock> result = new ArrayList<>();
|
List<MemoryBlock> result = new ArrayList<>();
|
||||||
forVisibleRegions(reg -> result.add(getBlock(reg)));
|
forVisibleRegions(reg -> result.add(getBlock(reg)));
|
||||||
|
Collections.sort(result, Comparator.comparing(b -> b.getStart()));
|
||||||
return result.toArray(new MemoryBlock[result.size()]);
|
return result.toArray(new MemoryBlock[result.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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) {
|
public DecoratorPanel(JComponent component, boolean isConnected) {
|
||||||
setLayout(new BorderLayout());
|
setLayout(new BorderLayout());
|
||||||
add(component);
|
add(component);
|
||||||
setConnnected( isConnected );
|
setConnected( isConnected );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setConnnected( boolean isConnected ) {
|
public void setConnected( boolean isConnected ) {
|
||||||
if ( !isConnected ) {
|
if ( !isConnected ) {
|
||||||
setBorder( BorderFactory.createLineBorder( Color.ORANGE, 2 ) );
|
setBorder( BorderFactory.createLineBorder( Color.ORANGE, 2 ) );
|
||||||
}
|
}
|
||||||
|
|
|
@ -275,6 +275,9 @@ public class AssembleDockingAction extends DockingAction {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
prepareLayout(context);
|
prepareLayout(context);
|
||||||
|
if (cv.isReadOnly()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ListingActionContext lac = (ListingActionContext) context;
|
ListingActionContext lac = (ListingActionContext) context;
|
||||||
|
|
||||||
ProgramLocation cur = lac.getLocation();
|
ProgramLocation cur = lac.getLocation();
|
||||||
|
@ -371,6 +374,13 @@ public class AssembleDockingAction extends DockingAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
ListingActionContext lac = (ListingActionContext) context;
|
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();
|
Program program = lac.getProgram();
|
||||||
if (program == null) {
|
if (program == null) {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -24,16 +24,21 @@ import ghidra.program.util.ProgramSelection;
|
||||||
public interface CodeBrowserPluginInterface {
|
public interface CodeBrowserPluginInterface {
|
||||||
|
|
||||||
PluginTool getTool();
|
PluginTool getTool();
|
||||||
|
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
void providerClosed(CodeViewerProvider codeViewerProvider);
|
void providerClosed(CodeViewerProvider codeViewerProvider);
|
||||||
|
|
||||||
boolean isDisposed();
|
boolean isDisposed();
|
||||||
|
|
||||||
void locationChanged(CodeViewerProvider codeViewerProvider, ProgramLocation loc);
|
void locationChanged(CodeViewerProvider codeViewerProvider, ProgramLocation loc);
|
||||||
|
|
||||||
void selectionChanged(CodeViewerProvider codeViewerProvider, ProgramSelection currentSelection);
|
void selectionChanged(CodeViewerProvider codeViewerProvider, ProgramSelection currentSelection);
|
||||||
|
|
||||||
void highlightChanged(CodeViewerProvider codeViewerProvider, ProgramSelection highlight);
|
void highlightChanged(CodeViewerProvider codeViewerProvider, ProgramSelection highlight);
|
||||||
|
|
||||||
ViewManagerService getViewManager(CodeViewerProvider codeViewerProvider);
|
ViewManagerService getViewManager(CodeViewerProvider codeViewerProvider);
|
||||||
|
|
||||||
CodeViewerProvider createNewDisconnectedProvider();
|
CodeViewerProvider createNewDisconnectedProvider();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,10 +158,14 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
|
||||||
listingPanel.setStringSelectionListener(this);
|
listingPanel.setStringSelectionListener(this);
|
||||||
listingPanel.addIndexMapChangeListener(this);
|
listingPanel.addIndexMapChangeListener(this);
|
||||||
|
|
||||||
codeViewerClipboardProvider = new CodeBrowserClipboardProvider(tool, this);
|
codeViewerClipboardProvider = newClipboardProvider();
|
||||||
tool.addPopupActionProvider(this);
|
tool.addPopupActionProvider(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected CodeBrowserClipboardProvider newClipboardProvider() {
|
||||||
|
return new CodeBrowserClipboardProvider(tool, this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSnapshot() {
|
public boolean isSnapshot() {
|
||||||
// we are a snapshot when we are 'disconnected'
|
// we are a snapshot when we are 'disconnected'
|
||||||
|
@ -175,6 +179,17 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
|
||||||
return false;
|
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,
|
private ListingHighlightProvider createListingHighlighter(ListingPanel panel,
|
||||||
PluginTool pluginTool, Component repaintComponent) {
|
PluginTool pluginTool, Component repaintComponent) {
|
||||||
ListingHighlightProvider listingHighlighter =
|
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
|
* @param listener the listener to add
|
||||||
*/
|
*/
|
||||||
public void addListingDisplayListener(ListingDisplayListener listener) {
|
public void addDisplayListener(AddressSetDisplayListener listener) {
|
||||||
listingPanel.addListingDisplayListener(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
|
* @param listener the listener to remove
|
||||||
*/
|
*/
|
||||||
public void removeListingDisplayListener(ListingDisplayListener listener) {
|
public void removeDisplayListener(AddressSetDisplayListener listener) {
|
||||||
listingPanel.removeListingDisplayListener(listener);
|
listingPanel.removeDisplayListener(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -219,11 +219,11 @@ public interface CodeViewerService {
|
||||||
* Adds a listener to be notified when the set of visible addresses change.
|
* Adds a listener to be notified when the set of visible addresses change.
|
||||||
* @param listener the listener to be notified;
|
* @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.
|
* Removes listener from being notified when the set of visible addresses change.
|
||||||
* @param listener the listener to be notified;
|
* @param listener the listener to be notified;
|
||||||
*/
|
*/
|
||||||
public void removeListingDisplayListener(ListingDisplayListener listener);
|
public void removeListingDisplayListener(AddressSetDisplayListener listener);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import ghidra.program.model.address.AddressSetView;
|
||||||
/**
|
/**
|
||||||
* Interface for being notified whenever the set of visible addresses change in the listing.
|
* 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.
|
* Callback whenever the set of visible addresses change in the listing.
|
||||||
* @param visibleAddresses the current set of visible addresses in the listing. If no
|
* @param visibleAddresses the current set of visible addresses in the listing. If no
|
|
@ -89,7 +89,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
|
||||||
// don't care
|
// don't care
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private List<ListingDisplayListener> displayListeners = new ArrayList<>();
|
private List<AddressSetDisplayListener> displayListeners = new ArrayList<>();
|
||||||
|
|
||||||
private String currentTextSelection;
|
private String currentTextSelection;
|
||||||
|
|
||||||
|
@ -479,12 +479,12 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
|
||||||
element.setPixelMap(pixmap);
|
element.setPixelMap(pixmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ListingDisplayListener listener : displayListeners) {
|
for (AddressSetDisplayListener listener : displayListeners) {
|
||||||
notifyDisplayListener(listener);
|
notifyDisplayListener(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyDisplayListener(ListingDisplayListener listener) {
|
private void notifyDisplayListener(AddressSetDisplayListener listener) {
|
||||||
AddressSetView displayAddresses = pixmap.getAddressSet();
|
AddressSetView displayAddresses = pixmap.getAddressSet();
|
||||||
try {
|
try {
|
||||||
listener.visibleAddressesChanged(displayAddresses);
|
listener.visibleAddressesChanged(displayAddresses);
|
||||||
|
@ -1160,11 +1160,11 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
|
||||||
layoutModel.dataChanged(true);
|
layoutModel.dataChanged(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addListingDisplayListener(ListingDisplayListener listener) {
|
public void addDisplayListener(AddressSetDisplayListener listener) {
|
||||||
displayListeners.add(listener);
|
displayListeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeListingDisplayListener(ListingDisplayListener listener) {
|
public void removeDisplayListener(AddressSetDisplayListener listener) {
|
||||||
displayListeners.remove(listener);
|
displayListeners.remove(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -307,7 +307,7 @@ public class ListingPanelTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
AtomicReference<AddressSetView> addresses = new AtomicReference<>();
|
AtomicReference<AddressSetView> addresses = new AtomicReference<>();
|
||||||
CodeViewerService cvs = tool.getService(CodeViewerService.class);
|
CodeViewerService cvs = tool.getService(CodeViewerService.class);
|
||||||
cvs.addListingDisplayListener(new ListingDisplayListener() {
|
cvs.addListingDisplayListener(new AddressSetDisplayListener() {
|
||||||
@Override
|
@Override
|
||||||
public void visibleAddressesChanged(AddressSetView visibleAddresses) {
|
public void visibleAddressesChanged(AddressSetView visibleAddresses) {
|
||||||
addresses.set(visibleAddresses);
|
addresses.set(visibleAddresses);
|
||||||
|
|
|
@ -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<P extends ProgramByteViewerComponentProvider>
|
||||||
|
extends Plugin {
|
||||||
|
|
||||||
|
protected Program currentProgram;
|
||||||
|
private boolean areEventsDisabled;
|
||||||
|
protected ProgramLocation currentLocation;
|
||||||
|
|
||||||
|
protected P connectedProvider;
|
||||||
|
|
||||||
|
protected List<P> 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<Long, Object> 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<Long, Object> 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<Long, Object> stateMap = (Map<Long, Object>) state;
|
||||||
|
restoreUndoRedoState(stateMap, domainObject, connectedProvider);
|
||||||
|
for (P provider : disconnectedProviders) {
|
||||||
|
restoreUndoRedoState(stateMap, domainObject, provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreUndoRedoState(Map<Long, Object> 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);
|
||||||
|
}
|
|
@ -25,10 +25,10 @@ import ghidra.framework.options.SaveState;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class to manage changes within byte blocks; determines what offsets
|
* Helper class to manage changes within byte blocks; determines what offsets have changed so the
|
||||||
* have changed so the changes can be rendered properly in the Byte Viewer.
|
* changes can be rendered properly in the Byte Viewer.
|
||||||
*/
|
*/
|
||||||
class ByteBlockChangeManager {
|
public class ByteBlockChangeManager {
|
||||||
|
|
||||||
private ProgramByteBlockSet blockSet;
|
private ProgramByteBlockSet blockSet;
|
||||||
private List<ByteEditInfo> changeList; // list of changes for this tool
|
private List<ByteEditInfo> changeList; // list of changes for this tool
|
||||||
|
@ -57,6 +57,7 @@ class ByteBlockChangeManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a change to the change list.
|
* Add a change to the change list.
|
||||||
|
*
|
||||||
* @param edit edit object that has the old value and new value
|
* @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
|
* Return true if any offset in the range offset to offset+unitByteSize-1 is in either of the
|
||||||
* is in either of the change lists.
|
* change lists.
|
||||||
|
*
|
||||||
* @param block block in question
|
* @param block block in question
|
||||||
* @param offset offset into the block
|
* @param offset offset into the block
|
||||||
* @param unitByteSize number of bytes in the unit (dictated by the
|
* @param unitByteSize number of bytes in the unit (dictated by the data format model)
|
||||||
* data format model)
|
|
||||||
*
|
*
|
||||||
* @return boolean true if an offset in the range was found
|
* @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.
|
* Return true if the block and offset are in the list.
|
||||||
|
*
|
||||||
* @param list either the local change list or the external change list
|
* @param list either the local change list or the external change list
|
||||||
* @param block block in question
|
* @param block block in question
|
||||||
* @param offset offset into the block
|
* @param offset offset into the block
|
||||||
|
|
|
@ -32,13 +32,12 @@ import docking.widgets.fieldpanel.field.Field;
|
||||||
import docking.widgets.fieldpanel.listener.*;
|
import docking.widgets.fieldpanel.listener.*;
|
||||||
import docking.widgets.fieldpanel.support.*;
|
import docking.widgets.fieldpanel.support.*;
|
||||||
import ghidra.app.plugin.core.format.*;
|
import ghidra.app.plugin.core.format.*;
|
||||||
import ghidra.program.model.address.AddressOutOfBoundsException;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FieldViewer to show data formatted according to the DataFormatModel that
|
* FieldViewer to show data formatted according to the DataFormatModel that is passed in to the
|
||||||
* is passed in to the constructor. The source of the data is an array
|
* constructor. The source of the data is an array of ByteBlocks that is managed by an IndexMap.
|
||||||
* of ByteBlocks that is managed by an IndexMap.
|
|
||||||
*/
|
*/
|
||||||
public class ByteViewerComponent extends FieldPanel implements FieldMouseListener,
|
public class ByteViewerComponent extends FieldPanel implements FieldMouseListener,
|
||||||
FieldLocationListener, FieldSelectionListener, FieldInputListener {
|
FieldLocationListener, FieldSelectionListener, FieldInputListener {
|
||||||
|
@ -70,14 +69,14 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* 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 layoutModel the layout model for this component
|
||||||
* @param model data format model that knows how the data should be
|
* @param model data format model that knows how the data should be displayed
|
||||||
* displayed
|
|
||||||
* @param bytesPerLine number of bytes displayed in a row
|
* @param bytesPerLine number of bytes displayed in a row
|
||||||
* @param fm the font metrics used for drawing
|
* @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) {
|
DataFormatModel model, int bytesPerLine, FontMetrics fm) {
|
||||||
super(layoutModel);
|
super(layoutModel);
|
||||||
|
|
||||||
|
@ -357,6 +356,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the color for the component that has focus.
|
* Set the color for the component that has focus.
|
||||||
|
*
|
||||||
* @param c the color to set
|
* @param c the color to set
|
||||||
*/
|
*/
|
||||||
void setCurrentCursorColor(Color c) {
|
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.
|
* Set the background color for the line containing the cursor.
|
||||||
|
*
|
||||||
* @param c the color to set
|
* @param c the color to set
|
||||||
*/
|
*/
|
||||||
void setCurrentCursorLineColor(Color c) {
|
void setCurrentCursorLineColor(Color c) {
|
||||||
|
@ -374,6 +375,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the color for showing gaps in indexes.
|
* Set the color for showing gaps in indexes.
|
||||||
|
*
|
||||||
* @param c the color to set
|
* @param c the color to set
|
||||||
*/
|
*/
|
||||||
void setSeparatorColor(Color c) {
|
void setSeparatorColor(Color c) {
|
||||||
|
@ -415,8 +417,17 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
|
||||||
updatingIndexMap = false;
|
updatingIndexMap = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected IndexMap getIndexMap() {
|
||||||
|
return indexMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ProgramByteBlockSet getBlockSet() {
|
||||||
|
return blockSet;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the new group size
|
* Set the new group size
|
||||||
|
*
|
||||||
* @param groupSize the group size
|
* @param groupSize the group size
|
||||||
* @throws UnsupportedOperationException if model for this view does not support groups
|
* @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();
|
FieldSelection fsel = new FieldSelection();
|
||||||
for (int i = 0; i < selection.getNumberOfRanges(); i++) {
|
for (int i = 0; i < selection.getNumberOfRanges(); i++) {
|
||||||
ByteBlockRange r = selection.getRange(i);
|
ByteBlockRange r = selection.getRange(i);
|
||||||
|
@ -487,8 +498,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
|
||||||
* @param block the block
|
* @param block the block
|
||||||
* @param index the index
|
* @param index the index
|
||||||
* @param characterOffset the offset into the UI field
|
* @param characterOffset the offset into the UI field
|
||||||
* @return index of the location; return -1 if there was an error
|
* @return index of the location; return -1 if there was an error setting the cursor location
|
||||||
* setting the cursor location
|
|
||||||
*/
|
*/
|
||||||
int setViewerCursorLocation(ByteBlock block, BigInteger index, int characterOffset) {
|
int setViewerCursorLocation(ByteBlock block, BigInteger index, int characterOffset) {
|
||||||
if (indexMap == null) {
|
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
|
* Set the edit mode according to the given param if the model for this view supports editing.
|
||||||
* for this view supports editing.
|
*
|
||||||
* @param editMode true means to enable editing, and change the cursor
|
* @param editMode true means to enable editing, and change the cursor color.
|
||||||
* color.
|
|
||||||
*/
|
*/
|
||||||
void setEditMode(boolean editMode) {
|
void setEditMode(boolean editMode) {
|
||||||
consumeKeyStrokes = editMode;
|
consumeKeyStrokes = editMode;
|
||||||
|
@ -742,9 +751,8 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
|
||||||
enableHelp();
|
enableHelp();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable help for this component; used the model name as part of
|
* Enable help for this component; used the model name as part of the help ID.
|
||||||
* the help ID.
|
|
||||||
*/
|
*/
|
||||||
private void enableHelp() {
|
private void enableHelp() {
|
||||||
HelpService helpService = Help.getHelpService();
|
HelpService helpService = Help.getHelpService();
|
||||||
|
@ -814,7 +822,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
|
||||||
/**
|
/**
|
||||||
* Create a byte block selection from the field selection.
|
* Create a byte block selection from the field selection.
|
||||||
*/
|
*/
|
||||||
private ByteBlockSelection processFieldSelection(FieldSelection selection) {
|
protected ByteBlockSelection processFieldSelection(FieldSelection selection) {
|
||||||
|
|
||||||
ByteBlockSelection sel = new ByteBlockSelection();
|
ByteBlockSelection sel = new ByteBlockSelection();
|
||||||
int count = selection.getNumRanges();
|
int count = selection.getNumRanges();
|
||||||
|
@ -865,6 +873,17 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
|
||||||
return null;
|
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 class ByteViewerBackgroundColorModel implements BackgroundColorModel {
|
||||||
|
|
||||||
private Color defaultBackgroundColor = Color.WHITE;
|
private Color defaultBackgroundColor = Color.WHITE;
|
||||||
|
|
|
@ -28,6 +28,7 @@ import ghidra.GhidraOptions;
|
||||||
import ghidra.GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES;
|
import ghidra.GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES;
|
||||||
import ghidra.app.plugin.core.format.*;
|
import ghidra.app.plugin.core.format.*;
|
||||||
import ghidra.app.services.MarkerService;
|
import ghidra.app.services.MarkerService;
|
||||||
|
import ghidra.app.util.viewer.listingpanel.AddressSetDisplayListener;
|
||||||
import ghidra.framework.options.*;
|
import ghidra.framework.options.*;
|
||||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
@ -87,25 +88,26 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
|
||||||
|
|
||||||
protected Map<String, ByteViewerComponent> viewMap = new HashMap<>();
|
protected Map<String, ByteViewerComponent> viewMap = new HashMap<>();
|
||||||
|
|
||||||
private ToggleEditAction editModeAction;
|
protected ToggleEditAction editModeAction;
|
||||||
protected OptionsAction setOptionsAction;
|
protected OptionsAction setOptionsAction;
|
||||||
|
|
||||||
protected ProgramByteBlockSet blockSet;
|
protected ProgramByteBlockSet blockSet;
|
||||||
|
|
||||||
protected final ByteViewerPlugin plugin;
|
protected final AbstractByteViewerPlugin<?> plugin;
|
||||||
|
|
||||||
protected SwingUpdateManager updateManager;
|
protected SwingUpdateManager updateManager;
|
||||||
|
|
||||||
private Map<String, Class<? extends DataFormatModel>> dataFormatModelClassMap;
|
private Map<String, Class<? extends DataFormatModel>> dataFormatModelClassMap;
|
||||||
|
|
||||||
protected ByteViewerComponentProvider(PluginTool tool, ByteViewerPlugin plugin, String name,
|
protected ByteViewerComponentProvider(PluginTool tool, AbstractByteViewerPlugin<?> plugin,
|
||||||
|
String name,
|
||||||
Class<?> contextType) {
|
Class<?> contextType) {
|
||||||
super(tool, name, plugin.getName(), contextType);
|
super(tool, name, plugin.getName(), contextType);
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
|
||||||
initializedDataFormatModelClassMap();
|
initializedDataFormatModelClassMap();
|
||||||
|
|
||||||
panel = new ByteViewerPanel(this);
|
panel = newByteViewerPanel();
|
||||||
bytesPerLine = DEFAULT_BYTES_PER_LINE;
|
bytesPerLine = DEFAULT_BYTES_PER_LINE;
|
||||||
setIcon(ResourceManager.loadImage("images/binaryData.gif"));
|
setIcon(ResourceManager.loadImage("images/binaryData.gif"));
|
||||||
setOptions();
|
setOptions();
|
||||||
|
@ -118,6 +120,10 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
|
||||||
setWindowMenuGroup("Byte Viewer");
|
setWindowMenuGroup("Byte Viewer");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ByteViewerPanel newByteViewerPanel() {
|
||||||
|
return new ByteViewerPanel(this);
|
||||||
|
}
|
||||||
|
|
||||||
private void initializedDataFormatModelClassMap() {
|
private void initializedDataFormatModelClassMap() {
|
||||||
dataFormatModelClassMap = new HashMap<>();
|
dataFormatModelClassMap = new HashMap<>();
|
||||||
Set<? extends DataFormatModel> models = getDataFormatModels();
|
Set<? extends DataFormatModel> models = getDataFormatModels();
|
||||||
|
@ -150,6 +156,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification that an option changed.
|
* Notification that an option changed.
|
||||||
|
*
|
||||||
* @param options options object containing the property that changed
|
* @param options options object containing the property that changed
|
||||||
* @param group
|
* @param group
|
||||||
* @param optionName name of option that changed
|
* @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();
|
DataModelInfo info = panel.getDataModelInfo();
|
||||||
saveState.putStrings(VIEW_NAMES, info.getNames());
|
saveState.putStrings(VIEW_NAMES, info.getNames());
|
||||||
saveState.putInt(HEX_VIEW_GROUPSIZE, hexGroupSize);
|
saveState.putInt(HEX_VIEW_GROUPSIZE, hexGroupSize);
|
||||||
|
@ -346,7 +353,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
|
||||||
saveState.putInt(OFFSET_NAME, offset);
|
saveState.putInt(OFFSET_NAME, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void readConfigState(SaveState saveState) {
|
protected void readConfigState(SaveState saveState) {
|
||||||
String[] names = saveState.getStrings(VIEW_NAMES, new String[0]);
|
String[] names = saveState.getStrings(VIEW_NAMES, new String[0]);
|
||||||
hexGroupSize = saveState.getInt(HEX_VIEW_GROUPSIZE, 1);
|
hexGroupSize = saveState.getInt(HEX_VIEW_GROUPSIZE, 1);
|
||||||
restoreViews(names, false);
|
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);
|
boolean export);
|
||||||
|
|
||||||
abstract void updateSelection(ByteBlockSelection selection);
|
protected abstract void updateSelection(ByteBlockSelection selection);
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
updateManager.dispose();
|
updateManager.dispose();
|
||||||
|
@ -445,7 +452,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteViewerPanel getByteViewerPanel() {
|
protected ByteViewerPanel getByteViewerPanel() {
|
||||||
return panel;
|
return panel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,7 +488,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return classy.newInstance();
|
return classy.getConstructor().newInstance();
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
// cannot happen, since we only get the value from valid class that we put into the map
|
// 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() {
|
public MarkerService getMarkerService() {
|
||||||
return tool.getService(MarkerService.class);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,15 +33,14 @@ import ghidra.app.plugin.core.format.DataFormatModel;
|
||||||
/**
|
/**
|
||||||
* Implements the LayoutModel for ByteViewer Components.
|
* Implements the LayoutModel for ByteViewer Components.
|
||||||
*/
|
*/
|
||||||
|
public class ByteViewerLayoutModel implements LayoutModel {
|
||||||
class ByteViewerLayoutModel implements LayoutModel {
|
|
||||||
private int width;
|
private int width;
|
||||||
private IndexMap indexMap;
|
private IndexMap indexMap;
|
||||||
private List<LayoutModelListener> listeners;
|
private List<LayoutModelListener> listeners;
|
||||||
private FieldFactory[] factorys;
|
private FieldFactory[] factorys;
|
||||||
private BigInteger numIndexes;
|
private BigInteger numIndexes;
|
||||||
|
|
||||||
ByteViewerLayoutModel() {
|
public ByteViewerLayoutModel() {
|
||||||
factorys = new FieldFactory[0];
|
factorys = new FieldFactory[0];
|
||||||
listeners = new ArrayList<LayoutModelListener>(1);
|
listeners = new ArrayList<LayoutModelListener>(1);
|
||||||
numIndexes = BigInteger.ZERO;
|
numIndexes = BigInteger.ZERO;
|
||||||
|
|
|
@ -28,25 +28,27 @@ import docking.help.HelpService;
|
||||||
import docking.widgets.fieldpanel.*;
|
import docking.widgets.fieldpanel.*;
|
||||||
import docking.widgets.fieldpanel.field.EmptyTextField;
|
import docking.widgets.fieldpanel.field.EmptyTextField;
|
||||||
import docking.widgets.fieldpanel.field.Field;
|
import docking.widgets.fieldpanel.field.Field;
|
||||||
import docking.widgets.fieldpanel.listener.IndexMapper;
|
import docking.widgets.fieldpanel.listener.*;
|
||||||
import docking.widgets.fieldpanel.listener.LayoutModelListener;
|
import docking.widgets.fieldpanel.support.*;
|
||||||
import docking.widgets.fieldpanel.support.SingleRowLayout;
|
|
||||||
import docking.widgets.fieldpanel.support.ViewerPosition;
|
|
||||||
import docking.widgets.indexedscrollpane.*;
|
import docking.widgets.indexedscrollpane.*;
|
||||||
import docking.widgets.label.GDLabel;
|
import docking.widgets.label.GDLabel;
|
||||||
import docking.widgets.label.GLabel;
|
import docking.widgets.label.GLabel;
|
||||||
import ghidra.app.plugin.core.format.*;
|
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.HelpLocation;
|
||||||
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.exception.InvalidInputException;
|
import ghidra.util.exception.InvalidInputException;
|
||||||
import ghidra.util.layout.HorizontalLayout;
|
import ghidra.util.layout.HorizontalLayout;
|
||||||
import ghidra.util.layout.PairLayout;
|
import ghidra.util.layout.PairLayout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top level component that contains has a scrolled pane for the panel of
|
* Top level component that contains has a scrolled pane for the panel of components that show the
|
||||||
* components that show the view for each format.
|
* 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 ByteViewerPlugin plugin;
|
||||||
private List<ByteViewerComponent> viewList; // list of field viewers
|
private List<ByteViewerComponent> viewList; // list of field viewers
|
||||||
private FieldPanel indexPanel; // panel for showing indexes
|
private FieldPanel indexPanel; // panel for showing indexes
|
||||||
|
@ -79,10 +81,12 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
|
||||||
// changes while this flag is true
|
// changes while this flag is true
|
||||||
private final ByteViewerComponentProvider provider;
|
private final ByteViewerComponentProvider provider;
|
||||||
|
|
||||||
|
private List<AddressSetDisplayListener> displayListeners = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
ByteViewerPanel(ByteViewerComponentProvider provider) {
|
protected ByteViewerPanel(ByteViewerComponentProvider provider) {
|
||||||
super();
|
super();
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
bytesPerLine = ByteViewerComponentProvider.DEFAULT_BYTES_PER_LINE;
|
bytesPerLine = ByteViewerComponentProvider.DEFAULT_BYTES_PER_LINE;
|
||||||
|
@ -203,7 +207,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
void setMouseButtonHighlightColor(Color color) {
|
void setMouseButtonHighlightColor(Color color) {
|
||||||
this.highlightColor = color;
|
this.highlightColor = color;
|
||||||
for (int i = 0; i < viewList.size(); i++) {
|
for (int i = 0; i < viewList.size(); i++) {
|
||||||
ByteViewerComponent comp = viewList.get(i);
|
ByteViewerComponent comp = viewList.get(i);
|
||||||
comp.setMouseButtonHighlightColor(color);
|
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
|
* Set the byte blocks and create an new IndexMap object that will be passed to the index panel
|
||||||
* passed to the index panel and to each component that shows a format.
|
* and to each component that shows a format.
|
||||||
*/
|
*/
|
||||||
void setByteBlocks(ByteBlockSet blockSet) {
|
void setByteBlocks(ByteBlockSet blockSet) {
|
||||||
this.blockSet = 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.
|
* 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.
|
* Get the data format model of the view that is in focus.
|
||||||
*
|
*
|
||||||
* @return DataFormatModel model of the view in focus; return null
|
* @return DataFormatModel model of the view in focus; return null if no views are shown
|
||||||
* if no views are shown
|
|
||||||
*/
|
*/
|
||||||
DataFormatModel getCurrentModel() {
|
DataFormatModel getCurrentModel() {
|
||||||
if (currentView == null) {
|
if (currentView == null) {
|
||||||
|
@ -376,17 +388,21 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
|
||||||
/**
|
/**
|
||||||
* Returns the currently focused view.
|
* Returns the currently focused view.
|
||||||
*/
|
*/
|
||||||
ByteViewerComponent getCurrentComponent() {
|
public ByteViewerComponent getCurrentComponent() {
|
||||||
return currentView;
|
return currentView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ByteViewerComponent newByteViewerComponent(DataFormatModel model) {
|
||||||
|
return new ByteViewerComponent(this, new ByteViewerLayoutModel(), model, bytesPerLine, fm);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a view to the panel.
|
* Add a view to the panel.
|
||||||
|
*
|
||||||
* @param viewName name of the format, e.g., Hex, Ascii, etc.
|
* @param viewName name of the format, e.g., Hex, Ascii, etc.
|
||||||
* @param model model that understands the format
|
* @param model model that understands the format
|
||||||
* @param editMode true if edit mode is on
|
* @param editMode true if edit mode is on
|
||||||
* @param updateViewPosition true if the view position should be
|
* @param updateViewPosition true if the view position should be set
|
||||||
* set
|
|
||||||
*/
|
*/
|
||||||
ByteViewerComponent addView(String viewName, DataFormatModel model, boolean editMode,
|
ByteViewerComponent addView(String viewName, DataFormatModel model, boolean editMode,
|
||||||
boolean updateViewPosition) {
|
boolean updateViewPosition) {
|
||||||
|
@ -398,8 +414,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
|
||||||
|
|
||||||
// create new ByteViewerComponent
|
// create new ByteViewerComponent
|
||||||
|
|
||||||
ByteViewerComponent c =
|
ByteViewerComponent c = newByteViewerComponent(model);
|
||||||
new ByteViewerComponent(this, new ByteViewerLayoutModel(), model, bytesPerLine, fm);
|
|
||||||
c.setEditColor(editColor);
|
c.setEditColor(editColor);
|
||||||
c.setNonFocusCursorColor(cursorColor);
|
c.setNonFocusCursorColor(cursorColor);
|
||||||
c.setCurrentCursorColor(currentCursorColor);
|
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
|
* Set the given component to be the current view; called by the mouse listener in the
|
||||||
* mouse listener in the ByteViewerComponent when the user clicks in the
|
* ByteViewerComponent when the user clicks in the panel.
|
||||||
* panel.
|
|
||||||
*/
|
*/
|
||||||
void setCurrentView(ByteViewerComponent c) {
|
void setCurrentView(ByteViewerComponent c) {
|
||||||
if (currentView != null && currentView != 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
|
* Set the cursor color on the current view to show that it is in edit mode.
|
||||||
* edit mode.
|
|
||||||
*/
|
*/
|
||||||
void setEditMode(boolean editMode) {
|
void setEditMode(boolean editMode) {
|
||||||
for (int i = 0; i < viewList.size(); i++) {
|
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
|
* Set the bytes per line. Bytes per line dictates the number of fields displayed in a row.
|
||||||
* displayed in a row.
|
|
||||||
*/
|
*/
|
||||||
void setBytesPerLine(int bytesPerLine) {
|
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
|
* Check that each model for the views can support the given bytes per line value.
|
||||||
* bytes per line value.
|
*
|
||||||
* @throws InvalidInputException if a model cannot support the
|
* @throws InvalidInputException if a model cannot support the bytesPerLine value
|
||||||
* bytesPerLine value
|
|
||||||
*/
|
*/
|
||||||
void checkBytesPerLine(int numBytesPerLine) throws InvalidInputException {
|
void checkBytesPerLine(int numBytesPerLine) throws InvalidInputException {
|
||||||
for (int i = 0; i < viewList.size(); i++) {
|
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.
|
* Set the group size on the current view.
|
||||||
|
*
|
||||||
* @param groupSize new group size
|
* @param groupSize new group size
|
||||||
*/
|
*/
|
||||||
void setCurrentGroupSize(int groupSize) {
|
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;
|
* Set the insertion field and tell other views to change location; called when the
|
||||||
* called when the ByteViewerComponent receives a notification that
|
* ByteViewerComponent receives a notification that the cursor location has changed.
|
||||||
* the cursor location has changed.
|
*
|
||||||
* @param source source of the change
|
* @param source source of the change
|
||||||
* @param block block for the new location
|
* @param block block for the new location
|
||||||
* @param offset offset into the block
|
* @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
|
* Called from the ByteViewerComponent when it received a notification that the selection has
|
||||||
* that the selection has changed.
|
* changed.
|
||||||
|
*
|
||||||
* @param source source of the change
|
* @param source source of the change
|
||||||
* @param selection selection
|
* @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
|
* Return array of names of views in the order that they appear in the panel. The name array
|
||||||
* panel. The name array includes an entry for the index panel.
|
* includes an entry for the index panel.
|
||||||
*/
|
*/
|
||||||
DataModelInfo getDataModelInfo() {
|
DataModelInfo getDataModelInfo() {
|
||||||
|
|
||||||
|
@ -689,11 +702,11 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
|
||||||
*
|
*
|
||||||
* @return ViewerPosition top viewer position
|
* @return ViewerPosition top viewer position
|
||||||
*/
|
*/
|
||||||
ViewerPosition getViewerPosition() {
|
public ViewerPosition getViewerPosition() {
|
||||||
return indexPanel.getViewerPosition();
|
return indexPanel.getViewerPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setViewerPosition(ViewerPosition pos) {
|
public void setViewerPosition(ViewerPosition pos) {
|
||||||
indexPanel.setViewerPosition(pos.getIndex(), pos.getXOffset(), pos.getYOffset());
|
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.
|
* Restore the configuration of the plugin.
|
||||||
|
*
|
||||||
* @param fontMetrics font metrics
|
* @param fontMetrics font metrics
|
||||||
* @param newEditColor color for showing edits
|
* @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.
|
* Get the font metrics that the panel is using.
|
||||||
*/
|
*/
|
||||||
FontMetrics getFontMetrics() {
|
protected FontMetrics getFontMetrics() {
|
||||||
return fm;
|
return fm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected int getBytesPerLine() {
|
||||||
|
return bytesPerLine;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the components for this top level panel.
|
* Create the components for this top level panel.
|
||||||
*/
|
*/
|
||||||
|
@ -804,6 +822,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
|
||||||
indexPanel.enableSelection(false);
|
indexPanel.enableSelection(false);
|
||||||
indexPanel.setCursorOn(false);
|
indexPanel.setCursorOn(false);
|
||||||
indexPanel.setFocusable(false);
|
indexPanel.setFocusable(false);
|
||||||
|
indexPanel.addLayoutListener(this);
|
||||||
|
|
||||||
compPanel = new CompositePanel(indexPanel);
|
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
|
* Create a new index map and update the map in the index field adapter and all the views.
|
||||||
* and all the views.
|
|
||||||
*/
|
*/
|
||||||
private void updateIndexMap() {
|
private void updateIndexMap() {
|
||||||
if (blockSet == null) {
|
if (blockSet == null) {
|
||||||
|
@ -1027,6 +1045,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Getter for the list of ByteViewer Components
|
* Getter for the list of ByteViewer Components
|
||||||
|
*
|
||||||
* @return viewList the list of ByteViewerComponents
|
* @return viewList the list of ByteViewerComponents
|
||||||
*/
|
*/
|
||||||
public List<ByteViewerComponent> getViewList() {
|
public List<ByteViewerComponent> getViewList() {
|
||||||
|
@ -1046,6 +1065,43 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
|
||||||
public void flushChanges() {
|
public void flushChanges() {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected AddressSetView computeVisibleAddresses(List<AnchoredLayout> 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<AnchoredLayout> 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 {
|
class CompositePanel extends JPanel implements IndexedScrollable, IndexScrollListener {
|
||||||
|
@ -1211,4 +1267,5 @@ class CompositePanel extends JPanel implements IndexedScrollable, IndexScrollLis
|
||||||
processingIndexRangeChanged = false;
|
processingIndexRangeChanged = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,94 +15,54 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.byteviewer;
|
package ghidra.app.plugin.core.byteviewer;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.Iterator;
|
||||||
|
|
||||||
import org.jdom.Element;
|
|
||||||
|
|
||||||
import ghidra.app.CorePluginPackage;
|
import ghidra.app.CorePluginPackage;
|
||||||
import ghidra.app.events.*;
|
import ghidra.app.events.*;
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
import ghidra.app.services.*;
|
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.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.util.ProgramLocation;
|
|
||||||
import ghidra.program.util.ProgramSelection;
|
import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.util.SystemUtilities;
|
import ghidra.util.SystemUtilities;
|
||||||
import utility.function.Callback;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Visible Plugin to show ByteBlock data in various formats.
|
* Visible Plugin to show ByteBlock data in various formats.
|
||||||
*/
|
*/
|
||||||
//@formatter:off
|
|
||||||
@PluginInfo(
|
@PluginInfo(
|
||||||
status = PluginStatus.RELEASED,
|
status = PluginStatus.RELEASED,
|
||||||
packageName = CorePluginPackage.NAME,
|
packageName = CorePluginPackage.NAME,
|
||||||
category = PluginCategoryNames.BYTE_VIEWER,
|
category = PluginCategoryNames.BYTE_VIEWER,
|
||||||
shortDescription = "Displays bytes in memory",
|
shortDescription = "Displays bytes in memory",
|
||||||
description = "Provides a component for showing the bytes in memory. " +
|
description = "Provides a component for showing the bytes in memory. " +
|
||||||
"Additional plugins provide capabilites for this plugin" +
|
"Additional plugins provide capabilites for this plugin" +
|
||||||
" to show the bytes in various formats (e.g., hex, octal, decimal)." +
|
" 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.",
|
" The hex format plugin is loaded by default when this plugin is loaded.",
|
||||||
servicesRequired = { ProgramManager.class, GoToService.class, NavigationHistoryService.class, ClipboardService.class },
|
servicesRequired = {
|
||||||
|
ProgramManager.class, GoToService.class, NavigationHistoryService.class,
|
||||||
|
ClipboardService.class,
|
||||||
|
},
|
||||||
eventsConsumed = {
|
eventsConsumed = {
|
||||||
ProgramLocationPluginEvent.class, ProgramActivatedPluginEvent.class,
|
ProgramLocationPluginEvent.class, ProgramActivatedPluginEvent.class,
|
||||||
ProgramSelectionPluginEvent.class, ProgramHighlightPluginEvent.class, ProgramClosedPluginEvent.class,
|
ProgramSelectionPluginEvent.class, ProgramHighlightPluginEvent.class,
|
||||||
ByteBlockChangePluginEvent.class },
|
ProgramClosedPluginEvent.class, ByteBlockChangePluginEvent.class,
|
||||||
eventsProduced = { ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class, ByteBlockChangePluginEvent.class }
|
},
|
||||||
)
|
eventsProduced = {
|
||||||
//@formatter:on
|
ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class,
|
||||||
public class ByteViewerPlugin extends Plugin {
|
ByteBlockChangePluginEvent.class,
|
||||||
|
})
|
||||||
private Program currentProgram;
|
public class ByteViewerPlugin extends AbstractByteViewerPlugin<ProgramByteViewerComponentProvider> {
|
||||||
private boolean areEventsDisabled;
|
|
||||||
private ProgramLocation currentLocation;
|
|
||||||
|
|
||||||
private ProgramByteViewerComponentProvider connectedProvider;
|
|
||||||
|
|
||||||
private List<ProgramByteViewerComponentProvider> disconnectedProviders = new ArrayList<>();
|
|
||||||
|
|
||||||
public ByteViewerPlugin(PluginTool tool) {
|
public ByteViewerPlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
|
|
||||||
connectedProvider = new ProgramByteViewerComponentProvider(tool, this, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void showConnectedProvider() {
|
|
||||||
tool.showComponentProvider(connectedProvider, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected ProgramByteViewerComponentProvider createProvider(boolean isConnected) {
|
||||||
ClipboardService clipboardService = tool.getService(ClipboardService.class);
|
return new ProgramByteViewerComponentProvider(tool, this, isConnected);
|
||||||
if (clipboardService != null) {
|
|
||||||
connectedProvider.setClipboardService(clipboardService);
|
|
||||||
for (ProgramByteViewerComponentProvider 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 (ProgramByteViewerComponentProvider provider : disconnectedProviders) {
|
|
||||||
removeProvider(provider);
|
|
||||||
}
|
|
||||||
disconnectedProviders.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process the plugin event; delegates the processing to the
|
|
||||||
* byte block.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void processEvent(PluginEvent event) {
|
public void processEvent(PluginEvent event) {
|
||||||
if (event instanceof ProgramClosedPluginEvent) {
|
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
|
@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<Long, Object> 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<Long, Object> 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<Long, Object> stateMap = (Map<Long, Object>) state;
|
|
||||||
restoreUndoRedoState(stateMap, domainObject, connectedProvider);
|
|
||||||
for (ProgramByteViewerComponentProvider provider : disconnectedProviders) {
|
|
||||||
restoreUndoRedoState(stateMap, domainObject, provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void restoreUndoRedoState(Map<Long, Object> 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,
|
public void updateLocation(ProgramByteViewerComponentProvider provider,
|
||||||
ProgramLocationPluginEvent event, boolean export) {
|
ProgramLocationPluginEvent event, boolean export) {
|
||||||
|
|
||||||
|
@ -370,16 +108,34 @@ public class ByteViewerPlugin extends Plugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exportLocation(Program program, ProgramLocation location) {
|
@Override
|
||||||
GoToService service = tool.getService(GoToService.class);
|
public void fireProgramLocationPluginEvent(ProgramByteViewerComponentProvider provider,
|
||||||
if (service != null) {
|
ProgramLocationPluginEvent event) {
|
||||||
service.goTo(location, program);
|
|
||||||
|
if (SystemUtilities.isEqual(event.getLocation(), currentLocation)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentLocation = event.getLocation();
|
||||||
|
if (provider == connectedProvider) {
|
||||||
|
firePluginEvent(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeProvider(ProgramByteViewerComponentProvider provider) {
|
@Override
|
||||||
tool.removeComponentProvider(provider);
|
public void updateSelection(ByteViewerComponentProvider provider,
|
||||||
provider.dispose();
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import java.math.BigInteger;
|
||||||
import ghidra.app.events.ProgramLocationPluginEvent;
|
import ghidra.app.events.ProgramLocationPluginEvent;
|
||||||
import ghidra.app.events.ProgramSelectionPluginEvent;
|
import ghidra.app.events.ProgramSelectionPluginEvent;
|
||||||
import ghidra.app.plugin.core.format.*;
|
import ghidra.app.plugin.core.format.*;
|
||||||
|
import ghidra.program.model.address.AddressSet;
|
||||||
|
|
||||||
public class EmptyByteBlockSet implements ByteBlockSet {
|
public class EmptyByteBlockSet implements ByteBlockSet {
|
||||||
|
|
||||||
|
@ -53,4 +54,8 @@ public class EmptyByteBlockSet implements ByteBlockSet {
|
||||||
byte[] newValue) {
|
byte[] newValue) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AddressSet getAddressSet(ByteBlockSelection selection) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.util.List;
|
||||||
import ghidra.app.events.ProgramLocationPluginEvent;
|
import ghidra.app.events.ProgramLocationPluginEvent;
|
||||||
import ghidra.app.events.ProgramSelectionPluginEvent;
|
import ghidra.app.events.ProgramSelectionPluginEvent;
|
||||||
import ghidra.app.plugin.core.format.*;
|
import ghidra.app.plugin.core.format.*;
|
||||||
|
import ghidra.program.model.address.AddressSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ByteBlockSet for a File object.
|
* ByteBlockSet for a File object.
|
||||||
|
@ -148,4 +149,10 @@ class FileByteBlockSet implements ByteBlockSet {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AddressSet getAddressSet(ByteBlockSelection selection) {
|
||||||
|
// Not applicable
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,11 +38,12 @@ public class MemoryByteBlock implements ByteBlock {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
*
|
||||||
* @param program program used in generating plugin events
|
* @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
|
* @param block block from memory
|
||||||
*/
|
*/
|
||||||
MemoryByteBlock(Program program, Memory memory, MemoryBlock block) {
|
protected MemoryByteBlock(Program program, Memory memory, MemoryBlock block) {
|
||||||
this.program = program;
|
this.program = program;
|
||||||
this.memory = memory;
|
this.memory = memory;
|
||||||
this.block = block;
|
this.block = block;
|
||||||
|
@ -53,6 +54,7 @@ public class MemoryByteBlock implements ByteBlock {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the location representation for the given index.
|
* Get the location representation for the given index.
|
||||||
|
*
|
||||||
* @param index byte index into this block
|
* @param index byte index into this block
|
||||||
* @throws IndexOutOfBoundsException if index in not in 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
|
* Return the name to be used for describing the indexes into the byte block.
|
||||||
* byte block.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getIndexName() {
|
public String getIndexName() {
|
||||||
|
@ -93,19 +94,20 @@ public class MemoryByteBlock implements ByteBlock {
|
||||||
|
|
||||||
long size = block.getSize();
|
long size = block.getSize();
|
||||||
if (size < 0) {
|
if (size < 0) {
|
||||||
return BigInteger.valueOf(size + 0x8000000000000000L).subtract(
|
return BigInteger.valueOf(size + 0x8000000000000000L)
|
||||||
BigInteger.valueOf(0x8000000000000000L));
|
.subtract(
|
||||||
|
BigInteger.valueOf(0x8000000000000000L));
|
||||||
}
|
}
|
||||||
return BigInteger.valueOf(size);
|
return BigInteger.valueOf(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the byte at the given index.
|
* Set the byte at the given index.
|
||||||
|
*
|
||||||
* @param index byte index
|
* @param index byte index
|
||||||
* @param value value to set
|
* @param value value to set
|
||||||
* @throws ByteBlockAccessException if the block cannot be updated
|
* @throws ByteBlockAccessException if the block cannot be updated
|
||||||
* @throws IndexOutOfBoundsException if the given index is not in this
|
* @throws IndexOutOfBoundsException if the given index is not in this block.
|
||||||
* block.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setByte(BigInteger index, byte value) throws ByteBlockAccessException {
|
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.
|
* Get the byte at the given index.
|
||||||
|
*
|
||||||
* @param index byte index
|
* @param index byte index
|
||||||
* @throws ByteBlockAccessException if the block cannot be read
|
* @throws ByteBlockAccessException if the block cannot be read
|
||||||
* @throws IndexOutOfBoundsException if the given index is not in this
|
* @throws IndexOutOfBoundsException if the given index is not in this block.
|
||||||
* block.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public byte getByte(BigInteger index) throws ByteBlockAccessException {
|
public byte getByte(BigInteger index) throws ByteBlockAccessException {
|
||||||
|
@ -145,11 +147,11 @@ public class MemoryByteBlock implements ByteBlock {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the long at the given index.
|
* Set the long at the given index.
|
||||||
|
*
|
||||||
* @param index byte index
|
* @param index byte index
|
||||||
* @param value value to set
|
* @param value value to set
|
||||||
* @throws ByteBlockAccessException if the block cannot be updated
|
* @throws ByteBlockAccessException if the block cannot be updated
|
||||||
* @throws IndexOutOfBoundsException if the given index is not in this
|
* @throws IndexOutOfBoundsException if the given index is not in this block.
|
||||||
* block.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setLong(BigInteger index, long value) throws ByteBlockAccessException {
|
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.
|
* Get the long at the given index.
|
||||||
|
*
|
||||||
* @param index byte index
|
* @param index byte index
|
||||||
* @throws ByteBlockAccessException if the block cannot be read
|
* @throws ByteBlockAccessException if the block cannot be read
|
||||||
* @throws IndexOutOfBoundsException if the given index is not in this
|
* @throws IndexOutOfBoundsException if the given index is not in this block.
|
||||||
* block.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public long getLong(BigInteger index) throws ByteBlockAccessException {
|
public long getLong(BigInteger index) throws ByteBlockAccessException {
|
||||||
|
@ -183,6 +185,7 @@ public class MemoryByteBlock implements ByteBlock {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the block according to the bigEndian parameter.
|
* Set the block according to the bigEndian parameter.
|
||||||
|
*
|
||||||
* @param bigEndian true means big endian; false means little endian
|
* @param bigEndian true means big endian; false means little endian
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@ -193,10 +196,10 @@ public class MemoryByteBlock implements ByteBlock {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the int value at the given index.
|
* Get the int value at the given index.
|
||||||
|
*
|
||||||
* @param index byte index
|
* @param index byte index
|
||||||
* @throws ByteBlockAccessException if the block cannot be read
|
* @throws ByteBlockAccessException if the block cannot be read
|
||||||
* @throws IndexOutOfBoundsException if the given index is not in this
|
* @throws IndexOutOfBoundsException if the given index is not in this block.
|
||||||
* block.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getInt(BigInteger index) throws ByteBlockAccessException {
|
public int getInt(BigInteger index) throws ByteBlockAccessException {
|
||||||
|
@ -211,11 +214,11 @@ public class MemoryByteBlock implements ByteBlock {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the int at the given index.
|
* Set the int at the given index.
|
||||||
|
*
|
||||||
* @param index byte index
|
* @param index byte index
|
||||||
* @param value value to set
|
* @param value value to set
|
||||||
* @throws ByteBlockAccessException if the block cannot be updated
|
* @throws ByteBlockAccessException if the block cannot be updated
|
||||||
* @throws IndexOutOfBoundsException if the given index is not in this
|
* @throws IndexOutOfBoundsException if the given index is not in this block.
|
||||||
* block.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setInt(BigInteger index, int value) throws ByteBlockAccessException {
|
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 true if the block is big endian.
|
||||||
|
*
|
||||||
* @return false if the block is little endian
|
* @return false if the block is little endian
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@ -247,12 +251,11 @@ public class MemoryByteBlock implements ByteBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the natural alignment (offset) for the given radix. If there is
|
* Returns the natural alignment (offset) for the given radix. If there is no natural alignment,
|
||||||
* no natural alignment, it should return 0. A natural alignment only exists if
|
* it should return 0. A natural alignment only exists if there is some underlying indexing
|
||||||
* there is some underlying indexing structure that isn't based at 0. For example,
|
* structure that isn't based at 0. For example, if the underlying structure is address based
|
||||||
* if the underlying structure is address based and the starting address is not 0,
|
* and the starting address is not 0, then the natural alignment is the address offset mod the
|
||||||
* then the natural alignment is the address offset mod the radix (if the starting
|
* radix (if the starting address is 10 and the radix is 4, then then the alignment is 2)).
|
||||||
* address is 10 and the radix is 4, then then the alignment is 2)).
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getAlignment(int radix) {
|
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
|
* Check for whether edits are allowed at the given address; edits are not allowed if a code
|
||||||
* not allowed if a code unit (other than undefined data) exists at the
|
* unit (other than undefined data) exists at the given address.
|
||||||
* given address.
|
|
||||||
*/
|
*/
|
||||||
private void checkEditsAllowed(Address addr, long length) throws ByteBlockAccessException {
|
protected void checkEditsAllowed(Address addr, long length) throws ByteBlockAccessException {
|
||||||
if (!editAllowed(addr, length)) {
|
if (!editAllowed(addr, length)) {
|
||||||
String msg = "Instruction exists at address " + addr;
|
String msg = "Instruction exists at address " + addr;
|
||||||
if (length > 1) {
|
if (length > 1) {
|
||||||
|
@ -330,10 +332,10 @@ public class MemoryByteBlock implements ByteBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if undefined data exists at the given address; return
|
* Return true if undefined data exists at the given address; return false if code unit exists,
|
||||||
* false if code unit exists, thus editing is not allowed.
|
* thus editing is not allowed.
|
||||||
*/
|
*/
|
||||||
private boolean editAllowed(Address addr, long length) {
|
protected boolean editAllowed(Address addr, long length) {
|
||||||
Listing listing = program.getListing();
|
Listing listing = program.getListing();
|
||||||
Address a = addr;
|
Address a = addr;
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
|
|
|
@ -33,15 +33,15 @@ import ghidra.program.util.ProgramSelection;
|
||||||
/**
|
/**
|
||||||
* ByteBlockSet implementation for a Program object.
|
* ByteBlockSet implementation for a Program object.
|
||||||
*/
|
*/
|
||||||
class ProgramByteBlockSet implements ByteBlockSet {
|
public class ProgramByteBlockSet implements ByteBlockSet {
|
||||||
|
|
||||||
private MemoryBlock[] memBlocks;
|
private MemoryBlock[] memBlocks;
|
||||||
private Program program;
|
protected final Program program;
|
||||||
private ByteBlockChangeManager bbcm;
|
private ByteBlockChangeManager bbcm;
|
||||||
private ByteBlock[] blocks;
|
private ByteBlock[] blocks;
|
||||||
private final ProgramByteViewerComponentProvider provider;
|
private final ProgramByteViewerComponentProvider provider;
|
||||||
|
|
||||||
ProgramByteBlockSet(ProgramByteViewerComponentProvider provider, Program program,
|
protected ProgramByteBlockSet(ProgramByteViewerComponentProvider provider, Program program,
|
||||||
ByteBlockChangeManager bbcm) {
|
ByteBlockChangeManager bbcm) {
|
||||||
|
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
|
@ -53,7 +53,7 @@ class ProgramByteBlockSet implements ByteBlockSet {
|
||||||
this.bbcm = new ByteBlockChangeManager(this, bbcm);
|
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.
|
* Get the appropriate plugin event for the given block selection.
|
||||||
|
*
|
||||||
* @param source source to use in the event
|
* @param source source to use in the event
|
||||||
* @param selection selection to use to generate 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.
|
* Get a plugin event for the given block and offset.
|
||||||
|
*
|
||||||
* @param source source to use in the event
|
* @param source source to use in the event
|
||||||
* @param block block to use to generate the event
|
* @param block block to use to generate the event
|
||||||
* @param offset offset into the block
|
* @param offset offset into the block
|
||||||
|
@ -105,38 +107,54 @@ class ProgramByteBlockSet implements ByteBlockSet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteBlockSelection getBlockSelection(ProgramSelection selection) {
|
protected void collectBlockSelection(AddressRange range, List<ByteBlockRange> result) {
|
||||||
|
// TODO: These blocks really ought to be indexed by start address
|
||||||
AddressRangeIterator iter = selection.getAddressRanges();
|
// Use a NavigableMap
|
||||||
List<ByteBlockRange> list = new ArrayList<ByteBlockRange>(3);
|
// I'll wait to assess speed before worrying about tree indexing
|
||||||
|
// Use entries that groups the relevant objects instead of co-indexed arrays
|
||||||
while (iter.hasNext()) {
|
// Though a nicety, it becomes necessary if indexing/sorting by start address
|
||||||
AddressRange range = iter.next();
|
for (int i = 0; i < blocks.length; i++) {
|
||||||
|
Address blockStart = memBlocks[i].getStart();
|
||||||
for (int i = 0; i < blocks.length; i++) {
|
Address blockEnd = memBlocks[i].getEnd();
|
||||||
Address blockStart = memBlocks[i].getStart();
|
AddressRange intersection =
|
||||||
Address blockEnd = memBlocks[i].getEnd();
|
range.intersect(new AddressRangeImpl(blockStart, blockEnd));
|
||||||
AddressRange intersection =
|
if (intersection != null) {
|
||||||
range.intersect(new AddressRangeImpl(blockStart, blockEnd));
|
ByteBlockInfo startInfo = getByteBlockInfo(intersection.getMinAddress());
|
||||||
if (intersection != null) {
|
ByteBlockInfo endInfo = getByteBlockInfo(intersection.getMaxAddress());
|
||||||
ByteBlockInfo startInfo = getByteBlockInfo(intersection.getMinAddress());
|
ByteBlockRange br = new ByteBlockRange(startInfo.getBlock(),
|
||||||
ByteBlockInfo endInfo = getByteBlockInfo(intersection.getMaxAddress());
|
startInfo.getOffset(), endInfo.getOffset());
|
||||||
ByteBlockRange br = new ByteBlockRange(startInfo.getBlock(),
|
result.add(br);
|
||||||
startInfo.getOffset(), endInfo.getOffset());
|
|
||||||
list.add(br);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ByteBlockRange[] bRange = new ByteBlockRange[list.size()];
|
}
|
||||||
bRange = list.toArray(bRange);
|
|
||||||
|
|
||||||
return new ByteBlockSelection(bRange);
|
public ByteBlockSelection getBlockSelection(AddressRange range) {
|
||||||
|
List<ByteBlockRange> list = new ArrayList<>();
|
||||||
|
collectBlockSelection(range, list);
|
||||||
|
return new ByteBlockSelection(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteBlockSelection getBlockSelection(AddressRangeIterator iter) {
|
||||||
|
List<ByteBlockRange> 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.
|
* 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
|
* @param length number of bytes in question
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@ -150,6 +168,7 @@ class ProgramByteBlockSet implements ByteBlockSet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a notification that a byte block edit occurred.
|
* Send a notification that a byte block edit occurred.
|
||||||
|
*
|
||||||
* @param block block being edited
|
* @param block block being edited
|
||||||
* @param index offset into the block
|
* @param index offset into the block
|
||||||
* @param oldValue old byte values
|
* @param oldValue old byte values
|
||||||
|
@ -171,21 +190,21 @@ class ProgramByteBlockSet implements ByteBlockSet {
|
||||||
return bbcm.getUndoRedoState();
|
return bbcm.getUndoRedoState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void restoreUndoReoState(SaveState saveState) {
|
void restoreUndoRedoState(SaveState saveState) {
|
||||||
bbcm.restoreUndoRedoState(saveState);
|
bbcm.restoreUndoRedoState(saveState);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the byte block change manager
|
* Get the byte block change manager
|
||||||
*/
|
*/
|
||||||
ByteBlockChangeManager getByteBlockChangeManager() {
|
protected ByteBlockChangeManager getByteBlockChangeManager() {
|
||||||
return bbcm;
|
return bbcm;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the address for the given block and offset.
|
* 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++) {
|
for (int i = 0; i < blocks.length; i++) {
|
||||||
if (blocks[i] != block) {
|
if (blocks[i] != block) {
|
||||||
|
@ -207,7 +226,7 @@ class ProgramByteBlockSet implements ByteBlockSet {
|
||||||
/**
|
/**
|
||||||
* Given an address, get the byte block info.
|
* Given an address, get the byte block info.
|
||||||
*/
|
*/
|
||||||
ByteBlockInfo getByteBlockInfo(Address address) {
|
protected ByteBlockInfo getByteBlockInfo(Address address) {
|
||||||
|
|
||||||
if (!program.getMemory().contains(address)) {
|
if (!program.getMemory().contains(address)) {
|
||||||
// this block set is out of date...eventually a new
|
// 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());
|
long off = address.subtract(memBlocks[i].getStart());
|
||||||
BigInteger offset =
|
BigInteger offset =
|
||||||
(off < 0)
|
(off < 0)
|
||||||
? BigInteger.valueOf(off + 0x8000000000000000L).subtract(
|
? BigInteger.valueOf(off + 0x8000000000000000L)
|
||||||
BigInteger.valueOf(0x8000000000000000L))
|
.subtract(
|
||||||
|
BigInteger.valueOf(0x8000000000000000L))
|
||||||
: BigInteger.valueOf(off);
|
: BigInteger.valueOf(off);
|
||||||
return new ByteBlockInfo(blocks[i], offset);
|
return new ByteBlockInfo(blocks[i], offset);
|
||||||
}
|
}
|
||||||
|
@ -236,15 +256,15 @@ class ProgramByteBlockSet implements ByteBlockSet {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Address getBlockStart(ByteBlock block) {
|
protected Address getBlockStart(ByteBlock block) {
|
||||||
return getAddress(block, BigInteger.ZERO);
|
return getAddress(block, BigInteger.ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
Address getBlockStart(int blockNumber) {
|
protected Address getBlockStart(int blockNumber) {
|
||||||
return memBlocks[blockNumber].getStart();
|
return memBlocks[blockNumber].getStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
int getByteBlockNumber(Address blockStartAddr) {
|
protected int getByteBlockNumber(Address blockStartAddr) {
|
||||||
for (int i = 0; i < memBlocks.length; i++) {
|
for (int i = 0; i < memBlocks.length; i++) {
|
||||||
if (memBlocks[i].getStart().compareTo(blockStartAddr) == 0) {
|
if (memBlocks[i].getStart().compareTo(blockStartAddr) == 0) {
|
||||||
return i;
|
return i;
|
||||||
|
@ -253,8 +273,8 @@ class ProgramByteBlockSet implements ByteBlockSet {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
AddressSet getAddressSet(ByteBlockSelection selection) {
|
@Override
|
||||||
|
public AddressSet getAddressSet(ByteBlockSelection selection) {
|
||||||
AddressSet addrSet = new AddressSet();
|
AddressSet addrSet = new AddressSet();
|
||||||
|
|
||||||
for (int i = 0; i < selection.getNumberOfRanges(); i++) {
|
for (int i = 0; i < selection.getNumberOfRanges(); i++) {
|
||||||
|
@ -267,15 +287,19 @@ class ProgramByteBlockSet implements ByteBlockSet {
|
||||||
return addrSet;
|
return addrSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getMemoryBlocks() {
|
protected void newMemoryBlocks() {
|
||||||
Memory memory = program.getMemory();
|
Memory memory = program.getMemory();
|
||||||
memBlocks = program.getMemory().getBlocks();
|
memBlocks = memory.getBlocks();
|
||||||
blocks = new ByteBlock[memBlocks.length];
|
blocks = new ByteBlock[memBlocks.length];
|
||||||
for (int i = 0; i < memBlocks.length; i++) {
|
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
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
// nothing to do?!?!?
|
// nothing to do?!?!?
|
||||||
|
|
|
@ -68,10 +68,14 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
|
||||||
|
|
||||||
private boolean disposed;
|
private boolean disposed;
|
||||||
|
|
||||||
public ProgramByteViewerComponentProvider(PluginTool tool, ByteViewerPlugin plugin,
|
public ProgramByteViewerComponentProvider(PluginTool tool, AbstractByteViewerPlugin<?> plugin,
|
||||||
boolean isConnected) {
|
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;
|
this.isConnected = isConnected;
|
||||||
setIcon(ResourceManager.loadImage("images/binaryData.gif"));
|
setIcon(ResourceManager.loadImage("images/binaryData.gif"));
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
|
@ -215,7 +219,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
|
||||||
clipboardProvider.setPasteEnabled(enabled);
|
clipboardProvider.setPasteEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
void doSetProgram(Program newProgram) {
|
protected void doSetProgram(Program newProgram) {
|
||||||
setOptionsAction.setEnabled(newProgram != null);
|
setOptionsAction.setEnabled(newProgram != null);
|
||||||
cloneByteViewerAction.setEnabled(newProgram != null);
|
cloneByteViewerAction.setEnabled(newProgram != null);
|
||||||
|
|
||||||
|
@ -239,7 +243,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
|
||||||
updateTitle();
|
updateTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTitle() {
|
protected void updateTitle() {
|
||||||
String title =
|
String title =
|
||||||
"Bytes: " + (program == null ? "No Program" : program.getDomainFile().getName());
|
"Bytes: " + (program == null ? "No Program" : program.getDomainFile().getName());
|
||||||
if (!isConnected()) {
|
if (!isConnected()) {
|
||||||
|
@ -428,7 +432,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
|
||||||
return loc;
|
return loc;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setLocation(ProgramLocation location) {
|
protected void setLocation(ProgramLocation location) {
|
||||||
setLocation(location, false);
|
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) {
|
if (program == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -572,12 +576,12 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
|
||||||
blockSet.dispose();
|
blockSet.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
blockSet = getByteBlockSet(changeManager);
|
blockSet = newByteBlockSet(changeManager);
|
||||||
panel.setByteBlocks(blockSet);
|
panel.setByteBlocks(blockSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void updateSelection(ByteBlockSelection selection) {
|
protected void updateSelection(ByteBlockSelection selection) {
|
||||||
ProgramSelectionPluginEvent event = blockSet.getPluginEvent(plugin.getName(), selection);
|
ProgramSelectionPluginEvent event = blockSet.getPluginEvent(plugin.getName(), selection);
|
||||||
currentSelection = event.getSelection();
|
currentSelection = event.getSelection();
|
||||||
plugin.updateSelection(this, event, program);
|
plugin.updateSelection(this, event, program);
|
||||||
|
@ -586,7 +590,8 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void updateLocation(ByteBlock block, BigInteger blockOffset, int column, boolean export) {
|
protected void updateLocation(ByteBlock block, BigInteger blockOffset, int column,
|
||||||
|
boolean export) {
|
||||||
ProgramLocationPluginEvent event =
|
ProgramLocationPluginEvent event =
|
||||||
blockSet.getPluginEvent(plugin.getName(), block, blockOffset, column);
|
blockSet.getPluginEvent(plugin.getName(), block, blockOffset, column);
|
||||||
currentLocation = event.getLocation();
|
currentLocation = event.getLocation();
|
||||||
|
@ -595,7 +600,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
|
||||||
contextChanged();
|
contextChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void readDataState(SaveState saveState) {
|
protected void readDataState(SaveState saveState) {
|
||||||
unRegisterNavigatable();
|
unRegisterNavigatable();
|
||||||
initializeInstanceID(saveState.getLong("NAV_ID", getInstanceID()));
|
initializeInstanceID(saveState.getLong("NAV_ID", getInstanceID()));
|
||||||
registerNavigatable();
|
registerNavigatable();
|
||||||
|
@ -632,10 +637,10 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SaveState saveState = (SaveState) state;
|
SaveState saveState = (SaveState) state;
|
||||||
blockSet.restoreUndoReoState(saveState);
|
blockSet.restoreUndoRedoState(saveState);
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeDataState(SaveState saveState) {
|
protected void writeDataState(SaveState saveState) {
|
||||||
saveState.putLong("NAV_ID", getInstanceID());
|
saveState.putLong("NAV_ID", getInstanceID());
|
||||||
ByteBlockInfo info = panel.getCursorLocation();
|
ByteBlockInfo info = panel.getCursorLocation();
|
||||||
int blockNumber = -1;
|
int blockNumber = -1;
|
||||||
|
@ -708,6 +713,22 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
|
||||||
return dataFormatModels;
|
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
|
// Inner Classes
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
@ -727,23 +748,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionContext context) {
|
public void actionPerformed(ActionContext context) {
|
||||||
ProgramByteViewerComponentProvider newProvider =
|
cloneWindow();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,32 +15,33 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.byteviewer;
|
package ghidra.app.plugin.core.byteviewer;
|
||||||
|
|
||||||
import ghidra.framework.plugintool.Plugin;
|
|
||||||
|
|
||||||
import java.awt.event.InputEvent;
|
import java.awt.event.InputEvent;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
import resources.ResourceManager;
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
|
import ghidra.framework.plugintool.Plugin;
|
||||||
|
import resources.ResourceManager;
|
||||||
|
|
||||||
class ToggleEditAction extends ToggleDockingAction {
|
class ToggleEditAction extends ToggleDockingAction {
|
||||||
private final ByteViewerComponentProvider provider;
|
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 ) );
|
|
||||||
|
|
||||||
setDescription("Enable/Disable editing of bytes in Byte Viewer panels.");
|
public ToggleEditAction(ByteViewerComponentProvider provider, Plugin plugin) {
|
||||||
setSelected(false);
|
super("Enable/Disable Byteviewer Editing", plugin.getName());
|
||||||
setEnabled(true);
|
this.provider = provider;
|
||||||
}
|
setToolBarData(new ToolBarData(
|
||||||
@Override
|
ResourceManager.loadImage("images/editbytes.gif"), "Byteviewer"));
|
||||||
public void actionPerformed(ActionContext context) {
|
setKeyBindingData(new KeyBindingData(
|
||||||
boolean isSelected = isSelected();
|
KeyEvent.VK_E, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK));
|
||||||
provider.setEditMode(isSelected);
|
|
||||||
}
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.
|
* Defines a selection for byte blocks.
|
||||||
|
*
|
||||||
|
* The selection is a list of disjoint ranges.
|
||||||
*/
|
*/
|
||||||
public class ByteBlockSelection {
|
public class ByteBlockSelection {
|
||||||
|
private final List<ByteBlockRange> list;
|
||||||
|
|
||||||
private List<ByteBlockRange> list;
|
/**
|
||||||
|
* Construct a selection from a list of ranges
|
||||||
|
*
|
||||||
|
* @param ranges the ranges
|
||||||
|
*/
|
||||||
|
public ByteBlockSelection(List<ByteBlockRange> ranges) {
|
||||||
|
list = new ArrayList<>(ranges);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct an empty selection.
|
* Construct an empty selection.
|
||||||
*/
|
*/
|
||||||
public ByteBlockSelection() {
|
public ByteBlockSelection() {
|
||||||
list = new ArrayList<ByteBlockRange>();
|
this(new ArrayList<>());
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
public ByteBlockSelection(ByteBlockRange[] ranges) {
|
|
||||||
List<ByteBlockRange> l = Arrays.asList(ranges);
|
|
||||||
list = new ArrayList<ByteBlockRange>(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a range to the selection.
|
* Construct a selection from an array of ranges
|
||||||
*/
|
*
|
||||||
public void add(ByteBlockRange range) {
|
* @param ranges the ranges
|
||||||
list.add(range);
|
*/
|
||||||
}
|
public ByteBlockSelection(ByteBlockRange[] ranges) {
|
||||||
|
this(Arrays.asList(ranges));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the number of byte block ranges in this selection.
|
* Add a range to the selection.
|
||||||
*
|
*
|
||||||
* @return int
|
* @param range the range to add
|
||||||
*/
|
*/
|
||||||
public int getNumberOfRanges() {
|
public void add(ByteBlockRange range) {
|
||||||
return list.size();
|
list.add(range);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the byte block range at the given index.
|
* Get the number of byte block ranges in this selection.
|
||||||
*/
|
*
|
||||||
public ByteBlockRange getRange(int index) {
|
* @return the number of (disjoint) ranges
|
||||||
return list.get(index);
|
*/
|
||||||
}
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,32 +19,35 @@ import java.math.BigInteger;
|
||||||
|
|
||||||
import ghidra.app.events.ProgramLocationPluginEvent;
|
import ghidra.app.events.ProgramLocationPluginEvent;
|
||||||
import ghidra.app.events.ProgramSelectionPluginEvent;
|
import ghidra.app.events.ProgramSelectionPluginEvent;
|
||||||
|
import ghidra.program.model.address.AddressSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface to define methods for getting byte blocks and translating
|
* Interface to define methods for getting byte blocks and translating events.
|
||||||
* events.
|
|
||||||
*/
|
*/
|
||||||
public interface ByteBlockSet {
|
public interface ByteBlockSet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the blocks in this set.
|
* Get the blocks in this set.
|
||||||
|
*
|
||||||
* @return the blocks
|
* @return the blocks
|
||||||
*/
|
*/
|
||||||
public ByteBlock[] getBlocks();
|
public ByteBlock[] getBlocks();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a plugin event for the given block and offset.
|
* Get a plugin event for the given block and offset.
|
||||||
|
*
|
||||||
* @param source source to use in the event
|
* @param source source to use in the event
|
||||||
* @param block block to use to generate the event
|
* @param block block to use to generate the event
|
||||||
* @param offset offset into the block
|
* @param offset offset into the block
|
||||||
* @param column the column within the UI byte field
|
* @param column the column within the UI byte field
|
||||||
* @return the event
|
* @return the event
|
||||||
*/
|
*/
|
||||||
public ProgramLocationPluginEvent getPluginEvent(String source, ByteBlock block,
|
public ProgramLocationPluginEvent getPluginEvent(String source, ByteBlock block,
|
||||||
BigInteger offset, int column);
|
BigInteger offset, int column);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the appropriate plugin event for the given block selection.
|
* Get the appropriate plugin event for the given block selection.
|
||||||
|
*
|
||||||
* @param source source to use in the event
|
* @param source source to use in the event
|
||||||
* @param selection selection to use to generate the event
|
* @param selection selection to use to generate the event
|
||||||
* @return the event
|
* @return the event
|
||||||
|
@ -53,8 +56,9 @@ public interface ByteBlockSet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the block has been changed at the given index.
|
* 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
|
* @param length number of bytes in question
|
||||||
* @return true if changed
|
* @return true if changed
|
||||||
*/
|
*/
|
||||||
|
@ -62,6 +66,7 @@ public interface ByteBlockSet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a notification that a byte block edit occurred.
|
* Send a notification that a byte block edit occurred.
|
||||||
|
*
|
||||||
* @param block block being edited
|
* @param block block being edited
|
||||||
* @param index offset into the block
|
* @param index offset into the block
|
||||||
* @param oldValue old byte values
|
* @param oldValue old byte values
|
||||||
|
@ -72,7 +77,14 @@ public interface ByteBlockSet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Release resources that this object may be using.
|
* Release resources that this object may be using.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public void dispose();
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,9 +50,9 @@ public class FieldPanel extends JPanel
|
||||||
private boolean repaintPosted;
|
private boolean repaintPosted;
|
||||||
private boolean inFocus;
|
private boolean inFocus;
|
||||||
|
|
||||||
private BackgroundColorModel backgroundColorModel =
|
protected BackgroundColorModel backgroundColorModel =
|
||||||
new DefaultBackgroundColorModel(Color.WHITE);
|
new DefaultBackgroundColorModel(Color.WHITE);
|
||||||
private PaintContext paintContext = new PaintContext();
|
protected PaintContext paintContext = new PaintContext();
|
||||||
|
|
||||||
private AnchoredLayoutHandler layoutHandler;
|
private AnchoredLayoutHandler layoutHandler;
|
||||||
private CursorHandler cursorHandler = new CursorHandler();
|
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 backgroundColor = backgroundColorModel.getBackgroundColor(layoutIndex);
|
||||||
Color defaultBackColor = backgroundColorModel.getDefaultBackgroundColor();
|
Color defaultBackColor = backgroundColorModel.getDefaultBackgroundColor();
|
||||||
boolean isDefault = backgroundColor.equals(defaultBackColor);
|
boolean isDefault = backgroundColor.equals(defaultBackColor);
|
||||||
|
|
|
@ -129,8 +129,8 @@ public class ColorUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A method to produce a color (either black or white) that contrasts with the given color.
|
* A method to produce a color (either black or white) that contrasts with the given color. This
|
||||||
* This is useful for finding a readable foreground color for a given background.
|
* is useful for finding a readable foreground color for a given background.
|
||||||
*
|
*
|
||||||
* @param color the color for which to find a contrast.
|
* @param color the color for which to find a contrast.
|
||||||
* @return the contrasting color.
|
* @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
|
* Takes the first color, blending into it the second color, using the given ratio. A lower
|
||||||
* lower ratio (say .1f) signals to use very little of the first color; a larger ratio
|
* ratio (say .1f) signals to use very little of the first color; a larger ratio signals to use
|
||||||
* signals to use more of the first color.
|
* more of the first color.
|
||||||
*
|
*
|
||||||
* @param c1 the first color
|
* @param c1 the first color
|
||||||
* @param c2 the second color
|
* @param c2 the second color
|
||||||
|
@ -185,4 +185,50 @@ public class ColorUtils {
|
||||||
|
|
||||||
return color;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue