mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
Merge remote-tracking branch
'origin/GP-5326_ghidragon_edit_structure_field--SQUASHED' (Closes #7407)
This commit is contained in:
commit
928fd09692
15 changed files with 672 additions and 137 deletions
|
@ -308,6 +308,7 @@ src/main/help/help/topics/DataPlugin/images/CreateStructureDialog.png||GHIDRA|||
|
||||||
src/main/help/help/topics/DataPlugin/images/CreateStructureDialogWithTableSelection.png||GHIDRA||||END|
|
src/main/help/help/topics/DataPlugin/images/CreateStructureDialogWithTableSelection.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DataPlugin/images/DataSelectionSettings.png||GHIDRA||||END|
|
src/main/help/help/topics/DataPlugin/images/DataSelectionSettings.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DataPlugin/images/DefaultSettings.png||GHIDRA||||END|
|
src/main/help/help/topics/DataPlugin/images/DefaultSettings.png||GHIDRA||||END|
|
||||||
|
src/main/help/help/topics/DataPlugin/images/EditFieldDialog.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DataPlugin/images/InstanceSettings.png||GHIDRA||||END|
|
src/main/help/help/topics/DataPlugin/images/InstanceSettings.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DataTypeEditors/DataTypeSelectionDialog.htm||GHIDRA||||END|
|
src/main/help/help/topics/DataTypeEditors/DataTypeSelectionDialog.htm||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DataTypeEditors/EnumEditor.htm||GHIDRA||||END|
|
src/main/help/help/topics/DataTypeEditors/EnumEditor.htm||GHIDRA||||END|
|
||||||
|
|
|
@ -970,21 +970,14 @@
|
||||||
quickly changing the name of a single member:</P>
|
quickly changing the name of a single member:</P>
|
||||||
|
|
||||||
<OL>
|
<OL>
|
||||||
<LI>Right mouse click on the field name of a structure member in the Code Browser</LI>
|
<LI>Right mouse click on a structure member in the Listing</LI>
|
||||||
|
|
||||||
<LI>Choose the <B>Data</B><IMG src="help/shared/arrow.gif"> <B>Rename Field</B>
|
<LI>Choose the <B>Data</B><IMG src="help/shared/arrow.gif"> <B>Edit Field</B>
|
||||||
option</LI>
|
action to bring the up the <A href=
|
||||||
|
"#Edit_Field_Dialog">Edit Field Dialog</A> </LI>
|
||||||
|
|
||||||
<LI>Enter a new name in the <B>Rename Data Field</B> dialog</LI>
|
|
||||||
</OL>
|
</OL>
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
|
||||||
<P><IMG src="help/shared/note.png">The "Field Name" field must be added to the
|
|
||||||
"Open Data" tab in the Code Browser <A href=
|
|
||||||
"../CodeBrowserPlugin/Browser_Field_Formatter.htm">header</A> in order for the
|
|
||||||
data structure field names to show up in the Code Browser.</P>
|
|
||||||
</BLOCKQUOTE>
|
|
||||||
|
|
||||||
<P>The second way is more useful for changing the names of multiple members:</P>
|
<P>The second way is more useful for changing the names of multiple members:</P>
|
||||||
|
|
||||||
<OL>
|
<OL>
|
||||||
|
@ -996,10 +989,6 @@
|
||||||
<LI>Edit the field name for the structure member</LI>
|
<LI>Edit the field name for the structure member</LI>
|
||||||
</OL>
|
</OL>
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
|
||||||
<P><IMG src="help/shared/note.png"> You cannot set the field name of undefined
|
|
||||||
member</P>
|
|
||||||
</BLOCKQUOTE>
|
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
|
@ -1374,6 +1363,27 @@
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
|
<H2><A name="Edit_Field"></A>Quick Editing of a Structure or Union Field</H2>
|
||||||
|
<BLOCKQUOTE>
|
||||||
|
<P>As a convenience, a structure or union field can be edited directly from the listing without
|
||||||
|
bringing up the entire structure or union editor. To edit a field, click anywhere on the line
|
||||||
|
displaying that field in the listing and then right click and select <B>Data</B><IMG
|
||||||
|
src="help/shared/arrow.gif"> <B>Edit Field</B> from the popup context menu.</P>
|
||||||
|
<H3><A name="Edit_Field_Dialog"></A>Edit Field Dialog</H3>
|
||||||
|
<P align="center"><IMG src="images/EditFieldDialog.png" alt=""> </P>
|
||||||
|
<UL>
|
||||||
|
<LI><B>Field Name</B>: The name of the structure or union field can be changed here.</LI>
|
||||||
|
<LI><B>Comment</B>: The comment for the field can be entered or changed here.</LI>
|
||||||
|
<LI><B>DataType</B>: The data can be changed here. The text field is read only so you must
|
||||||
|
press the ... button to bring up the datatype chooser to change the datatype.</LI>
|
||||||
|
<P><IMG src="help/shared/note.png"> If a default field (a field with an undefined
|
||||||
|
datatype (??)) is named or given a comment, the datatype will be set to <B>undefined1</B> if
|
||||||
|
no specific datatype is set. This is because undefined fields are not stored and therefore
|
||||||
|
can't have an associated name or comment.</P>
|
||||||
|
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
|
|
||||||
<P class="providedbyplugin">Provided By: <I>Data</I> Plugin</P>
|
<P class="providedbyplugin">Provided By: <I>Data</I> Plugin</P>
|
||||||
|
|
||||||
<P class="relatedtopic">Related Topics:</P>
|
<P class="relatedtopic">Related Topics:</P>
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
|
@ -34,6 +34,7 @@ import ghidra.app.plugin.core.compositeeditor.*;
|
||||||
import ghidra.app.plugin.core.datamgr.DataTypesActionContext;
|
import ghidra.app.plugin.core.datamgr.DataTypesActionContext;
|
||||||
import ghidra.app.plugin.core.datamgr.tree.DataTypeNode;
|
import ghidra.app.plugin.core.datamgr.tree.DataTypeNode;
|
||||||
import ghidra.app.plugin.core.datamgr.tree.DataTypeTreeNode;
|
import ghidra.app.plugin.core.datamgr.tree.DataTypeTreeNode;
|
||||||
|
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
|
||||||
import ghidra.app.services.DataService;
|
import ghidra.app.services.DataService;
|
||||||
import ghidra.app.services.DataTypeManagerService;
|
import ghidra.app.services.DataTypeManagerService;
|
||||||
import ghidra.docking.settings.SettingsDefinition;
|
import ghidra.docking.settings.SettingsDefinition;
|
||||||
|
@ -100,7 +101,6 @@ public class DataPlugin extends Plugin implements DataService {
|
||||||
private DockingAction editDataTypeAction;
|
private DockingAction editDataTypeAction;
|
||||||
private CreateStructureAction createStructureAction;
|
private CreateStructureAction createStructureAction;
|
||||||
private CreateArrayAction createArrayAction;
|
private CreateArrayAction createArrayAction;
|
||||||
private RenameDataFieldAction renameDataFieldAction;
|
|
||||||
|
|
||||||
private List<DataAction> favoriteActions = new ArrayList<>();
|
private List<DataAction> favoriteActions = new ArrayList<>();
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ public class DataPlugin extends Plugin implements DataService {
|
||||||
public DataPlugin(PluginTool tool) {
|
public DataPlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
|
|
||||||
addActions();
|
createActions();
|
||||||
|
|
||||||
favoritesUpdateManager = new SwingUpdateManager(1000, 30000, () -> updateFavoriteActions());
|
favoritesUpdateManager = new SwingUpdateManager(1000, 30000, () -> updateFavoriteActions());
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,7 @@ public class DataPlugin extends Plugin implements DataService {
|
||||||
/**
|
/**
|
||||||
* Add actions
|
* Add actions
|
||||||
*/
|
*/
|
||||||
private void addActions() {
|
private void createActions() {
|
||||||
recentlyUsedAction = new RecentlyUsedAction(this);
|
recentlyUsedAction = new RecentlyUsedAction(this);
|
||||||
recentlyUsedAction.setEnabled(false);
|
recentlyUsedAction.setEnabled(false);
|
||||||
|
|
||||||
|
@ -147,12 +147,17 @@ public class DataPlugin extends Plugin implements DataService {
|
||||||
createArrayAction = new CreateArrayAction(this);
|
createArrayAction = new CreateArrayAction(this);
|
||||||
tool.addAction(createArrayAction);
|
tool.addAction(createArrayAction);
|
||||||
|
|
||||||
renameDataFieldAction = new RenameDataFieldAction(this);
|
|
||||||
tool.addAction(renameDataFieldAction);
|
|
||||||
|
|
||||||
pointerAction = new PointerDataAction(this);
|
pointerAction = new PointerDataAction(this);
|
||||||
tool.addAction(pointerAction);
|
tool.addAction(pointerAction);
|
||||||
|
|
||||||
|
new ActionBuilder("Edit Field", getName())
|
||||||
|
.popupMenuPath("Data", "Edit Field")
|
||||||
|
.keyBinding("ctrl shift E")
|
||||||
|
.withContext(ListingActionContext.class)
|
||||||
|
.enabledWhen(this::canEditField)
|
||||||
|
.onAction(this::editField)
|
||||||
|
.buildAndInstall(tool);
|
||||||
|
|
||||||
// Data instance settings action based upon data selection in listing
|
// Data instance settings action based upon data selection in listing
|
||||||
new ActionBuilder("Data Settings", getName()).sharedKeyBinding()
|
new ActionBuilder("Data Settings", getName()).sharedKeyBinding()
|
||||||
.popupMenuPath(DATA_SETTINGS_POPUP_PATH)
|
.popupMenuPath(DATA_SETTINGS_POPUP_PATH)
|
||||||
|
@ -832,4 +837,27 @@ public class DataPlugin extends Plugin implements DataService {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DataType pickDataType() {
|
||||||
|
return dtmService.getDataType("");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean canEditField(ListingActionContext context) {
|
||||||
|
ProgramLocation location = context.getLocation();
|
||||||
|
int[] componentPath = location.getComponentPath();
|
||||||
|
return componentPath != null && componentPath.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void editField(ListingActionContext context) {
|
||||||
|
Program program = context.getProgram();
|
||||||
|
ProgramLocation location = context.getLocation();
|
||||||
|
Address address = location.getAddress();
|
||||||
|
int[] path = location.getComponentPath();
|
||||||
|
DataTypeComponent component = DataTypeUtils.getDataTypeComponent(program, address, path);
|
||||||
|
if (component != null) {
|
||||||
|
EditDataFieldDialog dialog =
|
||||||
|
new EditDataFieldDialog(tool, dtmService, location, component);
|
||||||
|
tool.showDialog(dialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.data;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import docking.DialogComponentProvider;
|
||||||
|
import docking.widgets.button.BrowseButton;
|
||||||
|
import ghidra.app.cmd.data.CreateDataInStructureCmd;
|
||||||
|
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
|
||||||
|
import ghidra.app.services.DataTypeManagerService;
|
||||||
|
import ghidra.framework.cmd.Command;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.data.*;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.util.ProgramLocation;
|
||||||
|
import ghidra.util.HelpLocation;
|
||||||
|
import ghidra.util.MessageType;
|
||||||
|
import ghidra.util.exception.DuplicateNameException;
|
||||||
|
import ghidra.util.layout.PairLayout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog for editing the name, comment, and datatype for a structure or union field.
|
||||||
|
*/
|
||||||
|
public class EditDataFieldDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
|
private JTextField nameField;
|
||||||
|
private JTextField commentField;
|
||||||
|
private JTextField dataTypeTextField;
|
||||||
|
|
||||||
|
private DataTypeComponent component;
|
||||||
|
private PluginTool tool;
|
||||||
|
private DataType newDataType;
|
||||||
|
private ProgramLocation programLocation;
|
||||||
|
private DataTypeManagerService dtmService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param tool The tool hosting this dialog
|
||||||
|
* @param dtmService the DataTypeManagerService used for choosing datatypes
|
||||||
|
* @param location the location of the field being edited
|
||||||
|
* @param dataTypeComponent the component of the field being edited
|
||||||
|
*/
|
||||||
|
public EditDataFieldDialog(PluginTool tool, DataTypeManagerService dtmService,
|
||||||
|
ProgramLocation location, DataTypeComponent dataTypeComponent) {
|
||||||
|
super("Edit Field Dialog", true, true, true, false);
|
||||||
|
this.tool = tool;
|
||||||
|
this.dtmService = dtmService;
|
||||||
|
this.programLocation = location;
|
||||||
|
this.component = dataTypeComponent;
|
||||||
|
setTitle(generateTitle());
|
||||||
|
|
||||||
|
addWorkPanel(buildMainPanel());
|
||||||
|
initializeFields();
|
||||||
|
setFocusComponent(nameField);
|
||||||
|
setHelpLocation(new HelpLocation("DataPlugin", "Edit_Field_Dialog"));
|
||||||
|
|
||||||
|
addOKButton();
|
||||||
|
addCancelButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
programLocation = null;
|
||||||
|
component = null;
|
||||||
|
tool = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the pending new datatype to change to.
|
||||||
|
* @return the pending new datatype to change to
|
||||||
|
*/
|
||||||
|
public DataType getNewDataType() {
|
||||||
|
return newDataType != null ? newDataType : new Undefined1DataType();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text currently in the text field for the field name.
|
||||||
|
* @return the text currently in the text field for the field name
|
||||||
|
*/
|
||||||
|
public String getNameText() {
|
||||||
|
return nameField.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the dialog's name text field to the given text.
|
||||||
|
* @param newName the text to put into the name text field
|
||||||
|
*/
|
||||||
|
public void setNameText(String newName) {
|
||||||
|
nameField.setText(newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text currently in the text field for the field comment.
|
||||||
|
* @return the text currently in the text field for the field commment
|
||||||
|
*/
|
||||||
|
public String getCommentText() {
|
||||||
|
return commentField.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the dialog's comment text field to the given text.
|
||||||
|
* @param newComment the text to put into the comment text field
|
||||||
|
*/
|
||||||
|
public void setCommentText(String newComment) {
|
||||||
|
commentField.setText(newComment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the pending new datatype and updates the datatype text field to the name of that
|
||||||
|
* datatype.
|
||||||
|
* @param dataType the new pending datatype
|
||||||
|
*/
|
||||||
|
public void setDataType(DataType dataType) {
|
||||||
|
newDataType = dataType;
|
||||||
|
updateDataTypeTextField();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeFields() {
|
||||||
|
String name = component.getFieldName();
|
||||||
|
if (StringUtils.isBlank(name)) {
|
||||||
|
name = component.getDefaultFieldName();
|
||||||
|
}
|
||||||
|
nameField.setText(name);
|
||||||
|
commentField.setText(component.getComment());
|
||||||
|
dataTypeTextField.setText(component.getDataType().getDisplayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void okCallback() {
|
||||||
|
if (updateComponent()) {
|
||||||
|
close();
|
||||||
|
programLocation = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean updateComponent() {
|
||||||
|
if (!hasChanges()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Command<Program> cmd = new UpdateDataComponentCommand();
|
||||||
|
if (!tool.execute(cmd, programLocation.getProgram())) {
|
||||||
|
setStatusText(cmd.getStatusMsg(), MessageType.ERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasChanges() {
|
||||||
|
return hasNameChange() || hasCommentChange() || hasDataTypeChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasCommentChange() {
|
||||||
|
String newComment = getNewFieldComment();
|
||||||
|
if (StringUtils.isBlank(newComment) && StringUtils.isBlank(component.getComment())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !newComment.equals(component.getComment());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasDataTypeChange() {
|
||||||
|
return newDataType != null && !newDataType.equals(component.getDataType());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasNameChange() {
|
||||||
|
String newName = getNewFieldName();
|
||||||
|
if (newName.equals(component.getFieldName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (newName.equals(component.getDefaultFieldName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getNewFieldName() {
|
||||||
|
return nameField.getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getNewFieldComment() {
|
||||||
|
return commentField.getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel buildMainPanel() {
|
||||||
|
JPanel panel = new JPanel(new PairLayout(10, 10));
|
||||||
|
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
|
||||||
|
|
||||||
|
nameField = new JTextField(20);
|
||||||
|
nameField.setEditable(true);
|
||||||
|
nameField.addActionListener(e -> okCallback());
|
||||||
|
commentField = new JTextField(20);
|
||||||
|
commentField.setEditable(true);
|
||||||
|
commentField.addActionListener(e -> okCallback());
|
||||||
|
|
||||||
|
panel.add(new JLabel("Field Name:", SwingConstants.LEFT));
|
||||||
|
panel.add(nameField);
|
||||||
|
panel.add(new JLabel("Comment:", SwingConstants.LEFT));
|
||||||
|
panel.add(commentField);
|
||||||
|
panel.add(new JLabel("Datatype:", SwingConstants.LEFT));
|
||||||
|
panel.add(buildDataTypeChooserPanel());
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel buildDataTypeChooserPanel() {
|
||||||
|
JPanel panel = new JPanel(new BorderLayout(10, 0));
|
||||||
|
|
||||||
|
dataTypeTextField = new JTextField();
|
||||||
|
dataTypeTextField.setEditable(false);
|
||||||
|
BrowseButton browseButton = new BrowseButton();
|
||||||
|
browseButton.setToolTipText("Browse the Data Manager");
|
||||||
|
browseButton.addActionListener(e -> showDataTypeBrowser());
|
||||||
|
|
||||||
|
panel.add(dataTypeTextField, BorderLayout.CENTER);
|
||||||
|
panel.add(browseButton, BorderLayout.EAST);
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showDataTypeBrowser() {
|
||||||
|
newDataType = dtmService.getDataType("");
|
||||||
|
updateDataTypeTextField();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDataTypeTextField() {
|
||||||
|
if (newDataType != null) {
|
||||||
|
dataTypeTextField.setText(newDataType.getDisplayName());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dataTypeTextField.setText(component.getDataType().getDisplayName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateTitle() {
|
||||||
|
DataType parent = component.getParent();
|
||||||
|
String compositeName = parent.getName();
|
||||||
|
return "Edit " + compositeName + ", Field " + component.getOrdinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDataTypeText() {
|
||||||
|
return dataTypeTextField.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UpdateDataComponentCommand implements Command<Program> {
|
||||||
|
private String statusMessage = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean applyTo(Program program) {
|
||||||
|
if (component.isUndefined() || hasDataTypeChange()) {
|
||||||
|
DataType dt = getNewDataType();
|
||||||
|
Address address = programLocation.getAddress();
|
||||||
|
int[] path = programLocation.getComponentPath();
|
||||||
|
Command<Program> cmd = new CreateDataInStructureCmd(address, path, dt, false);
|
||||||
|
if (!cmd.applyTo(program)) {
|
||||||
|
statusMessage = cmd.getStatusMsg();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
component = DataTypeUtils.getDataTypeComponent(program, address, path);
|
||||||
|
}
|
||||||
|
if (hasNameChange()) {
|
||||||
|
try {
|
||||||
|
component.setFieldName(getNewFieldName());
|
||||||
|
}
|
||||||
|
catch (DuplicateNameException e) {
|
||||||
|
statusMessage = "Duplicate field name";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasCommentChange()) {
|
||||||
|
component.setComment(getNewFieldComment());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStatusMsg() {
|
||||||
|
return statusMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Update Structure Field";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,89 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.data;
|
|
||||||
|
|
||||||
import java.awt.event.KeyEvent;
|
|
||||||
|
|
||||||
import docking.action.KeyBindingData;
|
|
||||||
import docking.action.MenuData;
|
|
||||||
import ghidra.app.context.ListingActionContext;
|
|
||||||
import ghidra.app.context.ListingContextAction;
|
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
|
||||||
import ghidra.program.model.data.*;
|
|
||||||
import ghidra.program.model.listing.Data;
|
|
||||||
import ghidra.program.model.listing.Program;
|
|
||||||
import ghidra.program.util.FieldNameFieldLocation;
|
|
||||||
import ghidra.program.util.ProgramLocation;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for comment actions to edit and delete comments.
|
|
||||||
*/
|
|
||||||
class RenameDataFieldAction extends ListingContextAction {
|
|
||||||
|
|
||||||
private DataPlugin plugin;
|
|
||||||
|
|
||||||
public RenameDataFieldAction(DataPlugin plugin) {
|
|
||||||
super("Rename Data Field", plugin.getName());
|
|
||||||
|
|
||||||
setPopupMenuData(
|
|
||||||
new MenuData(
|
|
||||||
new String[] { "Data", "Rename Field" }, null, "BasicData"));
|
|
||||||
|
|
||||||
setKeyBindingData(new KeyBindingData(
|
|
||||||
KeyEvent.VK_N, 0));
|
|
||||||
|
|
||||||
this.plugin = plugin;
|
|
||||||
setEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void actionPerformed(ListingActionContext context) {
|
|
||||||
ListingActionContext programActionContext =
|
|
||||||
(ListingActionContext) context.getContextObject();
|
|
||||||
PluginTool tool = plugin.getTool();
|
|
||||||
Program program = programActionContext.getProgram();
|
|
||||||
ProgramLocation loc = programActionContext.getLocation();
|
|
||||||
Data data = program.getListing().getDataContaining(loc.getAddress());
|
|
||||||
DataType type = data.getDataType();
|
|
||||||
|
|
||||||
if (type instanceof Composite) {
|
|
||||||
Composite comp = (Composite) type;
|
|
||||||
int[] compPath = loc.getComponentPath();
|
|
||||||
for (int i = 0; i < compPath.length - 1; i++) {
|
|
||||||
DataTypeComponent subComp = comp.getComponent(compPath[i]);
|
|
||||||
type = subComp.getDataType();
|
|
||||||
if (type instanceof Composite) {
|
|
||||||
comp = (Composite) type;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Data instance = data.getComponent(compPath);
|
|
||||||
DataTypeComponent subComp = comp.getComponent(compPath[compPath.length - 1]);
|
|
||||||
RenameDataFieldDialog dialog = new RenameDataFieldDialog(plugin);
|
|
||||||
dialog.setDataComponent(program, subComp, instance.getFieldName());
|
|
||||||
tool.showDialog(dialog);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isEnabledForContext(ListingActionContext context) {
|
|
||||||
return (context.getLocation() instanceof FieldNameFieldLocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -24,8 +24,12 @@ import javax.swing.Icon;
|
||||||
import generic.theme.GColor;
|
import generic.theme.GColor;
|
||||||
import generic.theme.GIcon;
|
import generic.theme.GIcon;
|
||||||
import ghidra.app.services.DataTypeQueryService;
|
import ghidra.app.services.DataTypeQueryService;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.data.*;
|
import ghidra.program.model.data.*;
|
||||||
|
import ghidra.program.model.data.Composite;
|
||||||
import ghidra.program.model.data.Enum;
|
import ghidra.program.model.data.Enum;
|
||||||
|
import ghidra.program.model.listing.Data;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
import resources.MultiIcon;
|
import resources.MultiIcon;
|
||||||
|
@ -469,6 +473,30 @@ public class DataTypeUtils {
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the DataTypeComponent at an address and component path in a program.
|
||||||
|
* @param program the program to look for a datatype component
|
||||||
|
* @param address the address to look for a datatype component
|
||||||
|
* @param componentPath the component path (an array of indexes into hierarchy of nested
|
||||||
|
* datatypes)
|
||||||
|
* @return The datatype component at that address and component path or null if there is
|
||||||
|
* none at that location.
|
||||||
|
*/
|
||||||
|
public static DataTypeComponent getDataTypeComponent(Program program, Address address,
|
||||||
|
int[] componentPath) {
|
||||||
|
Data data = program.getListing().getDataContaining(address);
|
||||||
|
DataType dt = data.getDataType();
|
||||||
|
DataTypeComponent comp = null;
|
||||||
|
for (int i = 0; i < componentPath.length; i++) {
|
||||||
|
if (!(dt instanceof Composite composite)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
comp = composite.getComponent(componentPath[i]);
|
||||||
|
dt = comp.getDataType();
|
||||||
|
}
|
||||||
|
return comp;
|
||||||
|
}
|
||||||
|
|
||||||
// finds the index of the first element in the given list--this is used in conjunction with
|
// finds the index of the first element in the given list--this is used in conjunction with
|
||||||
// the binary search, which doesn't produce the desired results when searching lists with
|
// the binary search, which doesn't produce the desired results when searching lists with
|
||||||
// duplicates
|
// duplicates
|
||||||
|
|
|
@ -383,7 +383,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||||
new ActionBuilder("Search Text", getName())
|
new ActionBuilder("Search Text", getName())
|
||||||
.menuPath("&Search", "Program &Text...")
|
.menuPath("&Search", "Program &Text...")
|
||||||
.menuGroup("search", subGroup)
|
.menuGroup("search", subGroup)
|
||||||
.keyBinding("ctrl shift E")
|
.keyBinding("ctrl F")
|
||||||
.description(DESCRIPTION)
|
.description(DESCRIPTION)
|
||||||
.helpLocation(new HelpLocation(HelpTopics.SEARCH, "Search Text"))
|
.helpLocation(new HelpLocation(HelpTopics.SEARCH, "Search Text"))
|
||||||
.withContext(NavigatableActionContext.class, true)
|
.withContext(NavigatableActionContext.class, true)
|
||||||
|
|
|
@ -0,0 +1,232 @@
|
||||||
|
/* ###
|
||||||
|
* 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.data;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.junit.*;
|
||||||
|
|
||||||
|
import docking.action.DockingActionIf;
|
||||||
|
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.data.*;
|
||||||
|
import ghidra.program.model.listing.Data;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.test.*;
|
||||||
|
|
||||||
|
public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
private TestEnv env;
|
||||||
|
private PluginTool tool;
|
||||||
|
private Program program;
|
||||||
|
private DockingActionIf editFieldAction;
|
||||||
|
private EditDataFieldDialog dialog;
|
||||||
|
private CodeBrowserPlugin codeBrowser;
|
||||||
|
private Structure structure;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
env = new TestEnv();
|
||||||
|
tool = env.getTool();
|
||||||
|
tool.addPlugin(DataPlugin.class.getName());
|
||||||
|
tool.addPlugin(CodeBrowserPlugin.class.getName());
|
||||||
|
|
||||||
|
DataPlugin plugin = getPlugin(tool, DataPlugin.class);
|
||||||
|
codeBrowser = env.getPlugin(CodeBrowserPlugin.class);
|
||||||
|
|
||||||
|
program = buildProgram();
|
||||||
|
env.open(program);
|
||||||
|
env.showTool();
|
||||||
|
editFieldAction = getAction(plugin, "Edit Field");
|
||||||
|
Data dataAt = program.getListing().getDataAt(addr(0x100));
|
||||||
|
structure = (Structure) dataAt.getDataType();
|
||||||
|
codeBrowser.toggleOpen(dataAt);
|
||||||
|
waitForSwing();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Program buildProgram() throws Exception {
|
||||||
|
ToyProgramBuilder builder = new ToyProgramBuilder("Test", true);
|
||||||
|
builder.createMemory("Test", "0", 1000);
|
||||||
|
StructureDataType struct = new StructureDataType("TestStruct", 4);
|
||||||
|
struct.add(new WordDataType(), "count", "This is the count field");
|
||||||
|
struct.add(new WordDataType(), "color", "This is the color field");
|
||||||
|
builder.applyDataType("0x100", struct);
|
||||||
|
return builder.getProgram();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
env.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditDefinedFieldName() {
|
||||||
|
goTo(0x104);
|
||||||
|
showFieldEditDialog();
|
||||||
|
assertEquals("count", structure.getComponent(4).getFieldName());
|
||||||
|
assertEquals("count", getNameText());
|
||||||
|
|
||||||
|
setNameText("weight");
|
||||||
|
|
||||||
|
pressOk();
|
||||||
|
waitForTasks();
|
||||||
|
assertEquals("weight", structure.getComponent(4).getFieldName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditDefinedFieldComment() {
|
||||||
|
goTo(0x104);
|
||||||
|
showFieldEditDialog();
|
||||||
|
assertEquals("This is the count field", structure.getComponent(4).getComment());
|
||||||
|
assertEquals("This is the count field", getCommentText());
|
||||||
|
|
||||||
|
setCommentText("This is the weight field");
|
||||||
|
|
||||||
|
pressOk();
|
||||||
|
waitForTasks();
|
||||||
|
assertEquals("This is the weight field", structure.getComponent(4).getComment());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditDefinedFieldDataType() {
|
||||||
|
goTo(0x104);
|
||||||
|
showFieldEditDialog();
|
||||||
|
assertEquals("word", structure.getComponent(4).getDataType().getDisplayName());
|
||||||
|
assertEquals("word", getDataTypeText());
|
||||||
|
|
||||||
|
setDataType(new CharDataType());
|
||||||
|
|
||||||
|
pressOk();
|
||||||
|
|
||||||
|
waitForTasks();
|
||||||
|
assertFalse(isDialogVisible());
|
||||||
|
|
||||||
|
assertEquals("char", structure.getComponent(4).getDataType().getDisplayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditUndefinedFieldName() {
|
||||||
|
goTo(0x101);
|
||||||
|
showFieldEditDialog();
|
||||||
|
assertNull(structure.getComponent(1).getFieldName());
|
||||||
|
assertEquals("field1_0x1", getNameText());
|
||||||
|
|
||||||
|
setNameText("abc");
|
||||||
|
|
||||||
|
pressOk();
|
||||||
|
waitForTasks();
|
||||||
|
assertEquals("abc", structure.getComponent(1).getFieldName());
|
||||||
|
assertEquals("undefined1", structure.getComponent(1).getDataType().getDisplayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditUndefinedComment() {
|
||||||
|
goTo(0x101);
|
||||||
|
showFieldEditDialog();
|
||||||
|
assertNull(structure.getComponent(1).getComment());
|
||||||
|
assertEquals("", getCommentText());
|
||||||
|
|
||||||
|
setCommentText("comment");
|
||||||
|
|
||||||
|
pressOk();
|
||||||
|
waitForTasks();
|
||||||
|
assertEquals("comment", structure.getComponent(1).getComment());
|
||||||
|
assertEquals("undefined1", structure.getComponent(1).getDataType().getDisplayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditUndefinedDataType() {
|
||||||
|
goTo(0x101);
|
||||||
|
showFieldEditDialog();
|
||||||
|
assertNull(structure.getComponent(1).getComment());
|
||||||
|
assertEquals("undefined", getDataTypeText());
|
||||||
|
|
||||||
|
setDataType(new ByteDataType());
|
||||||
|
|
||||||
|
pressOk();
|
||||||
|
waitForTasks();
|
||||||
|
assertEquals("byte", structure.getComponent(1).getDataType().getDisplayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRenameToDuplicateNameError() {
|
||||||
|
goTo(0x104);
|
||||||
|
showFieldEditDialog();
|
||||||
|
assertEquals("count", structure.getComponent(4).getFieldName());
|
||||||
|
assertEquals("count", getNameText());
|
||||||
|
|
||||||
|
setNameText("color");
|
||||||
|
|
||||||
|
pressOk();
|
||||||
|
waitForTasks();
|
||||||
|
assertTrue(isDialogVisible());
|
||||||
|
assertEquals("Duplicate field name", getDialogStatusText());
|
||||||
|
|
||||||
|
assertEquals("count", structure.getComponent(4).getFieldName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDialogVisible() {
|
||||||
|
return runSwing(() -> dialog.isVisible());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showFieldEditDialog() {
|
||||||
|
performAction(editFieldAction, false);
|
||||||
|
dialog = waitForDialogComponent(EditDataFieldDialog.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void goTo(long addressOffset) {
|
||||||
|
Address address = addr(addressOffset);
|
||||||
|
codeBrowser.goToField(address, "Address", 0, 0);
|
||||||
|
waitForSwing();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Address addr(long offset) {
|
||||||
|
return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pressOk() {
|
||||||
|
runSwing(() -> dialog.okCallback());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getNameText() {
|
||||||
|
return runSwing(() -> dialog.getNameText());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setNameText(String newName) {
|
||||||
|
runSwing(() -> dialog.setNameText(newName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCommentText() {
|
||||||
|
return runSwing(() -> dialog.getCommentText());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCommentText(String newComment) {
|
||||||
|
runSwing(() -> dialog.setCommentText(newComment));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDataTypeText() {
|
||||||
|
return runSwing(() -> dialog.getDataTypeText());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDataType(DataType dataType) {
|
||||||
|
runSwing(() -> dialog.setDataType(dataType));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDialogStatusText() {
|
||||||
|
return runSwing(() -> dialog.getStatusText());
|
||||||
|
}
|
||||||
|
}
|
|
@ -455,11 +455,8 @@ class DataTypeComponentDB implements InternalDataTypeComponent {
|
||||||
return InternalDataTypeComponent.toString(this);
|
return InternalDataTypeComponent.toString(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Determine if component is an undefined filler component
|
public boolean isUndefined() {
|
||||||
* @return true if undefined filler component, else false
|
|
||||||
*/
|
|
||||||
boolean isUndefined() {
|
|
||||||
return record == null && cachedDataType == null;
|
return record == null && cachedDataType == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -146,6 +146,11 @@ public class AlignedStructureInspector extends AlignedStructurePacker {
|
||||||
this.dataType = dataType;
|
this.dataType = dataType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUndefined() {
|
||||||
|
return component.isUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -173,4 +173,10 @@ public interface DataTypeComponent {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this this component is not defined. It is just a placeholder.
|
||||||
|
* @return true if this this component is not defined. It is just a placeholder.
|
||||||
|
*/
|
||||||
|
public boolean isUndefined();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -334,11 +334,8 @@ public class DataTypeComponentImpl implements InternalDataTypeComponent, Seriali
|
||||||
dataType = dt;
|
dataType = dt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Determine if component is an undefined filler component
|
public boolean isUndefined() {
|
||||||
* @return true if undefined filler component, else false
|
|
||||||
*/
|
|
||||||
boolean isUndefined() {
|
|
||||||
return dataType == DataType.DEFAULT;
|
return dataType == DataType.DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -195,4 +195,9 @@ public class ReadOnlyDataTypeComponent implements DataTypeComponent, Serializabl
|
||||||
return s1.equals(s2);
|
return s1.equals(s2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUndefined() {
|
||||||
|
return dataType == DataType.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,16 @@ public class DataPluginScreenShots extends GhidraScreenShotGenerator {
|
||||||
captureDialog(500, 400);
|
captureDialog(500, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditFieldDialog() {
|
||||||
|
positionListingTop(0x400080);
|
||||||
|
positionCursor(0x400080, "+");
|
||||||
|
leftClickCursor();
|
||||||
|
positionListingTop(0x4000a4);
|
||||||
|
performAction("Edit Field", "DataPlugin", false);
|
||||||
|
captureDialog();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateStructureDialogWithTableSelection() {
|
public void testCreateStructureDialogWithTableSelection() {
|
||||||
positionListingTop(0x40d3a4);
|
positionListingTop(0x40d3a4);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue