(activeProgram);
+ }
+
+ /**
+ * Return the new activated program. May be null.
+ * @return null if the event if for a program closing.
+ */
+ public Program getActiveProgram() {
+ return newProgramRef.get();
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/ProgramPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/ProgramPlugin.java
index b42366287c..2c976a65a2 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/ProgramPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/ProgramPlugin.java
@@ -55,6 +55,7 @@ public abstract class ProgramPlugin extends Plugin {
public ProgramPlugin(PluginTool plugintool) {
super(plugintool);
internalRegisterEventConsumed(ProgramActivatedPluginEvent.class);
+ internalRegisterEventConsumed(ProgramPostActivatedPluginEvent.class);
internalRegisterEventConsumed(ProgramLocationPluginEvent.class);
internalRegisterEventConsumed(ProgramSelectionPluginEvent.class);
internalRegisterEventConsumed(ProgramHighlightPluginEvent.class);
@@ -160,12 +161,16 @@ public abstract class ProgramPlugin extends Plugin {
}
highlightChanged(currentHighlight);
}
+ else if (event instanceof ProgramPostActivatedPluginEvent ev) {
+ postProgramActivated(ev.getActiveProgram());
+ }
+
}
/**
* Subclass should override this method if it is interested when programs become active.
* Note: this method is called in response to a ProgramActivatedPluginEvent.
- *
+ *
* At the time this method is called,
* the "currentProgram" variable will be set the new active program.
*
@@ -175,6 +180,20 @@ public abstract class ProgramPlugin extends Plugin {
// override
}
+ /**
+ * Subclass should override this method if it is interested when programs become active and
+ * all plugins have had a chance to process the {@link ProgramActivatedPluginEvent}.
+ * Note: this method is called in response to a {@link ProgramPostActivatedPluginEvent}
+ *
+ * At the time this method is called,
+ * the "currentProgram" variable will be set the new active program.
+ *
+ * @param program the new program going active.
+ */
+ protected void postProgramActivated(Program program) {
+ // override
+ }
+
/**
* Subclasses should override this method if it is interested when a program is closed.
*
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java
index 0b0449b5cc..16292e396b 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java
@@ -18,6 +18,7 @@ package ghidra.app.plugin.core.analysis;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.Stack;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import javax.swing.JFrame;
@@ -132,7 +133,7 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
private MessageLog log = new MessageLog();
- private List listeners = new ArrayList<>();
+ private List listeners = new CopyOnWriteArrayList<>();
private EventQueueID eventQueueID;
@@ -850,6 +851,7 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
for (AnalysisTaskList list : taskArray) {
list.notifyAnalysisEnded(program);
}
+
for (AutoAnalysisManagerListener listener : listeners) {
listener.analysisEnded(this);
}
@@ -1268,13 +1270,13 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
if (testLen > spacer.length()) {
testLen = spacer.length() - 5;
}
- taskTimesStringBuf.append(
- " " + element + spacer.substring(testLen) + secString + "\n");
+ taskTimesStringBuf
+ .append(" " + element + spacer.substring(testLen) + secString + "\n");
}
taskTimesStringBuf.append("-----------------------------------------------------\n");
- taskTimesStringBuf.append(
- " Total Time " + (int) (totalTaskTime / 1000.00) + " secs\n");
+ taskTimesStringBuf
+ .append(" Total Time " + (int) (totalTaskTime / 1000.00) + " secs\n");
taskTimesStringBuf.append("-----------------------------------------------------\n");
return taskTimesStringBuf.toString();
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java
index df8aecff22..d017c0c7aa 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java
@@ -17,8 +17,6 @@ package ghidra.app.plugin.core.analysis;
import java.util.*;
-import javax.swing.SwingUtilities;
-
import docking.ActionContext;
import docking.DockingWindowManager;
import docking.action.DockingAction;
@@ -59,7 +57,7 @@ import ghidra.util.task.TaskLauncher;
category = PluginCategoryNames.ANALYSIS,
shortDescription = "Manages auto-analysis",
description = "Provides coordination and a service for All Auto Analysis tasks.",
- eventsConsumed = { ProgramOpenedPluginEvent.class, ProgramClosedPluginEvent.class, ProgramActivatedPluginEvent.class }
+ eventsConsumed = { ProgramOpenedPluginEvent.class, ProgramClosedPluginEvent.class, ProgramActivatedPluginEvent.class, ProgramPostActivatedPluginEvent.class }
)
//@formatter:on
public class AutoAnalysisPlugin extends Plugin implements AutoAnalysisManagerListener {
@@ -180,16 +178,20 @@ public class AutoAnalysisPlugin extends Plugin implements AutoAnalysisManagerLis
private void analyzeCallback(Program program, ProgramSelection selection) {
AutoAnalysisManager analysisMgr = AutoAnalysisManager.getAnalysisManager(program);
- analysisMgr.initializeOptions(); // get initial options
+ analysisMgr.initializeOptions(); // this allows analyzers to register options with defaults
if (!showOptionsDialog(program)) {
return;
}
- analysisMgr.initializeOptions(); // options may have changed
+ analysisMgr.initializeOptions(); // reloads the options in case the user changed them
- // At this point, any analysis that is done is consider to be true for analyzed.
- GhidraProgramUtilities.setAnalyzedFlag(program, true);
+ // check if this is the first time this program is being analyzed. If so,
+ // schedule a callback when it is completed to send a FirstTimeAnalyzedPluginEvent
+ boolean isAnalyzed = GhidraProgramUtilities.isAnalyzedFlagSet(program);
+ if (!isAnalyzed) {
+ analysisMgr.addListener(new FirstTimeAnalyzedCallback());
+ }
// start analysis to set the flag, but it probably won't do more. A bit goofy but better
// than the way it was
@@ -224,16 +226,13 @@ public class AutoAnalysisPlugin extends Plugin implements AutoAnalysisManagerLis
@Override
public void processEvent(PluginEvent event) {
- if (event instanceof ProgramClosedPluginEvent) {
- ProgramClosedPluginEvent ev = (ProgramClosedPluginEvent) event;
+ if (event instanceof ProgramClosedPluginEvent ev) {
programClosed(ev.getProgram());
}
- else if (event instanceof ProgramOpenedPluginEvent) {
- ProgramOpenedPluginEvent ev = (ProgramOpenedPluginEvent) event;
+ else if (event instanceof ProgramOpenedPluginEvent ev) {
programOpened(ev.getProgram());
}
- else if (event instanceof ProgramActivatedPluginEvent) {
- ProgramActivatedPluginEvent ev = (ProgramActivatedPluginEvent) event;
+ else if (event instanceof ProgramActivatedPluginEvent ev) {
Program program = ev.getActiveProgram();
if (program == null) {
removeOneShotActions();
@@ -243,6 +242,12 @@ public class AutoAnalysisPlugin extends Plugin implements AutoAnalysisManagerLis
addOneShotActions(program);
}
}
+ else if (event instanceof ProgramPostActivatedPluginEvent ev) {
+ Program program = ev.getActiveProgram();
+ if (program != null) {
+ postProgramActivated(program);
+ }
+ }
}
protected void programOpened(final Program program) {
@@ -256,30 +261,18 @@ public class AutoAnalysisPlugin extends Plugin implements AutoAnalysisManagerLis
new HelpLocation("AutoAnalysisPlugin", "Auto_Analysis_Option"));
}
- private void programActivated(final Program program) {
-
+ private void programActivated(Program program) {
program.getOptions(StoredAnalyzerTimes.OPTIONS_LIST)
- .registerOption(
- StoredAnalyzerTimes.OPTION_NAME, OptionType.CUSTOM_TYPE, null, null,
- "Cumulative analysis task times", new StoredAnalyzerTimesPropertyEditor());
+ .registerOption(StoredAnalyzerTimes.OPTION_NAME, OptionType.CUSTOM_TYPE, null, null,
+ "Cumulative analysis task times", new StoredAnalyzerTimesPropertyEditor());
- // invokeLater() to ensure that all other plugins have been notified of the program
- // activated. This makes sure plugins like the Listing have opened and painted the
- // program.
- //
- // If the user decided to instantly close the code browser before we get to run anything,
- // an exception could be thrown! Therefore, we must check to see if the program is closed
- // at this point before we run anything.
- //
- SwingUtilities.invokeLater(() -> {
- if (program.isClosed()) {
- return;
- }
- final AutoAnalysisManager analysisMgr = AutoAnalysisManager.getAnalysisManager(program);
- if (analysisMgr.askToAnalyze(tool)) {
- analyzeCallback(program, null);
- }
- });
+ }
+
+ private void postProgramActivated(Program program) {
+ AutoAnalysisManager analysisMgr = AutoAnalysisManager.getAnalysisManager(program);
+ if (analysisMgr.askToAnalyze(tool)) {
+ analyzeCallback(program, null);
+ }
}
/**
@@ -373,4 +366,13 @@ public class AutoAnalysisPlugin extends Plugin implements AutoAnalysisManagerLis
return canAnalyze;
}
}
+
+ private class FirstTimeAnalyzedCallback implements AutoAnalysisManagerListener {
+ @Override
+ public void analysisEnded(AutoAnalysisManager manager) {
+ manager.removeListener(this);
+ tool.firePluginEvent(new FirstTimeAnalyzedPluginEvent(AutoAnalysisPlugin.this.getName(),
+ manager.getProgram()));
+ }
+ }
}
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
index ab9c0a6fef..ff58bd0e52 100644
--- 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
@@ -29,10 +29,18 @@ import ghidra.util.HelpLocation;
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:";
+ public static final String START_LOCATION_SUB_OPTION = "Starting Program Location";
+ public static final String START_LOCATION_TYPE_OPTION =
+ START_LOCATION_SUB_OPTION + ".Start At: ";
+ public static final String START_SYMBOLS_OPTION =
+ START_LOCATION_SUB_OPTION + ".Start Symbols: ";
+ public static final String UNDERSCORE_OPTION = START_LOCATION_SUB_OPTION + ".Use Underscores:";
+
+ public static final String AFTER_ANALYSIS_SUB_OPTION = "After Initial Analysis";
+ public static final String ASK_TO_MOVE_OPTION =
+ AFTER_ANALYSIS_SUB_OPTION + ".Ask To Reposition Program";
+ public static final String AUTO_MOVE_OPTION =
+ AFTER_ANALYSIS_SUB_OPTION + ".Auto Reposition If Not Moved";
private static final String START_LOCATION_DESCRIPTION =
"Determines the start location for newly opened programs.\n" +
@@ -44,6 +52,12 @@ public class ProgramStartingLocationOptions implements OptionsChangeListener {
"(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 \"__\".";
+ public static final String ASK_TO_MOVE_DESCRIPTION =
+ "When initial analysis completed, asks the user if they want to reposition the" +
+ " program to a newly discovered starting symbol.";
+ public static final String AUTO_MOVE_DESCRIPTION =
+ "When initial analysis is completed, automatically repositions the program to " +
+ "a newly discovered starting symbol, provided the user hasn't manually moved.";
private static final String DEFAULT_STARTING_SYMBOLS =
"main, WinMain, libc_start_main, WinMainStartup, start, entry";
@@ -71,13 +85,15 @@ public class ProgramStartingLocationOptions implements OptionsChangeListener {
private boolean useUnderscorePrefixes;
private ToolOptions options;
+ private boolean askToMove;
+ private boolean autoMove;
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);
+ Options subOptions = options.getOptions(START_LOCATION_SUB_OPTION);
subOptions.setOptionsHelpLocation(help);
options.registerOption(START_LOCATION_TYPE_OPTION, StartLocationType.LAST_LOCATION, help,
@@ -88,6 +104,13 @@ public class ProgramStartingLocationOptions implements OptionsChangeListener {
options.registerOption(UNDERSCORE_OPTION, true, help, SYMBOL_PREFIX_DESCRIPTION);
+ help = new HelpLocation(NAVIGATION_TOPIC, "After_Initial_Analysis");
+ subOptions = options.getOptions(AFTER_ANALYSIS_SUB_OPTION);
+ subOptions.setOptionsHelpLocation(help);
+
+ options.registerOption(ASK_TO_MOVE_OPTION, true, help, ASK_TO_MOVE_DESCRIPTION);
+ options.registerOption(AUTO_MOVE_OPTION, true, help, AUTO_MOVE_DESCRIPTION);
+
startLocationType =
options.getEnum(START_LOCATION_TYPE_OPTION, StartLocationType.SYMBOL_NAME);
@@ -95,6 +118,10 @@ public class ProgramStartingLocationOptions implements OptionsChangeListener {
startSymbols = parse(symbolNames);
useUnderscorePrefixes = options.getBoolean(UNDERSCORE_OPTION, true);
+
+ askToMove = options.getBoolean(ASK_TO_MOVE_OPTION, true);
+ autoMove = options.getBoolean(AUTO_MOVE_OPTION, true);
+
options.addOptionsChangeListener(this);
}
@@ -145,6 +172,32 @@ public class ProgramStartingLocationOptions implements OptionsChangeListener {
options.removeOptionsChangeListener(this);
}
+ /**
+ * Returns true if the user should be asked after first analysis if they would like the
+ * program to be repositioned to a newly discovered starting symbol (e.g. "main")
+ *
+ * @return true if the user should be asked after first analysis if they would like the
+ * program to be repositioned to a newly discovered starting symbol (e.g. "main")
+ */
+ public boolean shouldAskToRepostionAfterAnalysis() {
+ return askToMove;
+ }
+
+ /**
+ * Returns true if the program should be repositioned to a newly discovered starting symbol
+ * (e.g. "main") when the first analysis is completed, provided the user hasn't manually
+ * changed the program's location. Note that this option has precedence over the
+ * {@link #shouldAskToRepostionAfterAnalysis()} option and the user will only be asked
+ * if they have manually moved the program.
+ *
+ * @return true if the program should be repositioned to a newly discovered starting symbol
+ * (e.g. "main") when the first analysis is completed, provided the user hasn't manually
+ * changed the program's location.
+ */
+ public boolean shouldAutoRepositionIfNotMoved() {
+ return autoMove;
+ }
+
@Override
public void optionsChanged(ToolOptions toolOptions, String optionName, Object oldValue,
Object newValue) {
@@ -157,5 +210,12 @@ public class ProgramStartingLocationOptions implements OptionsChangeListener {
else if (UNDERSCORE_OPTION.equals(optionName)) {
useUnderscorePrefixes = (Boolean) newValue;
}
+ else if (ASK_TO_MOVE_OPTION.equals(optionName)) {
+ askToMove = (Boolean) newValue;
+ }
+ else if (AUTO_MOVE_OPTION.equals(optionName)) {
+ autoMove = (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
index 75611f8b82..9b78f13455 100644
--- 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
@@ -21,14 +21,14 @@ import java.util.*;
import org.jdom.Element;
import org.jdom.JDOMException;
+import docking.widgets.OptionDialog;
import ghidra.app.CorePluginPackage;
+import ghidra.app.events.FirstTimeAnalyzedPluginEvent;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin;
-import ghidra.app.plugin.core.navigation.ProgramStartingLocationOptions.StartLocationType;
import ghidra.app.services.GoToService;
import ghidra.framework.options.SaveState;
-import ghidra.framework.plugintool.PluginInfo;
-import ghidra.framework.plugintool.PluginTool;
+import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program;
@@ -36,7 +36,6 @@ import ghidra.program.model.listing.ProgramUserData;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolTable;
import ghidra.program.util.ProgramLocation;
-import ghidra.util.Swing;
import ghidra.util.xml.XmlUtilities;
//@formatter:off
@@ -46,37 +45,62 @@ import ghidra.util.xml.XmlUtilities;
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 }
+ servicesRequired = { GoToService.class },
+ eventsConsumed = { FirstTimeAnalyzedPluginEvent.class }
)
//@formatter:on
public class ProgramStartingLocationPlugin extends ProgramPlugin {
+ public static enum NonActiveProgramState {
+ NEWLY_OPENED,
+ RESTORED,
+ FIRST_ANALYSIS_COMPLETED
+ }
+
private static final String LAST_LOCATION_PROPERTY = "LAST_PROGRAM_LOCATION";
- private Program lastOpenedProgram;
private ProgramStartingLocationOptions startOptions;
- private Map lastLocationMap = new HashMap<>();
+ private WeakHashMap currentLocationsMap = new WeakHashMap<>();
+ private WeakHashMap startLocationsMap = new WeakHashMap<>();
+ private WeakHashMap programStateMap = new WeakHashMap<>();
public ProgramStartingLocationPlugin(PluginTool tool) {
super(tool);
startOptions = new ProgramStartingLocationOptions(tool);
}
+ @Override
+ public void processEvent(PluginEvent event) {
+ super.processEvent(event);
+
+ if (event instanceof FirstTimeAnalyzedPluginEvent ev) {
+ Program program = ev.getProgram();
+ if (program != null) {
+ firstAnalysisCompleted(program);
+ }
+ }
+ }
+
+ private void firstAnalysisCompleted(Program program) {
+ if (program.equals(currentProgram)) {
+ processFirstAnalysisCompleted();
+ }
+ else {
+ programStateMap.put(program, NonActiveProgramState.FIRST_ANALYSIS_COMPLETED);
+ }
+ }
+
@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;
+ programStateMap.put(program, NonActiveProgramState.RESTORED);
}
- if (startOptions.getStartLocationType() == StartLocationType.LOWEST_ADDRESS) {
- // this is what happens by default, so no need to do anything
- return;
+ else {
+ programStateMap.put(program, NonActiveProgramState.NEWLY_OPENED);
}
- lastOpenedProgram = program;
}
protected void programClosed(Program program) {
- ProgramLocation lastLocation = lastLocationMap.remove(program);
+ ProgramLocation lastLocation = currentLocationsMap.remove(program);
if (lastLocation == null) {
return;
}
@@ -87,22 +111,66 @@ public class ProgramStartingLocationPlugin extends ProgramPlugin {
String xmlString = XmlUtilities.toString(saveState.saveToXml());
programUserData.setStringProperty(LAST_LOCATION_PROPERTY, xmlString);
+ programStateMap.remove(program);
+ currentLocationsMap.remove(program);
+
}
@Override
- protected void programActivated(Program program) {
- super.programActivated(program);
- if (program == lastOpenedProgram) {
- Swing.runLater(this::setStartingLocationForNewProgram);
+ protected void postProgramActivated(Program program) {
+ NonActiveProgramState state = programStateMap.remove(program);
+ if (state == NonActiveProgramState.NEWLY_OPENED) {
+ setStartingLocationForNewProgram();
}
- lastOpenedProgram = null;
+ else if (state == NonActiveProgramState.FIRST_ANALYSIS_COMPLETED) {
+ processFirstAnalysisCompleted();
+ }
+ }
+
+ private void processFirstAnalysisCompleted() {
+ boolean shouldAskToRepostion = startOptions.shouldAskToRepostionAfterAnalysis();
+ boolean autoRepositionIfNotMoved = startOptions.shouldAutoRepositionIfNotMoved();
+
+ if (!shouldAskToRepostion && !autoRepositionIfNotMoved) {
+ return;
+ }
+
+ // if analysis didn't find any starting symbol, nothing to do
+ Symbol symbol = findStartingSymbol(currentProgram);
+ if (symbol == null) {
+ return;
+ }
+
+ // if already at the symbol's address, don't do anything
+ if (currentLocation != null && currentLocation.getAddress().equals(symbol.getAddress())) {
+ return;
+ }
+
+ if (autoRepositionIfNotMoved && isProgramAtStartingLocation()) {
+ gotoLocation(symbol.getProgramLocation());
+ }
+ else if (shouldAskToRepostion && askToPositionProgram(symbol)) {
+ gotoLocation(symbol.getProgramLocation());
+ }
+ }
+
+ private boolean askToPositionProgram(Symbol symbol) {
+ int result = OptionDialog.showYesNoDialog(null, "Reposition Program?",
+ "Analysis found the symbol \"" + symbol.getName() +
+ "\". Would you like to go to that symbol?");
+ return result == OptionDialog.YES_OPTION;
}
@Override
protected void locationChanged(ProgramLocation loc) {
if (loc != null) {
Program program = loc.getProgram();
- lastLocationMap.put(program, loc);
+ currentLocationsMap.put(program, loc);
+
+ // the startLocationsMap only gets updated with the first location
+ if (!startLocationsMap.containsKey(program)) {
+ startLocationsMap.put(program, loc);
+ }
}
}
@@ -111,15 +179,29 @@ public class ProgramStartingLocationPlugin extends ProgramPlugin {
return;
}
- GoToService gotoService = tool.getService(GoToService.class);
-
ProgramLocation location = getStartingProgramLocation(currentProgram);
if (location != null) {
- gotoService.goTo(location);
+ gotoLocation(location);
+ startLocationsMap.put(currentProgram, location);
}
}
+ private void gotoLocation(ProgramLocation location) {
+ GoToService gotoService = tool.getService(GoToService.class);
+ gotoService.goTo(location);
+ }
+
+ private boolean isProgramAtStartingLocation() {
+ ProgramLocation startLocation = startLocationsMap.get(currentProgram);
+ if (startLocation == null || currentLocation == null) {
+ return true;
+ }
+ // just compare address, analysis may have tweaked the current location even
+ // the user didn't move
+ return startLocation.getAddress().equals(currentLocation.getAddress());
+ }
+
private ProgramLocation getStartingProgramLocation(Program program) {
switch (startOptions.getStartLocationType()) {
case LAST_LOCATION:
@@ -129,7 +211,7 @@ public class ProgramStartingLocationPlugin extends ProgramPlugin {
}
// fall through and try symbol name
case SYMBOL_NAME:
- Symbol symbol = fingStartingSymbol(program);
+ Symbol symbol = findStartingSymbol(program);
if (symbol != null) {
return symbol.getProgramLocation();
}
@@ -158,7 +240,7 @@ public class ProgramStartingLocationPlugin extends ProgramPlugin {
}
}
- private Symbol fingStartingSymbol(Program program) {
+ private Symbol findStartingSymbol(Program program) {
List symbolNames = startOptions.getStartingSymbolNames();
boolean useUnderscores = startOptions.useUnderscorePrefixes();
for (String symbolName : symbolNames) {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiProgramManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiProgramManager.java
index 80b6dbdb79..717227e8bb 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiProgramManager.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiProgramManager.java
@@ -51,6 +51,7 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
private Runnable programChangedRunnable;
private boolean hasUnsavedPrograms;
+ private String pluginName;
// These data structures are accessed from multiple threads. Rather than synchronizing all
// accesses, we have chosen to be weakly consistent. We assume that any out-of-date checks
@@ -64,6 +65,7 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
MultiProgramManager(ProgramManagerPlugin programManagerPlugin) {
this.plugin = programManagerPlugin;
this.tool = programManagerPlugin.getTool();
+ this.pluginName = plugin.getName();
txMonitor = new TransactionMonitor();
txMonitor.setName("Transaction Open (Program being modified)");
@@ -173,9 +175,9 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
Program[] getOtherPrograms() {
Program currentProgram = getCurrentProgram();
List list = openPrograms.stream()
- .map(info -> info.program)
- .filter(program -> program != currentProgram)
- .collect(Collectors.toList());
+ .map(info -> info.program)
+ .filter(program -> program != currentProgram)
+ .collect(Collectors.toList());
return list.toArray(new Program[list.size()]);
}
@@ -260,25 +262,35 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
if (toolState != null) {
toolState.restoreTool();
}
+ // only fire the post activated event when a program is activated (we send activated with
+ // null program to represent a phantom de-activated event)
+ if (newProgram != null) {
+ firePostActivatedEvent(newProgram);
+ }
}
private void fireOpenEvents(Program program) {
- plugin.firePluginEvent(new ProgramOpenedPluginEvent("", program));
- plugin.firePluginEvent(new OpenProgramPluginEvent("", program));
+ plugin.firePluginEvent(new ProgramOpenedPluginEvent(pluginName, program));
+ plugin.firePluginEvent(new OpenProgramPluginEvent(pluginName, program));
}
private void fireCloseEvents(Program program) {
- plugin.firePluginEvent(new ProgramClosedPluginEvent("", program));
- plugin.firePluginEvent(new CloseProgramPluginEvent("", program, true));
+ plugin.firePluginEvent(new ProgramClosedPluginEvent(pluginName, program));
+ plugin.firePluginEvent(new CloseProgramPluginEvent(pluginName, program, true));
// tool.contextChanged();
}
private void fireActivatedEvent(Program newProgram) {
- plugin.firePluginEvent(new ProgramActivatedPluginEvent("", newProgram));
+ plugin.firePluginEvent(new ProgramActivatedPluginEvent(pluginName, newProgram));
+ }
+
+ private void firePostActivatedEvent(Program newProgram) {
+ plugin.firePluginEvent(new ProgramPostActivatedPluginEvent(pluginName, newProgram));
}
private void fireVisibilityChangeEvent(Program program, boolean isVisible) {
- plugin.firePluginEvent(new ProgramVisibilityChangePluginEvent("", program, isVisible));
+ plugin.firePluginEvent(
+ new ProgramVisibilityChangePluginEvent(pluginName, program, isVisible));
}
@Override
@@ -503,13 +515,13 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
private static final AtomicInteger nextAvailableId = new AtomicInteger();
public final Program program;
-
+
// NOTE: domainFile and ghidraURL use are mutually exclusive and reflect how program was
// opened. Supported cases include:
// 1. Opened via Program file
// 2. Opened via ProgramLink file
// 3. Opened via Program URL
-
+
public final DomainFile domainFile; // may be link file
public final URL ghidraURL;
@@ -532,7 +544,7 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
this.visible = visible;
instance = nextAvailableId.incrementAndGet();
}
-
+
ProgramInfo(Program p, URL ghidraURL, boolean visible) {
this.program = p;
this.domainFile = null;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/GhidraProgramUtilities.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/GhidraProgramUtilities.java
index 50b83970f4..c9de4fa333 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/GhidraProgramUtilities.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/GhidraProgramUtilities.java
@@ -89,4 +89,23 @@ public class GhidraProgramUtilities {
program.endTransaction(transactionID, true);
}
}
+
+ /**
+ * Returns true if the program has been analyzed at least once.
+ * @param program the program to test to see if it has been analyzed
+ * @return true if the program has been analyzed at least once.
+ */
+ public static boolean isAnalyzedFlagSet(Program program) {
+ Options options = program.getOptions(Program.PROGRAM_INFO);
+
+ // we first have to check if the flag has even been created because checking the flag
+ // directly causes it to be created if it doesn't exist and we unfortunately use the
+ // existence of the flag to know whether or not to ask the user if they want to start
+ // analysis
+ if (!options.isRegistered(Program.ANALYZED)) {
+ return false;
+ }
+
+ return options.getBoolean(Program.ANALYZED, false);
+ }
}
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
index 36af3269bb..453d3b1760 100644
--- 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
@@ -19,7 +19,9 @@ import static org.junit.Assert.*;
import org.junit.*;
+import docking.widgets.OptionDialog;
import ghidra.GhidraOptions;
+import ghidra.app.events.FirstTimeAnalyzedPluginEvent;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool;
@@ -57,7 +59,7 @@ public class ProgramStartPluginTest extends AbstractGhidraHeadedIntegrationTest
@Test
public void testOpensToStartingSymbolByDefault() throws Exception {
- ProgramBuilder builder = getProgramBuilder("0x100");
+ ProgramBuilder builder = getProgramBuilder("program 1", "0x100");
builder.createLabel("0x105", "main");
loadProgram(builder.getProgram());
@@ -66,7 +68,7 @@ public class ProgramStartPluginTest extends AbstractGhidraHeadedIntegrationTest
@Test
public void testOpensToLowestCodeBlock() throws Exception {
- ProgramBuilder builder = getProgramBuilder("0x100");
+ ProgramBuilder builder = getProgramBuilder("program 1", "0x100");
MemoryBlock block = builder.createMemory(".text", "0x200", 0x200);
builder.setExecute(block, true);
builder.createLabel("0x105", "main");
@@ -80,7 +82,7 @@ public class ProgramStartPluginTest extends AbstractGhidraHeadedIntegrationTest
@Test
public void testOpensToStartingSymbolNotFirstInSymbolList() throws Exception {
- ProgramBuilder builder = getProgramBuilder("0x100");
+ ProgramBuilder builder = getProgramBuilder("program 1", "0x100");
setSymbolListOption("main, foobar, start");
builder.createLabel("0x107", "start");
@@ -91,7 +93,7 @@ public class ProgramStartPluginTest extends AbstractGhidraHeadedIntegrationTest
@Test
public void testOpensToFirstSymbolWhenMutlipesAreFoud() throws Exception {
- ProgramBuilder builder = getProgramBuilder("0x100");
+ ProgramBuilder builder = getProgramBuilder("program 1", "0x100");
setSymbolListOption("main, start");
builder.createLabel("0x110", "start");
builder.createLabel("0x105", "start");
@@ -103,7 +105,7 @@ public class ProgramStartPluginTest extends AbstractGhidraHeadedIntegrationTest
@Test
public void testOpensToStartingSymbolWithOneUndercore() throws Exception {
- ProgramBuilder builder = getProgramBuilder("0x100");
+ ProgramBuilder builder = getProgramBuilder("program 1", "0x100");
setSymbolListOption("main, start");
builder.createLabel("0x105", "_main");
builder.createLabel("0x107", "start");
@@ -115,7 +117,7 @@ public class ProgramStartPluginTest extends AbstractGhidraHeadedIntegrationTest
@Test
public void testOpensToStartingSymbolWithTwoUndercores() throws Exception {
- ProgramBuilder builder = getProgramBuilder("0x100");
+ ProgramBuilder builder = getProgramBuilder("program 1", "0x100");
setSymbolListOption("main, start");
builder.createLabel("0x105", "__main");
builder.createLabel("0x107", "_start");
@@ -128,7 +130,7 @@ public class ProgramStartPluginTest extends AbstractGhidraHeadedIntegrationTest
@Test
public void testNoUnderscoresSearching() throws Exception {
- ProgramBuilder builder = getProgramBuilder("0x100");
+ ProgramBuilder builder = getProgramBuilder("program 1", "0x100");
setSymbolListOption("main, start");
setUnderscoreOption(false);
builder.createLabel("0x105", "__main");
@@ -142,7 +144,7 @@ public class ProgramStartPluginTest extends AbstractGhidraHeadedIntegrationTest
@Test
public void testOptionToStartAtLowestAddress() throws Exception {
- ProgramBuilder builder = getProgramBuilder("0x100");
+ ProgramBuilder builder = getProgramBuilder("program 1", "0x100");
builder.createLabel("0x105", "main");
setOptionToLowestAddress();
loadProgram(builder.getProgram());
@@ -152,7 +154,7 @@ public class ProgramStartPluginTest extends AbstractGhidraHeadedIntegrationTest
@Test
public void testOptionToStartAtLastLocation() throws Exception {
- ProgramBuilder builder = getProgramBuilder("0x100");
+ ProgramBuilder builder = getProgramBuilder("program 1", "0x100");
ProgramDB program = builder.getProgram();
loadProgram(program);
cb.goTo(new ProgramLocation(program, addr("0x107")));
@@ -162,10 +164,124 @@ public class ProgramStartPluginTest extends AbstractGhidraHeadedIntegrationTest
assertEquals(addr("0x107"), cb.getCurrentAddress());
}
+ @Test
+ public void testProgramAutoRepostionsAfterAnalysis() throws Exception {
+ ProgramBuilder builder = getProgramBuilder("program 1", "0x100");
+ loadProgram(builder.getProgram());
+
+ assertEquals(addr("0x100"), cb.getCurrentAddress());
+
+ simulateAnaysisCreatingMain(builder, "0x105");
+
+ assertEquals(addr("0x105"), cb.getCurrentAddress());
+
+ }
+
+ @Test
+ public void testProgramAsksToRepostionsAfterAnalysisYes() throws Exception {
+ ProgramBuilder builder = getProgramBuilder("program 1", "0x100");
+ loadProgram(builder.getProgram());
+
+ assertEquals(addr("0x100"), cb.getCurrentAddress());
+ cb.goToField(addr("0x102"), "Address", 0, 0);
+ assertEquals(addr("0x102"), cb.getCurrentAddress());
+
+ simulateAnaysisCreatingMain(builder, "0x105");
+
+ OptionDialog dialog = waitForDialogComponent(OptionDialog.class);
+ pressButtonByText(dialog, "Yes");
+ waitForSwing();
+ assertEquals(addr("0x105"), cb.getCurrentAddress());
+
+ }
+
+ @Test
+ public void testProgramAsksToRepostionsAfterAnalysisNo() throws Exception {
+ ProgramBuilder builder = getProgramBuilder("program 1", "0x100");
+ loadProgram(builder.getProgram());
+
+ assertEquals(addr("0x100"), cb.getCurrentAddress());
+ cb.goToField(addr("0x102"), "Address", 0, 0);
+ assertEquals(addr("0x102"), cb.getCurrentAddress());
+
+ simulateAnaysisCreatingMain(builder, "0x105");
+
+ OptionDialog dialog = waitForDialogComponent(OptionDialog.class);
+ pressButtonByText(dialog, "No");
+ waitForSwing();
+ assertEquals(addr("0x102"), cb.getCurrentAddress());
+
+ }
+
+ @Test
+ public void testAutoMoveOff() throws Exception {
+ ProgramBuilder builder = getProgramBuilder("program 1", "0x100");
+ loadProgram(builder.getProgram());
+ setOptionToNotAutoMove();
+ setOptionToNotAsk();
+
+ assertEquals(addr("0x100"), cb.getCurrentAddress());
+
+ simulateAnaysisCreatingMain(builder, "0x105");
+
+ assertEquals(addr("0x100"), cb.getCurrentAddress());
+ }
+
+ @Test
+ public void testAnalysisHappensWhenProgramIsNotActiveNoUserMove() throws Exception {
+ ProgramBuilder builder1 = getProgramBuilder("program 1", "0x100");
+ loadProgram(builder1.getProgram());
+ ProgramBuilder builder2 = getProgramBuilder("program 2", "0x100");
+ loadProgram(builder2.getProgram());
+
+ simulateAnaysisCreatingMain(builder1, "0x104");
+ assertEquals(addr("0x100"), cb.getCurrentAddress());
+
+ env.close(builder2.getProgram());
+
+ assertEquals(addr("0x104"), cb.getCurrentAddress());
+ }
+
+ @Test
+ public void testAnalysisHappensWhenProgramIsNotActiveWithUserMove() throws Exception {
+ ProgramBuilder builder1 = getProgramBuilder("program 1", "0x100");
+ loadProgram(builder1.getProgram());
+ cb.goToField(addr("0x102"), "Address", 0, 0);
+ assertEquals(addr("0x102"), cb.getCurrentAddress());
+
+ ProgramBuilder builder2 = getProgramBuilder("program 2", "0x100");
+ loadProgram(builder2.getProgram());
+
+ simulateAnaysisCreatingMain(builder1, "0x104");
+ assertEquals(addr("0x100"), cb.getCurrentAddress());
+
+ runSwingLater(() -> env.close(builder2.getProgram()));
+ OptionDialog dialog = waitForDialogComponent(OptionDialog.class);
+ pressButtonByText(dialog, "Yes");
+ waitForSwing();
+
+ assertEquals(addr("0x104"), cb.getCurrentAddress());
+ }
+
+ private void simulateAnaysisCreatingMain(ProgramBuilder builder, String address) {
+ builder.createLabel(address, "main");
+ runSwingLater(() -> tool
+ .firePluginEvent(new FirstTimeAnalyzedPluginEvent("test", builder.getProgram())));
+ waitForSwing();
+ }
+
private void setUnderscoreOption(boolean b) {
options.setBoolean(ProgramStartingLocationOptions.UNDERSCORE_OPTION, b);
}
+ private void setOptionToNotAsk() {
+ options.setBoolean(ProgramStartingLocationOptions.ASK_TO_MOVE_OPTION, false);
+ }
+
+ private void setOptionToNotAutoMove() {
+ options.setBoolean(ProgramStartingLocationOptions.AUTO_MOVE_OPTION, false);
+ }
+
private void setOptionToLowestAddress() {
options.setEnum(ProgramStartingLocationOptions.START_LOCATION_TYPE_OPTION,
ProgramStartingLocationOptions.StartLocationType.LOWEST_ADDRESS);
@@ -180,8 +296,8 @@ public class ProgramStartPluginTest extends AbstractGhidraHeadedIntegrationTest
options.setString(ProgramStartingLocationOptions.START_SYMBOLS_OPTION, symbolListString);
}
- private ProgramBuilder getProgramBuilder(String baseAddress) throws Exception {
- ProgramBuilder builder = new ProgramBuilder();
+ private ProgramBuilder getProgramBuilder(String name, String baseAddress) throws Exception {
+ ProgramBuilder builder = new ProgramBuilder(name, ProgramBuilder._TOY);
builder.createMemory(".data", baseAddress, 0x100);
return builder;
}