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: +

    + +

    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);