From 23a587ca05970770b9465d8c2f4cef43fd2b1213 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Tue, 16 Aug 2022 11:21:14 -0400 Subject: [PATCH] GP-1984:Add Register and Watch Type Settings --- .../DebuggerRegistersPlugin.html | 6 + .../DebuggerWatchesPlugin.html | 6 + .../core/debug/gui/DebuggerResources.java | 8 ++ .../register/DebuggerRegistersProvider.java | 79 ++++++++++- .../core/debug/gui/register/RegisterRow.java | 5 + .../gui/watch/DebuggerWatchesPlugin.java | 31 ++-- .../gui/watch/DebuggerWatchesProvider.java | 132 +++++++++++++++--- .../core/debug/gui/watch/SavedSettings.java | 93 ++++++++++++ .../plugin/core/debug/gui/watch/WatchRow.java | 69 +++++++-- .../DebuggerRegistersProviderTest.java | 40 ++++++ .../watch/DebuggerWatchesProviderTest.java | 114 +++++++++++++++ .../data/DBTraceDataSettingsAdapter.java | 6 - .../listing/AbstractDBTraceDataComponent.java | 2 +- .../trace/database/listing/DBTraceData.java | 7 +- .../listing/UndefinedDBTraceData.java | 3 +- .../core/data/AbstractSettingsDialog.java | 2 +- .../plugin/core/data/DataSettingsDialog.java | 2 +- .../core/data/DataTypeSettingsDialog.java | 2 +- 18 files changed, 540 insertions(+), 67 deletions(-) create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/SavedSettings.java diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html index 0cc6e5708e..71cb6fa711 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html @@ -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.

+

Register Type Settings

+ +

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.

+

Enable Edits

This toggle is a write protector for machine state. To modify register values, this toggle diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html index d3b4cb5546..b35676df36 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html @@ -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.

+

Watch Type Settings

+ +

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.

+

Select Range

This action is available when there's an active trace, and at least one watch with memory diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java index 1b288160a9..d7488c0222 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java @@ -2377,4 +2377,12 @@ public interface DebuggerResources { } } + 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"; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java index 5ba13f399b..626ef90f5e 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java @@ -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 { 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 regsFilterPanel; + GhidraTableFilterPanel regsFilterPanel; Map 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 diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/RegisterRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/RegisterRow.java index e2ee378a3a..407290fdb4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/RegisterRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/RegisterRow.java @@ -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); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesPlugin.java index 2a3f0fa884..b9ba224867 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesPlugin.java @@ -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); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java index 27186b9a49..0ac2a1a9a6 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java @@ -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 { 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", true)) { 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 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 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); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/SavedSettings.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/SavedSettings.java new file mode 100644 index 0000000000..ef2ca83e60 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/SavedSettings.java @@ -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(); + } + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java index bbe47dd173..3ad1c468a4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java @@ -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 + * + *

+ * 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()); + } + } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java index 54e24987ac..1bddd5c7a9 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java @@ -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); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java index afaa2067ed..8c96a006f2 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java @@ -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 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())); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataSettingsAdapter.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataSettingsAdapter.java index 21353d9a82..43fe788b75 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataSettingsAdapter.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataSettingsAdapter.java @@ -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); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/AbstractDBTraceDataComponent.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/AbstractDBTraceDataComponent.java index 9a6d5bffc7..dc447229e6 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/AbstractDBTraceDataComponent.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/AbstractDBTraceDataComponent.java @@ -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 diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceData.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceData.java index 98fe97f0aa..3e22af51fc 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceData.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceData.java @@ -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 } @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 diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/UndefinedDBTraceData.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/UndefinedDBTraceData.java index d55a17b78a..aa8c7a97ff 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/UndefinedDBTraceData.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/UndefinedDBTraceData.java @@ -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 diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/AbstractSettingsDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/AbstractSettingsDialog.java index a7102eea02..b261da7826 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/AbstractSettingsDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/AbstractSettingsDialog.java @@ -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 diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataSettingsDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataSettingsDialog.java index 5ebd839a1d..41ba8db891 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataSettingsDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataSettingsDialog.java @@ -336,7 +336,7 @@ public class DataSettingsDialog extends AbstractSettingsDialog { } @Override - String[] getSuggestedValues(StringSettingsDefinition settingsDefinition) { + protected String[] getSuggestedValues(StringSettingsDefinition settingsDefinition) { if (!settingsDefinition.supportsSuggestedValues()) { return null; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataTypeSettingsDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataTypeSettingsDialog.java index 147a4edc84..75f11419fe 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataTypeSettingsDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataTypeSettingsDialog.java @@ -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()); }