Merge remote-tracking branch 'origin/GP-6692-dragonmacher-too-many-symbols--SQUASHED'

This commit is contained in:
Ryan Kurtz
2026-04-29 07:02:22 -04:00
32 changed files with 1424 additions and 600 deletions
@@ -112,14 +112,18 @@
certain Navigation behaviors.</P>
<H3>Xrefs</H3>
<BLOCKQUOTE>
<A name="Show_Xrefs"></A>
<P>In the XRef field, sometimes there are too many addresses to display so the field will
display "[more]" to indicate that one or more cross-reference addresses are not shown.</P>
<BLOCKQUOTE>
<P><IMG border="0" src="help/shared/tip.png" alt=""> Double-clicking on the "<FONT color=
"green"><TT>XREF[n]:</TT></FONT> or <FONT><TT>[more]</TT></FONT>" text will cause a
dialog containing all the Xrefs to appear.</P>
"green"><TT>XREF[n]:</TT></FONT> or <FONT color="green"><TT>[more]</TT></FONT>"
text will cause a dialog containing all the Xrefs to appear.</P>
<P>
This differs from the <A href=
"help/topics/LocationReferencesPlugin/Location_References.html#LocationReferencesPlugin">
@@ -178,6 +182,51 @@
</BLOCKQUOTE>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H3>Labels</H3>
<BLOCKQUOTE>
<A name="Show_Labels"></A>
<P>In the Labels field, sometimes there are too many labels to display so the field will
display "[more]" to indicate that one or more labels are not shown.</P>
<BLOCKQUOTE>
<P><IMG border="0" src="help/shared/tip.png" alt=""> Double-clicking on the
"<FONT><TT>[more]</TT></FONT>" text will cause a dialog containing all the
labels to appear.</P>
</BLOCKQUOTE>
<H3><A NAME="Refresh_Labels"></A>Refresh
<IMG border="0" src="Icons.REFRESH_ICON" alt=""></H3>
<BLOCKQUOTE>
<P>
This action will refresh the table of labels. This table does not respond to
program changes, such as adding or deleting lables. Thus, if you would like
the table to update to the current state of the program, then you can press
the refresh button.
</P>
</BLOCKQUOTE>
<H3><A NAME="Delete_Label"></A>Delete Label
<IMG border="0" src="Icons.DELETE_ICON" alt=""></H3>
<BLOCKQUOTE>
<P>
This action will delete all selected labels from the database. This differs
from the <A HREF="help/topics/Search/Query_Results_Dialog.htm#Remove_Items">
Remove Items </A> action, which will simply remove items from the table.
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
</BLOCKQUOTE>
@@ -848,6 +848,10 @@
this off, the function name will only appear in the function signature. If it's on, the
function name will also appear as a label below the function header.</P>
<P><B>Maximum Number of Labels to Display -</B> Sets the maximum number of labels to
display. If the max is reached, a '[more]' field will appear. You can double-click that
field to see all labels at that address.</P>
<P><B>Display Non-local Namespace -</B> Select this option to prepend the namespace to all
labels that are not in the current Function's namespace.&nbsp; Currently, this would only
affect a label that is not global, but is in a namespace other than the function that
@@ -254,20 +254,12 @@
the address using the <I>Set Label</I> dialog.</P>
</BLOCKQUOTE>
<TABLE x-use-null-cells="" width="100%">
<TBODY>
<TR>
<TD align="center" width="100%"><IMG src="images/SetLabel.png" border="0"></TD>
</TR>
</TBODY>
</TABLE><BR>
<BR>
<BLOCKQUOTE>
<H3>Label</H3>
<BLOCKQUOTE>
<P>The list in the combo box will show all symbols associated with the address shown in the
<P>The drop-down list will show all symbols associated with the address shown in the
dialog title. Choosing a label from the list will cause that symbol to be associated with
the operand reference being modified. Typing in a new name will cause a new symbol to be
created at the target address before associating it with the operand reference.</P>
@@ -45,6 +45,15 @@
</P>
</BLOCKQUOTE>
<H3><A NAME="Set_Primary">Set Primary</A></H3>
<BLOCKQUOTE>
<P>
Sets the selected symbol to be the primary symbol at the symbol's address. The primary
symbol is the symbol displayed by default for the 'from' side of an xref.
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
@@ -60,6 +69,9 @@
</LI>
<LI>
<A href="help/topics/SymbolTreePlugin/SymbolTree.htm">Symbol Tree</A>
</LI>
<LI>
<A href="help/topics/CodeBrowserPlugin/CodeBrowser.htm#Show_Labels">Show All Labels</A>
</LI>
</UL>
</BLOCKQUOTE>
@@ -102,10 +102,6 @@ public class LabelMgrPlugin extends Plugin {
return new EditFieldNameDialog("", tool);
}
OperandLabelDialog getOperandLabelDialog() {
return new OperandLabelDialog(this);
}
/**
* Removes the label or alias that the cursor is over from the current label field. If an
* exception is caught during the removal of the label or alias, a message is written to the
@@ -171,17 +167,18 @@ public class LabelMgrPlugin extends Plugin {
}
void setOperandLabelCallback(ListingActionContext context) {
getOperandLabelDialog().setOperandLabel(context);
SymbolChooserDialog dialog = new SymbolChooserDialog(this, context);
dialog.show();
}
Symbol getSymbol(ListingActionContext context) {
ProgramLocation location = context.getLocation();
if (location instanceof LabelFieldLocation) {
LabelFieldLocation lfl = (LabelFieldLocation) location;
if (location instanceof LabelFieldLocation lfl) {
return lfl.getSymbol();
}
else if (location instanceof OperandFieldLocation) {
VariableOffset variableOffset = ((OperandFieldLocation) location).getVariableOffset();
else if (location instanceof OperandFieldLocation ofl) {
VariableOffset variableOffset = ofl.getVariableOffset();
if (variableOffset != null) {
Variable var = variableOffset.getVariable();
if (var != null) {
@@ -1,178 +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.label;
import javax.swing.*;
import docking.DialogComponentProvider;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.label.GDLabel;
import ghidra.app.cmd.label.AddLabelCmd;
import ghidra.app.cmd.refs.AssociateSymbolCmd;
import ghidra.app.context.ListingActionContext;
import ghidra.app.util.HelpTopics;
import ghidra.framework.cmd.CompoundCmd;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.program.util.OperandFieldLocation;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HelpLocation;
import ghidra.util.layout.PairLayout;
public class OperandLabelDialog extends DialogComponentProvider {
private JLabel label;
private GhidraComboBox<String> myChoice;
private LabelMgrPlugin plugin;
private ListingActionContext programActionContext;
public OperandLabelDialog(LabelMgrPlugin plugin) {
super("");
this.plugin = plugin;
setHelpLocation(new HelpLocation(HelpTopics.LABEL, "OperandLabelDialog"));
addWorkPanel(buildMainPanel());
addOKButton();
addCancelButton();
}
/**
* Define the Main panel for the dialog here.
* @return JPanel the completed Main Panel
*/
protected JPanel buildMainPanel() {
JPanel mainPanel = new JPanel(new PairLayout(5, 5));
mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
label = new GDLabel("Label: ");
myChoice = new GhidraComboBox<>();
myChoice.setName("MYCHOICE");
myChoice.setEditable(true);
myChoice.getAccessibleContext().setAccessibleName("My Choice");
mainPanel.add(label);
mainPanel.add(myChoice);
mainPanel.getAccessibleContext().setAccessibleName("Operand Label");
return mainPanel;
}
/**
* This method gets called when the user clicks on the Ok Button. The base
* class calls this method.
*/
@Override
protected void okCallback() {
Program program = programActionContext.getProgram();
ProgramLocation loc = programActionContext.getLocation();
OperandFieldLocation location = (OperandFieldLocation) loc;
Symbol sym = getSymbol(programActionContext);
String currentLabel = myChoice.getText();
if (currentLabel.equals(sym.getName(true))) {
close();
return;
}
ReferenceManager refMgr = program.getReferenceManager();
SymbolTable symTable = program.getSymbolTable();
int opIndex = location.getOperandIndex();
Address addr = location.getAddress();
Address symAddr = sym.getAddress();
Reference ref = refMgr.getReference(addr, symAddr, opIndex);
CompoundCmd<Program> cmd = new CompoundCmd<>("Set Label");
Namespace scope = null;
Symbol newSym = findSymbol(symTable, currentLabel, symAddr);
if (newSym == null) {
cmd.add(new AddLabelCmd(symAddr, currentLabel, SourceType.USER_DEFINED));
}
else {
scope = newSym.getParentNamespace();
currentLabel = newSym.getName();
}
cmd.add(new AssociateSymbolCmd(ref, currentLabel, scope));
if (!plugin.getTool().execute(cmd, program)) {
setStatusText(cmd.getStatusMsg());
return;
}
close();
}
// Find and return the first symbol at the address with the given name. Since this is about
// the presentation at the call or jump instruction, it doesn't matter which symbol of the
// same name you pick.
private Symbol findSymbol(SymbolTable symTable, String currentLabel, Address symAddr) {
SymbolIterator symbols = symTable.getSymbolsAsIterator(symAddr);
for (Symbol symbol : symbols) {
if (symbol.getName(true).equals(currentLabel)) {
return symbol;
}
}
return null;
}
/**
* This method gets called when the user clicks on the Cancel Button. The base
* class calls this method.
*/
@Override
protected void cancelCallback() {
close();
}
@Override
public void close() {
programActionContext = null;
super.close();
}
public void setOperandLabel(ListingActionContext context) {
programActionContext = context;
setStatusText("");
myChoice.clearModel();
Symbol s = getSymbol(context);
Symbol[] symbols = context.getProgram().getSymbolTable().getSymbols(s.getAddress());
for (Symbol symbol : symbols) {
myChoice.addToModel(symbol.getName(true));
}
setTitle("Set Label at " + s.getAddress());
myChoice.setSelectedItem(s.getName(true));
PluginTool tool = plugin.getTool();
tool.showDialog(this);
}
private Symbol getSymbol(ListingActionContext context) {
Program program = context.getProgram();
OperandFieldLocation location = (OperandFieldLocation) context.getLocation();
Address address = location.getAddress();
int opIndex = location.getOperandIndex();
ReferenceManager refMgr = program.getReferenceManager();
Reference ref = refMgr.getPrimaryReferenceFrom(address, opIndex);
if (ref != null) {
SymbolTable st = program.getSymbolTable();
return st.getSymbol(ref);
}
return null;
}
}
@@ -4,9 +4,9 @@
* 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.
@@ -26,20 +26,12 @@ import ghidra.app.context.ListingActionContext;
import ghidra.app.context.ListingContextAction;
import ghidra.program.util.OperandFieldLocation;
/**
* <CODE>AddLabelAction</CODE> allows the user to add a label.
*/
class SetOperandLabelAction extends ListingContextAction {
private LabelMgrPlugin plugin;
private static final String[] POPUP_PATH = { "Set Associated Label..." };
private static final KeyStroke KEYBINDING =
KeyStroke.getKeyStroke(KeyEvent.VK_L, InputEvent.CTRL_MASK | InputEvent.ALT_MASK);
KeyStroke.getKeyStroke(KeyEvent.VK_L, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK);
/**
* Creates a new instance of the action.
*
* @param plugin Label Manager Plugin instance
*/
SetOperandLabelAction(LabelMgrPlugin plugin) {
super("Set Operand Label", plugin.getName());
@@ -59,13 +51,8 @@ class SetOperandLabelAction extends ListingContextAction {
plugin.isOnSymbol(context);
}
/**
* Method called when the action is invoked.
* @param ActionEvent details regarding the invocation of this action
*/
@Override
public void actionPerformed(ListingActionContext context) {
plugin.setOperandLabelCallback(context);
}
}
@@ -0,0 +1,192 @@
/* ###
* 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.label;
import java.awt.BorderLayout;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import docking.widgets.DefaultDropDownSelectionDataModel;
import docking.widgets.DropDownSelectionTextField;
import docking.widgets.DropDownTextFieldDataModel.SearchMode;
import ghidra.app.cmd.label.AddLabelCmd;
import ghidra.app.cmd.refs.AssociateSymbolCmd;
import ghidra.app.context.ListingActionContext;
import ghidra.framework.cmd.CompoundCmd;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.program.util.OperandFieldLocation;
public class SymbolChooserDialog extends DialogComponentProvider {
private List<String> names;
private DropDownSelectionTextField<String> textField;
private LabelMgrPlugin plugin;
private ListingActionContext context;
public SymbolChooserDialog(LabelMgrPlugin plugin, ListingActionContext context) {
super("Choose Label");
this.plugin = plugin;
this.context = context;
Symbol symbol = getOperandLabel();
Program program = context.getProgram();
SymbolTable st = program.getSymbolTable();
Address address = symbol.getAddress();
Symbol[] symbols = st.getSymbols(address);
names = Arrays.stream(symbols)
.map(s -> s.getName())
.collect(Collectors.toList());
addWorkPanel(buildWorkPanel());
addOKButton();
addCancelButton();
}
public void show() {
DockingWindowManager.showDialog(this);
}
private JComponent buildWorkPanel() {
DefaultDropDownSelectionDataModel<String> model =
DefaultDropDownSelectionDataModel.getStringModel(names);
textField = new DropDownSelectionTextField<>(model);
textField.setShowMatchingListOnEmptyText(true);
textField.setSearchMode(SearchMode.CONTAINS);
Document doc = textField.getDocument();
doc.addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
updateOk();
}
@Override
public void insertUpdate(DocumentEvent e) {
updateOk();
}
@Override
public void changedUpdate(DocumentEvent e) {
updateOk();
}
});
JPanel panel = new JPanel(new BorderLayout());
panel.add(textField, BorderLayout.NORTH);
return panel;
}
private void updateOk() {
setOkEnabled(!textField.getText().isEmpty());
}
@Override
protected void okCallback() {
Program program = context.getProgram();
OperandFieldLocation location = (OperandFieldLocation) context.getLocation();
Symbol currentSymbol = getOperandLabel();
String newLabel = textField.getSelectedValue();
if (newLabel == null) {
setStatusText("Please choose a label");
return;
}
if (newLabel.equals(currentSymbol.getName(true))) {
close();
return;
}
ReferenceManager refMgr = program.getReferenceManager();
SymbolTable symTable = program.getSymbolTable();
int opIndex = location.getOperandIndex();
Address addr = location.getAddress();
Address symAddr = currentSymbol.getAddress();
Reference ref = refMgr.getReference(addr, symAddr, opIndex);
CompoundCmd<Program> cmd = new CompoundCmd<>("Set Label");
Namespace scope = null;
Symbol newSym = findSymbol(symTable, newLabel, symAddr);
if (newSym == null) {
cmd.add(new AddLabelCmd(symAddr, newLabel, SourceType.USER_DEFINED));
}
else {
scope = newSym.getParentNamespace();
newLabel = newSym.getName();
}
cmd.add(new AssociateSymbolCmd(ref, newLabel, scope));
if (!plugin.getTool().execute(cmd, program)) {
setStatusText(cmd.getStatusMsg());
return;
}
close();
}
// Find and return the first symbol at the address with the given name. Since this is about
// the presentation at the call or jump instruction, it doesn't matter which symbol of the
// same name you pick.
private Symbol findSymbol(SymbolTable symTable, String currentLabel, Address symAddr) {
SymbolIterator symbols = symTable.getSymbolsAsIterator(symAddr);
for (Symbol symbol : symbols) {
if (symbol.getName(true).equals(currentLabel)) {
return symbol;
}
}
return null;
}
private Symbol getOperandLabel() {
Program program = context.getProgram();
OperandFieldLocation location = (OperandFieldLocation) context.getLocation();
Address address = location.getAddress();
int opIndex = location.getOperandIndex();
ReferenceManager refMgr = program.getReferenceManager();
Reference ref = refMgr.getPrimaryReferenceFrom(address, opIndex);
if (ref != null) {
SymbolTable st = program.getSymbolTable();
return st.getSymbol(ref);
}
return null;
}
String getChoice() {
return textField.getSelectedValue();
}
void setSelectedItem(String value) {
textField.setSelectedValue(value);
}
}
@@ -291,7 +291,8 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
goToExternalAction.setEnabled(false);
CloneSymbolTreeAction cloneAction = new CloneSymbolTreeAction(plugin, this);
CreateSymbolTableAction tableAction = new CreateSymbolTableAction(plugin);
CreateSymbolTableAction tableAction = new CreateSymbolTableAction(plugin.getTool());
SetSymbolPrimaryAction primaryAction = new SetSymbolPrimaryAction();
tool.addLocalAction(this, createImportAction);
tool.addLocalAction(this, setExternalProgramAction);
@@ -311,6 +312,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
tool.addLocalAction(this, goToExternalAction);
tool.addLocalAction(this, cloneAction);
tool.addLocalAction(this, tableAction);
tool.addLocalAction(this, primaryAction);
}
//==================================================================================================
@@ -22,6 +22,7 @@ import javax.swing.table.TableColumnModel;
import docking.action.KeyBindingType;
import docking.action.MenuData;
import docking.tool.ToolConstants;
import docking.widgets.table.GTable;
import docking.widgets.table.threaded.GThreadedTablePanel;
import ghidra.app.context.ProgramSymbolActionContext;
@@ -32,8 +33,8 @@ import ghidra.app.plugin.core.table.TableComponentProvider;
import ghidra.app.services.GoToService;
import ghidra.app.util.SymbolInspector;
import ghidra.app.util.query.TableService;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol;
import ghidra.util.HelpLocation;
@@ -43,11 +44,11 @@ import ghidra.util.table.GhidraThreadedTablePanel;
public class CreateSymbolTableAction extends ProgramSymbolContextAction {
private Plugin plugin;
private ServiceProvider services;
public CreateSymbolTableAction(Plugin plugin) {
super("Create Table", plugin.getName(), KeyBindingType.SHARED);
this.plugin = plugin;
public CreateSymbolTableAction(ServiceProvider services) {
super("Create Table", ToolConstants.SHARED_OWNER, KeyBindingType.SHARED);
this.services = services;
setPopupMenuData(new MenuData(new String[] { "Create Table" },
SymbolTreeContextAction.MIDDLE_MENU_GROUP));
@@ -72,25 +73,39 @@ public class CreateSymbolTableAction extends ProgramSymbolContextAction {
rowObjects.add(new SymbolRowObject(symbol));
}
PluginTool tool = plugin.getTool();
Program program = context.getProgram();
TransientSymbolTableModel model = new TransientSymbolTableModel(tool, program, rowObjects);
TransientSymbolTableModel model =
new TransientSymbolTableModel(services, program, rowObjects);
showTransientTable(services, "Symbols", context.getProgram(), model);
}
/**
* A utility method to show a table of symbols.
* @param services the service provider
* @param title the provider's title
* @param program the program
* @param model the model
* @return the new provider
*/
public static TableComponentProvider<SymbolRowObject> showTransientTable(
ServiceProvider services, String title, Program program,
TransientSymbolTableModel model) {
TableService service = services.getService(TableService.class);
if (service == null) {
Msg.showError(CreateSymbolTableAction.class, null, "Table Service Not Installed",
"You must have a Table Service installed to create a Symbol Table");
return null;
}
Navigatable navigatable = null;
GoToService goToService = tool.getService(GoToService.class);
GoToService goToService = services.getService(GoToService.class);
if (goToService != null) {
navigatable = goToService.getDefaultNavigatable();
}
TableService service = tool.getService(TableService.class);
if (service == null) {
Msg.showError(this, null, "Table Service Not Installed",
"You must have a Table Service installed to create a Symbol Table");
return;
}
TableComponentProvider<SymbolRowObject> provider =
service.showTable("Symbols", "Symbols", model, "Symbols", navigatable);
service.showTable(title, "Symbols", model, "Symbols", navigatable);
provider.setActionContextProvider(mouseEvent -> {
@@ -101,32 +116,38 @@ public class CreateSymbolTableAction extends ProgramSymbolContextAction {
return new ProgramSymbolActionContext(provider, program, selectedSymbols, table);
});
// replace the generic provider help with this action's help
provider.setHelpLocation(getHelpLocation());
// replace the generic provider help
provider.setHelpLocation(new HelpLocation("SymbolTablePlugin", "Temporary_Symbol_Table"));
addActions(provider, model);
addActions(services, provider, model);
GhidraThreadedTablePanel<SymbolRowObject> tablePanel = provider.getThreadedTablePanel();
GhidraTable table = tablePanel.getTable();
configureSymbolTable(tool, table, model, program);
configureSymbolTable(services, table, model, program);
return provider;
}
private void addActions(TableComponentProvider<SymbolRowObject> provider,
TransientSymbolTableModel model) {
private static void addActions(ServiceProvider services,
TableComponentProvider<SymbolRowObject> provider, TransientSymbolTableModel model) {
provider.installRemoveItemsAction();
CreateSymbolTableAction tableAction = new CreateSymbolTableAction(plugin);
provider.getTool().addLocalAction(provider, tableAction);
CreateSymbolTableAction tableAction = new CreateSymbolTableAction(services);
PluginTool tool = provider.getTool();
tool.addLocalAction(provider, tableAction);
SetSymbolPrimaryAction primaryAction = new SetSymbolPrimaryAction();
tool.addLocalAction(provider, primaryAction);
}
private void configureSymbolTable(PluginTool tool, GhidraTable table,
private static void configureSymbolTable(ServiceProvider services, GhidraTable table,
TransientSymbolTableModel model, Program program) {
new TransientSymbolTableDnDAdapter(table, model);
SymbolInspector symbolInspector = new SymbolInspector(tool, table);
SymbolInspector symbolInspector = new SymbolInspector(services, table);
SymbolRenderer renderer = model.getSymbolRenderer();
renderer.setSymbolInspector(symbolInspector);
@@ -141,7 +162,7 @@ public class CreateSymbolTableAction extends ProgramSymbolContextAction {
}
}
private List<Symbol> getSelectedSymbols(GTable table, TransientSymbolTableModel model) {
private static List<Symbol> getSelectedSymbols(GTable table, TransientSymbolTableModel model) {
List<Symbol> list = new ArrayList<>();
int[] rows = table.getSelectedRows();
for (int row : rows) {
@@ -0,0 +1,86 @@
/* ###
* 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.symboltree.actions;
import org.apache.commons.lang3.StringUtils;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.tool.ToolConstants;
import ghidra.app.cmd.label.SetLabelPrimaryCmd;
import ghidra.app.context.ProgramSymbolActionContext;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.Symbol;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.task.TaskLauncher;
public class SetSymbolPrimaryAction extends DockingAction {
private static final String NAME = "Set Label Primary";
public SetSymbolPrimaryAction() {
super(NAME, ToolConstants.SHARED_OWNER);
// Note: the group '2' is that of the PinSymbolAction. That group seems like a nice place.
setPopupMenuData(new MenuData(new String[] { "Set Primary" }, "2"));
setHelpLocation(new HelpLocation("SymbolTablePlugin", "Set_Primary"));
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof ProgramSymbolActionContext psac)) {
return false;
}
int n = psac.getSymbolCount();
if (n != 1) {
return false;
}
Symbol s = psac.getFirstSymbol();
return !s.isPrimary();
}
@Override
public void actionPerformed(ActionContext context) {
ProgramSymbolActionContext psac = (ProgramSymbolActionContext) context;
Symbol s = psac.getFirstSymbol();
Namespace ns = s.getParentNamespace();
String name = s.getName();
Address addr = s.getAddress();
SetLabelPrimaryCmd cmd = new SetLabelPrimaryCmd(addr, name, ns);
TaskLauncher.launchModal(NAME, () -> {
Program p = psac.getProgram();
p.withTransaction(NAME, () -> {
cmd.applyTo(p);
});
});
String errorMessage = cmd.getStatusMsg();
if (!StringUtils.isBlank(errorMessage)) {
Msg.showError(getClass(), null, "Unable to Set Label Primary", errorMessage);
}
}
}
@@ -23,8 +23,8 @@ import ghidra.app.cmd.label.DeleteLabelCmd;
import ghidra.app.cmd.label.RenameLabelCmd;
import ghidra.app.util.template.TemplateSimplifier;
import ghidra.docking.settings.Settings;
import ghidra.framework.cmd.Command;
import ghidra.framework.cmd.CompoundCmd;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
@@ -33,9 +33,11 @@ import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.table.AddressBasedTableModel;
import ghidra.util.table.column.*;
import ghidra.util.table.field.*;
import ghidra.util.task.*;
public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
@@ -51,7 +53,6 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
public static final int SOURCE_COL = 5;
public static final int REFS_COL = 6;
private PluginTool tool;
protected SymbolTable symbolTable;
protected ReferenceManager refMgr;
protected SymbolRowObject lastSymbol;
@@ -59,9 +60,8 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
protected SymbolRenderer symbolRenderer = new SymbolRenderer();
AbstractSymbolTableModel(PluginTool tool) {
super("Symbols", tool, null, null);
this.tool = tool;
AbstractSymbolTableModel(ServiceProvider sp) {
super("Symbols", sp, null, null);
this.filter = new NewSymbolFilter();
}
@@ -166,9 +166,11 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
return;
}
RenameLabelCmd cmd = new RenameLabelCmd(symbol, newName, SourceType.USER_DEFINED);
if (!tool.execute(cmd, getProgram())) {
Msg.showError(getClass(), null, "Error Renaming Symbol", cmd.getStatusMsg());
RenameTask task = new RenameTask(symbol, newName);
TaskLauncher.launch(task);
if (!task.success()) {
Msg.showError(getClass(), null, "Error Renaming Symbol", task.status());
}
}
@@ -229,15 +231,14 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
}
}
void delete(List<Symbol> rowObjects) {
if (rowObjects == null || rowObjects.isEmpty()) {
protected void delete(List<Symbol> symbols) {
if (symbols == null || symbols.isEmpty()) {
return;
}
tool.setStatusInfo("");
List<Symbol> deleteList = new LinkedList<>();
CompoundCmd<Program> cmd = new CompoundCmd<>("Delete symbol(s)");
for (Symbol symbol : rowObjects) {
for (Symbol symbol : symbols) {
if (symbol.isDynamic()) {
continue; // can't delete dynamic symbols...
}
@@ -262,16 +263,19 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
return;
}
if (tool.execute(cmd, getProgram())) {
DeleteTask task = new DeleteTask(cmd);
TaskLauncher.launch(task);
if (!task.success()) {
reload();
Msg.showError(getClass(), null, "Error Deleting Symbol(s)", task.status());
}
else {
for (Symbol s : deleteList) {
removeObject(new SymbolRowObject(s));
}
updateNow();
}
else {
tool.setStatusInfo(cmd.getStatusMsg());
reload();
}
}
public SymbolFilter getFilter() {
@@ -328,16 +332,102 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
return symbolRenderer;
}
//==================================================================================================
// Task Classes
//==================================================================================================
private class DeleteTask extends Task {
private CompoundCmd<Program> compoundCmd;
private boolean success;
private String status;
DeleteTask(CompoundCmd<Program> compoundCmd) {
super("Delete Symbols");
this.compoundCmd = compoundCmd;
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
program.withTransaction(getName(), () -> {
doRun(monitor);
});
}
private void doRun(TaskMonitor monitor) throws CancelledException {
monitor.initialize(compoundCmd.size());
success = true;
List<Command<Program>> commands = compoundCmd.getCommands();
for (Command<Program> cmd : commands) {
monitor.increment();
if (!cmd.applyTo(program)) {
success = false;
status = cmd.getStatusMsg();
break;
}
}
}
boolean success() {
return success;
}
String status() {
return status;
}
}
private class RenameTask extends Task {
private Symbol symbol;
private String newName;
private boolean success;
private String status;
public RenameTask(Symbol symbol, String newName) {
super("Rename Symbol");
this.symbol = symbol;
this.newName = newName;
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
program.withTransaction(getName(), () -> {
RenameLabelCmd cmd = new RenameLabelCmd(symbol, newName, SourceType.USER_DEFINED);
success = cmd.applyTo(program);
});
}
boolean success() {
return success;
}
String status() {
return status;
}
}
//==================================================================================================
// Table Column Classes
//==================================================================================================
private class NameTableColumn
protected class NameTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Symbol> {
public static final String NAME = "Name";
@Override
public String getColumnName() {
return "Name";
return NAME;
}
@Override
@@ -347,7 +437,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
}
}
private class PinnedTableColumn
protected class PinnedTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Boolean> {
private PinnedRenderer renderer = new PinnedRenderer();
@@ -378,7 +468,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
}
}
private class LocationTableColumn
protected class LocationTableColumn
extends AbstractProgramLocationTableColumn<SymbolRowObject, AddressBasedLocation> {
@Override
@@ -404,12 +494,14 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
}
}
private class SymbolTypeTableColumn
protected class SymbolTypeTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
public static final String NAME = "Type";
@Override
public String getColumnName() {
return "Type";
return NAME;
}
@Override
@@ -426,14 +518,14 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
}
}
private class VariableSymbolLocation extends AddressBasedLocation {
protected class VariableSymbolLocation extends AddressBasedLocation {
VariableSymbolLocation(Variable variable) {
super(variable.getSymbol().getAddress(), variable.getVariableStorage().toString());
}
}
private class DataTypeTableColumn
protected class DataTypeTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override
@@ -470,7 +562,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
}
}
private class NamespaceTableColumn
protected class NamespaceTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override
@@ -489,9 +581,11 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
}
}
private class SourceTableColumn
protected class SourceTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, SourceType> {
public static final String NAME = "Source";
private GColumnRenderer<SourceType> renderer = new AbstractGColumnRenderer<>() {
@Override
protected String getText(Object value) {
@@ -509,7 +603,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
@Override
public String getColumnName() {
return "Source";
return NAME;
}
@Override
@@ -529,14 +623,16 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
}
}
private class ReferenceCountTableColumn
protected class ReferenceCountTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Integer> {
public static final String NAME = "Ref Count";
private ReferenceCountRenderer renderer = new ReferenceCountRenderer();
@Override
public String getColumnName() {
return "Reference Count";
return NAME;
}
@Override
@@ -562,7 +658,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
}
}
private class OffcutReferenceCountTableColumn
protected class OffcutReferenceCountTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Integer> {
private OffcutReferenceCountRenderer renderer = new OffcutReferenceCountRenderer();
@@ -613,7 +709,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
}
}
private class UserTableColumn
protected class UserTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override
@@ -650,7 +746,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
}
class OriginalNameColumn
protected class OriginalNameColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override
@@ -688,7 +784,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
}
}
private class SimplifiedNameColumn
protected class SimplifiedNameColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
private TemplateSimplifier simplifier = new TemplateSimplifier();
@@ -36,6 +36,7 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolTable;
import ghidra.program.util.LabelFieldLocation;
import ghidra.program.util.ProgramLocation;
import ghidra.util.table.*;
@@ -126,12 +127,17 @@ class SymbolPanel extends JPanel {
Program program = location.getProgram();
SymbolTable symbolTable = program.getSymbolTable();
Address address = location.getAddress();
Symbol primarySymbol = symbolTable.getPrimarySymbol(address);
if (primarySymbol == null) {
return;
Symbol symbol = null;
if (location instanceof LabelFieldLocation lfl) {
symbol = lfl.getSymbol();
}
SymbolRowObject rowObject = new SymbolRowObject(primarySymbol);
if (symbol == null) {
symbol = symbolTable.getPrimarySymbol(address);
}
SymbolRowObject rowObject = new SymbolRowObject(symbol);
int index = symbolModel.getRowIndex(rowObject);
if (index >= 0) {
gTable.selectRow(index);
@@ -21,16 +21,19 @@ import static ghidra.program.util.ProgramEvent.*;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
import javax.swing.Icon;
import docking.ActionContext;
import docking.action.*;
import docking.action.builder.ActionBuilder;
import docking.tool.ToolConstants;
import docking.widgets.OptionDialog;
import docking.widgets.OptionDialogBuilder;
import docking.widgets.table.DynamicTableColumn;
import docking.widgets.table.TableColumnDescriptor;
import generic.theme.GIcon;
import ghidra.app.CorePluginPackage;
import ghidra.app.cmd.refs.RemoveReferenceCmd;
@@ -39,17 +42,20 @@ import ghidra.app.events.ProgramActivatedPluginEvent;
import ghidra.app.events.ProgramLocationPluginEvent;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.symboltree.actions.*;
import ghidra.app.plugin.core.table.TableComponentProvider;
import ghidra.app.services.BlockModelService;
import ghidra.app.services.GoToService;
import ghidra.app.util.HelpTopics;
import ghidra.app.util.SymbolInspector;
import ghidra.app.util.viewer.field.LabelFieldSymbolLoader;
import ghidra.app.util.viewer.field.LabelFieldSymbolLoader.Symbols;
import ghidra.framework.model.DomainObjectListener;
import ghidra.framework.model.DomainObjectListenerBuilder;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramChangeRecord;
import ghidra.program.util.ProgramLocation;
@@ -79,12 +85,13 @@ import resources.Icons;
"allows symbols to be renamed and deleted. This plugin also " +
"shows references to a symbol. Filters can be set " +
"to show subsets of the symbols.",
servicesProvided = { SymbolTableService.class },
servicesRequired = { GoToService.class, BlockModelService.class },
eventsProduced = { ProgramLocationPluginEvent.class },
eventsConsumed = { ProgramActivatedPluginEvent.class, ProgramLocationPluginEvent.class }
)
//@formatter:on
public class SymbolTablePlugin extends Plugin {
public class SymbolTablePlugin extends Plugin implements SymbolTableService {
private static final String NAVIGATE_ON_INCOMING_EVENT_KEY = "NAVIGATE_ON_INCOMING_EVENT";
private static final String NAVIGATE_ON_OUTGOING_EVENT_KEY = "NAVIGATE_ON_OUTGOING_EVENT";
@@ -106,6 +113,10 @@ public class SymbolTablePlugin extends Plugin {
private BlockModelService blockModelService;
private SwingUpdateManager swingMgr;
// providers shown by the service interface
private Map<String, TableComponentProvider<SymbolRowObject>> transientTableProviders =
new HashMap<>();
private DomainObjectListener domainObjectListener = createDomainObjectListener();
/**
@@ -126,6 +137,7 @@ public class SymbolTablePlugin extends Plugin {
@Override
protected void init() {
gotoService = tool.getService(GoToService.class);
blockModelService = tool.getService(BlockModelService.class);
@@ -138,11 +150,6 @@ public class SymbolTablePlugin extends Plugin {
inspector = new SymbolInspector(getTool(), symProvider.getComponent());
}
/**
* Tells a plugin that it is no longer needed.
* The plugin should remove itself from anything that
* it is registered to and release any resources.
*/
@Override
public void dispose() {
super.dispose();
@@ -456,10 +463,14 @@ public class SymbolTablePlugin extends Plugin {
DockingAction clearPinnedAction = new ClearPinSymbolAction(getName(), pinnedPopupGroup);
tool.addAction(clearPinnedAction);
CreateSymbolTableAction tableAction = new CreateSymbolTableAction(this);
CreateSymbolTableAction tableAction = new CreateSymbolTableAction(getTool());
tableAction.getPopupMenuData().setMenuGroup(popupGroup);
tool.addLocalAction(symProvider, tableAction);
SetSymbolPrimaryAction primaryAction = new SetSymbolPrimaryAction();
primaryAction.getPopupMenuData().setMenuGroup(popupGroup);
tool.addLocalAction(symProvider, primaryAction);
//@formatter:off
String bottomGroup = "ShowReferencesTo" + 1;
new ActionBuilder("Delete All References", getName())
@@ -641,6 +652,163 @@ public class SymbolTablePlugin extends Plugin {
action.setSelected(true);
}
//=================================================================================================
// Service Methods
//=================================================================================================
@Override
public TableComponentProvider<SymbolRowObject> showSymbols(CodeUnit codeUnit) {
Objects.requireNonNull(codeUnit);
Program program = codeUnit.getProgram();
Address addr = codeUnit.getMinAddress();
String title = "Labels at " + addr;
TableComponentProvider<SymbolRowObject> provider = transientTableProviders.get(title);
if (provider != null) {
if (provider.isShowing()) {
LabelFieldSymbolModel model = (LabelFieldSymbolModel) provider.getModel();
reload(codeUnit, model);
provider.toFront();
return provider;
}
transientTableProviders.remove(title);
}
LabelFieldSymbolLoader loader =
new LabelFieldSymbolLoader(codeUnit, Integer.MAX_VALUE, true);
Symbols symbols = loader.getSymbols();
List<Symbol> list = symbols.getAllSymbols();
HashSet<SymbolRowObject> rowObjects = list.stream()
.map(s -> new SymbolRowObject(s))
.collect(Collectors.toCollection(HashSet::new));
LabelFieldSymbolModel model =
new LabelFieldSymbolModel(tool, program, rowObjects);
provider = CreateSymbolTableAction.showTransientTable(tool, title, program, model);
if (provider == null) {
return null;
}
provider.setClosedCallback(() -> {
transientTableProviders.remove(title);
});
addActions(provider, model, codeUnit);
transientTableProviders.put(title, provider);
return provider;
}
private void addActions(TableComponentProvider<SymbolRowObject> provider,
LabelFieldSymbolModel model, CodeUnit cu) {
new ActionBuilder("Refresh", ToolConstants.SHARED_OWNER)
.toolBarGroup("_", "1") // first
.toolBarIcon(Icons.REFRESH_ICON)
.helpLocation(new HelpLocation(HelpTopics.CODE_BROWSER, "Refresh_Labels"))
.onAction(c -> {
reload(cu, model);
})
.buildAndInstallLocal(provider);
new ActionBuilder("Delete", ToolConstants.SHARED_OWNER)
.toolBarGroup("_", "2") // first
.toolBarIcon(Icons.DELETE_ICON)
.helpLocation(new HelpLocation(HelpTopics.CODE_BROWSER, "Delete_Label"))
.enabledWhen(c -> {
GhidraTable table = provider.getTable();
return table.getSelectedRowCount() > 0;
})
.onAction(c -> {
deleteSymbols(provider, model);
})
.buildAndInstallLocal(provider);
}
private void deleteSymbols(TableComponentProvider<SymbolRowObject> provider,
LabelFieldSymbolModel model) {
List<Symbol> symbols = new ArrayList<>();
GhidraTable table = provider.getTable();
int[] rows = table.getSelectedRows();
for (int row : rows) {
SymbolRowObject ro = model.getRowObject(row);
Symbol symbol = ro.getSymbol();
if (symbol.isDeleted()) {
// this symbol was deleted outside of the table and the table did not update
model.removeObject(ro);
continue;
}
symbols.add(symbol);
}
model.delete(symbols);
}
private void reload(CodeUnit cu, LabelFieldSymbolModel model) {
LabelFieldSymbolLoader loader =
new LabelFieldSymbolLoader(cu, Integer.MAX_VALUE, true);
Symbols symbols = loader.getSymbols();
List<Symbol> list = symbols.getAllSymbols();
HashSet<SymbolRowObject> rowObjects = list.stream()
.map(s -> new SymbolRowObject(s))
.collect(Collectors.toCollection(HashSet::new));
model.setData(rowObjects);
}
private class LabelFieldSymbolModel extends TransientSymbolTableModel {
public LabelFieldSymbolModel(ServiceProvider sp, Program program,
HashSet<SymbolRowObject> rowObjects) {
super(sp, program, rowObjects);
}
public void setData(HashSet<SymbolRowObject> rowObjects) {
this.rowObjects = rowObjects;
reload();
}
@Override
protected void delete(List<Symbol> symbols) {
super.delete(symbols);
}
@Override
protected TableColumnDescriptor<SymbolRowObject> createTableColumnDescriptor() {
TableColumnDescriptor<SymbolRowObject> descriptor = super.createTableColumnDescriptor();
//@formatter:off
Set<String> visibleNames = new HashSet<>(Set.of(
NameTableColumn.NAME,
SymbolTypeTableColumn.NAME,
SourceTableColumn.NAME,
ReferenceCountTableColumn.NAME));
//@formatter:on
List<DynamicTableColumn<SymbolRowObject, ?, ?>> allColumns = descriptor.getAllColumns();
for (DynamicTableColumn<SymbolRowObject, ?, ?> column : allColumns) {
String columnName = column.getColumnName();
boolean visible = visibleNames.contains(columnName);
descriptor.setVisible(columnName, visible);
}
return descriptor;
}
}
//==================================================================================================
// Table Update Jobs
//==================================================================================================
@@ -0,0 +1,33 @@
/* ###
* 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.symtable;
import ghidra.app.plugin.core.table.TableComponentProvider;
import ghidra.app.plugin.processors.sleigh.symbol.Symbol;
import ghidra.program.model.listing.CodeUnit;
/**
* Service for showing {@link Symbol}s in a table.
*/
public interface SymbolTableService {
/**
* Shows all symbols and offcut symbols contained in the given code unit.
* @param codeUnit the code unit
* @return the table provider that is shown
*/
public TableComponentProvider<SymbolRowObject> showSymbols(CodeUnit codeUnit);
}
@@ -4,9 +4,9 @@
* 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.
@@ -22,7 +22,7 @@ import java.util.HashSet;
import java.util.List;
import ghidra.framework.model.DomainObjectListenerBuilder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramChangeRecord;
import ghidra.util.datastruct.Accumulator;
@@ -36,13 +36,13 @@ import ghidra.util.task.TaskMonitor;
*/
public class TransientSymbolTableModel extends AbstractSymbolTableModel {
private HashSet<SymbolRowObject> rowObjects;
protected HashSet<SymbolRowObject> rowObjects;
private SwingUpdateManager updater = new SwingUpdateManager(this::fireTableDataChanged);
public TransientSymbolTableModel(PluginTool tool, Program program,
public TransientSymbolTableModel(ServiceProvider sp, Program program,
HashSet<SymbolRowObject> rowObjects) {
super(tool);
super(sp);
this.rowObjects = rowObjects;
setProgram(program);
symbolTable = program.getSymbolTable();
@@ -34,17 +34,20 @@ public class LabelCodeUnitFormat extends BrowserCodeUnitFormat {
@Override
protected String getOffcutLabelStringForInstruction(Address offcutAddress,
Instruction instruction, Address markupAddress) {
Instruction instruction, Address markupAddress, Symbol symbol) {
if (markupAddress != null) {
throw new UnsupportedOperationException();
}
Program program = instruction.getProgram();
Symbol offsym = program.getSymbolTable().getPrimarySymbol(offcutAddress);
if (symbol == null) {
symbol = program.getSymbolTable().getPrimarySymbol(offcutAddress);
}
Address instructionAddress = instruction.getMinAddress();
long diff = offcutAddress.subtract(instructionAddress);
boolean decorate = !offsym.isDynamic();
boolean decorate = !symbol.isDynamic();
boolean simplify = true;
return getDefaultOffcutString(offsym, instruction, diff, decorate, simplify);
return getDefaultOffcutString(symbol, instruction, diff, decorate, simplify);
}
@Override
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,95 @@
/* ###
* 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.util.viewer.field;
import java.awt.event.MouseEvent;
import docking.widgets.fieldpanel.field.FieldElement;
import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.core.symtable.SymbolTableService;
import ghidra.app.services.GoToService;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.util.*;
import ghidra.util.Msg;
/**
* A handler to process Label field clicks
*/
public class LabelFieldMouseHandler implements FieldMouseHandlerExtension {
private final static Class<?>[] SUPPORTED_CLASSES =
new Class<?>[] { LabelFieldLocation.class, MoreLabelFieldLocation.class };
@Override
public boolean fieldElementClicked(Object clickedObject, Navigatable sourceNavigatable,
ProgramLocation location, MouseEvent mouseEvent, ServiceProvider serviceProvider) {
if (mouseEvent.getClickCount() != 2 || mouseEvent.getButton() != MouseEvent.BUTTON1) {
return false;
}
GoToService goToService = serviceProvider.getService(GoToService.class);
if (goToService == null) {
Msg.error(this, GoToService.class.getSimpleName() + " not installed!");
return false;
}
SymbolTableService service = serviceProvider.getService(SymbolTableService.class);
if (service == null) {
Msg.error(this, SymbolTableService.class.getSimpleName() + " not installed!");
return false;
}
String clickedText = getText(clickedObject);
if (MoreLabelFieldLocation.MORE_LABELS_STRING.equals(clickedText)) {
showLabelsDialog(service, location);
return true;
}
if (location instanceof LabelFieldLocation) {
// Allow double-clicking of any label to show the label dialog. This allows the user to
// use the dialog even when the [more] is not showing.
showLabelsDialog(service, location);
return true;
}
return false;
}
private void showLabelsDialog(SymbolTableService service, ProgramLocation location) {
Address addr = location.getAddress();
Program program = location.getProgram();
Listing listing = program.getListing();
CodeUnit cu = listing.getCodeUnitAt(addr);
service.showSymbols(cu);
}
private String getText(Object clickedObject) {
if (clickedObject instanceof FieldElement) {
FieldElement fieldElement = (FieldElement) clickedObject;
return fieldElement.getText();
}
return clickedObject.toString();
}
@Override
public Class<?>[] getSupportedProgramLocations() {
return SUPPORTED_CLASSES;
}
}
@@ -0,0 +1,223 @@
/* ###
* 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.util.viewer.field;
import java.util.*;
import generic.json.Json;
import ghidra.program.database.symbol.FunctionSymbol;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressIterator;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
/**
* A simple class to load all symbols for a given code unit
*/
public class LabelFieldSymbolLoader {
private boolean displayFunctionLabel;
private Symbols symbols;
private boolean hasMore;
public LabelFieldSymbolLoader(CodeUnit cu, int max, boolean displayFunctionLabel) {
this.displayFunctionLabel = displayFunctionLabel;
symbols = new Symbols();
gatherRealSymbols(cu, max);
gatherOffcutSymbols(cu, max);
}
public Symbols getSymbols() {
return symbols;
}
public boolean hasMore() {
return hasMore;
}
private void gatherRealSymbols(CodeUnit cu, int max) {
Address addr = cu.getMinAddress();
Program program = cu.getProgram();
//
// Place the primary symbol to the front so that it is always rendered, even if we hit the
// symbol limit. Also, remove the function symbol if the user doesn't want to see it.
//
SymbolTable st = program.getSymbolTable();
SymbolIterator it = st.getSymbolsAsIterator(addr);
Symbol primary = st.getPrimarySymbol(addr);
if (primary == null) {
return;
}
while (it.hasNext()) {
Symbol s = it.next();
if (s.isPrimary()) {
continue;
}
if ((max - 1) == symbols.size()) { // -1 to save space for primary
hasMore = true;
break;
}
if (s instanceof FunctionSymbol && !displayFunctionLabel) {
continue;
}
symbols.add(s);
}
symbols.add(primary);
}
private void gatherOffcutSymbols(CodeUnit cu, int max) {
if (max == 0) {
return;
}
Address startAddr = cu.getMinAddress();
if (!startAddr.isMemoryAddress()) {
return;
}
Program program = cu.getProgram();
if (cu.getLength() == 1) {
return;
}
Address nextAddr = startAddr.next();
if (nextAddr == null) {
return;
}
SymbolTable symbolTable = program.getSymbolTable();
Address endAddress = cu.getMaxAddress();
ReferenceManager referenceManager = program.getReferenceManager();
AddressIterator it = referenceManager.getReferenceDestinationIterator(nextAddr, true);
while (it.hasNext()) {
Address addr = it.next();
if (addr.compareTo(endAddress) > 0 ||
// Note: check for wrapping - temporary work-around
addr.compareTo(cu.getMinAddress()) <= 0) {
break;
}
if (max == symbols.size()) {
hasMore = true;
return;
}
Symbol s = symbolTable.getPrimarySymbol(addr);
symbols.addOffcut(s);
}
SymbolIterator symIter = symbolTable.getSymbolIterator(nextAddr, true);
while (symIter.hasNext()) {
Symbol s = symIter.next();
Address addr = s.getAddress();
if (addr.compareTo(endAddress) > 0 ||
// Note: check for wrapping - temporary work-around
addr.compareTo(cu.getMinAddress()) <= 0) {
break;
}
if (max == symbols.size()) {
hasMore = true;
return;
}
// remove to handle the case where this symbol was added in the above loop
symbols.removeOffct(s);
symbols.addOffcut(s);
}
}
/**
* A simple class to hold all real and offcut symbols for a given code unit. The client will
* limit the number of symbols added to this class. The real symbols are loaded first, with any
* remaining space filled with existing offcut symbols.
*/
public class Symbols {
private List<Symbol> realSymbols = new ArrayList<>();
private List<Symbol> offcutSymbols = new ArrayList<>();
void addOffcut(Symbol s) {
offcutSymbols.add(s);
}
void removeOffct(Symbol s) {
offcutSymbols.remove(s);
}
void add(Symbol s) {
realSymbols.add(s);
}
void add(int index, Symbol s) {
realSymbols.add(index, s);
}
void remove(Symbol s) {
realSymbols.remove(s);
}
int size() {
return offcutSymbols.size() + realSymbols.size();
}
Symbol get(int index) {
if (index < offcutSymbols.size()) {
return offcutSymbols.get(index);
}
int updatedIndex = index - offcutSymbols.size();
return realSymbols.get(updatedIndex);
}
public List<Symbol> getAllSymbols() {
List<Symbol> list = new ArrayList<>();
list.addAll(realSymbols);
list.addAll(offcutSymbols);
return list;
}
List<Symbol> getOffcuts() {
return Collections.unmodifiableList(offcutSymbols);
}
void reverseSymbols() {
realSymbols = realSymbols.reversed();
}
@Override
public String toString() {
return Json.toString(this);
}
}
}
@@ -30,7 +30,7 @@ import ghidra.program.model.symbol.Reference;
import ghidra.program.util.*;
/**
* A handler to process {@link XRefFieldMouseHandler} clicks
* A handler to process XRef field clicks
*/
public class XRefFieldMouseHandler implements FieldMouseHandlerExtension {
@@ -1367,7 +1367,7 @@ public class CodeUnitFormat {
if (symbolAddress.isMemoryAddress()) {
CodeUnit cu = program.getListing().getCodeUnitContaining(symbolAddress);
if (isOffcut(symbolAddress, cu)) {
return getOffcutLabelString(symbolAddress, cu, markupAddress);
return getOffcutLabelString(symbolAddress, cu, markupAddress, symbol);
}
else if (isStringData(cu)) {
return getLabelStringForStringData((Data) cu, symbol);
@@ -1408,10 +1408,11 @@ public class CodeUnitFormat {
return prefix + UNDERSCORE + SymbolUtilities.getAddressString(symbol.getAddress());
}
public String getOffcutLabelString(Address offcutAddress, CodeUnit cu, Address markupAddress) {
public String getOffcutLabelString(Address offcutAddress, CodeUnit cu, Address markupAddress,
Symbol symbol) {
if (cu instanceof Instruction) {
return getOffcutLabelStringForInstruction(offcutAddress, (Instruction) cu,
markupAddress);
markupAddress, symbol);
}
return getOffcutDataString(offcutAddress, (Data) cu);
}
@@ -1460,17 +1461,22 @@ public class CodeUnitFormat {
* @param offcutAddress address for which generated label represents
* @param instruction instruction containing offcut address
* @param markupAddress address where a label will be referenced from (may be null)
* @param symbol an optional symbol that is used to generate the symbol name
* @return generated offcut label
*/
protected String getOffcutLabelStringForInstruction(Address offcutAddress,
Instruction instruction, Address markupAddress) {
Instruction instruction, Address markupAddress, Symbol symbol) {
Program program = instruction.getProgram();
Symbol offsym = program.getSymbolTable().getPrimarySymbol(offcutAddress);
if (symbol == null) {
symbol = program.getSymbolTable().getPrimarySymbol(offcutAddress);
}
Address instructionAddress = instruction.getMinAddress();
long diff = offcutAddress.subtract(instructionAddress);
boolean decorate = false; // we never decorate in the operand field
boolean simplify = true; // we always simplify names of instruction labels
if (offsym.isDynamic()) {
if (symbol.isDynamic()) {
Symbol containingSymbol = program.getSymbolTable().getPrimarySymbol(instructionAddress);
if (containingSymbol != null) {
String displayName = containingSymbol.getName();
@@ -1481,7 +1487,7 @@ public class CodeUnitFormat {
return simplifyTemplate(displayName) + PLUS + SymbolUtilities.getDiffString(diff);
}
}
return getDefaultOffcutString(offsym, instruction, diff, decorate, simplify);
return getDefaultOffcutString(symbol, instruction, diff, decorate, simplify);
}
protected String addOffcutInformation(String prefix, String addressString, int diff,
@@ -4,9 +4,9 @@
* 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.
@@ -20,7 +20,6 @@ import static org.junit.Assert.*;
import org.junit.*;
import docking.ActionContext;
import docking.widgets.combobox.GhidraComboBox;
import ghidra.app.events.ProgramLocationPluginEvent;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.framework.plugintool.PluginTool;
@@ -74,10 +73,9 @@ public class OperandLabelDialogTest extends AbstractGhidraHeadedIntegrationTest
performAction(setLabelAction, context, false);
waitForSwing();
OperandLabelDialog dialog = waitForDialogComponent(OperandLabelDialog.class);
GhidraComboBox<?> combo = (GhidraComboBox<?>) findComponentByName(dialog, "MYCHOICE");
SymbolChooserDialog dialog = waitForDialogComponent(SymbolChooserDialog.class);
setSelectedItem(combo, "bob");
setSelectedItem(dialog, "bob");
pressButtonByText(dialog, "OK");
waitForSwing();
@@ -91,10 +89,9 @@ public class OperandLabelDialogTest extends AbstractGhidraHeadedIntegrationTest
performAction(setLabelAction, context, false);
waitForSwing();
dialog = waitForDialogComponent(OperandLabelDialog.class);
combo = (GhidraComboBox<?>) findComponentByName(dialog, "MYCHOICE");
dialog = waitForDialogComponent(SymbolChooserDialog.class);
setSelectedItem(combo, "b");
setSelectedItem(dialog, "b");
pressButtonByText(dialog, "OK");
program.flushEvents();
@@ -108,7 +105,7 @@ public class OperandLabelDialogTest extends AbstractGhidraHeadedIntegrationTest
assertEquals("dword ptr [b]", cb.getCurrentFieldText());
}
private void setSelectedItem(GhidraComboBox<?> combo, String s) {
runSwing(() -> combo.setSelectedItem(s));
private void setSelectedItem(SymbolChooserDialog dialog, String item) {
runSwing(() -> dialog.setSelectedItem(item));
}
}
@@ -169,8 +169,9 @@ public class LabelFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest {
ListingTextField tf = (ListingTextField) cb.getCurrentField();
// 4 offcut labels and one dynamic label
String actual = tf.getText();
assertEquals("LAB_01002000+1 LAB_01002000+2 LAB_01002000+3 LAB_01002000+4 LAB_01002000",
tf.getText()); // bad offcut put on instruction
actual); // bad offcut put on instruction
assertEquals(5, tf.getNumRows());
}
@@ -93,6 +93,13 @@ public class FieldLocation implements Comparable<FieldLocation> {
this(BigInteger.valueOf(index), fieldNum, row, col);
}
/**
* Construct a new FieldLocation with the given index,fieldNum,row, and col.
* @param index the index of the layout containing the location
* @param fieldNum the index of the field in the layout containing the location
* @param row the text row in the field containing the location.
* @param col the character position in the row containing the location.
*/
public FieldLocation(BigInteger index, int fieldNum, int row, int col) {
this.index = index;
this.fieldNum = fieldNum;
@@ -4,9 +4,9 @@
* 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.
@@ -38,4 +38,21 @@ public interface DynamicColumnTableModel<ROW_TYPE>
* @return the model index
*/
public int getColumnIndex(DynamicTableColumn<ROW_TYPE, ?, ?> column);
/**
* Allows table models to create their own preference key.
* <p>
* The preference key is used to save the column state of this configurable model. All models
* that share a preference key will share the same visible column state.
* The {@link GTableColumnModel} manages this state for the framework. The column model will
* ask the table for its preference key, which will ask the model when not key has been set on
* the table. In the case that no key is set in the table or the model, a key will be created
* based on the classname and default columns.
*
* @return the preference key; the default value is null
*/
public default String getPreferenceKey() {
return null;
}
}
@@ -15,10 +15,10 @@
*/
package docking.widgets.table;
import static docking.DockingUtils.CONTROL_KEY_MODIFIER_MASK;
import static docking.action.MenuData.NO_MNEMONIC;
import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
import static javax.swing.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION;
import static docking.DockingUtils.*;
import static docking.action.MenuData.*;
import static java.awt.event.InputEvent.*;
import static javax.swing.ListSelectionModel.*;
import java.awt.*;
import java.awt.event.*;
@@ -636,7 +636,7 @@ public class GTable extends JTable {
}
/**
* {@return the underlying ConfigurableColumnTableModel if one is in-use}
* {@return the underlying ConfigurableColumnTableModel if one is in use}
*/
public ConfigurableColumnTableModel getConfigurableColumnTableModel() {
TableModel model = getUnwrappedTableModel();
@@ -646,6 +646,17 @@ public class GTable extends JTable {
return null;
}
/**
* {@return the underlying DynamicColumnTableModel if one is in use}
*/
public DynamicColumnTableModel<?> getDynamicTableModel() {
TableModel model = getUnwrappedTableModel();
if (model instanceof DynamicColumnTableModel<?>) {
return (DynamicColumnTableModel<?>) model;
}
return null;
}
/**
* Unrolls the current model by checking if the current model is inside of a wrapper table
* model.
@@ -912,7 +923,17 @@ public class GTable extends JTable {
* @see #setPreferenceKey(String)
*/
public String getPreferenceKey() {
return preferenceKey;
if (preferenceKey != null) {
// prefer the key that has been set programmatically
return preferenceKey;
}
DynamicColumnTableModel<?> dynamicModel = getDynamicTableModel();
if (dynamicModel != null) {
return dynamicModel.getPreferenceKey();
}
return null;
}
/**
@@ -4,9 +4,9 @@
* 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.
@@ -15,9 +15,9 @@
*/
package ghidra.framework.cmd;
import ghidra.framework.model.DomainObject;
import java.util.*;
import java.util.ArrayList;
import ghidra.framework.model.DomainObject;
/**
* Implementation for multiple commands that are done as a unit.
@@ -28,7 +28,7 @@ import java.util.ArrayList;
* @param <T> {@link DomainObject} implementation interface
*/
public class CompoundCmd<T extends DomainObject> implements Command<T> {
private ArrayList<Command<T>> cmds;
private List<Command<T>> cmds;
private String statusMsg;
private String name;
@@ -81,4 +81,10 @@ public class CompoundCmd<T extends DomainObject> implements Command<T> {
return cmds.size();
}
/**
* {@return the commands in this compound command}
*/
public List<Command<T>> getCommands() {
return Collections.unmodifiableList(cmds);
}
}
@@ -65,7 +65,7 @@ public interface SymbolTable {
* @param name the name of the symbol
* @param source the source of this symbol. In general, a source of {@link SourceType#DEFAULT}
* should never be specified using this method.
* @return new labe or function symbol
* @return new label or function symbol
* @throws InvalidInputException if name contains white space, is zero length, or is null for
* non-default source
* @throws IllegalArgumentException if {@link SourceType#DEFAULT} is improperly specified, or
@@ -463,7 +463,7 @@ public interface SymbolTable {
/**
* Get all the symbols of the given type within the given address set.
* <p>
* <b>NOTE:</b> All external symbols will be omiitted unless the full
* <b>NOTE:</b> All external symbols will be omitted unless the full
* {@link AddressSpace#EXTERNAL_SPACE} range is included within the specified address set
* or a null addressSet is specified. All global dynamic label symbols will be omitted.
*
@@ -4,9 +4,9 @@
* 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.
@@ -36,8 +36,8 @@ public class CodeUnitLocation extends ProgramLocation {
* @param componentPath if this is not null it is the path to a data
* component inside of another data component
* @param row the row within the field.
* @param col - the display item index on the given row. (Note most fields only have one display item per row)
* @param charOffset - the character offset within the display item.
* @param col the display item index on the given row. (Note most fields only have one display item per row)
* @param charOffset the character offset within the display item.
*
*/
public CodeUnitLocation(Program program, Address addr, int[] componentPath, int row, int col,
@@ -53,8 +53,8 @@ public class CodeUnitLocation extends ProgramLocation {
* @param componentPath if this is not null it is the path to a data
* component inside of another data component
* @param row the row within the field.
* @param col - the display item index on the given row. (Note most fields only have one display item per row)
* @param charOffset - the character offset within the display item.
* @param col the display item index on the given row. (Note most fields only have one display item per row)
* @param charOffset the character offset within the display item.
*
*/
protected CodeUnitLocation(Program program, Address addr, Address byteAddr, int[] componentPath,
@@ -70,8 +70,8 @@ public class CodeUnitLocation extends ProgramLocation {
* @param program the program for obtaining the code unit
* @param addr address of the location; should not be null
* @param row the row within the field.
* @param col - the display item index on the given row. (Note most fields only have one display item per row)
* @param charOffset - the character offset within the display item.
* @param col the display item index on the given row. (Note most fields only have one display item per row)
* @param charOffset the character offset within the display item.
*
*/
public CodeUnitLocation(Program program, Address addr, int row, int col, int charOffset) {
@@ -0,0 +1,40 @@
/* ###
* 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.program.util;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
/**
* Represents the '[more]' text used by the label field factory.
*/
public class MoreLabelFieldLocation extends CodeUnitLocation {
public static final String MORE_LABELS_STRING = "[more]";
public MoreLabelFieldLocation() {
// for serialization
}
public MoreLabelFieldLocation(Program p, Address addr, int row, int charOffset) {
super(p, addr, row, 0, charOffset);
}
@Override
public String toString() {
return MORE_LABELS_STRING;
}
}
@@ -21,8 +21,8 @@ import javax.swing.*;
import org.junit.Test;
import docking.widgets.combobox.GhidraComboBox;
import ghidra.app.plugin.core.label.*;
import ghidra.app.plugin.core.label.LabelHistoryDialog;
import ghidra.app.plugin.core.label.LabelHistoryInputDialog;
import ghidra.app.util.*;
import ghidra.program.model.address.*;
import ghidra.program.model.symbol.LabelHistory;
@@ -85,22 +85,6 @@ public class LabelMgrPluginScreenShots extends GhidraScreenShotGenerator {
captureDialog();
}
@Test
public void testSetLabel() {
LabelMgrPlugin plugin = getPlugin(tool, LabelMgrPlugin.class);
final OperandLabelDialog dialog = new OperandLabelDialog(plugin);
final GhidraComboBox<?> combo = (GhidraComboBox<?>) getInstanceField("myChoice", dialog);
runSwing(new Runnable() {
@Override
public void run() {
dialog.setTitle("Set Label at 004a671");
combo.setSelectedItem("LAB_0040a671");
}
});
showDialogWithoutBlocking(tool, dialog);
captureDialog(350, 116);
}
@Test
public void testShowLabelHistory() {
AddressSpace space = new GenericAddressSpace("Test", 32, AddressSpace.TYPE_RAM, 0);