From 6563cebcf52268c291bad15d141f69c23e65e163 Mon Sep 17 00:00:00 2001
From: ghidragon <106987263+ghidragon@users.noreply.github.com>
Date: Tue, 24 Jan 2023 16:26:04 -0500
Subject: [PATCH] GP-2141 adding feature for better starting location when
opening a program.
---
Ghidra/Features/Base/certification.manifest | 1 -
.../help/topics/Navigation/Navigation.htm | 33 +++-
.../navigation/NextPrevAddressPlugin.java | 7 +-
.../ProgramStartingLocationOptions.java | 175 +++++++++++++++++
.../ProgramStartingLocationPlugin.java | 148 ++++++++++++++
.../navigation/NextPrevAddressPluginTest.java | 33 ++--
.../NextPrevCodeUnitPluginTest.java | 11 +-
.../navigation/ProgramStartPluginTest.java | 184 ++++++++++++++++++
.../framework/plugintool/PluginManager.java | 8 +-
.../framework/plugintool/PluginTool.java | 15 +-
.../program/model/symbol/SymbolTable.java | 84 ++++++++
11 files changed, 671 insertions(+), 28 deletions(-)
create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProgramStartingLocationOptions.java
create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProgramStartingLocationPlugin.java
create mode 100644 Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/ProgramStartPluginTest.java
diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest
index 6fbeea6a06..c1f70ee0b7 100644
--- a/Ghidra/Features/Base/certification.manifest
+++ b/Ghidra/Features/Base/certification.manifest
@@ -798,7 +798,6 @@ src/main/resources/images/field.header.up.png||GHIDRA||||END|
src/main/resources/images/fingerPointer.png||GHIDRA||||END|
src/main/resources/images/flag-green.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/resources/images/flag-yellow.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
-src/main/resources/images/font.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/format-text-bold.png||Tango Icons - Public Domain|||tango icon set|END|
src/main/resources/images/functionDef.png||GHIDRA||||END|
src/main/resources/images/function_graph.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Navigation/Navigation.htm b/Ghidra/Features/Base/src/main/help/help/topics/Navigation/Navigation.htm
index 739b731d61..d29d4dd35a 100644
--- a/Ghidra/Features/Base/src/main/help/help/topics/Navigation/Navigation.htm
+++ b/Ghidra/Features/Base/src/main/help/help/topics/Navigation/Navigation.htm
@@ -25,6 +25,9 @@
Use the Navigation History to return to a
previously visited location
+ Specify a specific Program Start
+ Location when
+ opening a program
@@ -734,10 +737,6 @@
Navigation Options
-
-
-
-
'Go To' in Current Program Only -
@@ -788,6 +787,32 @@
range being navigated.
+ Starting Program Location Options
+ The staring location for newly opened programs can be configured using the following
+ options:
+
+ Start At - Choose a starting program location option:
+
+ - Lowest Address - The program will open at the lowest address.
+ - Lowest Code Block Address - The program will open at the firest executable
+ memory block. If no executable block found, it will go to lowest address.
+ - Preferred Symbol Name - The program will open at the first symbol name it
+ finds that matches a name in the "Start Symbols" option. If no symbol is found,
+ it will try the lowest code block, and finally the lowest address.
+
+
+ Start Symbols - A comma separated list of symbol names to be be used as the
+ starting location for the program if the "Preferred Symbol Name" option is selected
+ above. The first matching symbol found will be used as the starting location for
+ newly opened programs.
+
+ Use Underscores - If selected, each of the preferred symbols listed above
+ will also be searched for with either one or two underscores prepended to the name. For
+ example, if selected, it will search for "_main" and "__main" in addition to "main"
+ when trying to find a starting symbol.
+
+
+
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPrevAddressPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPrevAddressPlugin.java
index 6c548044b9..272beab831 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPrevAddressPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPrevAddressPlugin.java
@@ -69,8 +69,7 @@ public class NextPrevAddressPlugin extends Plugin {
private static final String PREVIOUS_ACTION_NAME = "Previous Location in History";
private static final String NEXT_ACTION_NAME = "Next Location in History";
- private static final String PREVIOUS_FUNCTION_ACTION_NAME =
- "Previous Function in History";
+ private static final String PREVIOUS_FUNCTION_ACTION_NAME = "Previous Function in History";
private static final String NEXT_FUNCTION_ACTION_NAME = "Next Function in History";
private static final String[] CLEAR_MENUPATH = { "Navigation", "Clear History" };
@@ -114,6 +113,10 @@ public class NextPrevAddressPlugin extends Plugin {
return nextFunctionAction;
}
+ DockingAction getClearHistoryAction() {
+ return clearAction;
+ }
+
//==================================================================================================
// Private Methods
//==================================================================================================
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProgramStartingLocationOptions.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProgramStartingLocationOptions.java
new file mode 100644
index 0000000000..e898877589
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProgramStartingLocationOptions.java
@@ -0,0 +1,175 @@
+/* ###
+ * 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.navigation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import ghidra.GhidraOptions;
+import ghidra.framework.options.*;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.util.HelpLocation;
+
+/**
+ * Class for managing the options associated with the {@link ProgramStartingLocationPlugin}
+ */
+public class ProgramStartingLocationOptions implements OptionsChangeListener {
+
+ static final String NAVIGATION_TOPIC = "Navigation";
+ public static final String SUB_OPTION = "Starting Program Location";
+ public static final String START_LOCATION_TYPE_OPTION = SUB_OPTION + ".Start At: ";
+ public static final String START_SYMBOLS_OPTION = SUB_OPTION + ".Start Symbols: ";
+ public static final String UNDERSCORE_OPTION = SUB_OPTION + ".Use Underscores:";
+
+ private static final String START_LOCATION_DESCRIPTION =
+ "Determines the start location for newly opened programs.\n" +
+ "Either lowest address, lowest code address, or symbol name.\nEach higher " +
+ "option will revert to the next lower option if that option can't be satisfied.";
+ private static final String STARTING_SYSMBOLS_DESCRIPTION =
+ "A comma separated list of symbol names in preference order. " +
+ "(Used when option above is set to \"Preferred Symbol Name\")";
+ private static final String SYMBOL_PREFIX_DESCRIPTION =
+ "When searching for symbols, also search for the names prepended with \"_\" and \"__\".";
+
+ private static final String DEFAULT_STARTING_SYMBOLS =
+ "main, WinMain, libc_start_main, WinMainStartup, start, entry";
+
+ public static enum StartLocationType {
+ LOWEST_ADDRESS("Lowest Address"),
+ LOWEST_CODE_BLOCK("Lowest Code Block Address"),
+ SYMBOL_NAME("Preferred Symbol Name");
+
+ private String label;
+
+ private StartLocationType(String label) {
+ this.label = label;
+ }
+
+ @Override
+ public String toString() {
+ return label;
+ }
+ }
+
+ private StartLocationType startLocationType;
+ private List startSymbols;
+ private boolean useUnderscorePrefixes;
+
+ private ToolOptions options;
+
+ public ProgramStartingLocationOptions(PluginTool tool) {
+ options = tool.getOptions(GhidraOptions.NAVIGATION_OPTIONS);
+ HelpLocation help = new HelpLocation(NAVIGATION_TOPIC, "Starting_Program_Location");
+
+ // set a help location on the group
+ Options subOptions = options.getOptions(SUB_OPTION);
+ subOptions.setOptionsHelpLocation(help);
+
+ options.registerOption(START_LOCATION_TYPE_OPTION, StartLocationType.SYMBOL_NAME, help,
+ START_LOCATION_DESCRIPTION);
+
+ options.registerOption(START_SYMBOLS_OPTION, DEFAULT_STARTING_SYMBOLS, help,
+ STARTING_SYSMBOLS_DESCRIPTION);
+
+ options.registerOption(UNDERSCORE_OPTION, true, help, SYMBOL_PREFIX_DESCRIPTION);
+
+ startLocationType =
+ options.getEnum(START_LOCATION_TYPE_OPTION, StartLocationType.SYMBOL_NAME);
+
+ String symbolNames = options.getString(START_SYMBOLS_OPTION, DEFAULT_STARTING_SYMBOLS);
+ startSymbols = parse(symbolNames);
+
+ useUnderscorePrefixes = options.getBoolean(UNDERSCORE_OPTION, true);
+ options.addOptionsChangeListener(this);
+ }
+
+ private List parse(String symbolNames) {
+ List names = new ArrayList<>();
+ String[] split = symbolNames.split(",");
+ for (String string : split) {
+ String trimmed = string.trim();
+ if (!trimmed.isBlank()) {
+ names.add(trimmed);
+ }
+ }
+ return names;
+ }
+
+ /**
+ * Returns true if the program should start on one of the starting symbols.
+ * @return true if the program should start on one of the starting symbols
+ */
+ public boolean shouldStartOnSymbol() {
+ return startLocationType == StartLocationType.SYMBOL_NAME;
+ }
+
+ /**
+ * Returns true if the program should start at the lowest address.
+ * @return true if the program should start at the lowest address
+ */
+ public boolean shouldStartAtLowestAddress() {
+ return startLocationType == StartLocationType.LOWEST_ADDRESS;
+ }
+
+ /**
+ * Returns true if the program should start at the first code block.
+ * @return true if the program should start at the first code block.
+ */
+ public boolean shouldStartAtLowestCodeBlock() {
+ return startLocationType == StartLocationType.LOWEST_CODE_BLOCK;
+ }
+
+ /**
+ * Returns a list of possible starting symbol names. The symbols are returned in order
+ * of preference.
+ * @return a list of starting symbols.
+ */
+ public List getStartingSymbolNames() {
+ return startSymbols;
+ }
+
+ /**
+ * Returns true if the list of starting symbol names should also be search for with "_" and
+ * "__" prepended.
+ * @return true if the list of starting symbol names should also be search for with "_" and
+ * "__" prepended.
+ */
+ public boolean useUnderscorePrefixes() {
+ return useUnderscorePrefixes;
+ }
+
+ /**
+ * Removes the options listener
+ */
+ public void dispose() {
+ options.removeOptionsChangeListener(this);
+ }
+
+ @Override
+ public void optionsChanged(ToolOptions toolOptions, String optionName, Object oldValue,
+ Object newValue) {
+ if (START_LOCATION_TYPE_OPTION.equals(optionName)) {
+ startLocationType = (StartLocationType) newValue;
+ }
+ else if (START_SYMBOLS_OPTION.equals(optionName)) {
+ startSymbols = parse((String) newValue);
+ }
+ else if (UNDERSCORE_OPTION.equals(optionName)) {
+ useUnderscorePrefixes = (Boolean) newValue;
+ }
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProgramStartingLocationPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProgramStartingLocationPlugin.java
new file mode 100644
index 0000000000..0526f29484
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProgramStartingLocationPlugin.java
@@ -0,0 +1,148 @@
+/* ###
+ * 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.navigation;
+
+import java.util.Collections;
+import java.util.List;
+
+import ghidra.app.CorePluginPackage;
+import ghidra.app.plugin.PluginCategoryNames;
+import ghidra.app.plugin.ProgramPlugin;
+import ghidra.app.services.GoToService;
+import ghidra.framework.plugintool.PluginInfo;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.framework.plugintool.util.PluginStatus;
+import ghidra.program.model.address.AddressSetView;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.symbol.Symbol;
+import ghidra.program.model.symbol.SymbolTable;
+import ghidra.program.util.ProgramLocation;
+import ghidra.util.Swing;
+
+//@formatter:off
+@PluginInfo(
+ status = PluginStatus.RELEASED,
+ packageName = CorePluginPackage.NAME,
+ category = PluginCategoryNames.COMMON,
+ shortDescription = "Determines the starting location when a program is opened.",
+ description = "This plugin watches for new programs being opened and determines the best starting location for the listing view.",
+ servicesRequired = { GoToService.class }
+)
+//@formatter:on
+public class ProgramStartingLocationPlugin extends ProgramPlugin {
+
+ private Program lastOpenedProgram;
+ private ProgramStartingLocationOptions startOptions;
+
+ public ProgramStartingLocationPlugin(PluginTool tool) {
+ super(tool);
+ startOptions = new ProgramStartingLocationOptions(tool);
+ }
+
+ @Override
+ protected void programOpened(Program program) {
+ // if the open program event is a result of restoring the tool's data state, don't
+ // interfere with the tool's restoration of the last location for that program
+ if (tool.isRestoringDataState()) {
+ return;
+ }
+ if (startOptions.shouldStartAtLowestAddress()) {
+ // this is what happens by default, so no need to do anything
+ return;
+ }
+ lastOpenedProgram = program;
+ }
+
+ @Override
+ protected void programActivated(Program program) {
+ super.programActivated(program);
+ if (program == lastOpenedProgram) {
+ Swing.runLater(this::setStartingLocationForNewProgram);
+ }
+ lastOpenedProgram = null;
+ }
+
+ private void setStartingLocationForNewProgram() {
+ if (currentProgram == null) {
+ return;
+ }
+
+ GoToService gotoService = tool.getService(GoToService.class);
+
+ ProgramLocation location = getStartingProgramLocation(currentProgram);
+ if (location != null) {
+ gotoService.goTo(location);
+ }
+
+ }
+
+ private ProgramLocation getStartingProgramLocation(Program program) {
+ if (startOptions.shouldStartOnSymbol()) {
+ List symbolNames = startOptions.getStartingSymbolNames();
+ boolean useUnderscores = startOptions.useUnderscorePrefixes();
+ for (String symbolName : symbolNames) {
+ Symbol symbol = findSymbol(program, symbolName, useUnderscores);
+ if (symbol != null) {
+ return symbol.getProgramLocation();
+ }
+ }
+ }
+ // if the option is start on first code block, or we couldn't find a symbol, try and
+ // find the first executable code block
+ return findLowestCodeBlockLocation(program);
+ }
+
+ private ProgramLocation findLowestCodeBlockLocation(Program program) {
+ AddressSetView executeSet = program.getMemory().getExecuteSet();
+ if (executeSet.isEmpty()) {
+ return null;
+ }
+ return new ProgramLocation(program, executeSet.getMinAddress());
+ }
+
+ private Symbol findSymbol(Program program, String symbolName, boolean useUnderscores) {
+ Symbol symbol = findSymbol(program, symbolName);
+ if (symbol != null) {
+ return symbol;
+ }
+ if (!useUnderscores) {
+ return null;
+ }
+ symbol = findSymbol(program, "_" + symbolName);
+ if (symbol != null) {
+ return symbol;
+ }
+ return findSymbol(program, "__" + symbolName);
+ }
+
+ private Symbol findSymbol(Program program, String symbolName) {
+ SymbolTable symbolTable = program.getSymbolTable();
+ List symbols = symbolTable.getLabelOrFunctionSymbols(symbolName, null);
+ if (symbols.isEmpty()) {
+ return null;
+ }
+ if (symbols.size() > 1) {
+ Collections.sort(symbols, (s1, s2) -> s1.getAddress().compareTo(s2.getAddress()));
+ }
+ return symbols.get(0);
+ }
+
+ @Override
+ protected void dispose() {
+ super.dispose();
+ startOptions.dispose();
+ }
+}
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevAddressPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevAddressPluginTest.java
index c9a187ff91..e0da96c225 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevAddressPluginTest.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevAddressPluginTest.java
@@ -53,6 +53,7 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe
private MultiActionDockingAction nextAction;
private DockingAction previousFunctionAction;
private DockingAction nextFunctionAction;
+ private DockingAction clearHistoryAction;
private CodeBrowserPlugin cbPlugin;
private CodeViewerProvider provider;
@@ -71,9 +72,12 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe
nextAction = plugin.getNextAction();
previousFunctionAction = plugin.getPreviousFunctionAction();
nextFunctionAction = plugin.getNextFunctionAction();
+ clearHistoryAction = plugin.getClearHistoryAction();
cbPlugin = env.getPlugin(CodeBrowserPlugin.class);
provider = cbPlugin.getProvider();
+ goTo(program.getMinAddress());
+ clearHistory();
}
@After
@@ -90,15 +94,13 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe
Symbol bulkNavigationSymbol = navigatedSymbols.remove(0);
// the previous list of actions should be the size of the list - 1
- List actionList =
- previousAction.getActionList(getContext());
+ List actionList = previousAction.getActionList(getContext());
// verify the size...
- // (the navigated symbols plus the original location before we started)
- assertEquals(actionList.size(), navigatedSymbols.size() + 1);
+ assertEquals(actionList.size(), navigatedSymbols.size());
// ...verify the order
- int n = actionList.size() - 1; // don't compare the first location
+ int n = actionList.size(); // don't compare the first location
for (int i = 0; i < n; i++) {
DockingActionIf dockableAction = actionList.get(i);
LocationMemento location =
@@ -122,7 +124,7 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe
// ...make sure the 'previous' list is updated with only actions after the one in our list
actionList = previousAction.getActionList(getContext());
- n = actionList.size() - 1; // don't compare the first location
+ n = actionList.size();
int navigatedIndexOffset = (navigatedIndex + 1); // don't count the navigated index
int remainingSymbols = navigatedSymbols.size() - navigatedIndexOffset;
assertEquals(n, remainingSymbols);
@@ -169,7 +171,7 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe
// ...make sure that the 'previous' list is properly updated
actionList = previousAction.getActionList(getContext());
- n = actionList.size() - 1; // don't compare the first location
+ n = actionList.size();
navigatedIndexOffset = (navigatedIndex + 1); // don't count the navigated index
remainingSymbols = navigatedSymbols.size() - navigatedIndexOffset;
assertEquals(n, remainingSymbols);
@@ -358,6 +360,14 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe
waitForSwing();
}
+ private void clearHistory() {
+ assertTrue(clearHistoryAction.isEnabledForContext(getContext()));
+ performAction(clearHistoryAction, true);
+ cbPlugin.updateNow();
+ waitForSwing();
+
+ }
+
private void previous() {
assertTrue(previousAction.isEnabledForContext(getContext()));
performAction(previousAction, true);
@@ -437,9 +447,8 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe
Object actionToGuiMapper = TestUtils.getInstanceField("actionToGuiMapper", windowManager);
Object menuAndToolBarManager =
TestUtils.getInstanceField("menuAndToolBarManager", actionToGuiMapper);
- Map map =
- (Map) TestUtils.getInstanceField(
- "windowToActionManagerMap", menuAndToolBarManager);
+ Map map = (Map) TestUtils
+ .getInstanceField("windowToActionManagerMap", menuAndToolBarManager);
Iterator iterator = map.values().iterator();
while (iterator.hasNext()) {
@@ -448,8 +457,8 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe
(ToolBarManager) TestUtils.getInstanceField("toolBarMgr", wam);
Map> groupToItemsMap =
- (Map>) TestUtils.getInstanceField(
- "groupToItemsMap", toolBarManager);
+ (Map>) TestUtils
+ .getInstanceField("groupToItemsMap", toolBarManager);
ToolBarData toolBarData = action.getToolBarData();
String group = toolBarData.getToolBarGroup();
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevCodeUnitPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevCodeUnitPluginTest.java
index 37b8e85f39..b1dcd7c546 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevCodeUnitPluginTest.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevCodeUnitPluginTest.java
@@ -30,6 +30,7 @@ import ghidra.app.cmd.data.CreateDataCmd;
import ghidra.app.cmd.disassemble.DisassembleCommand;
import ghidra.app.plugin.core.bookmark.BookmarkEditCmd;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
+import ghidra.app.services.GoToService;
import ghidra.framework.cmd.CompoundCmd;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramBuilder;
@@ -85,6 +86,7 @@ public class NextPrevCodeUnitPluginTest extends AbstractGhidraHeadedIntegrationT
nextBookmark = (MultiStateDockingAction) getAction(p, "Next Bookmark");
cb = env.getPlugin(CodeBrowserPlugin.class);
+ goTo(program.getMinAddress());
}
@After
@@ -112,8 +114,7 @@ public class NextPrevCodeUnitPluginTest extends AbstractGhidraHeadedIntegrationT
addType("0100102a", "15 00", new DWordDataType());
}
- private void addType(String addrString, String bytes, DataType dt)
- throws Exception {
+ private void addType(String addrString, String bytes, DataType dt) throws Exception {
builder.setBytes(addrString, bytes);
builder.applyDataType(addrString, dt);
}
@@ -938,4 +939,10 @@ public class NextPrevCodeUnitPluginTest extends AbstractGhidraHeadedIntegrationT
});
}
+ private void goTo(Address a) throws Exception {
+ GoToService goToService = tool.getService(GoToService.class);
+ goToService.goTo(a);
+ cb.updateNow();
+ waitForSwing();
+ }
}
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/ProgramStartPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/ProgramStartPluginTest.java
new file mode 100644
index 0000000000..c7c4a1d8ca
--- /dev/null
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/ProgramStartPluginTest.java
@@ -0,0 +1,184 @@
+/* ###
+ * 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.navigation;
+
+import static org.junit.Assert.*;
+
+import org.junit.*;
+
+import ghidra.GhidraOptions;
+import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
+import ghidra.framework.options.Options;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.database.ProgramBuilder;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressFactory;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.MemoryBlock;
+import ghidra.test.AbstractGhidraHeadedIntegrationTest;
+import ghidra.test.TestEnv;
+
+public class ProgramStartPluginTest extends AbstractGhidraHeadedIntegrationTest {
+ private TestEnv env;
+ private PluginTool tool;
+ private AddressFactory addrFactory;
+ private CodeBrowserPlugin cb;
+ private Options options;
+
+ @Before
+ public void setUp() throws Exception {
+ env = new TestEnv();
+ tool = env.launchDefaultTool();
+ cb = env.getPlugin(CodeBrowserPlugin.class);
+ options = tool.getOptions(GhidraOptions.NAVIGATION_OPTIONS);
+
+ }
+
+ @After
+ public void tearDown() {
+ env.closeTool(tool);
+ env.dispose();
+ }
+
+ @Test
+ public void testOpensToStartingSymbolByDefault() throws Exception {
+ ProgramBuilder builder = getProgramBuilder("0x100");
+ builder.createLabel("0x105", "main");
+ loadProgram(builder.getProgram());
+
+ assertEquals(addr("0x105"), cb.getCurrentAddress());
+ }
+
+ @Test
+ public void testOpensToLowestCodeBlock() throws Exception {
+ ProgramBuilder builder = getProgramBuilder("0x100");
+ MemoryBlock block = builder.createMemory(".text", "0x200", 0x200);
+ builder.setExecute(block, true);
+ builder.createLabel("0x105", "main");
+
+ setOptionToLowestCodeBlock();
+
+ loadProgram(builder.getProgram());
+
+ assertEquals(addr("0x200"), cb.getCurrentAddress());
+ }
+
+ @Test
+ public void testOpensToStartingSymbolNotFirstInSymbolList() throws Exception {
+ ProgramBuilder builder = getProgramBuilder("0x100");
+ setSymbolListOption("main, foobar, start");
+ builder.createLabel("0x107", "start");
+
+ loadProgram(builder.getProgram());
+
+ assertEquals(addr("0x107"), cb.getCurrentAddress());
+ }
+
+ @Test
+ public void testOpensToFirstSymbolWhenMutlipesAreFoud() throws Exception {
+ ProgramBuilder builder = getProgramBuilder("0x100");
+ setSymbolListOption("main, start");
+ builder.createLabel("0x110", "start");
+ builder.createLabel("0x105", "start");
+
+ loadProgram(builder.getProgram());
+
+ assertEquals(addr("0x105"), cb.getCurrentAddress());
+ }
+
+ @Test
+ public void testOpensToStartingSymbolWithOneUndercore() throws Exception {
+ ProgramBuilder builder = getProgramBuilder("0x100");
+ setSymbolListOption("main, start");
+ builder.createLabel("0x105", "_main");
+ builder.createLabel("0x107", "start");
+
+ loadProgram(builder.getProgram());
+
+ assertEquals(addr("0x105"), cb.getCurrentAddress());
+ }
+
+ @Test
+ public void testOpensToStartingSymbolWithTwoUndercores() throws Exception {
+ ProgramBuilder builder = getProgramBuilder("0x100");
+ setSymbolListOption("main, start");
+ builder.createLabel("0x105", "__main");
+ builder.createLabel("0x107", "_start");
+ builder.createLabel("0x109", "start");
+
+ loadProgram(builder.getProgram());
+
+ assertEquals(addr("0x105"), cb.getCurrentAddress());
+ }
+
+ @Test
+ public void testNoUnderscoresSearching() throws Exception {
+ ProgramBuilder builder = getProgramBuilder("0x100");
+ setSymbolListOption("main, start");
+ setUnderscoreOption(false);
+ builder.createLabel("0x105", "__main");
+ builder.createLabel("0x107", "_start");
+ builder.createLabel("0x109", "start");
+
+ loadProgram(builder.getProgram());
+
+ assertEquals(addr("0x109"), cb.getCurrentAddress());
+ }
+
+ @Test
+ public void testOptionToStartAtLowestAddress() throws Exception {
+ ProgramBuilder builder = getProgramBuilder("0x100");
+ builder.createLabel("0x105", "main");
+ setOptionToLowestAddress();
+ loadProgram(builder.getProgram());
+
+ assertEquals(addr("0x100"), cb.getCurrentAddress());
+ }
+
+ private void setUnderscoreOption(boolean b) {
+ options.setBoolean(ProgramStartingLocationOptions.UNDERSCORE_OPTION, b);
+ }
+
+ private void setOptionToLowestAddress() {
+ options.setEnum(ProgramStartingLocationOptions.START_LOCATION_TYPE_OPTION,
+ ProgramStartingLocationOptions.StartLocationType.LOWEST_ADDRESS);
+ }
+
+ private void setOptionToLowestCodeBlock() {
+ options.setEnum(ProgramStartingLocationOptions.START_LOCATION_TYPE_OPTION,
+ ProgramStartingLocationOptions.StartLocationType.LOWEST_CODE_BLOCK);
+ }
+
+ private void setSymbolListOption(String symbolListString) {
+ options.setString(ProgramStartingLocationOptions.START_SYMBOLS_OPTION, symbolListString);
+ }
+
+ private ProgramBuilder getProgramBuilder(String baseAddress) throws Exception {
+ ProgramBuilder builder = new ProgramBuilder();
+ builder.createMemory(".data", baseAddress, 0x100);
+ return builder;
+ }
+
+ private void loadProgram(Program program) throws Exception {
+ env.open(program);
+ addrFactory = program.getAddressFactory();
+ waitForSwing();
+ }
+
+ private Address addr(String address) {
+ return addrFactory.getAddress(address);
+ }
+}
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginManager.java
index 6cfae8f1d4..2f8ee18f0b 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginManager.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginManager.java
@@ -351,8 +351,8 @@ class PluginManager {
}
Map badMap = new LinkedHashMap<>();
- List list = getPluginsByServiceOrder(0);
- for (Plugin p : list) {
+ List plugins = getPluginsByServiceOrder(0);
+ for (Plugin p : plugins) {
SaveState saveState = map.get(p.getName());
if (saveState != null) {
try {
@@ -376,9 +376,7 @@ class PluginManager {
Msg.showError(this, null, "Data State Error",
"Errors in plugin data states - check console for details");
}
- for (Plugin plugin : list) {
- plugin.dataStateRestoreCompleted();
- }
+ plugins.forEach(Plugin::dataStateRestoreCompleted);
}
Element saveDataStateToXml(boolean savingProject) {
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java
index d42bab306b..e77753c89d 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java
@@ -121,6 +121,7 @@ public abstract class PluginTool extends AbstractDockingTool {
private boolean isConfigurable = true;
protected boolean isDisposed = false;
+ private boolean restoringDataState;
/**
* Construct a new PluginTool.
@@ -574,8 +575,14 @@ public abstract class PluginTool extends AbstractDockingTool {
}
public void restoreDataStateFromXml(Element root) {
- pluginMgr.restoreDataStateFromXml(root);
- setConfigChanged(false);
+ restoringDataState = true;
+ try {
+ pluginMgr.restoreDataStateFromXml(root);
+ setConfigChanged(false);
+ }
+ finally {
+ restoringDataState = false;
+ }
}
public Element saveDataStateToXml(boolean savingProject) {
@@ -1502,6 +1509,10 @@ public abstract class PluginTool extends AbstractDockingTool {
super.contextChanged(provider);
}
+ public boolean isRestoringDataState() {
+ return restoringDataState;
+ }
+
//==================================================================================================
// Inner Classes
//==================================================================================================
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolTable.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolTable.java
index 2bf13eec85..73a5075aac 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolTable.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolTable.java
@@ -151,8 +151,13 @@ public interface SymbolTable {
* @param namespace the namespace of the symbol to retrieve. May be null which indicates the
* global namespace.
* @return the symbol which matches the specified criteria or null if not found
+<<<<<<< Upstream, based on origin/master
* @throws IllegalArgumentException if the given parent namespace is from a different program
* than that of this symbol table
+=======
+ * @throws IllegalArgumentException namespace which does not correspond to this
+ * symbol table's program.
+>>>>>>> ba3dc0f GP-2141 adding feature for better starting location when opening a program.
* @see #getGlobalSymbol(String, Address) for a convenience method if the namespace is the
* global namespace.
*/
@@ -204,16 +209,28 @@ public interface SymbolTable {
* default source type) since it mirrors the name and parent namespace of the function it
* thunks.
*
+<<<<<<< Upstream, based on origin/master
* @param name the name of the symbols to search for
* @param namespace the namespace to search. If null, then the global namespace is assumed.
* @return a list of all the label or function symbols with the given name in the given parent
* namespace
* @throws IllegalArgumentException if the given parent namespace is from a different program
* than that of this symbol table
+=======
+ * NOTE: This method will not return a default thunk (i.e., thunk function symbol with default source type)
+ * since it mirrors the name and parent namespace of the function it thunks.
+ *
+ * @param name the name of the symbols to search for.
+ * @param namespace the namespace to search. If null, then the global namespace is assumed.
+ * @return a list of all the label or function symbols with the given name in the given namespace.
+ * @throws IllegalArgumentException namespace which does not correspond to this
+ * symbol table's program.
+>>>>>>> ba3dc0f GP-2141 adding feature for better starting location when opening a program.
*/
public List getLabelOrFunctionSymbols(String name, Namespace namespace);
/**
+<<<<<<< Upstream, based on origin/master
* Get a generic namespace symbol with the given name in the given parent namespace
*
* @param name the name of the namespace symbol to retrieve
@@ -221,6 +238,14 @@ public interface SymbolTable {
* @return the symbol, or null
* @throws IllegalArgumentException if the given parent namespace is from a different program
* than that of this symbol table
+=======
+ * Returns a generic namespace symbol with the given name in the given namespace.
+ * @param name the name of the namespace symbol to retrieve.
+ * @param namespace the namespace containing the symbol to retrieve.
+ * @return a generic namespace symbol with the given name in the given namespace.
+ * @throws IllegalArgumentException namespace which does not correspond to this
+ * symbol table's program.
+>>>>>>> ba3dc0f GP-2141 adding feature for better starting location when opening a program.
*/
public Symbol getNamespaceSymbol(String name, Namespace namespace);
@@ -233,6 +258,7 @@ public interface SymbolTable {
public Symbol getLibrarySymbol(String name);
/**
+<<<<<<< Upstream, based on origin/master
* Get the class symbol with the given name in the given namespace
*
* @param name the name of the class
@@ -240,10 +266,19 @@ public interface SymbolTable {
* @return the class symbol with the given name in the given namespace
* @throws IllegalArgumentException if the given parent namespace is from a different program
* than that of this symbol table
+=======
+ * Returns the class symbol with the given name in the given namespace.
+ * @param name the name of the class.
+ * @param namespace the namespace to search for the class.
+ * @return the class symbol with the given name in the given namespace.
+ * @throws IllegalArgumentException namespace which does not correspond to this
+ * symbol table's program.
+>>>>>>> ba3dc0f GP-2141 adding feature for better starting location when opening a program.
*/
public Symbol getClassSymbol(String name, Namespace namespace);
/**
+<<<<<<< Upstream, based on origin/master
* Get the parameter symbol with the given name in the given namespace
*
* @param name the name of the parameter
@@ -251,10 +286,19 @@ public interface SymbolTable {
* @return the parameter symbol with the given name in the given namespace
* @throws IllegalArgumentException if the given parent namespace is from a different program
* than that of this symbol table
+=======
+ * Returns the parameter symbol with the given name in the given namespace.
+ * @param name the name of the parameter.
+ * @param namespace the namespace (function) to search for the class.
+ * @return the parameter symbol with the given name in the given namespace.
+ * @throws IllegalArgumentException namespace which does not correspond to this
+ * symbol table's program.
+>>>>>>> ba3dc0f GP-2141 adding feature for better starting location when opening a program.
*/
public Symbol getParameterSymbol(String name, Namespace namespace);
/**
+<<<<<<< Upstream, based on origin/master
* Get the local variable symbol with the given name in the given namespace
*
* @param name the name of the local variable
@@ -262,6 +306,14 @@ public interface SymbolTable {
* @return the local variable symbol with the given name in the given namespace
* @throws IllegalArgumentException if the given parent namespace is from a different program
* than that of this symbol table
+=======
+ * Returns the local variable symbol with the given name in the given namespace.
+ * @param name the name of the local variable.
+ * @param namespace the namespace (function) to search for the class.
+ * @return the local variable symbol with the given name in the given namespace.
+ * @throws IllegalArgumentException namespace which does not correspond to this
+ * symbol table's program.
+>>>>>>> ba3dc0f GP-2141 adding feature for better starting location when opening a program.
*/
public Symbol getLocalVariableSymbol(String name, Namespace namespace);
@@ -276,11 +328,22 @@ public interface SymbolTable {
* NOTE: The resulting iterator will not return default thunks (i.e., thunk function
* symbol with default source type).
*
+<<<<<<< Upstream, based on origin/master
* @param name the name of the symbols to retrieve
* @param namespace the namespace to search for symbols
* @return a list of symbols which satisfy specified criteria
* @throws IllegalArgumentException if the given parent namespace is from a different program
* than that of this symbol table
+=======
+ * NOTE: The resulting iterator will not return default thunks (i.e.,
+ * thunk function symbol with default source type).
+ *
+ * @param name the name of the symbols to retrieve.
+ * @param namespace the namespace to search for symbols.
+ * @return all symbols which satisfy specified criteria
+ * @throws IllegalArgumentException namespace which does not correspond to this
+ * symbol table's program.
+>>>>>>> ba3dc0f GP-2141 adding feature for better starting location when opening a program.
*/
public List getSymbols(String name, Namespace namespace);
@@ -296,6 +359,7 @@ public interface SymbolTable {
public Symbol getVariableSymbol(String name, Function function);
/**
+<<<<<<< Upstream, based on origin/master
* Get the namespace with the given name in the given parent namespace.
*
* The returned namespace can be a generic namespace ({@link SymbolType#NAMESPACE},
@@ -310,6 +374,16 @@ public interface SymbolTable {
* @return the namespace with the given name in the given parent namespace
* @throws IllegalArgumentException if the given parent namespace is from a different program
* than that of this symbol table
+=======
+ * Returns the namespace with the given name in the given parent namespace. The namespace
+ * returned can be either a generic namespace or a class or library. It does not include
+ * functions.
+ * @param name the name of the namespace to be retrieved.
+ * @param namespace the parent namespace of the namespace to be retrieved.
+ * @return the namespace with the given name in the given parent namespace.
+ * @throws IllegalArgumentException namespace which does not correspond to this
+ * symbol table's program.
+>>>>>>> ba3dc0f GP-2141 adding feature for better starting location when opening a program.
*/
public Namespace getNamespace(String name, Namespace namespace);
@@ -405,10 +479,20 @@ public interface SymbolTable {
* NOTE: The resulting iterator will not return default thunks (i.e., thunk function
* symbol with default source type).
*
+<<<<<<< Upstream, based on origin/master
* @param namespace the namespace to search for symbols
* @return an iterator over the symbols
* @throws IllegalArgumentException if the given parent namespace is from a different program
* than that of this symbol table
+=======
+ *
NOTE: The resulting iterator will not return default thunks (i.e.,
+ * thunk function symbol with default source type).
+ *
+ * @param namespace the namespace to search for symbols.
+ * @return symbol iterator
+ * @throws IllegalArgumentException namespace which does not correspond to this
+ * symbol table's program.
+>>>>>>> ba3dc0f GP-2141 adding feature for better starting location when opening a program.
*/
public SymbolIterator getSymbols(Namespace namespace);