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. // 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 // NOTE: if you call getProgram() more than once, the consumer will be added multiple times
// and you must release it 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("Name = " + name);
println("Count = " + age); 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 * 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> * <br>
* Regardless of environment -- if script arguments have been set, this method will use the * 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 * 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 * @param title the title of the pop-up dialog (in GUI mode) or the variable name (in
* headless mode) * headless mode)
* @return the user-selected Program with this script as the consumer or null if a program was * @return the user-selected Program with this script as the consumer if a program was
* not selected. NOTE: It is very important that the program instance returned by this method * selected. Null is returned if a program is not selected. NOTE: It is very important that
* ALWAYS be properly released when no longer needed. The script which invoked this method must be * 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 * 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 * 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. * 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 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 * @throws IllegalArgumentException if in headless mode, there was a missing or invalid program
* specified in the .properties file * specified in the .properties file
*/ */
public Program askProgram(String title) public Program askProgram(String title)
throws VersionException, IOException, CancelledException { 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); DomainFile choice = loadAskValue(this::parseDomainFile, title);
if (!isRunningHeadless()) { if (!isRunningHeadless()) {
@ -2779,7 +2836,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
DataTreeDialog dtd = new DataTreeDialog(null, title, DataTreeDialog.OPEN); DataTreeDialog dtd = new DataTreeDialog(null, title, DataTreeDialog.OPEN);
dtd.show(); dtd.show();
if (dtd.wasCancelled()) { if (dtd.wasCancelled()) {
throw new CancelledException(); return null;
} }
return dtd.getDomainFile(); return dtd.getDomainFile();
@ -2790,7 +2847,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
return null; return null;
} }
Program p = (Program) choice.getDomainObject(this, false, false, monitor); Program p = doOpenProgram(choice, upgradeIfNeeded);
PluginTool tool = state.getTool(); PluginTool tool = state.getTool();
if (tool == null) { if (tool == null) {
@ -2802,6 +2859,24 @@ public abstract class GhidraScript extends FlatProgramAPI {
return p; 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. * 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 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 * @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. * program will only be added to the tool once even if this method is called multiple times.
* @return the project folder value * @param upgradeIfNeeded if true, program will be upgraded if needed and possible. If false,
* @throws VersionException if the Program being opened is an older version than the * 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. * current Ghidra Program version.
* @throws IOException if there is an error accessing the Program's DomainObject * @throws IOException if there is an error accessing the Program's DomainObject
* @throws CancelledException if the operation is cancelled * @throws CancelledException if the operation is cancelled
* @throws IllegalArgumentException if the name hasn't been defined as a project folder type * @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 { throws VersionException, IOException, CancelledException {
ProgramFileValue programFileValue = getValue(name, ProgramFileValue.class, "Program"); 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.DomainFile;
import ghidra.framework.model.Project; import ghidra.framework.model.Project;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.SystemUtilities;
import ghidra.util.VersionExceptionHandler;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException; import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -89,23 +91,27 @@ public class ProgramFileValue extends ProjectFileValue {
* <P> * <P>
* @param consumer the consumer to be used to open the program * @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 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. * @param monitor task monitor for cancelling the open program.
* @return a program for the current program value value. If the current program file value * @return a program for the currently selected program file. If no file chosen, returns null
* is null, then null will be returned. * @throws VersionException if the Program is out-of-date from the version of GHIDRA and an
* @throws VersionException if the Program being opened is an older version than the * upgrade was not been performed. In non-headless mode, the user will have already been
* notified via a popup dialog.
* current Ghidra Program version. * current Ghidra Program version.
* @throws IOException if there is an error accessing the Program's DomainObject * @throws IOException if there is an error accessing the Program's DomainObject
* @throws CancelledException if the operation is cancelled * @throws CancelledException if the operation is cancelled
*/ */
public Program openProgram(Object consumer, Tool tool, TaskMonitor monitor) public Program openProgram(Object consumer, Tool tool, boolean upgradeIfNeeded,
throws VersionException, IOException, CancelledException { TaskMonitor monitor) throws VersionException, IOException, CancelledException {
DomainFile domainFile = getValue(); DomainFile domainFile = getValue();
if (domainFile == null) { if (domainFile == null) {
return null; return null;
} }
Program program = (Program) domainFile.getDomainObject(consumer, true, false, monitor); Program program = doOpenProgram(domainFile, consumer, upgradeIfNeeded, monitor);
if (tool != null && program != null) { if (tool != null && program != null) {
tool.getService(ProgramManager.class).openProgram(program); tool.getService(ProgramManager.class).openProgram(program);
@ -113,4 +119,21 @@ public class ProgramFileValue extends ProjectFileValue {
return program; 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); values.setProgram(NAME, programA);
assertTrue(values.hasValue(NAME)); assertTrue(values.hasValue(NAME));
assertEquals(programA, values.getProgram(NAME, this, null)); assertEquals(programA, values.getProgram(NAME, this, null, true));
} }
@Test @Test
@ -46,12 +46,12 @@ public class ProgramFileValueTest extends AbstractValueIntegrationTest {
assertTrue(values.isDefined(NAME)); assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME)); assertTrue(values.hasValue(NAME));
assertEquals(programA, values.getProgram(NAME, this, null)); assertEquals(programA, values.getProgram(NAME, this, null, true));
values.setProgram(NAME, programB); values.setProgram(NAME, programB);
assertTrue(values.hasValue(NAME)); assertTrue(values.hasValue(NAME));
assertEquals(programB, values.getProgram(NAME, this, null)); assertEquals(programB, values.getProgram(NAME, this, null, true));
values.setProgram(NAME, null); values.setProgram(NAME, null);
assertFalse(values.hasValue(NAME)); assertFalse(values.hasValue(NAME));
@ -96,7 +96,7 @@ public class ProgramFileValueTest extends AbstractValueIntegrationTest {
pressOk(); pressOk();
assertFalse(values.hasValue(NAME)); assertFalse(values.hasValue(NAME));
assertNull(values.getProgram(NAME, this, null)); assertNull(values.getProgram(NAME, this, null, true));
} }
@Test @Test
@ -109,7 +109,7 @@ public class ProgramFileValueTest extends AbstractValueIntegrationTest {
pressOk(); pressOk();
assertTrue(values.hasValue(NAME)); assertTrue(values.hasValue(NAME));
assertEquals(programA, values.getProgram(NAME, this, null)); assertEquals(programA, values.getProgram(NAME, this, null, true));
} }
@Test @Test
@ -121,7 +121,7 @@ public class ProgramFileValueTest extends AbstractValueIntegrationTest {
pressOk(); pressOk();
assertTrue(values.hasValue(NAME)); assertTrue(values.hasValue(NAME));
assertEquals(programA, values.getProgram(NAME, this, null)); assertEquals(programA, values.getProgram(NAME, this, null, true));
} }
@Test @Test
@ -134,7 +134,7 @@ public class ProgramFileValueTest extends AbstractValueIntegrationTest {
pressOk(); pressOk();
assertTrue(values.hasValue(NAME)); assertTrue(values.hasValue(NAME));
assertEquals(programB, values.getProgram(NAME, this, null)); assertEquals(programB, values.getProgram(NAME, this, null, true));
} }
@Test @Test
@ -149,7 +149,7 @@ public class ProgramFileValueTest extends AbstractValueIntegrationTest {
setProjectFileOnProjectTree(values.getAbstractValue(NAME), programA.getDomainFile()); setProjectFileOnProjectTree(values.getAbstractValue(NAME), programA.getDomainFile());
pressOk(); pressOk();
Program p = values.getProgram(NAME, this, tool); Program p = values.getProgram(NAME, this, tool, true);
allOpenPrograms = programManagerService.getAllOpenPrograms(); allOpenPrograms = programManagerService.getAllOpenPrograms();
assertEquals(1, allOpenPrograms.length); assertEquals(1, allOpenPrograms.length);
@ -164,9 +164,9 @@ public class ProgramFileValueTest extends AbstractValueIntegrationTest {
setProjectFileOnProjectTree(values.getAbstractValue(NAME), programA.getDomainFile()); setProjectFileOnProjectTree(values.getAbstractValue(NAME), programA.getDomainFile());
pressOk(); pressOk();
Program p1 = values.getProgram(NAME, this, null); Program p1 = values.getProgram(NAME, this, null, true);
assertEquals(2, programA.getConsumerList().size()); assertEquals(2, programA.getConsumerList().size());
Program p2 = values.getProgram(NAME, this, null); Program p2 = values.getProgram(NAME, this, null, true);
assertEquals(p1, p2); assertEquals(p1, p2);
assertEquals(3, programA.getConsumerList().size()); assertEquals(3, programA.getConsumerList().size());
p1.release(this); p1.release(this);

View file

@ -115,17 +115,19 @@ public class AutoVersionTrackingScript extends GhidraScript {
}); });
startupValues = askValues("Enter Auto Version Tracking Information", startupValues = askValues("Enter Auto Version Tracking Information",
"Changing these options will not change the corresponding tool options", "Changing these options will not change the corresponding tool options", startupValues);
startupValues);
DomainFolder folder = startupValues.getProjectFolder("Version Tracking Session Folder"); DomainFolder folder = startupValues.getProjectFolder("Version Tracking Session Folder");
String name = startupValues.getString("Version Tracking Session Name"); String name = startupValues.getString("Version Tracking Session Name");
boolean isCurrentProgramSourceProg = boolean isCurrentProgramSourceProg =
startupValues.getBoolean("Check if current program is the Source Program"); 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) { if (isCurrentProgramSourceProg) {
sourceProgram = currentProgram; sourceProgram = currentProgram;
@ -140,15 +142,12 @@ public class AutoVersionTrackingScript extends GhidraScript {
return; return;
} }
// Need to end the script transaction or it interferes with vt things that need locks // Need to end the script transaction or it interferes with vt things that need locks
end(true); end(true);
VTSession session = VTSession session =
VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this); VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this);
if (folder.getFile(name) == null) { if (folder.getFile(name) == null) {
folder.createFile(name, session, monitor); folder.createFile(name, session, monitor);
} }
@ -177,12 +176,10 @@ public class AutoVersionTrackingScript extends GhidraScript {
ToolOptions vtOptions = setToolOptionsFromOptionsMap(optionsMap); ToolOptions vtOptions = setToolOptionsFromOptionsMap(optionsMap);
AutoVersionTrackingTask autoVtTask = AutoVersionTrackingTask autoVtTask = new AutoVersionTrackingTask(session, vtOptions);
new AutoVersionTrackingTask(session, vtOptions);
TaskLauncher.launch(autoVtTask); TaskLauncher.launch(autoVtTask);
// if not running headless user can decide whether to save or not // 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 // if running headless - must save here or nothing that was done in this script will be
// accessible later. // accessible later.
@ -191,7 +188,6 @@ public class AutoVersionTrackingScript extends GhidraScript {
session.save(); session.save();
} }
println(autoVtTask.getStatusMsg()); println(autoVtTask.getStatusMsg());
otherProgram.release(this); otherProgram.release(this);
} }
@ -257,8 +253,7 @@ public class AutoVersionTrackingScript extends GhidraScript {
GhidraValuesMap optionsValues = createDefaultOptions(); GhidraValuesMap optionsValues = createDefaultOptions();
optionsValues = askValues("Enter Auto Version Tracking Options", optionsValues = askValues("Enter Auto Version Tracking Options",
"These options will not be saved to your current tool options.", "These options will not be saved to your current tool options.", optionsValues);
optionsValues);
return optionsValues; return optionsValues;
} }