Merge remote-tracking branch

'origin/GP-1984_Dan_RegisterAndWatchDataTypeSettings--SQUASHED'

Conflicts:
	Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java
This commit is contained in:
Ryan Kurtz 2022-08-16 14:30:38 -04:00
commit e999a24433
18 changed files with 540 additions and 67 deletions

View file

@ -92,6 +92,12 @@
recorder, but they can still be populated by the user. Modifying the values of unknown
registers cannot affect the target. Register sets are memorized per compiler specification.</P>
<H3><A name="type_settings"></A>Register Type Settings</H3>
<P>This action is available on the context menu when there is a single register selected with a
data type assigned. It permits the adjustment of that data type's settings, e.g., to display
decimal vs hexadecimal. The settings are saved to the data unit for the register.</P>
<H3><A name="enable_edits"></A>Enable Edits</H3>
<P>This toggle is a write protector for machine state. To modify register values, this toggle

View file

@ -109,6 +109,12 @@
listing. That is, it attempts to apply the selected data type to the evaluated address, sizing
it to the value's size.</P>
<H3><A name="type_settings"></A>Watch Type Settings</H3>
<P>This action is available on the context menu when there is a single watch selected with a
data type assigned. It permits the adjustment of that data type's settings, e.g., to display
decimal vs. hexadecimal.</P>
<H3><A name="select_addresses"></A>Select Range</H3>
<P>This action is available when there's an active trace, and at least one watch with memory

View file

@ -2385,4 +2385,12 @@ public interface DebuggerResources {
String DESCRIPTION_CHOOSE_MORE_PLATFORMS =
"Choose from more platforms to use with the current trace";
String NAME_CLEAR_REGISTER_TYPE = "Clear Register Type";
String DESCRIPTION_CLEAR_REGISTER_TYPE = "Clear the register's data type";
String NAME_REGISTER_TYPE_SETTINGS = "Register Type Settings";
String DESCRIPTION_REGISTER_TYPE_SETTINGS = "Set the register's data type settings";
String NAME_WATCH_TYPE_SETTINGS = "Watch Type Settings";
String DESCRIPTION_WATCH_TYPE_SETTINGS = "Set the watch's data type settings";
}

View file

@ -40,6 +40,7 @@ import docking.actions.PopupActionProvider;
import docking.widgets.table.*;
import docking.widgets.table.ColumnSortState.SortDirection;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.data.DataSettingsDialog;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerProvider;
@ -53,18 +54,19 @@ import ghidra.base.widgets.table.DataTypeTableCellEditor;
import ghidra.dbg.error.DebuggerModelAccessException;
import ghidra.dbg.target.TargetRegisterBank;
import ghidra.dbg.target.TargetThread;
import ghidra.docking.settings.Settings;
import ghidra.framework.model.DomainObject;
import ghidra.framework.model.DomainObjectChangeRecord;
import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.SaveState;
import ghidra.framework.options.annotation.*;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeEncodeException;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Data;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.trace.model.*;
import ghidra.trace.model.Trace.*;
@ -74,8 +76,7 @@ import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.*;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.*;
import ghidra.util.data.DataTypeParser.AllowedDataTypes;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException;
@ -87,6 +88,50 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
implements DebuggerProvider, PopupActionProvider {
private static final String KEY_DEBUGGER_COORDINATES = "DebuggerCoordinates";
interface ClearRegisterType {
String NAME = DebuggerResources.NAME_CLEAR_REGISTER_TYPE;
String DESCRIPTION = DebuggerResources.DESCRIPTION_CLEAR_REGISTER_TYPE;
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION);
}
}
interface RegisterTypeSettings {
String NAME = DebuggerResources.NAME_REGISTER_TYPE_SETTINGS;
String DESCRIPTION = DebuggerResources.DESCRIPTION_REGISTER_TYPE_SETTINGS;
String HELP_ANCHOR = "type_settings";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.popupMenuPath(NAME)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
/**
* This only exists so that tests can access it
*/
protected static class RegisterDataSettingsDialog extends DataSettingsDialog {
public RegisterDataSettingsDialog(Data data) {
super(data);
}
@Override
protected Settings getSettings() {
return super.getSettings();
}
@Override
protected void okCallback() {
super.okCallback();
}
}
protected enum RegisterTableColumns
implements EnumeratedTableColumn<RegisterTableColumns, RegisterRow> {
FAV("Fav", Boolean.class, RegisterRow::isFavorite, RegisterRow::setFavorite, //
@ -429,7 +474,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
GhidraTable regsTable;
RegistersTableModel regsTableModel = new RegistersTableModel();
private GhidraTableFilterPanel<RegisterRow> regsFilterPanel;
GhidraTableFilterPanel<RegisterRow> regsFilterPanel;
Map<Register, RegisterRow> regMap = new HashMap<>();
private final DebuggerAvailableRegistersDialog availableRegsDialog;
@ -438,6 +483,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
DockingAction actionCreateSnapshot;
ToggleDockingAction actionEnableEdits;
DockingAction actionClearDataType;
DockingAction actionDataTypeSettings;
DebuggerRegisterActionContext myActionContext;
AddressSetView viewKnown;
@ -618,11 +664,16 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
.onAction(c -> {
})
.buildAndInstallLocal(this);
actionClearDataType = new ActionBuilder("Clear Register Type", plugin.getName())
actionClearDataType = ClearRegisterType.builder(plugin)
.enabledWhen(c -> current.getThread() != null)
.keyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0))
.onAction(c -> clearDataTypeActivated())
.buildAndInstallLocal(this);
actionDataTypeSettings = RegisterTypeSettings.builder(plugin)
.withContext(DebuggerRegisterActionContext.class)
.enabledWhen(this::contextHasSingleRegisterWithType)
.onAction(this::dataTypeSettingsActivated)
.buildAndInstallLocal(this);
}
private void selectRegistersActivated() {
@ -653,6 +704,22 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
row.setDataType(null);
}
private boolean contextHasSingleRegisterWithType(DebuggerRegisterActionContext ctx) {
return ctx.getSelected() != null && ctx.getSelected().getData() != null;
}
private void dataTypeSettingsActivated(DebuggerRegisterActionContext ctx) {
RegisterRow row = ctx.getSelected();
if (row == null) {
return;
}
Data data = row.getData();
if (data == null) {
return;
}
tool.showDialog(new RegisterDataSettingsDialog(data));
}
// TODO: "Refresh" action to flush cache and re-fetch selected registers
@Override

View file

@ -21,6 +21,7 @@ import java.util.Objects;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Data;
import ghidra.util.Msg;
public class RegisterRow {
@ -84,6 +85,10 @@ public class RegisterRow {
return provider.getRegisterValue(register);
}
public Data getData() {
return provider.getRegisterData(register);
}
public void setDataType(DataType dataType) {
provider.writeRegisterDataType(register, dataType);
}

View file

@ -24,24 +24,23 @@ import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
@PluginInfo( //
shortDescription = "Debugger watches manager", //
description = "GUI to watch values of expressions", //
category = PluginCategoryNames.DEBUGGER, //
packageName = DebuggerPluginPackage.NAME, //
status = PluginStatus.RELEASED, //
eventsConsumed = {
TraceActivatedPluginEvent.class, //
}, //
servicesRequired = { //
DebuggerModelService.class, //
DebuggerTraceManagerService.class, //
DataTypeManagerService.class, // For DataType selection field
} //
)
@PluginInfo(
shortDescription = "Debugger watches manager",
description = "GUI to watch values of expressions",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
eventsConsumed = {
TraceActivatedPluginEvent.class,
},
servicesRequired = {
DebuggerModelService.class,
DebuggerTraceManagerService.class,
DataTypeManagerService.class, // For DataType selection field
})
public class DebuggerWatchesPlugin extends AbstractDebuggerPlugin {
private DebuggerWatchesProvider provider;
DebuggerWatchesProvider provider;
public DebuggerWatchesPlugin(PluginTool tool) {
super(tool);

View file

@ -29,14 +29,18 @@ import javax.swing.*;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import org.jdom.Element;
import docking.ActionContext;
import docking.WindowPosition;
import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import docking.action.builder.ActionBuilder;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.context.ListingActionContext;
import ghidra.app.context.ProgramLocationActionContext;
import ghidra.app.plugin.core.data.AbstractSettingsDialog;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
@ -47,14 +51,13 @@ import ghidra.app.services.*;
import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer;
import ghidra.base.widgets.table.DataTypeTableCellEditor;
import ghidra.docking.settings.Settings;
import ghidra.docking.settings.*;
import ghidra.framework.model.DomainObject;
import ghidra.framework.model.DomainObjectChangeRecord;
import ghidra.framework.options.SaveState;
import ghidra.framework.options.annotation.AutoOptionDefined;
import ghidra.framework.options.annotation.HelpInfo;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.pcode.exec.trace.TraceSleighUtils;
import ghidra.program.model.address.*;
@ -71,16 +74,64 @@ import ghidra.trace.model.Trace.TraceMemoryStateChangeType;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.*;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
import ghidra.util.table.column.AbstractGColumnRenderer;
public class DebuggerWatchesProvider extends ComponentProviderAdapter {
private static final String KEY_EXPRESSION_LIST = "expressionList";
private static final String KEY_TYPE_LIST = "typeList";
private static final String KEY_ROW_COUNT = "rowCount";
private static final String PREFIX_ROW = "row";
interface WatchTypeSettings {
String NAME = DebuggerResources.NAME_WATCH_TYPE_SETTINGS;
String DESCRIPTION = DebuggerResources.DESCRIPTION_WATCH_TYPE_SETTINGS;
String HELP_ANCHOR = "type_settings";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.popupMenuPath(NAME)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
protected static class WatchDataSettingsDialog extends AbstractSettingsDialog {
private final WatchRow row;
public WatchDataSettingsDialog(WatchRow row) {
super("Data Type Settings", row.getDataType().getSettingsDefinitions(),
row.getSettings());
this.row = row;
}
@Override
protected Settings getSettings() {
return super.getSettings();
}
@Override
protected void okCallback() {
super.okCallback();
}
@Override
protected String[] getSuggestedValues(StringSettingsDefinition settingsDefinition) {
if (!settingsDefinition.supportsSuggestedValues()) {
return null;
}
return settingsDefinition.getSuggestedValues(row.getSettings());
}
@Override
protected void applySettings() throws CancelledException {
copySettings(getSettings(), row.getSettings(), getSettingsDefinitions());
row.settingsChanged();
}
}
protected enum WatchTableColumns implements EnumeratedTableColumn<WatchTableColumns, WatchRow> {
EXPRESSION("Expression", String.class, WatchRow::getExpression, WatchRow::setExpression),
@ -151,6 +202,12 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
}
}
protected static void copySettings(Settings src, Settings dst, SettingsDefinition[] defs) {
for (SettingsDefinition sd : defs) {
sd.copySetting(src, dst);
}
}
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
if (!Objects.equals(a.getTrace(), b.getTrace())) {
return false;
@ -299,6 +356,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
DockingAction actionSelectAllReads;
DockingAction actionAdd;
DockingAction actionRemove;
DockingAction actionDataTypeSettings;
DockingAction actionAddFromLocation;
DockingAction actionAddFromRegister;
@ -442,6 +500,12 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
.onAction(this::activatedRemove)
.buildAndInstallLocal(this);
actionDataTypeSettings = WatchTypeSettings.builder(plugin)
.withContext(DebuggerWatchActionContext.class)
.enabledWhen(this::selIsOneWithDataType)
.onAction(this::activatedDataTypeSettings)
.buildAndInstallLocal(this);
// Pop-up context actions
actionAddFromLocation = WatchAction.builder(plugin)
.withContext(ProgramLocationActionContext.class)
@ -491,6 +555,11 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
return false;
}
protected boolean selIsOneWithDataType(DebuggerWatchActionContext ctx) {
WatchRow row = ctx.getWatchRow();
return row != null && row.getDataType() != null;
}
private void activatedApplyDataType(DebuggerWatchActionContext context) {
if (current.getTrace() == null) {
return;
@ -526,7 +595,8 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
UndoableTransaction.start(current.getTrace(), "Apply Watch Data Type")) {
try {
listing.clearCodeUnits(row.getAddress(), row.getRange().getMaxAddress(), false);
listing.createData(address, dataType, size);
Data data = listing.createData(address, dataType, size);
copySettings(row.getSettings(), data, dataType.getSettingsDefinitions());
}
catch (CodeUnitInsertionException e) {
errs.add(address + " " + dataType + "(" + size + "): " + e.getMessage());
@ -579,6 +649,18 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
watchTableModel.deleteWith(context.getWatchRows()::contains);
}
private void activatedDataTypeSettings(DebuggerWatchActionContext context) {
WatchRow row = context.getWatchRow();
if (row == null) {
return;
}
DataType type = row.getDataType();
if (type == null) {
return;
}
tool.showDialog(new WatchDataSettingsDialog(row));
}
private ProgramLocation getDynamicLocation(ProgramLocation someLoc) {
if (someLoc == null) {
return null;
@ -797,26 +879,30 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
public void writeConfigState(SaveState saveState) {
List<WatchRow> rows = List.copyOf(watchTableModel.getModelData());
String[] expressions = rows.stream().map(WatchRow::getExpression).toArray(String[]::new);
String[] types = rows.stream().map(WatchRow::getTypePath).toArray(String[]::new);
saveState.putStrings(KEY_EXPRESSION_LIST, expressions);
saveState.putStrings(KEY_TYPE_LIST, types);
saveState.putInt(KEY_ROW_COUNT, rows.size());
for (int i = 0; i < rows.size(); i++) {
WatchRow row = rows.get(i);
String stateName = PREFIX_ROW + i;
SaveState rowState = new SaveState();
row.writeConfigState(rowState);
saveState.putXmlElement(stateName, rowState.saveToXml());
}
}
public void readConfigState(SaveState saveState) {
String[] expressions = saveState.getStrings(KEY_EXPRESSION_LIST, new String[] {});
String[] types = saveState.getStrings(KEY_TYPE_LIST, new String[] {});
if (expressions.length != types.length) {
Msg.error(this, "Watch provider config error. Unequal number of expressions and types");
return;
}
int len = expressions.length;
int rowCount = saveState.getInt(KEY_ROW_COUNT, 0);
List<WatchRow> rows = new ArrayList<>();
for (int i = 0; i < len; i++) {
WatchRow r = new WatchRow(this, expressions[i]);
r.setTypePath(types[i]);
rows.add(r);
for (int i = 0; i < rowCount; i++) {
String stateName = PREFIX_ROW + i;
Element rowElement = saveState.getXmlElement(stateName);
if (rowElement != null) {
WatchRow r = new WatchRow(this, "");
SaveState rowState = new SaveState(rowElement);
r.readConfigState(rowState);
rows.add(r);
}
}
watchTableModel.clear();
watchTableModel.addAll(rows);
}

View file

@ -0,0 +1,93 @@
/* ###
* 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.watch;
import ghidra.docking.settings.*;
import ghidra.framework.options.SaveState;
import ghidra.program.model.data.TypeDefSettingsDefinition;
public class SavedSettings {
private final Settings settings;
private SaveState state;
public SavedSettings(Settings settings) {
this.settings = settings;
setState(state);
}
public void setState(SaveState state) {
if (state == null) {
state = new SaveState("Settings");
}
this.state = state;
}
public SaveState getState() {
return state;
}
public void write(SettingsDefinition[] definitions, Settings defaultSettings) {
for (SettingsDefinition sd : definitions) {
if (sd.hasSameValue(settings, defaultSettings)) {
continue;
}
if (sd instanceof BooleanSettingsDefinition bsd) {
state.putBoolean(sd.getStorageKey(), bsd.getValue(settings));
}
else if (sd instanceof EnumSettingsDefinition esd) {
state.putInt(sd.getStorageKey(), esd.getChoice(settings));
}
else if (sd instanceof NumberSettingsDefinition nsd) {
state.putLong(sd.getStorageKey(), nsd.getValue(settings));
}
else if (sd instanceof StringSettingsDefinition ssd) {
state.putString(sd.getStorageKey(), ssd.getValue(settings));
}
else if (sd instanceof TypeDefSettingsDefinition tdsd) {
// Toss this on the floor
}
else {
throw new AssertionError();
}
}
}
public void read(SettingsDefinition[] definitions, Settings defaultSettings) {
for (SettingsDefinition sd : definitions) {
if (!state.hasValue(sd.getStorageKey())) {
continue;
}
if (sd instanceof BooleanSettingsDefinition bsd) {
bsd.setValue(settings, state.getBoolean(sd.getStorageKey(), false));
}
else if (sd instanceof EnumSettingsDefinition esd) {
esd.setChoice(settings, state.getInt(sd.getStorageKey(), 0));
}
else if (sd instanceof NumberSettingsDefinition nsd) {
nsd.setValue(settings, state.getLong(sd.getStorageKey(), 0));
}
else if (sd instanceof StringSettingsDefinition ssd) {
ssd.setValue(settings, state.getString(sd.getStorageKey(), null));
}
else if (sd instanceof TypeDefSettingsDefinition tdsd) {
// Toss this on the floor
}
else {
throw new AssertionError();
}
}
}
}

View file

@ -27,7 +27,9 @@ import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.services.DebuggerStateEditingService;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.docking.settings.Settings;
import ghidra.docking.settings.SettingsImpl;
import ghidra.framework.options.SaveState;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.trace.TraceBytesPcodeExecutorState;
import ghidra.pcode.exec.trace.TraceSleighUtils;
@ -51,6 +53,9 @@ import ghidra.util.*;
public class WatchRow {
public static final int TRUNCATE_BYTES_LENGTH = 64;
private static final String KEY_EXPRESSION = "expression";
private static final String KEY_DATA_TYPE = "dataType";
private static final String KEY_SETTINGS = "settings";
private final DebuggerWatchesProvider provider;
private Trace trace;
@ -63,6 +68,8 @@ public class WatchRow {
private String expression;
private String typePath;
private DataType dataType;
private SettingsImpl settings = new SettingsImpl();
private SavedSettings savedSettings = new SavedSettings(settings);
private PcodeExpression compiled;
private TraceMemoryState state;
@ -150,7 +157,7 @@ public class WatchRow {
return "";
}
MemBuffer buffer = new ByteMemBufferImpl(address, value, language.isBigEndian());
return dataType.getRepresentation(buffer, SettingsImpl.NO_SETTINGS, value.length);
return dataType.getRepresentation(buffer, settings, value.length);
}
// TODO: DataType settings
@ -296,18 +303,23 @@ public class WatchRow {
protected void updateType() {
dataType = null;
if (trace == null || typePath == null) {
if (typePath == null) {
return;
}
dataType = trace.getDataTypeManager().getDataType(typePath);
if (dataType != null) {
return;
// Try from the trace first
if (trace != null) {
dataType = trace.getDataTypeManager().getDataType(typePath);
if (dataType != null) {
return;
}
}
// Either we have no trace, or the trace doesn't have the type.
// Try built-ins
DataTypeManagerService dtms = provider.getTool().getService(DataTypeManagerService.class);
if (dtms == null) {
return;
if (dtms != null) {
dataType = dtms.getBuiltInDataTypesManager().getDataType(typePath);
}
dataType = dtms.getBuiltInDataTypesManager().getDataType(typePath);
// We're out of things to try, let null be null
}
public void setTypePath(String typePath) {
@ -325,12 +337,37 @@ public class WatchRow {
valueString = parseAsDataTypeStr();
valueObj = parseAsDataTypeObj();
provider.contextChanged();
settings.setDefaultSettings(dataType == null ? null : dataType.getDefaultSettings());
if (dataType != null) {
savedSettings.read(dataType.getSettingsDefinitions(), dataType.getDefaultSettings());
}
}
public DataType getDataType() {
return dataType;
}
/**
* Get the row's (mutable) data type settings
*
* <p>
* After mutating these settings, the client must call {@link #settingsChanged()} to update the
* row's display and save state.
*
* @return the settings
*/
public Settings getSettings() {
return settings;
}
public void settingsChanged() {
if (dataType != null) {
savedSettings.write(dataType.getSettingsDefinitions(), dataType.getDefaultSettings());
}
valueString = parseAsDataTypeStr();
provider.watchTableModel.fireTableDataChanged();
}
public Address getAddress() {
return address;
}
@ -552,4 +589,20 @@ public class WatchRow {
}
return !Arrays.equals(value, prevValue);
}
protected void writeConfigState(SaveState saveState) {
saveState.putString(KEY_EXPRESSION, expression);
saveState.putString(KEY_DATA_TYPE, typePath);
saveState.putSaveState(KEY_SETTINGS, savedSettings.getState());
}
protected void readConfigState(SaveState saveState) {
setExpression(saveState.getString(KEY_EXPRESSION, ""));
setTypePath(saveState.getString(KEY_DATA_TYPE, null));
savedSettings.setState(saveState.getSaveState(KEY_SETTINGS));
if (dataType != null) {
savedSettings.read(dataType.getSettingsDefinitions(), dataType.getDefaultSettings());
}
}
}

View file

@ -33,11 +33,14 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider.RegisterDataSettingsDialog;
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider.RegisterTableColumns;
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
import ghidra.app.services.DebuggerStateEditingService;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.TraceRecorder;
import ghidra.docking.settings.FormatSettingsDefinition;
import ghidra.docking.settings.Settings;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.util.CodeUnitInsertionException;
@ -460,6 +463,43 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
assertTypeEquals(PointerDataType.dataType, data.getDataType());
}
@Test
public void testModifyTypeSettingsAffectsTrace() throws Exception {
traceManager.openTrace(tb.trace);
TraceThread thread = addThread();
try (UndoableTransaction tid = tb.startTransaction()) {
tb.exec(0, 0, thread, List.of("pc = 100;"));
}
traceManager.activateThread(thread);
waitForSwing();
RegisterRow row = findRegisterRow(pc);
row.setDataType(LongLongDataType.dataType);
waitForSwing();
DBTraceCodeRegisterSpace regCode =
tb.trace.getCodeManager().getCodeRegisterSpace(thread, false);
assertNotNull(regCode);
TraceData data = regCode.data().getForRegister(0L, pc);
assertTypeEquals(LongLongDataType.dataType, data.getDataType());
assertEquals("64h", row.getRepresentation());
registersProvider.regsFilterPanel.setSelectedItem(row);
waitForSwing();
performEnabledAction(registersProvider, registersProvider.actionDataTypeSettings, false);
RegisterDataSettingsDialog dialog =
waitForDialogComponent(RegisterDataSettingsDialog.class);
Settings settings = dialog.getSettings();
FormatSettingsDefinition format = FormatSettingsDefinition.DEF;
format.setChoice(settings, FormatSettingsDefinition.DECIMAL);
runSwing(() -> dialog.okCallback());
// The data is the settings. Wonderful :/
assertEquals(FormatSettingsDefinition.DECIMAL, format.getChoice(data));
assertEquals("100", row.getRepresentation());
}
@Test
public void testModifySubRegTypeAffectsTrace() throws Exception {
traceManager.openTrace(tb.trace);

View file

@ -20,6 +20,7 @@ import static org.junit.Assert.*;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.stream.Collectors;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.junit.*;
@ -33,15 +34,20 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
import ghidra.app.plugin.core.debug.gui.register.*;
import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesProvider.WatchDataSettingsDialog;
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.dbg.model.TestTargetRegisterBankInThread;
import ghidra.docking.settings.FormatSettingsDefinition;
import ghidra.docking.settings.Settings;
import ghidra.framework.options.SaveState;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.Data;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.symbol.Symbol;
@ -173,6 +179,76 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertEquals(TraceRegisterUtils.rangeForRegister(r0), row.getRange());
}
@Test
public void testActionApplyDataType() {
setRegisterValues(thread);
WatchRow row = watchesProvider.addWatch("*:4 r0");
row.setDataType(LongDataType.dataType);
FormatSettingsDefinition format = FormatSettingsDefinition.DEF;
format.setChoice(row.getSettings(), FormatSettingsDefinition.DECIMAL);
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
watchesProvider.watchFilterPanel.setSelectedItem(row);
waitForSwing();
performEnabledAction(watchesProvider, watchesProvider.actionApplyDataType, true);
Data u400000 = tb.trace.getCodeManager().data().getAt(0, tb.addr(0x00400000));
assertTrue(LongDataType.dataType.isEquivalent(u400000.getDataType()));
assertEquals(FormatSettingsDefinition.DECIMAL, format.getChoice(u400000));
}
@Test
public void testWatchWithDataTypeSettings() {
setRegisterValues(thread);
performAction(watchesProvider.actionAdd);
WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
row.setExpression("r0");
row.setDataType(LongLongDataType.dataType);
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertEquals("0x400000", row.getRawValueString());
assertEquals("400000h", row.getValueString());
assertNoErr(row);
Settings settings = row.getSettings();
FormatSettingsDefinition format = FormatSettingsDefinition.DEF;
runSwing(() -> format.setChoice(settings, FormatSettingsDefinition.DECIMAL));
assertEquals("4194304", row.getValueString());
}
@Test
public void testActionDataTypeSettings() {
setRegisterValues(thread);
performAction(watchesProvider.actionAdd);
WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
row.setExpression("r0");
row.setDataType(LongLongDataType.dataType);
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
watchesProvider.watchFilterPanel.setSelectedItem(row);
waitForSwing();
performEnabledAction(watchesProvider, watchesProvider.actionDataTypeSettings, false);
WatchDataSettingsDialog dialog = waitForDialogComponent(WatchDataSettingsDialog.class);
Settings settings = dialog.getSettings();
FormatSettingsDefinition format = FormatSettingsDefinition.DEF;
format.setChoice(settings, FormatSettingsDefinition.DECIMAL);
runSwing(() -> dialog.okCallback());
assertEquals("4194304", row.getValueString());
}
@Test
public void testConstantWatch() {
setRegisterValues(thread);
@ -677,4 +753,42 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertEquals(symbol, row.getSymbol());
}
@Test
public void testSaveConfigState() throws Throwable {
// Setup some state
WatchRow row0 = watchesProvider.addWatch("r0");
WatchRow row1 = watchesProvider.addWatch("*:4 r1");
row0.setDataType(LongLongDataType.dataType);
Settings settings = row0.getSettings();
FormatSettingsDefinition format = FormatSettingsDefinition.DEF;
format.setChoice(settings, FormatSettingsDefinition.DECIMAL);
row0.settingsChanged();
// Save the state
SaveState saveState = new SaveState();
watchesPlugin.writeConfigState(saveState);
// Change some things
row1.setDataType(Pointer64DataType.dataType);
WatchRow row2 = watchesProvider.addWatch("r2");
waitForSwing();
assertEquals(Set.of(row0, row1, row2),
Set.copyOf(watchesProvider.watchTableModel.getModelData()));
// Restore saved state
watchesPlugin.readConfigState(saveState);
waitForSwing();
// Assert the older state
Map<String, WatchRow> rows = watchesProvider.watchTableModel.getModelData()
.stream()
.collect(Collectors.toMap(r -> r.getExpression(), r -> r));
assertEquals(2, rows.size());
WatchRow rRow0 = rows.get("r0");
assertTrue(LongLongDataType.dataType.isEquivalent(rRow0.getDataType()));
assertEquals(FormatSettingsDefinition.DECIMAL, format.getChoice(rRow0.getSettings()));
}
}

View file

@ -31,7 +31,6 @@ import ghidra.trace.database.map.*;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
import ghidra.trace.database.thread.DBTraceThreadManager;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.database.*;
import ghidra.util.database.annot.*;
import ghidra.util.exception.VersionException;
@ -224,11 +223,6 @@ public class DBTraceDataSettingsAdapter
dataFactory);
}
@Override
public DBTraceDataSettingsSpace get(TraceAddressSpace space, boolean createIfAbsent) {
return (DBTraceDataSettingsSpace) super.get(space, createIfAbsent);
}
@Override
public DBTraceDataSettingsSpace getForSpace(AddressSpace space, boolean createIfAbsent) {
return (DBTraceDataSettingsSpace) super.getForSpace(space, createIfAbsent);

View file

@ -231,7 +231,7 @@ public abstract class AbstractDBTraceDataComponent implements DBTraceDefinedData
@Override
public DBTraceDataSettingsSpace getSettingsSpace(boolean createIfAbsent) {
return root.getSettingsSpace(createIfAbsent);
return (DBTraceDataSettingsSpace) root.getSettingsSpace(createIfAbsent);
}
@Override

View file

@ -25,7 +25,7 @@ import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Language;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.data.DBTraceDataSettingsAdapter.DBTraceDataSettingsSpace;
import ghidra.trace.database.data.DBTraceDataSettingsOperations;
import ghidra.trace.database.guest.InternalTracePlatform;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree;
import ghidra.trace.model.guest.TracePlatform;
@ -243,8 +243,9 @@ public class DBTraceData extends AbstractDBTraceCodeUnit<DBTraceData>
}
@Override
public DBTraceDataSettingsSpace getSettingsSpace(boolean createIfAbsent) {
return getTrace().getDataSettingsAdapter().get(space, createIfAbsent);
public DBTraceDataSettingsOperations getSettingsSpace(boolean createIfAbsent) {
return (DBTraceDataSettingsOperations) getTrace().getDataSettingsAdapter()
.get(space, createIfAbsent);
}
@Override

View file

@ -276,7 +276,8 @@ public class UndefinedDBTraceData implements DBTraceDataAdapter, DBTraceSpaceKey
@Override
public DBTraceDataSettingsOperations getSettingsSpace(boolean createIfAbsent) {
return getTrace().getDataSettingsAdapter().get(this, createIfAbsent);
return (DBTraceDataSettingsOperations) getTrace().getDataSettingsAdapter()
.get(this, createIfAbsent);
}
@Override

View file

@ -258,7 +258,7 @@ public abstract class AbstractSettingsDialog extends DialogComponentProvider {
* @param settingsDefinition string settings definition
* @return suggested string value (may be empty array or null)
*/
abstract String[] getSuggestedValues(StringSettingsDefinition settingsDefinition);
protected abstract String[] getSuggestedValues(StringSettingsDefinition settingsDefinition);
/**
* Apply changes to settings. This method must be ov

View file

@ -336,7 +336,7 @@ public class DataSettingsDialog extends AbstractSettingsDialog {
}
@Override
String[] getSuggestedValues(StringSettingsDefinition settingsDefinition) {
protected String[] getSuggestedValues(StringSettingsDefinition settingsDefinition) {
if (!settingsDefinition.supportsSuggestedValues()) {
return null;
}

View file

@ -140,7 +140,7 @@ public class DataTypeSettingsDialog extends AbstractSettingsDialog {
}
@Override
String[] getSuggestedValues(StringSettingsDefinition settingsDefinition) {
protected String[] getSuggestedValues(StringSettingsDefinition settingsDefinition) {
if (settingsDefinition.supportsSuggestedValues()) {
return settingsDefinition.getSuggestedValues(getOriginalSettings());
}