GP-4040 added ability for scripts to open programs that need to be upgraded, with ask options

This commit is contained in:
ghidragon 2023-12-05 18:33:17 -05:00 committed by ghidra1
parent e81515d9d8
commit 514c04906b
6 changed files with 142 additions and 43 deletions

View file

@ -75,7 +75,7 @@ public class AskValuesExampleScript extends GhidraScript {
// show up in the tool and when you release the consumer, it will be closed.
// NOTE: if you call getProgram() more than once, the consumer will be added multiple times
// and you must release it multiple times
Program program = values.getProgram("Other Program", this, state.getTool());
Program program = values.getProgram("Other Program", this, state.getTool(), true);
println("Name = " + name);
println("Count = " + age);

View file

@ -2732,7 +2732,9 @@ public abstract class GhidraScript extends FlatProgramAPI {
/**
* Returns a Program, using the title parameter for guidance. The actual behavior of the
* method depends on your environment, which can be GUI or headless.
* method depends on your environment, which can be GUI or headless. If in headless mode,
* the program will not be upgraded (see {@link #askProgram(String, boolean)} if you want
* more control). In GUI mode, the user will be prompted to upgrade.
* <br>
* Regardless of environment -- if script arguments have been set, this method will use the
* next argument in the array and advance the array index so the next call to an ask method
@ -2757,20 +2759,75 @@ public abstract class GhidraScript extends FlatProgramAPI {
*
* @param title the title of the pop-up dialog (in GUI mode) or the variable name (in
* headless mode)
* @return the user-selected Program with this script as the consumer or null if a program was
* not selected. NOTE: It is very important that the program instance returned by this method
* ALWAYS be properly released when no longer needed. The script which invoked this method must be
* @return the user-selected Program with this script as the consumer if a program was
* selected. Null is returned if a program is not selected. NOTE: It is very important that
* the program instance returned by this method ALWAYS be properly released when no longer
* needed. The script which invoked this method must be
* specified as the consumer upon release (i.e., {@code program.release(this) } - failure to
* properly release the program may result in improper project disposal. If the program was
* opened by the tool, the tool will be a second consumer responsible for its own release.
* @throws VersionException if the Program is out-of-date from the version of GHIDRA
* @throws VersionException if the Program is out-of-date from the version of Ghidra and an
* upgrade was not been performed. In non-headless mode, the user will have already been
* notified via a popup dialog.
* @throws IOException if there is an error accessing the Program's DomainObject
* @throws CancelledException if the operation is cancelled
* @throws CancelledException if the program open operation is cancelled
* @throws IllegalArgumentException if in headless mode, there was a missing or invalid program
* specified in the .properties file
*/
public Program askProgram(String title)
throws VersionException, IOException, CancelledException {
return askProgram(title, false);
}
/**
* Returns a Program, using the title parameter for guidance with the option to upgrade
* if needed. The actual behavior of the method depends on your environment, which can be
* GUI or headless. You can control whether or not the program is allowed to upgrade via
* the {@code upgradeIfNeeded} parameter.
* <br>
* Regardless of environment -- if script arguments have been set, this method will use the
* next argument in the array and advance the array index so the next call to an ask method
* will get the next argument. If there are no script arguments and a .properties file
* sharing the same base name as the Ghidra Script exists (i.e., Script1.properties for
* Script1.java), then this method will then look there for the String value to return.
* The method will look in the .properties file by searching for a property name that is the
* title String parameter. If that property name exists and its value represents a valid
* program, then the .properties value will be used in the following way:
* <ol>
* <li>In the GUI environment, this method displays a popup dialog that prompts the user
* to select a program.</li>
* <li>In the headless environment, if a .properties file sharing the same base name as the
* Ghidra Script exists (i.e., Script1.properties for Script1.java), then this method
* looks there for the name of the program to return. The method will look in the
* .properties file by searching for a property name equal to the 'title' parameter. If
* that property name exists and its value represents a valid Program in the project,
* then that value is returned. Otherwise, an Exception is thrown if there is an
* invalid or missing .properties value.</li>
* </ol>
*
*
* @param title the title of the pop-up dialog (in GUI mode) or the variable name (in
* headless mode)
* @param upgradeIfNeeded if true, program will be upgraded if needed and possible. If false,
* the program will only be upgraded after first prompting the user. In headless mode, it will
* attempt to upgrade only if the parameter is true.
* @return the user-selected Program with this script as the consumer if a program was
* selected. Null is returned if a program is not selected. NOTE: It is very important that
* the program instance returned by this method ALWAYS be properly released when no longer
* needed. The script which invoked this method must be
* specified as the consumer upon release (i.e., {@code program.release(this) } - failure to
* properly release the program may result in improper project disposal. If the program was
* opened by the tool, the tool will be a second consumer responsible for its own release.
* @throws VersionException if the Program is out-of-date from the version of GHIDRA and an
* upgrade was not been performed. In non-headless mode, the user will have already been
* notified via a popup dialog.
* @throws IOException if there is an error accessing the Program's DomainObject
* @throws CancelledException if the program open operation is cancelled
* @throws IllegalArgumentException if in headless mode, there was a missing or invalid program
* specified in the .properties file
*/
public Program askProgram(String title, boolean upgradeIfNeeded)
throws VersionException, IOException, CancelledException {
DomainFile choice = loadAskValue(this::parseDomainFile, title);
if (!isRunningHeadless()) {
@ -2779,7 +2836,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
DataTreeDialog dtd = new DataTreeDialog(null, title, DataTreeDialog.OPEN);
dtd.show();
if (dtd.wasCancelled()) {
throw new CancelledException();
return null;
}
return dtd.getDomainFile();
@ -2790,7 +2847,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
return null;
}
Program p = (Program) choice.getDomainObject(this, false, false, monitor);
Program p = doOpenProgram(choice, upgradeIfNeeded);
PluginTool tool = state.getTool();
if (tool == null) {
@ -2802,6 +2859,24 @@ public abstract class GhidraScript extends FlatProgramAPI {
return p;
}
private Program doOpenProgram(DomainFile domainFile, boolean upgradeIfNeeded)
throws CancelledException, IOException, VersionException {
try {
return (Program) domainFile.getDomainObject(this, upgradeIfNeeded, false, monitor);
}
catch (VersionException e) {
if (isRunningHeadless()) {
throw e;
}
// in Gui mode, ask the user if they would like to upgrade
if (VersionExceptionHandler.isUpgradeOK(null, domainFile, "Open ", e)) {
return (Program) domainFile.getDomainObject(this, true, false, monitor);
}
throw e;
}
}
/**
* Parses a DomainFile from a string.
*

View file

@ -209,17 +209,23 @@ public class GhidraValuesMap extends GValuesMap {
* @param consumer the consumer to be used to open the program
* @param tool if non-null, the program will also be opened in the given tool. Note: the
* program will only be added to the tool once even if this method is called multiple times.
* @return the project folder value
* @throws VersionException if the Program being opened is an older version than the
* @param upgradeIfNeeded if true, program will be upgraded if needed and possible. If false,
* the program will only be upgraded after first prompting the user. In headless mode, it will
* attempt to upgrade only if the parameter is true.
* @return an opened program with the given consumer for the selected domain file or null if
* no program was selected.
* @throws VersionException if the Program is out-of-date from the version of GHIDRA and an
* upgrade was not been performed. In non-headless mode, the user will have already been
* notified via a popup dialog.
* current Ghidra Program version.
* @throws IOException if there is an error accessing the Program's DomainObject
* @throws CancelledException if the operation is cancelled
* @throws IllegalArgumentException if the name hasn't been defined as a project folder type
*/
public Program getProgram(String name, Object consumer, Tool tool)
public Program getProgram(String name, Object consumer, Tool tool, boolean upgradeIfNeeded)
throws VersionException, IOException, CancelledException {
ProgramFileValue programFileValue = getValue(name, ProgramFileValue.class, "Program");
return programFileValue.openProgram(consumer, tool, monitor);
return programFileValue.openProgram(consumer, tool, upgradeIfNeeded, monitor);
}
/**

View file

@ -27,6 +27,8 @@ import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.Project;
import ghidra.program.model.listing.Program;
import ghidra.util.SystemUtilities;
import ghidra.util.VersionExceptionHandler;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
@ -89,23 +91,27 @@ public class ProgramFileValue extends ProjectFileValue {
* <P>
* @param consumer the consumer to be used to open the program
* @param tool optional tool that if non-null, the program will also be opened in the tool
* @param upgradeIfNeeded if true, program will be upgraded if needed and possible. If false,
* the program will only be upgraded after first prompting the user. In headless mode, it will
* attempt to upgrade only if the parameter is true.
* @param monitor task monitor for cancelling the open program.
* @return a program for the current program value value. If the current program file value
* is null, then null will be returned.
* @throws VersionException if the Program being opened is an older version than the
* @return a program for the currently selected program file. If no file chosen, returns null
* @throws VersionException if the Program is out-of-date from the version of GHIDRA and an
* upgrade was not been performed. In non-headless mode, the user will have already been
* notified via a popup dialog.
* current Ghidra Program version.
* @throws IOException if there is an error accessing the Program's DomainObject
* @throws CancelledException if the operation is cancelled
*/
public Program openProgram(Object consumer, Tool tool, TaskMonitor monitor)
throws VersionException, IOException, CancelledException {
public Program openProgram(Object consumer, Tool tool, boolean upgradeIfNeeded,
TaskMonitor monitor) throws VersionException, IOException, CancelledException {
DomainFile domainFile = getValue();
if (domainFile == null) {
return null;
}
Program program = (Program) domainFile.getDomainObject(consumer, true, false, monitor);
Program program = doOpenProgram(domainFile, consumer, upgradeIfNeeded, monitor);
if (tool != null && program != null) {
tool.getService(ProgramManager.class).openProgram(program);
@ -113,4 +119,21 @@ public class ProgramFileValue extends ProjectFileValue {
return program;
}
private Program doOpenProgram(DomainFile domainFile, Object consumer, boolean upgradeIfNeeded,
TaskMonitor monitor) throws VersionException, IOException, CancelledException {
try {
return (Program) domainFile.getDomainObject(consumer, upgradeIfNeeded, false, monitor);
}
catch (VersionException e) {
if (SystemUtilities.isInHeadlessMode()) {
throw e;
}
if (VersionExceptionHandler.isUpgradeOK(null, domainFile, "Open ", e)) {
return (Program) domainFile.getDomainObject(consumer, true, false, monitor);
}
throw e;
}
}
}

View file

@ -36,7 +36,7 @@ public class ProgramFileValueTest extends AbstractValueIntegrationTest {
values.setProgram(NAME, programA);
assertTrue(values.hasValue(NAME));
assertEquals(programA, values.getProgram(NAME, this, null));
assertEquals(programA, values.getProgram(NAME, this, null, true));
}
@Test
@ -46,12 +46,12 @@ public class ProgramFileValueTest extends AbstractValueIntegrationTest {
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
assertEquals(programA, values.getProgram(NAME, this, null));
assertEquals(programA, values.getProgram(NAME, this, null, true));
values.setProgram(NAME, programB);
assertTrue(values.hasValue(NAME));
assertEquals(programB, values.getProgram(NAME, this, null));
assertEquals(programB, values.getProgram(NAME, this, null, true));
values.setProgram(NAME, null);
assertFalse(values.hasValue(NAME));
@ -96,7 +96,7 @@ public class ProgramFileValueTest extends AbstractValueIntegrationTest {
pressOk();
assertFalse(values.hasValue(NAME));
assertNull(values.getProgram(NAME, this, null));
assertNull(values.getProgram(NAME, this, null, true));
}
@Test
@ -109,7 +109,7 @@ public class ProgramFileValueTest extends AbstractValueIntegrationTest {
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(programA, values.getProgram(NAME, this, null));
assertEquals(programA, values.getProgram(NAME, this, null, true));
}
@Test
@ -121,7 +121,7 @@ public class ProgramFileValueTest extends AbstractValueIntegrationTest {
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(programA, values.getProgram(NAME, this, null));
assertEquals(programA, values.getProgram(NAME, this, null, true));
}
@Test
@ -134,7 +134,7 @@ public class ProgramFileValueTest extends AbstractValueIntegrationTest {
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(programB, values.getProgram(NAME, this, null));
assertEquals(programB, values.getProgram(NAME, this, null, true));
}
@Test
@ -149,7 +149,7 @@ public class ProgramFileValueTest extends AbstractValueIntegrationTest {
setProjectFileOnProjectTree(values.getAbstractValue(NAME), programA.getDomainFile());
pressOk();
Program p = values.getProgram(NAME, this, tool);
Program p = values.getProgram(NAME, this, tool, true);
allOpenPrograms = programManagerService.getAllOpenPrograms();
assertEquals(1, allOpenPrograms.length);
@ -164,9 +164,9 @@ public class ProgramFileValueTest extends AbstractValueIntegrationTest {
setProjectFileOnProjectTree(values.getAbstractValue(NAME), programA.getDomainFile());
pressOk();
Program p1 = values.getProgram(NAME, this, null);
Program p1 = values.getProgram(NAME, this, null, true);
assertEquals(2, programA.getConsumerList().size());
Program p2 = values.getProgram(NAME, this, null);
Program p2 = values.getProgram(NAME, this, null, true);
assertEquals(p1, p2);
assertEquals(3, programA.getConsumerList().size());
p1.release(this);

View file

@ -115,17 +115,19 @@ public class AutoVersionTrackingScript extends GhidraScript {
});
startupValues = askValues("Enter Auto Version Tracking Information",
"Changing these options will not change the corresponding tool options",
startupValues);
"Changing these options will not change the corresponding tool options", startupValues);
DomainFolder folder = startupValues.getProjectFolder("Version Tracking Session Folder");
String name = startupValues.getString("Version Tracking Session Name");
boolean isCurrentProgramSourceProg =
startupValues.getBoolean("Check if current program is the Source Program");
Program otherProgram =
startupValues.getProgram("Please select the other program", this, state.getTool());
// setting auto upgrade to isHeadless, will cause headless uses to auto upgrade, but in
// Gui mode, will prompt before upgrading.
boolean autoUpgradeIfNeeded = isRunningHeadless();
Program otherProgram = startupValues.getProgram("Please select the other program", this,
state.getTool(), autoUpgradeIfNeeded);
if (isCurrentProgramSourceProg) {
sourceProgram = currentProgram;
@ -140,15 +142,12 @@ public class AutoVersionTrackingScript extends GhidraScript {
return;
}
// Need to end the script transaction or it interferes with vt things that need locks
end(true);
VTSession session =
VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this);
if (folder.getFile(name) == null) {
folder.createFile(name, session, monitor);
}
@ -157,7 +156,7 @@ public class AutoVersionTrackingScript extends GhidraScript {
GhidraValuesMap optionsMap = createDefaultOptions();
// if running script in GUI get options from user and update the vtOptions with them
if(!isRunningHeadless()) {
if (!isRunningHeadless()) {
optionsMap = getOptionsFromUser();
}
@ -177,12 +176,10 @@ public class AutoVersionTrackingScript extends GhidraScript {
ToolOptions vtOptions = setToolOptionsFromOptionsMap(optionsMap);
AutoVersionTrackingTask autoVtTask =
new AutoVersionTrackingTask(session, vtOptions);
AutoVersionTrackingTask autoVtTask = new AutoVersionTrackingTask(session, vtOptions);
TaskLauncher.launch(autoVtTask);
// if not running headless user can decide whether to save or not
// if running headless - must save here or nothing that was done in this script will be
// accessible later.
@ -191,7 +188,6 @@ public class AutoVersionTrackingScript extends GhidraScript {
session.save();
}
println(autoVtTask.getStatusMsg());
otherProgram.release(this);
}
@ -257,8 +253,7 @@ public class AutoVersionTrackingScript extends GhidraScript {
GhidraValuesMap optionsValues = createDefaultOptions();
optionsValues = askValues("Enter Auto Version Tracking Options",
"These options will not be saved to your current tool options.",
optionsValues);
"These options will not be saved to your current tool options.", optionsValues);
return optionsValues;
}