GP-4906: Implement @image-opt. Have local-gdb use it. Fix 'null'.

This commit is contained in:
Dan 2024-10-31 11:50:38 -04:00
parent e476b20476
commit d5a25fa6a3
31 changed files with 497 additions and 462 deletions

View file

@ -124,7 +124,7 @@ public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmi
}
@Override
public boolean requiresImage() {
return !attrs.noImage();
public LaunchParameter<?> imageParameter() {
return attrs.imageOpt();
}
}

View file

@ -57,7 +57,6 @@ import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer {
public static final String PARAM_DISPLAY_IMAGE = "Image";
public static final String PREFIX_PARAM_EXTTOOL = "env:GHIDRA_LANG_EXTTOOL_";
public static final int DEFAULT_TIMEOUT_MILLIS = 10000;
@ -299,15 +298,10 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
protected Map<String, ValStr<?>> generateDefaultLauncherArgs(
Map<String, LaunchParameter<?>> params) {
Map<String, ValStr<?>> map = new LinkedHashMap<>();
ImageParamSetter imageSetter = null;
for (Entry<String, LaunchParameter<?>> entry : params.entrySet()) {
LaunchParameter<?> param = entry.getValue();
map.put(entry.getKey(), ValStr.cast(Object.class, param.defaultValue()));
if (PARAM_DISPLAY_IMAGE.equals(param.display())) {
imageSetter = ImageParamSetter.get(param);
// May still be null if type is not supported
}
else if (param.name().startsWith(PREFIX_PARAM_EXTTOOL)) {
if (param.name().startsWith(PREFIX_PARAM_EXTTOOL)) {
String tool = param.name().substring(PREFIX_PARAM_EXTTOOL.length());
List<String> names =
program.getLanguage().getLanguageDescription().getExternalNames(tool);
@ -328,7 +322,8 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
}
}
}
if (imageSetter != null && program != null) {
if (supportsImage() && program != null) {
ImageParamSetter imageSetter = ImageParamSetter.get(imageParameter());
imageSetter.setImage(map, program);
}
return map;
@ -559,9 +554,18 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
return auto == null ? ByModuleAutoMapSpec.instance() : auto.getAutoMapSpec(trace);
}
protected void initializeMonitor(TaskMonitor monitor) {
protected boolean providesImage(Map<String, ValStr<?>> args) {
LaunchParameter<?> param = imageParameter();
if (param == null) {
return false;
}
return !"".equals(param.get(args).str());
}
protected void updateMonitorMax(TaskMonitor monitor, Map<String, ValStr<?>> args) {
AutoMapSpec spec = getAutoMapSpec();
if (requiresImage() && spec.hasTask()) {
boolean image = args == null ? supportsImage() : providesImage(args);
if (image && spec.hasTask()) {
monitor.setMaximum(6);
}
else {
@ -621,7 +625,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
Trace trace = null;
Throwable lastExc = null;
initializeMonitor(monitor);
updateMonitorMax(monitor, null);
while (true) {
try {
monitor.setMessage("Gathering arguments");
@ -633,6 +637,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
return new LaunchResult(program, sessions, acceptor, connection, trace,
lastExc);
}
updateMonitorMax(monitor, args);
monitor.increment();
acceptor = null;

View file

@ -44,45 +44,46 @@ public abstract class ScriptAttributesParser {
public static final String ENV_GHIDRA_TRACE_RMI_HOST = "GHIDRA_TRACE_RMI_HOST";
public static final String ENV_GHIDRA_TRACE_RMI_PORT = "GHIDRA_TRACE_RMI_PORT";
public static final String AT_TITLE = "@title";
public static final String AT_DESC = "@desc";
public static final String AT_MENU_PATH = "@menu-path";
public static final String AT_MENU_GROUP = "@menu-group";
public static final String AT_MENU_ORDER = "@menu-order";
public static final String AT_ICON = "@icon";
public static final String AT_HELP = "@help";
public static final String AT_ENUM = "@enum";
public static final String AT_ENV = "@env";
public static final String AT_ARG = "@arg";
public static final String AT_ARGS = "@args";
public static final String AT_TTY = "@tty";
public static final String AT_DESC = "@desc";
public static final String AT_ENUM = "@enum";
public static final String AT_ENV = "@env";
public static final String AT_HELP = "@help";
public static final String AT_ICON = "@icon";
public static final String AT_IMAGE_OPT = "@image-opt";
public static final String AT_MENU_GROUP = "@menu-group";
public static final String AT_MENU_ORDER = "@menu-order";
public static final String AT_MENU_PATH = "@menu-path";
public static final String AT_TITLE = "@title";
public static final String AT_TIMEOUT = "@timeout";
public static final String AT_NOIMAGE = "@no-image";
public static final String AT_TTY = "@tty";
public static final String PREFIX_ENV = "env:";
public static final String PREFIX_ARG = "arg:";
public static final String KEY_ARGS = "args";
public static final String PREFIX_ARG = "arg:";
public static final String PREFIX_ENV = "env:";
public static final String MSGPAT_INVALID_HELP_SYNTAX =
"%s: Invalid %s syntax. Use Topic#anchor";
public static final String MSGPAT_INVALID_ENUM_SYNTAX =
"%s: Invalid %s syntax. Use NAME:type Choice1 [ChoiceN...]";
public static final String MSGPAT_INVALID_ENV_SYNTAX =
"%s: Invalid %s syntax. Use NAME:type=default \"Display\" \"Tool Tip\"";
public static final String MSGPAT_DUPLICATE_TAG = "%s: Duplicate %s";
public static final String MSGPAT_INVALID_ARG_SYNTAX =
"%s: Invalid %s syntax. Use :type \"Display\" \"Tool Tip\"";
public static final String MSGPAT_INVALID_ARGS_SYNTAX =
"%s: Invalid %s syntax. Use \"Display\" \"Tool Tip\"";
public static final String MSGPAT_INVALID_TTY_SYNTAX =
"%s: Invalid %s syntax. Use TTY_TARGET [if env:OPT [== VAL]]";
public static final String MSGPAT_INVALID_ENUM_SYNTAX =
"%s: Invalid %s syntax. Use NAME:type Choice1 [ChoiceN...]";
public static final String MSGPAT_INVALID_ENV_SYNTAX =
"%s: Invalid %s syntax. Use NAME:type=default \"Display\" \"Tool Tip\"";
public static final String MSGPAT_INVALID_HELP_SYNTAX =
"%s: Invalid %s syntax. Use Topic#anchor";
public static final String MSGPAT_INVALID_TIMEOUT_SYNTAX = "" +
"%s: Invalid %s syntax. Use [milliseconds]";
public static final String MSGPAT_INVALID_TTY_BAD_VAL =
"%s: In %s: Parameter '%s' has type %s, but '%s' cannot be parsed as such";
public static final String MSGPAT_INVALID_TTY_NO_PARAM =
"%s: In %s: No such parameter '%s'";
public static final String MSGPAT_INVALID_TTY_NOT_BOOL =
"%s: In %s: Parameter '%s' must have bool type";
public static final String MSGPAT_INVALID_TTY_BAD_VAL =
"%s: In %s: Parameter '%s' has type %s, but '%s' cannot be parsed as such";
public static final String MSGPAT_INVALID_TIMEOUT_SYNTAX = "" +
"%s: Invalid %s syntax. Use [milliseconds]";
public static final String MSGPAT_INVALID_TTY_SYNTAX =
"%s: Invalid %s syntax. Use TTY_TARGET [if env:OPT [== VAL]]";
public static class ParseException extends Exception {
private Location loc;
@ -289,8 +290,9 @@ public abstract class ScriptAttributesParser {
return tac.withCastDefault(new ValStr<>(value, defaultString));
}
public LaunchParameter<T> createParameter(String name, String display, String description) {
return type.createParameter(name, display, description, false, defaultValue);
public LaunchParameter<T> createParameter(String name, String display, String description,
boolean required) {
return type.createParameter(name, display, description, required, defaultValue);
}
}
@ -340,7 +342,7 @@ public abstract class ScriptAttributesParser {
public record ScriptAttributes(String title, String description, List<String> menuPath,
String menuGroup, String menuOrder, Icon icon, HelpLocation helpLocation,
Map<String, LaunchParameter<?>> parameters, Map<String, TtyCondition> extraTtys,
int timeoutMillis, boolean noImage) {}
int timeoutMillis, LaunchParameter<?> imageOpt) {}
/**
* Convert an arguments map into a command line and environment variables
@ -371,7 +373,7 @@ public abstract class ScriptAttributesParser {
LaunchParameter<?> param;
for (int i = 1; (param = parameters.get("arg:" + i)) != null; i++) {
// Don't use ValStr.str here. I'd like the script's input normalized
commandLine.add(Objects.toString(param.get(args).val()));
commandLine.add(param.get(args).normStr());
}
param = parameters.get("args");
@ -384,29 +386,24 @@ public abstract class ScriptAttributesParser {
if (key.startsWith(PREFIX_ENV)) {
String varName = key.substring(PREFIX_ENV.length());
ValStr<?> val = ent.getValue().get(args);
if (val == null || val.val() == null) {
env.put(varName, "");
}
else {
env.put(varName, Objects.toString(val.val()));
}
env.put(varName, ValStr.normStr(val));
}
}
}
private int argc = 0;
private int argc;
private String title;
private StringBuilder description;
private List<String> menuPath;
private String menuGroup;
private String menuOrder;
private String iconId;
private HelpLocation helpLocation;
private String menuGroup;
private String menuOrder;
private List<String> menuPath;
private final Map<String, UserType<?>> userTypes = new HashMap<>();
private final Map<String, LaunchParameter<?>> parameters = new LinkedHashMap<>();
private final Map<String, TtyCondition> extraTtys = new LinkedHashMap<>();
private int timeoutMillis = AbstractTraceRmiLaunchOffer.DEFAULT_TIMEOUT_MILLIS;
private boolean noImage = false;
private String imageOptKey;
/**
* Check if a line should just be ignored, e.g., blank lines, or the "shebang" line on UNIX.
@ -489,36 +486,66 @@ public abstract class ScriptAttributesParser {
return;
}
if (parts.length == 1) {
switch (parts[0].trim()) {
case AT_NOIMAGE -> parseNoImage(loc);
default -> parseUnrecognized(loc, comment);
}
parseUnrecognized(loc, comment);
}
else {
switch (parts[0].trim()) {
case AT_TITLE -> parseTitle(loc, parts[1]);
case AT_DESC -> parseDesc(loc, parts[1]);
case AT_MENU_PATH -> parseMenuPath(loc, parts[1]);
case AT_MENU_GROUP -> parseMenuGroup(loc, parts[1]);
case AT_MENU_ORDER -> parseMenuOrder(loc, parts[1]);
case AT_ICON -> parseIcon(loc, parts[1]);
case AT_HELP -> parseHelp(loc, parts[1]);
case AT_ENUM -> parseEnum(loc, parts[1]);
case AT_ENV -> parseEnv(loc, parts[1]);
case AT_ARG -> parseArg(loc, parts[1], ++argc);
case AT_ARGS -> parseArgs(loc, parts[1]);
case AT_TTY -> parseTty(loc, parts[1]);
case AT_DESC -> parseDesc(loc, parts[1]);
case AT_ENUM -> parseEnum(loc, parts[1]);
case AT_ENV -> parseEnv(loc, parts[1]);
case AT_HELP -> parseHelp(loc, parts[1]);
case AT_ICON -> parseIcon(loc, parts[1]);
case AT_IMAGE_OPT -> parseImageOpt(loc, parts[1]);
case AT_MENU_GROUP -> parseMenuGroup(loc, parts[1]);
case AT_MENU_ORDER -> parseMenuOrder(loc, parts[1]);
case AT_MENU_PATH -> parseMenuPath(loc, parts[1]);
case AT_TIMEOUT -> parseTimeout(loc, parts[1]);
case AT_TITLE -> parseTitle(loc, parts[1]);
case AT_TTY -> parseTty(loc, parts[1]);
default -> parseUnrecognized(loc, comment);
}
}
}
protected void parseTitle(Location loc, String str) {
if (title != null) {
reportWarning("%s: Duplicate %s".formatted(loc, AT_TITLE));
protected void parseArg(Location loc, String str, int argNum) {
List<String> parts = ShellUtils.parseArgs(str);
if (parts.size() != 3) {
reportError(MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG));
return;
}
String colonType = parts.get(0).trim();
if (!colonType.startsWith(":")) {
reportError(MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG));
return;
}
OptType<?> type;
boolean required = colonType.endsWith("!");
int endType = required ? colonType.length() - 1 : colonType.length();
try {
type = OptType.parse(loc, colonType.substring(1, endType), userTypes);
String name = PREFIX_ARG + argNum;
parameters.put(name, type.createParameter(name, parts.get(1), parts.get(2), required,
new ValStr<>(null, "")));
}
catch (ParseException e) {
reportError(e.getMessage());
}
}
protected void parseArgs(Location loc, String str) {
List<String> parts = ShellUtils.parseArgs(str);
if (parts.size() != 2) {
reportError(MSGPAT_INVALID_ARGS_SYNTAX.formatted(loc, AT_ARGS));
return;
}
LaunchParameter<String> parameter = BaseType.STRING.createParameter(
"args", parts.get(0), parts.get(1), false, ValStr.str(""));
if (parameters.put(KEY_ARGS, parameter) != null) {
reportWarning("%s: Duplicate %s. Replaced".formatted(loc, AT_ARGS));
}
title = str;
}
protected void parseDesc(Location loc, String str) {
@ -529,54 +556,6 @@ public abstract class ScriptAttributesParser {
description.append("\n");
}
protected void parseMenuPath(Location loc, String str) {
if (menuPath != null) {
reportWarning("%s: Duplicate %s".formatted(loc, AT_MENU_PATH));
}
menuPath = List.of(str.trim().split("\\."));
if (menuPath.isEmpty()) {
reportError(
"%s: Empty %s. Ignoring.".formatted(loc, AT_MENU_PATH));
}
}
protected void parseMenuGroup(Location loc, String str) {
if (menuGroup != null) {
reportWarning("%s: Duplicate %s".formatted(loc, AT_MENU_GROUP));
}
menuGroup = str;
}
protected void parseMenuOrder(Location loc, String str) {
if (menuOrder != null) {
reportWarning("%s: Duplicate %s".formatted(loc, AT_MENU_ORDER));
}
menuOrder = str;
}
protected void parseIcon(Location loc, String str) {
if (iconId != null) {
reportWarning("%s: Duplicate %s".formatted(loc, AT_ICON));
}
iconId = str.trim();
if (!Gui.hasIcon(iconId)) {
reportError(
"%s: Icon id %s not registered in the theme".formatted(loc, iconId));
}
}
protected void parseHelp(Location loc, String str) {
if (helpLocation != null) {
reportWarning("%s: Duplicate %s".formatted(loc, AT_HELP));
}
String[] parts = str.trim().split("#", 2);
if (parts.length != 2) {
reportError(MSGPAT_INVALID_HELP_SYNTAX.formatted(loc, AT_HELP));
return;
}
helpLocation = new HelpLocation(parts[0].trim(), parts[1].trim());
}
protected <T> UserType<T> parseEnumChoices(Location loc, BaseType<T> baseType,
List<String> choiceParts) {
List<T> choices = new ArrayList<>();
@ -642,10 +621,14 @@ public abstract class ScriptAttributesParser {
reportError(MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV));
return;
}
String typePart = tadParts[0].trim();
boolean required = typePart.endsWith("!");
int endType = required ? typePart.length() - 1 : typePart.length();
try {
TypeAndDefault<?> tad =
TypeAndDefault.parse(loc, tadParts[0].trim(), tadParts[1].trim(), userTypes);
LaunchParameter<?> param = tad.createParameter(name, parts.get(1), parts.get(2));
TypeAndDefault<?> tad = TypeAndDefault.parse(loc, typePart.substring(0, endType),
tadParts[1].trim(), userTypes);
LaunchParameter<?> param =
tad.createParameter(name, parts.get(1), parts.get(2), required);
if (parameters.put(name, param) != null) {
reportWarning("%s: Duplicate %s %s. Replaced.".formatted(loc, AT_ENV, trimmed));
}
@ -655,42 +638,75 @@ public abstract class ScriptAttributesParser {
}
}
protected void parseArg(Location loc, String str, int argNum) {
List<String> parts = ShellUtils.parseArgs(str);
if (parts.size() != 3) {
reportError(MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG));
protected void parseHelp(Location loc, String str) {
if (helpLocation != null) {
reportWarning(MSGPAT_DUPLICATE_TAG.formatted(loc, AT_HELP));
}
String[] parts = str.trim().split("#", 2);
if (parts.length != 2) {
reportError(MSGPAT_INVALID_HELP_SYNTAX.formatted(loc, AT_HELP));
return;
}
String colonType = parts.get(0).trim();
if (!colonType.startsWith(":")) {
reportError(MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG));
return;
helpLocation = new HelpLocation(parts[0].trim(), parts[1].trim());
}
protected void parseIcon(Location loc, String str) {
if (iconId != null) {
reportWarning(MSGPAT_DUPLICATE_TAG.formatted(loc, AT_ICON));
}
OptType<?> type;
try {
type = OptType.parse(loc, colonType.substring(1), userTypes);
String name = PREFIX_ARG + argNum;
parameters.put(name,
type.createParameter(name, parts.get(1), parts.get(2), true,
new ValStr<>(null, "")));
}
catch (ParseException e) {
reportError(e.getMessage());
iconId = str.trim();
if (!Gui.hasIcon(iconId)) {
reportError(
"%s: Icon id %s not registered in the theme".formatted(loc, iconId));
}
}
protected void parseArgs(Location loc, String str) {
List<String> parts = ShellUtils.parseArgs(str);
if (parts.size() != 2) {
reportError(MSGPAT_INVALID_ARGS_SYNTAX.formatted(loc, AT_ARGS));
return;
protected void parseImageOpt(Location loc, String str) {
if (imageOptKey != null) {
reportWarning(MSGPAT_DUPLICATE_TAG.formatted(loc, AT_IMAGE_OPT));
}
imageOptKey = str.strip();
}
LaunchParameter<String> parameter = BaseType.STRING.createParameter(
"args", parts.get(0), parts.get(1), false, ValStr.str(""));
if (parameters.put(KEY_ARGS, parameter) != null) {
reportWarning("%s: Duplicate %s. Replaced".formatted(loc, AT_ARGS));
protected void parseMenuGroup(Location loc, String str) {
if (menuGroup != null) {
reportWarning(MSGPAT_DUPLICATE_TAG.formatted(loc, AT_MENU_GROUP));
}
menuGroup = str;
}
protected void parseMenuOrder(Location loc, String str) {
if (menuOrder != null) {
reportWarning(MSGPAT_DUPLICATE_TAG.formatted(loc, AT_MENU_ORDER));
}
menuOrder = str;
}
protected void parseMenuPath(Location loc, String str) {
if (menuPath != null) {
reportWarning(MSGPAT_DUPLICATE_TAG.formatted(loc, AT_MENU_PATH));
}
menuPath = List.of(str.trim().split("\\."));
if (menuPath.isEmpty()) {
reportError(
"%s: Empty %s. Ignoring.".formatted(loc, AT_MENU_PATH));
}
}
protected void parseTimeout(Location loc, String str) {
try {
timeoutMillis = Integer.parseInt(str);
}
catch (NumberFormatException e) {
reportError(MSGPAT_INVALID_TIMEOUT_SYNTAX.formatted(loc, AT_TIMEOUT));
}
}
protected void parseTitle(Location loc, String str) {
if (title != null) {
reportWarning(MSGPAT_DUPLICATE_TAG.formatted(loc, AT_TITLE));
}
title = str;
}
protected void putTty(Location loc, String name, TtyCondition condition) {
@ -749,19 +765,6 @@ public abstract class ScriptAttributesParser {
reportError(MSGPAT_INVALID_TTY_SYNTAX.formatted(loc, AT_TTY));
}
protected void parseTimeout(Location loc, String str) {
try {
timeoutMillis = Integer.parseInt(str);
}
catch (NumberFormatException e) {
reportError(MSGPAT_INVALID_TIMEOUT_SYNTAX.formatted(loc, AT_TIMEOUT));
}
}
protected void parseNoImage(Location loc) {
noImage = true;
}
protected void parseUnrecognized(Location loc, String line) {
reportWarning("%s: Unrecognized metadata: %s".formatted(loc, line));
}
@ -784,10 +787,18 @@ public abstract class ScriptAttributesParser {
if (iconId == null) {
iconId = "icon.debugger";
}
LaunchParameter<?> imageOpt = null;
if (imageOptKey != null) {
imageOpt = parameters.get(imageOptKey);
if (imageOpt == null) {
reportError("%s: %s refers to %s, which is not a parameter name".formatted(fileName,
AT_IMAGE_OPT, imageOptKey));
}
}
return new ScriptAttributes(title, getDescription(), List.copyOf(menuPath), menuGroup,
menuOrder, new GIcon(iconId), helpLocation,
Collections.unmodifiableMap(new LinkedHashMap<>(parameters)),
Collections.unmodifiableMap(new LinkedHashMap<>(extraTtys)), timeoutMillis, noImage);
Collections.unmodifiableMap(new LinkedHashMap<>(extraTtys)), timeoutMillis, imageOpt);
}
private String getDescription() {