Merge remote-tracking branch 'origin/GP-4847_Dan_intPrefixInDbgLaunch--SQUASHED'

This commit is contained in:
Ryan Kurtz
2024-09-05 12:40:11 -04:00
36 changed files with 2842 additions and 1509 deletions
@@ -1,18 +1,18 @@
#!/usr/bin/env bash
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
#@title remote gdb
#@no-image
@@ -29,7 +29,7 @@
#@enum TargetType:str remote extended-remote
#@env OPT_TARGET_TYPE:TargetType="remote" "Target" "The type of remote target"
#@env OPT_HOST:str="localhost" "Host" "The hostname of the target"
#@env OPT_PORT:str="9999" "Port" "The host's listening port"
#@env OPT_PORT:int=9999 "Port" "The host's listening port"
#@env OPT_ARCH:str="" "Architecture (optional)" "Target architecture override"
#@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb on the local system. Omit the full path to resolve using the system PATH."
@@ -60,6 +60,7 @@ fi
-ex "show version" \
-ex "python import ghidragdb" \
$archcmd \
-ex "echo Connecting to $OPT_HOST:$OPT_PORT... " \
-ex "target $OPT_TARGET_TYPE $OPT_HOST:$OPT_PORT" \
-ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
-ex "ghidra trace start" \
@@ -0,0 +1,59 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.debug.api;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
public record ValStr<T>(T val, String str) {
public interface Decoder<T> {
default ValStr<T> decodeValStr(String string) {
return new ValStr<>(decode(string), string);
}
T decode(String string);
}
public static ValStr<String> str(String value) {
return new ValStr<>(value, value);
}
public static <T> ValStr<T> from(T value) {
return new ValStr<>(value, value == null ? "" : value.toString());
}
@SuppressWarnings("unchecked")
public static <T> ValStr<T> cast(Class<T> cls, ValStr<?> value) {
if (cls.isInstance(value.val)) {
return (ValStr<T>) value;
}
return new ValStr<>(cls.cast(value.val), value.str);
}
public static Map<String, ValStr<?>> fromPlainMap(Map<String, ?> map) {
return map.entrySet()
.stream()
.collect(Collectors.toMap(Entry::getKey, e -> ValStr.from(e.getValue())));
}
public static Map<String, ? super Object> toPlainMap(Map<String, ValStr<?>> map) {
return map.entrySet()
.stream()
.collect(Collectors.toMap(Entry::getKey, e -> e.getValue().val()));
}
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -24,6 +24,7 @@ import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetLauncher;
import ghidra.dbg.target.TargetObject;
import ghidra.debug.api.ValStr;
import ghidra.util.task.TaskMonitor;
/**
@@ -117,8 +118,8 @@ public interface DebuggerProgramLaunchOffer {
* @param relPrompt describes the timing of this callback relative to prompting the user
* @return the adjusted arguments
*/
default Map<String, ?> configureLauncher(TargetLauncher launcher,
Map<String, ?> arguments, RelPrompt relPrompt) {
default Map<String, ValStr<?>> configureLauncher(TargetLauncher launcher,
Map<String, ValStr<?>> arguments, RelPrompt relPrompt) {
return arguments;
}
}
@@ -0,0 +1,106 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.debug.api.tracermi;
import java.util.*;
import ghidra.debug.api.ValStr;
public record LaunchParameter<T>(Class<T> type, String name, String display, String description,
boolean required, List<T> choices, ValStr<T> defaultValue, ValStr.Decoder<T> decoder) {
public static <T> LaunchParameter<T> create(Class<T> type, String name, String display,
String description, boolean required, ValStr<T> defaultValue,
ValStr.Decoder<T> decoder) {
return new LaunchParameter<>(type, name, display, description, required, List.of(),
defaultValue, decoder);
}
public static <T> LaunchParameter<T> choices(Class<T> type, String name, String display,
String description, Collection<T> choices, ValStr<T> defaultValue) {
return new LaunchParameter<>(type, name, display, description, false,
List.copyOf(new LinkedHashSet<>(choices)), defaultValue, str -> {
for (T t : choices) {
if (t.toString().equals(str)) {
return t;
}
}
return null;
});
}
public static Map<String, LaunchParameter<?>> mapOf(Collection<LaunchParameter<?>> parameters) {
Map<String, LaunchParameter<?>> result = new LinkedHashMap<>();
for (LaunchParameter<?> param : parameters) {
LaunchParameter<?> exists = result.put(param.name(), param);
if (exists != null) {
throw new IllegalArgumentException(
"Duplicate names in parameter map: first=%s, second=%s".formatted(exists,
param));
}
}
return Collections.unmodifiableMap(result);
}
public static Map<String, ValStr<?>> validateArguments(
Map<String, LaunchParameter<?>> parameters, Map<String, ValStr<?>> arguments) {
if (!parameters.keySet().containsAll(arguments.keySet())) {
Set<String> extraneous = new TreeSet<>(arguments.keySet());
extraneous.removeAll(parameters.keySet());
throw new IllegalArgumentException("Extraneous parameters: " + extraneous);
}
Map<String, String> typeErrors = null;
for (Map.Entry<String, ValStr<?>> ent : arguments.entrySet()) {
String name = ent.getKey();
ValStr<?> val = ent.getValue();
LaunchParameter<?> param = parameters.get(name);
if (val.val() != null && !param.type.isAssignableFrom(val.val().getClass())) {
if (typeErrors == null) {
typeErrors = new LinkedHashMap<>();
}
typeErrors.put(name, "val '%s' is not a %s".formatted(val.val(), param.type()));
}
}
if (typeErrors != null) {
throw new IllegalArgumentException("Type errors: " + typeErrors);
}
return arguments;
}
public static Map<String, LaunchParameter<?>> mapOf(LaunchParameter<?>... parameters) {
return mapOf(Arrays.asList(parameters));
}
public ValStr<T> decode(String string) {
return decoder.decodeValStr(string);
}
public ValStr<T> get(Map<String, ValStr<?>> arguments) {
if (arguments.containsKey(name)) {
return ValStr.cast(type, arguments.get(name));
}
if (required) {
throw new IllegalArgumentException(
"Missing required parameter '%s' (%s)".formatted(display, name));
}
return defaultValue;
}
public void set(Map<String, ValStr<?>> arguments, ValStr<T> value) {
arguments.put(name, value);
}
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -20,7 +20,7 @@ import java.util.Map;
import javax.swing.Icon;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.debug.api.ValStr;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.util.HelpLocation;
@@ -150,8 +150,8 @@ public interface TraceRmiLaunchOffer {
* @param relPrompt describes the timing of this callback relative to prompting the user
* @return the adjusted arguments
*/
default Map<String, ?> configureLauncher(TraceRmiLaunchOffer offer,
Map<String, ?> arguments, RelPrompt relPrompt) {
default Map<String, ValStr<?>> configureLauncher(TraceRmiLaunchOffer offer,
Map<String, ValStr<?>> arguments, RelPrompt relPrompt) {
return arguments;
}
}
@@ -293,7 +293,7 @@ public interface TraceRmiLaunchOffer {
*
* @return the parameters
*/
Map<String, ParameterDescription<?>> getParameters();
Map<String, LaunchParameter<?>> getParameters();
/**
* Check if this offer requires an open program
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -28,6 +28,7 @@ import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
import ghidra.dbg.util.PathUtils;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.model.DebuggerProgramLaunchOffer;
import ghidra.debug.api.model.DebuggerProgramLaunchOffer.*;
import ghidra.debug.api.model.TraceRecorder;
@@ -578,10 +579,10 @@ public interface FlatDebuggerRecorderAPI extends FlatDebuggerAPI {
try {
return waitOn(offer.launchProgram(monitor, PromptMode.NEVER, new LaunchConfigurator() {
@Override
public Map<String, ?> configureLauncher(TargetLauncher launcher,
Map<String, ?> arguments, RelPrompt relPrompt) {
Map<String, Object> adjusted = new HashMap<>(arguments);
adjusted.put(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, commandLine);
public Map<String, ValStr<?>> configureLauncher(TargetLauncher launcher,
Map<String, ValStr<?>> arguments, RelPrompt relPrompt) {
Map<String, ValStr<?>> adjusted = new HashMap<>(arguments);
adjusted.put(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, ValStr.str(commandLine));
return adjusted;
}
}));
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,8 +16,10 @@
package ghidra.debug.flatapi;
import java.util.*;
import java.util.Map.Entry;
import ghidra.app.services.TraceRmiLauncherService;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.*;
import ghidra.program.model.listing.Program;
@@ -116,13 +118,15 @@ public interface FlatDebuggerRmiAPI extends FlatDebuggerAPI {
TaskMonitor monitor) {
return offer.launchProgram(monitor, new LaunchConfigurator() {
@Override
public Map<String, ?> configureLauncher(TraceRmiLaunchOffer offer,
Map<String, ?> arguments, RelPrompt relPrompt) {
public Map<String, ValStr<?>> configureLauncher(TraceRmiLaunchOffer offer,
Map<String, ValStr<?>> arguments, RelPrompt relPrompt) {
if (arguments.isEmpty()) {
return arguments;
}
Map<String, Object> args = new HashMap<>(arguments);
args.putAll(overrideArgs);
Map<String, ValStr<?>> args = new HashMap<>(arguments);
for (Entry<String, ?> ent : overrideArgs.entrySet()) {
args.put(ent.getKey(), ValStr.from(ent.getValue()));
}
return args;
}
});
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,397 +15,128 @@
*/
package ghidra.app.plugin.core.debug.gui.tracermi;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.beans.*;
import java.util.*;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.EmptyBorder;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.jdom.Element;
import docking.DialogComponentProvider;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiTarget;
import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils;
import ghidra.app.plugin.core.debug.gui.AbstractDebuggerParameterDialog;
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiTarget.Missing;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.SchemaContext;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.tracermi.RemoteParameter;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.AutoConfigState.ConfigStateField;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.Msg;
import ghidra.util.layout.PairLayout;
import ghidra.trace.model.target.TraceObject;
public class RemoteMethodInvocationDialog extends DialogComponentProvider
implements PropertyChangeListener {
private static final String KEY_MEMORIZED_ARGUMENTS = "memorizedArguments";
public class RemoteMethodInvocationDialog extends AbstractDebuggerParameterDialog<RemoteParameter> {
static class ChoicesPropertyEditor implements PropertyEditor {
private final List<?> choices;
private final String[] tags;
private final List<PropertyChangeListener> listeners = new ArrayList<>();
private Object value;
public ChoicesPropertyEditor(Set<?> choices) {
this.choices = List.copyOf(choices);
this.tags = choices.stream().map(Objects::toString).toArray(String[]::new);
}
/**
* TODO: Make this a proper editor which can browse and select objects of a required schema.
*/
public static class TraceObjectEditor extends PropertyEditorSupport {
private final JLabel unmodifiableField = new JLabel();
@Override
public void setValue(Object value) {
if (Objects.equals(value, this.value)) {
super.setValue(value);
if (value == null) {
unmodifiableField.setText("");
return;
}
if (!choices.contains(value)) {
throw new IllegalArgumentException("Unsupported value: " + value);
if (!(value instanceof TraceObject obj)) {
throw new IllegalArgumentException();
}
Object oldValue;
List<PropertyChangeListener> listeners;
synchronized (this.listeners) {
oldValue = this.value;
this.value = value;
if (this.listeners.isEmpty()) {
return;
}
listeners = List.copyOf(this.listeners);
}
PropertyChangeEvent evt = new PropertyChangeEvent(this, null, oldValue, value);
for (PropertyChangeListener l : listeners) {
l.propertyChange(evt);
}
}
@Override
public Object getValue() {
return value;
}
@Override
public boolean isPaintable() {
return false;
}
@Override
public void paintValue(Graphics gfx, Rectangle box) {
// Not paintable
}
@Override
public String getJavaInitializationString() {
if (value == null) {
return "null";
}
if (value instanceof String str) {
return "\"" + StringEscapeUtils.escapeJava(str) + "\"";
}
return Objects.toString(value);
}
@Override
public String getAsText() {
return Objects.toString(value);
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
int index = ArrayUtils.indexOf(tags, text);
if (index < 0) {
throw new IllegalArgumentException("Unsupported value: " + text);
}
setValue(choices.get(index));
}
@Override
public String[] getTags() {
return tags.clone();
}
@Override
public Component getCustomEditor() {
return null;
unmodifiableField.setText(obj.getCanonicalPath().toString());
}
@Override
public boolean supportsCustomEditor() {
return false;
return true;
}
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
synchronized (listeners) {
listeners.add(listener);
}
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
public Component getCustomEditor() {
return unmodifiableField;
}
}
record NameTypePair(String name, Class<?> type) {
public static NameTypePair fromParameter(SchemaContext ctx, RemoteParameter parameter) {
return new NameTypePair(parameter.name(), ctx.getSchema(parameter.type()).getType());
}
public static NameTypePair fromString(String name) throws ClassNotFoundException {
String[] parts = name.split(",", 2);
if (parts.length != 2) {
// This appears to be a bad assumption - empty fields results in solitary labels
return new NameTypePair(parts[0], String.class);
//throw new IllegalArgumentException("Could not parse name,type");
}
return new NameTypePair(parts[0], Class.forName(parts[1]));
}
static {
PropertyEditorManager.registerEditor(TraceObject.class, TraceObjectEditor.class);
}
private final BidiMap<RemoteParameter, PropertyEditor> paramEditors =
new DualLinkedHashBidiMap<>();
private final SchemaContext ctx;
private JPanel panel;
private JLabel descriptionLabel;
private JPanel pairPanel;
private PairLayout layout;
protected JButton invokeButton;
protected JButton resetButton;
private final PluginTool tool;
private SchemaContext ctx;
private Map<String, RemoteParameter> parameters;
private Map<String, Object> defaults;
// TODO: Not sure this is the best keying, but I think it works.
private Map<NameTypePair, Object> memorized = new HashMap<>();
private Map<String, Object> arguments;
public RemoteMethodInvocationDialog(PluginTool tool, String title, String buttonText,
Icon buttonIcon) {
super(title, true, true, true, false);
this.tool = tool;
populateComponents(buttonText, buttonIcon);
setRememberSize(false);
}
protected Object computeMemorizedValue(RemoteParameter parameter) {
return memorized.computeIfAbsent(NameTypePair.fromParameter(ctx, parameter),
ntp -> parameter.getDefaultValue());
}
public Map<String, Object> promptArguments(SchemaContext ctx,
Map<String, RemoteParameter> parameterMap, Map<String, Object> defaults) {
setParameters(ctx, parameterMap);
setDefaults(defaults);
tool.showDialog(this);
return getArguments();
}
public void setParameters(SchemaContext ctx, Map<String, RemoteParameter> parameterMap) {
public RemoteMethodInvocationDialog(PluginTool tool, SchemaContext ctx, String title,
String buttonText, Icon buttonIcon) {
super(tool, title, buttonText, buttonIcon);
this.ctx = ctx;
this.parameters = parameterMap;
populateOptions();
}
public void setDefaults(Map<String, Object> defaults) {
this.defaults = defaults;
}
private void populateComponents(String buttonText, Icon buttonIcon) {
panel = new JPanel(new BorderLayout());
panel.setBorder(new EmptyBorder(10, 10, 10, 10));
layout = new PairLayout(5, 5);
pairPanel = new JPanel(layout);
JPanel centering = new JPanel(new FlowLayout(FlowLayout.CENTER));
JScrollPane scrolling = new JScrollPane(centering, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
//scrolling.setPreferredSize(new Dimension(100, 130));
panel.add(scrolling, BorderLayout.CENTER);
centering.add(pairPanel);
descriptionLabel = new JLabel();
descriptionLabel.setMaximumSize(new Dimension(300, 100));
panel.add(descriptionLabel, BorderLayout.NORTH);
addWorkPanel(panel);
invokeButton = new JButton(buttonText, buttonIcon);
addButton(invokeButton);
resetButton = new JButton("Reset", DebuggerResources.ICON_REFRESH);
addButton(resetButton);
addCancelButton();
invokeButton.addActionListener(this::invoke);
resetButton.addActionListener(this::reset);
}
@Override
protected void cancelCallback() {
this.arguments = null;
close();
protected String parameterName(RemoteParameter parameter) {
return parameter.name();
}
protected void invoke(ActionEvent evt) {
this.arguments = collectArguments();
close();
}
private void reset(ActionEvent evt) {
this.arguments = new HashMap<>();
for (RemoteParameter param : parameters.values()) {
if (defaults.containsKey(param.name())) {
arguments.put(param.name(), defaults.get(param.name()));
}
else {
arguments.put(param.name(), param.getDefaultValue());
}
@Override
protected Class<?> parameterType(RemoteParameter parameter) {
Class<?> type = ctx.getSchema(parameter.type()).getType();
if (TargetObject.class.isAssignableFrom(type)) {
return TraceObject.class;
}
populateValues();
return type;
}
protected PropertyEditor createEditor(RemoteParameter param) {
Class<?> type = ctx.getSchema(param.type()).getType();
PropertyEditor editor = PropertyEditorManager.findEditor(type);
if (editor != null) {
return editor;
}
Msg.warn(this, "No editor for " + type + "? Trying String instead");
return PropertyEditorManager.findEditor(String.class);
@Override
protected String parameterLabel(RemoteParameter parameter) {
return "".equals(parameter.display()) ? parameter.name() : parameter.display();
}
void populateOptions() {
pairPanel.removeAll();
paramEditors.clear();
for (RemoteParameter param : parameters.values()) {
String text = param.display().equals("") ? param.name() : param.display();
JLabel label = new JLabel(text);
label.setToolTipText(param.description());
pairPanel.add(label);
PropertyEditor editor = createEditor(param);
Object val = computeMemorizedValue(param);
if (val == null || val.equals(TraceRmiTarget.Missing.MISSING)) {
editor.setValue("");
} else {
editor.setValue(val);
}
editor.addPropertyChangeListener(this);
pairPanel.add(MiscellaneousUtils.getEditorComponent(editor));
paramEditors.put(param, editor);
}
@Override
protected String parameterToolTip(RemoteParameter parameter) {
return parameter.description();
}
void populateValues() {
for (Map.Entry<String, Object> ent : arguments.entrySet()) {
RemoteParameter param = parameters.get(ent.getKey());
if (param == null) {
Msg.warn(this, "No parameter for argument: " + ent);
continue;
}
PropertyEditor editor = paramEditors.get(param);
editor.setValue(ent.getValue());
}
@Override
protected ValStr<?> parameterDefault(RemoteParameter parameter) {
return ValStr.from(parameter.getDefaultValue());
}
protected Map<String, Object> collectArguments() {
Map<String, Object> map = new LinkedHashMap<>();
for (RemoteParameter param : paramEditors.keySet()) {
Object val = memorized.get(NameTypePair.fromParameter(ctx, param));
if (val != null) {
map.put(param.name(), val);
}
}
return map;
@Override
protected Collection<?> parameterChoices(RemoteParameter parameter) {
return Set.of();
}
public Map<String, Object> getArguments() {
@Override
protected Map<String, ValStr<?>> validateArguments(Map<String, RemoteParameter> parameters,
Map<String, ValStr<?>> arguments) {
return arguments;
}
public <T> void setMemorizedArgument(String name, Class<T> type, T value) {
if (value == null) {
return;
}
memorized.put(new NameTypePair(name, type), value);
}
public <T> T getMemorizedArgument(String name, Class<T> type) {
return type.cast(memorized.get(new NameTypePair(name, type)));
@Override
protected void parameterSaveValue(RemoteParameter parameter, SaveState state, String key,
ValStr<?> value) {
ConfigStateField.putState(state, parameterType(parameter).asSubclass(Object.class), key,
value.val());
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
PropertyEditor editor = (PropertyEditor) evt.getSource();
RemoteParameter param = paramEditors.getKey(editor);
memorized.put(NameTypePair.fromParameter(ctx, param), editor.getValue());
protected ValStr<?> parameterLoadValue(RemoteParameter parameter, SaveState state, String key) {
return ValStr.from(
ConfigStateField.getState(state, parameterType(parameter), key));
}
public void writeConfigState(SaveState saveState) {
SaveState subState = new SaveState();
for (Map.Entry<NameTypePair, Object> ent : memorized.entrySet()) {
NameTypePair ntp = ent.getKey();
ConfigStateField.putState(subState, ntp.type().asSubclass(Object.class), ntp.name(),
ent.getValue());
}
saveState.putXmlElement(KEY_MEMORIZED_ARGUMENTS, subState.saveToXml());
}
public void readConfigState(SaveState saveState) {
Element element = saveState.getXmlElement(KEY_MEMORIZED_ARGUMENTS);
if (element == null) {
return;
}
SaveState subState = new SaveState(element);
for (String name : subState.getNames()) {
try {
NameTypePair ntp = NameTypePair.fromString(name);
memorized.put(ntp, ConfigStateField.getState(subState, ntp.type(), ntp.name()));
}
catch (Exception e) {
Msg.error(this, "Error restoring memorized parameter " + name, e);
}
}
}
public void setDescription(String htmlDescription) {
if (htmlDescription == null) {
descriptionLabel.setBorder(BorderFactory.createEmptyBorder());
descriptionLabel.setText("");
}
else {
descriptionLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
descriptionLabel.setText(htmlDescription);
}
@Override
protected void setEditorValue(PropertyEditor editor, RemoteParameter param, ValStr<?> val) {
ValStr<?> v = switch (val.val()) {
case Missing __ -> new ValStr<>(null, "");
case TraceObject obj -> new ValStr<>(obj, obj.getCanonicalPath().toString());
default -> val;
};
super.setEditorValue(editor, param, v);
}
}
@@ -0,0 +1,46 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.tracermi.connection;
import java.util.Map;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.TraceRmiLaunchDialog;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.tracermi.LaunchParameter;
import ghidra.framework.plugintool.PluginTool;
public class TraceRmiConnectDialog extends TraceRmiLaunchDialog {
static final LaunchParameter<String> PARAM_ADDRESS =
LaunchParameter.create(String.class, "address",
"Host/Address", "Address or hostname for interface(s) to listen on",
true, ValStr.str("localhost"), str -> str);
static final LaunchParameter<Integer> PARAM_PORT =
LaunchParameter.create(Integer.class, "port",
"Port", "TCP port number, 0 for ephemeral",
true, ValStr.from(0), Integer::decode);
private static final Map<String, LaunchParameter<?>> PARAMETERS =
LaunchParameter.mapOf(PARAM_ADDRESS, PARAM_PORT);
public TraceRmiConnectDialog(PluginTool tool, String title, String buttonText) {
super(tool, title, buttonText, DebuggerResources.ICON_CONNECTION);
}
public Map<String, ValStr<?>> promptArguments() {
return promptArguments(PARAMETERS, Map.of(), Map.of());
}
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -34,11 +34,9 @@ import docking.action.builder.ActionBuilder;
import docking.widgets.tree.*;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
import ghidra.app.plugin.core.debug.gui.tracermi.connection.tree.*;
import ghidra.app.services.*;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.control.ControlMode;
import ghidra.debug.api.target.Target;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
@@ -62,16 +60,6 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter
private static final String GROUP_CONNECT = "1. Connect";
private static final String GROUP_MAINTENANCE = "3. Maintenance";
private static final ParameterDescription<String> PARAM_ADDRESS =
ParameterDescription.create(String.class, "address", true, "localhost",
"Host/Address", "Address or hostname for interface(s) to listen on");
private static final ParameterDescription<Integer> PARAM_PORT =
ParameterDescription.create(Integer.class, "port", true, 0,
"Port", "TCP port number, 0 for ephemeral");
private static final TargetParameterMap PARAMETERS = TargetParameterMap.ofEntries(
Map.entry(PARAM_ADDRESS.name, PARAM_ADDRESS),
Map.entry(PARAM_PORT.name, PARAM_PORT));
interface StartServerAction {
String NAME = "Start Server";
String DESCRIPTION = "Start a TCP server for incoming connections (indefinitely)";
@@ -344,25 +332,24 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter
return traceRmiService != null && !traceRmiService.isServerStarted();
}
private InetSocketAddress promptSocketAddress(String title, String okText) {
DebuggerMethodInvocationDialog dialog = new DebuggerMethodInvocationDialog(tool,
title, okText, DebuggerResources.ICON_CONNECTION);
Map<String, ?> arguments;
do {
dialog.forgetMemorizedArguments();
arguments = dialog.promptArguments(PARAMETERS);
}
while (dialog.isResetRequested());
private InetSocketAddress promptSocketAddress(String title, String okText,
HelpLocation helpLocation) {
TraceRmiConnectDialog dialog = new TraceRmiConnectDialog(tool, title, okText);
dialog.setHelpLocation(helpLocation);
Map<String, ValStr<?>> arguments = dialog.promptArguments();
if (arguments == null) {
// Cancelled
return null;
}
String address = PARAM_ADDRESS.get(arguments);
int port = PARAM_PORT.get(arguments);
String address = TraceRmiConnectDialog.PARAM_ADDRESS.get(arguments).val();
int port = TraceRmiConnectDialog.PARAM_PORT.get(arguments).val();
return new InetSocketAddress(address, port);
}
private void doActionStartServerActivated(ActionContext __) {
InetSocketAddress sockaddr = promptSocketAddress("Start Trace RMI Server", "Start");
InetSocketAddress sockaddr = promptSocketAddress("Start Trace RMI Server", "Start",
actionStartServer.getHelpLocation());
if (sockaddr == null) {
return;
}
@@ -395,7 +382,8 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter
}
private void doActionConnectAcceptActivated(ActionContext __) {
InetSocketAddress sockaddr = promptSocketAddress("Accept Trace RMI Connection", "Listen");
InetSocketAddress sockaddr = promptSocketAddress("Accept Trace RMI Connection", "Listen",
actionConnectAccept.getHelpLocation());
if (sockaddr == null) {
return;
}
@@ -420,7 +408,8 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter
}
private void doActionConnectOutboundActivated(ActionContext __) {
InetSocketAddress sockaddr = promptSocketAddress("Connect to Trace RMI", "Connect");
InetSocketAddress sockaddr = promptSocketAddress("Connect to Trace RMI", "Connect",
actionConnectOutbound.getHelpLocation());
if (sockaddr == null) {
return;
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -23,7 +23,8 @@ import javax.swing.Icon;
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.ScriptAttributes;
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.TtyCondition;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.tracermi.LaunchParameter;
import ghidra.debug.api.tracermi.TerminalSession;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
@@ -84,7 +85,7 @@ public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmi
}
@Override
public Map<String, ParameterDescription<?>> getParameters() {
public Map<String, LaunchParameter<?>> getParameters() {
return attrs.parameters();
}
@@ -93,12 +94,15 @@ public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmi
return attrs.timeoutMillis();
}
protected abstract void prepareSubprocess(List<String> commandLine, Map<String, String> env,
Map<String, ?> args, SocketAddress address);
protected void prepareSubprocess(List<String> commandLine, Map<String, String> env,
Map<String, ValStr<?>> args, SocketAddress address) {
ScriptAttributesParser.processArguments(commandLine, env, script, attrs.parameters(), args,
address);
}
@Override
protected void launchBackEnd(TaskMonitor monitor, Map<String, TerminalSession> sessions,
Map<String, ?> args, SocketAddress address) throws Exception {
Map<String, ValStr<?>> args, SocketAddress address) throws Exception {
List<String> commandLine = new ArrayList<>();
Map<String, String> env = new HashMap<>(System.getenv());
prepareSubprocess(commandLine, env, args, address);
@@ -112,7 +116,7 @@ public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmi
}
NullPtyTerminalSession ns = nullPtyTerminal();
env.put(ent.getKey(), ns.name());
sessions.put(ns.name(), ns);
sessions.put(ent.getKey(), ns);
}
sessions.put("Shell",
@@ -28,7 +28,7 @@ import java.util.concurrent.*;
import javax.swing.Icon;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
import ghidra.app.plugin.core.debug.gui.action.ByModuleAutoMapSpec;
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.LaunchFailureDialog.ErrPromptResponse;
import ghidra.app.plugin.core.debug.service.tracermi.DefaultTraceRmiAcceptor;
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler;
@@ -36,8 +36,8 @@ import ghidra.app.plugin.core.terminal.TerminalListener;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.dbg.util.ShellUtils;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.action.AutoMapSpec;
import ghidra.debug.api.modules.DebuggerMissingProgramActionContext;
import ghidra.debug.api.modules.DebuggerStaticMappingChangeListener;
@@ -212,14 +212,13 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
return mappingService.getOpenMappedLocation(trace, probe, snap) != null;
}
protected SaveState saveLauncherArgsToState(Map<String, ?> args,
Map<String, ParameterDescription<?>> params) {
protected SaveState saveLauncherArgsToState(Map<String, ValStr<?>> args,
Map<String, LaunchParameter<?>> params) {
SaveState state = new SaveState();
for (ParameterDescription<?> param : params.values()) {
Object val = args.get(param.name);
for (LaunchParameter<?> param : params.values()) {
ValStr<?> val = args.get(param.name());
if (val != null) {
ConfigStateField.putState(state, param.type.asSubclass(Object.class),
"param_" + param.name, val);
state.putString("param_" + param.name(), val.str());
}
}
return state;
@@ -233,56 +232,56 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
plugin.writeProgramLaunchConfig(program, getConfigName(), state);
}
protected void saveLauncherArgs(Map<String, ?> args,
Map<String, ParameterDescription<?>> params) {
protected void saveLauncherArgs(Map<String, ValStr<?>> args,
Map<String, LaunchParameter<?>> params) {
saveState(saveLauncherArgsToState(args, params));
}
interface ImageParamSetter {
@SuppressWarnings("unchecked")
static ImageParamSetter get(ParameterDescription<?> param) {
if (param.type == String.class) {
return new StringImageParamSetter((ParameterDescription<String>) param);
static ImageParamSetter get(LaunchParameter<?> param) {
if (param.type() == String.class) {
return new StringImageParamSetter((LaunchParameter<String>) param);
}
if (param.type == PathIsFile.class) {
return new FileImageParamSetter((ParameterDescription<PathIsFile>) param);
if (param.type() == PathIsFile.class) {
return new FileImageParamSetter((LaunchParameter<PathIsFile>) param);
}
Msg.warn(ImageParamSetter.class,
"'Image' parameter has unsupported type: " + param.type);
"'Image' parameter has unsupported type: " + param.type());
return null;
}
void setImage(Map<String, Object> map, Program program);
void setImage(Map<String, ValStr<?>> map, Program program);
}
static class StringImageParamSetter implements ImageParamSetter {
private final ParameterDescription<String> param;
private final LaunchParameter<String> param;
public StringImageParamSetter(ParameterDescription<String> param) {
public StringImageParamSetter(LaunchParameter<String> param) {
this.param = param;
}
@Override
public void setImage(Map<String, Object> map, Program program) {
public void setImage(Map<String, ValStr<?>> map, Program program) {
// str-type Image is a hint that the launcher is remote
String value = TraceRmiLauncherServicePlugin.getProgramPath(program, false);
param.set(map, value);
param.set(map, ValStr.str(value));
}
}
static class FileImageParamSetter implements ImageParamSetter {
private final ParameterDescription<PathIsFile> param;
private final LaunchParameter<PathIsFile> param;
public FileImageParamSetter(ParameterDescription<PathIsFile> param) {
public FileImageParamSetter(LaunchParameter<PathIsFile> param) {
this.param = param;
}
@Override
public void setImage(Map<String, Object> map, Program program) {
public void setImage(Map<String, ValStr<?>> map, Program program) {
// file-type Image is a hint that the launcher is local
String str = TraceRmiLauncherServicePlugin.getProgramPath(program, true);
PathIsFile value = str == null ? null : new PathIsFile(Paths.get(str));
param.set(map, value);
param.set(map, new ValStr<>(value, str));
}
}
@@ -297,33 +296,34 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
* @return the default arguments
*/
@SuppressWarnings("unchecked")
protected Map<String, ?> generateDefaultLauncherArgs(
Map<String, ParameterDescription<?>> params) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
protected Map<String, ValStr<?>> generateDefaultLauncherArgs(
Map<String, LaunchParameter<?>> params) {
Map<String, ValStr<?>> map = new LinkedHashMap<>();
ImageParamSetter imageSetter = null;
for (Entry<String, ParameterDescription<?>> entry : params.entrySet()) {
ParameterDescription<?> param = entry.getValue();
map.put(entry.getKey(), param.defaultValue);
if (PARAM_DISPLAY_IMAGE.equals(param.display)) {
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)) {
String tool = param.name.substring(PREFIX_PARAM_EXTTOOL.length());
else if (param.name().startsWith(PREFIX_PARAM_EXTTOOL)) {
String tool = param.name().substring(PREFIX_PARAM_EXTTOOL.length());
List<String> names =
program.getLanguage().getLanguageDescription().getExternalNames(tool);
if (names != null && !names.isEmpty()) {
if (param.type == String.class) {
var paramStr = (ParameterDescription<String>) param;
paramStr.set(map, names.get(0));
String toolName = names.get(0);
if (param.type() == String.class) {
var paramStr = (LaunchParameter<String>) param;
paramStr.set(map, ValStr.str(toolName));
}
else if (param.type == PathIsFile.class) {
var paramPIF = (ParameterDescription<PathIsFile>) param;
paramPIF.set(map, PathIsFile.fromString(names.get(0)));
else if (param.type() == PathIsFile.class) {
var paramPIF = (LaunchParameter<PathIsFile>) param;
paramPIF.set(map, new ValStr<>(PathIsFile.fromString(toolName), toolName));
}
else if (param.type == PathIsDir.class) {
var paramPID = (ParameterDescription<PathIsDir>) param;
paramPID.set(map, PathIsDir.fromString(names.get(0)));
else if (param.type() == PathIsDir.class) {
var paramPID = (LaunchParameter<PathIsDir>) param;
paramPID.set(map, new ValStr<>(PathIsDir.fromString(toolName), toolName));
}
}
}
@@ -337,50 +337,33 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
/**
* Prompt the user for arguments, showing those last used or defaults
*
* @param lastExc
*
* @param params the parameters of the model's launcher
* @param configurator a thing to generate/modify the (default) arguments
* @param lastExc if re-prompting, an error to display
* @return the arguments given by the user, or null if cancelled
*/
protected Map<String, ?> promptLauncherArgs(LaunchConfigurator configurator,
protected Map<String, ValStr<?>> promptLauncherArgs(LaunchConfigurator configurator,
Throwable lastExc) {
Map<String, ParameterDescription<?>> params = getParameters();
DebuggerMethodInvocationDialog dialog =
new DebuggerMethodInvocationDialog(tool, getTitle(), "Launch", getIcon());
Map<String, LaunchParameter<?>> params = getParameters();
TraceRmiLaunchDialog dialog =
new TraceRmiLaunchDialog(tool, getTitle(), "Launch", getIcon());
dialog.setDescription(getDescription());
dialog.setHelpLocation(getHelpLocation());
if (lastExc != null) {
dialog.setStatusText(lastExc.toString(), MessageType.ERROR);
}
else {
dialog.setStatusText("");
}
// NB. Do not invoke read/writeConfigState
Map<String, ?> args;
boolean reset = false;
do {
args =
configurator.configureLauncher(this, loadLastLauncherArgs(true), RelPrompt.BEFORE);
for (ParameterDescription<?> param : params.values()) {
Object val = args.get(param.name);
if (val != null) {
dialog.setMemorizedArgument(param.name, param.type.asSubclass(Object.class),
val);
}
}
if (lastExc != null) {
dialog.setStatusText(lastExc.toString(), MessageType.ERROR);
}
else {
dialog.setStatusText("");
}
args = dialog.promptArguments(params);
if (args == null) {
// Cancelled
return null;
}
reset = dialog.isResetRequested();
if (reset) {
args = generateDefaultLauncherArgs(params);
}
Map<String, ValStr<?>> defaultArgs = generateDefaultLauncherArgs(params);
Map<String, ValStr<?>> lastArgs =
configurator.configureLauncher(this, loadLastLauncherArgs(true), RelPrompt.BEFORE);
Map<String, ValStr<?>> args = dialog.promptArguments(params, lastArgs, defaultArgs);
if (args != null) {
saveLauncherArgs(args, params);
}
while (reset);
return args;
}
@@ -398,31 +381,40 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
* @param forPrompt true if the user will be confirming the arguments
* @return the loaded arguments, or defaults
*/
protected Map<String, ?> loadLastLauncherArgs(boolean forPrompt) {
Map<String, ParameterDescription<?>> params = getParameters();
Map<String, ?> args = loadLauncherArgsFromState(loadState(forPrompt), params);
protected Map<String, ValStr<?>> loadLastLauncherArgs(boolean forPrompt) {
Map<String, LaunchParameter<?>> params = getParameters();
Map<String, ValStr<?>> args = loadLauncherArgsFromState(loadState(forPrompt), params);
saveLauncherArgs(args, params);
return args;
}
protected Map<String, ?> loadLauncherArgsFromState(SaveState state,
Map<String, ParameterDescription<?>> params) {
Map<String, ?> defaultArgs = generateDefaultLauncherArgs(params);
protected Map<String, ValStr<?>> loadLauncherArgsFromState(SaveState state,
Map<String, LaunchParameter<?>> params) {
Map<String, ValStr<?>> defaultArgs = generateDefaultLauncherArgs(params);
if (state == null) {
return defaultArgs;
}
List<String> names = List.of(state.getNames());
Map<String, Object> args = new LinkedHashMap<>();
for (ParameterDescription<?> param : params.values()) {
String key = "param_" + param.name;
Object configState =
names.contains(key) ? ConfigStateField.getState(state, param.type, key) : null;
if (configState != null) {
args.put(param.name, configState);
Map<String, ValStr<?>> args = new LinkedHashMap<>();
Set<String> names = Set.of(state.getNames());
for (LaunchParameter<?> param : params.values()) {
String key = "param_" + param.name();
if (!names.contains(key)) {
args.put(param.name(), defaultArgs.get(param.name()));
continue;
}
else {
args.put(param.name, defaultArgs.get(param.name));
String str = state.getString(key, null);
if (str != null) {
args.put(param.name(), param.decode(str));
continue;
}
// Perhaps wrong type; was saved in older version.
Object fallback = ConfigStateField.getState(state, param.type(), param.name());
if (fallback != null) {
args.put(param.name(), ValStr.from(fallback));
continue;
}
Msg.warn(this, "Could not load saved launcher arg '%s' (%s)".formatted(param.name(),
param.display()));
}
return args;
}
@@ -435,7 +427,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
}
/**
* Obtain the launcher args
* Obtain the launcher arguments
*
* <p>
* This should either call {@link #promptLauncherArgs(LaunchConfigurator, Throwable)} or
@@ -447,7 +439,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
* @param lastExc if retrying, the last exception to display as an error message
* @return the chosen arguments, or null if the user cancels at the prompt
*/
public Map<String, ?> getLauncherArgs(boolean prompt, LaunchConfigurator configurator,
public Map<String, ValStr<?>> getLauncherArgs(boolean prompt, LaunchConfigurator configurator,
Throwable lastExc) {
return prompt
? configurator.configureLauncher(this, promptLauncherArgs(configurator, lastExc),
@@ -543,8 +535,8 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
}
protected abstract void launchBackEnd(TaskMonitor monitor,
Map<String, TerminalSession> sessions, Map<String, ?> args, SocketAddress address)
throws Exception;
Map<String, TerminalSession> sessions, Map<String, ValStr<?>> args,
SocketAddress address) throws Exception;
static class NoStaticMappingException extends Exception {
public NoStaticMappingException(String message) {
@@ -557,9 +549,18 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
}
}
protected void initializeMonitor(TaskMonitor monitor) {
protected AutoMapSpec getAutoMapSpec() {
DebuggerAutoMappingService auto = tool.getService(DebuggerAutoMappingService.class);
AutoMapSpec spec = auto.getAutoMapSpec();
return auto == null ? ByModuleAutoMapSpec.instance() : auto.getAutoMapSpec();
}
protected AutoMapSpec getAutoMapSpec(Trace trace) {
DebuggerAutoMappingService auto = tool.getService(DebuggerAutoMappingService.class);
return auto == null ? ByModuleAutoMapSpec.instance() : auto.getAutoMapSpec(trace);
}
protected void initializeMonitor(TaskMonitor monitor) {
AutoMapSpec spec = getAutoMapSpec();
if (requiresImage() && spec.hasTask()) {
monitor.setMaximum(6);
}
@@ -574,8 +575,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
if (!requiresImage()) {
return;
}
DebuggerAutoMappingService auto = tool.getService(DebuggerAutoMappingService.class);
AutoMapSpec spec = auto.getAutoMapSpec(trace);
AutoMapSpec spec = getAutoMapSpec(trace);
if (!spec.hasTask()) {
return;
}
@@ -625,7 +625,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
while (true) {
try {
monitor.setMessage("Gathering arguments");
Map<String, ?> args = getLauncherArgs(prompt, configurator, lastExc);
Map<String, ValStr<?>> args = getLauncherArgs(prompt, configurator, lastExc);
if (args == null) {
if (lastExc == null) {
lastExc = new CancelledException();
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -22,18 +22,31 @@ import java.util.List;
import java.util.Map;
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.ScriptAttributes;
import ghidra.debug.api.ValStr;
import ghidra.program.model.listing.Program;
/**
* A launcher implemented by a simple DOS/Windows batch file.
*
* <p>
* The script must start with an attributes header in a comment block.
* The script must start with an attributes header in a comment block. See
* {@link ScriptAttributesParser}.
*/
public class BatchScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLaunchOffer {
public static final String REM = "::";
public static final int REM_LEN = REM.length();
/**
* Create a launch offer from the given batch file.
*
* @param plugin the launcher service plugin
* @param program the current program, usually the target image. In general, this should be used
* for at least two purposes. 1) To populate the default command line. 2) To ensure
* the target image is mapped in the resulting target trace.
* @param script the batch file that implements this offer
* @return the offer
* @throws FileNotFoundException if the batch file does not exist
*/
public static BatchScriptTraceRmiLaunchOffer create(TraceRmiLauncherServicePlugin plugin,
Program program, File script) throws FileNotFoundException {
ScriptAttributesParser parser = new ScriptAttributesParser() {
@@ -60,11 +73,4 @@ public class BatchScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLaunch
File script, String configName, ScriptAttributes attrs) {
super(plugin, program, script, configName, attrs);
}
@Override
protected void prepareSubprocess(List<String> commandLine, Map<String, String> env,
Map<String, ?> args, SocketAddress address) {
ScriptAttributesParser.processArguments(commandLine, env, script, attrs.parameters(), args,
address);
}
}
@@ -0,0 +1,86 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
import java.util.List;
import java.util.Map;
import javax.swing.Icon;
import ghidra.app.plugin.core.debug.gui.AbstractDebuggerParameterDialog;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.tracermi.LaunchParameter;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool;
public class TraceRmiLaunchDialog extends AbstractDebuggerParameterDialog<LaunchParameter<?>> {
public TraceRmiLaunchDialog(PluginTool tool, String title, String buttonText, Icon buttonIcon) {
super(tool, title, buttonText, buttonIcon);
}
@Override
protected String parameterName(LaunchParameter<?> parameter) {
return parameter.name();
}
@Override
protected Class<?> parameterType(LaunchParameter<?> parameter) {
return parameter.type();
}
@Override
protected String parameterLabel(LaunchParameter<?> parameter) {
return parameter.display();
}
@Override
protected String parameterToolTip(LaunchParameter<?> parameter) {
return parameter.description();
}
@Override
protected ValStr<?> parameterDefault(LaunchParameter<?> parameter) {
return parameter.defaultValue();
}
@Override
protected List<?> parameterChoices(LaunchParameter<?> parameter) {
return parameter.choices();
}
@Override
protected Map<String, ValStr<?>> validateArguments(Map<String, LaunchParameter<?>> parameters,
Map<String, ValStr<?>> arguments) {
return LaunchParameter.validateArguments(parameters, arguments);
}
@Override
protected void parameterSaveValue(LaunchParameter<?> parameter, SaveState state, String key,
ValStr<?> value) {
state.putString(key, value.str());
}
@Override
protected ValStr<?> parameterLoadValue(LaunchParameter<?> parameter, SaveState state,
String key) {
String str = state.getString(key, null);
if (str == null) {
return null;
}
return parameter.decode(str);
}
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -22,6 +22,7 @@ import java.util.List;
import java.util.Map;
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.ScriptAttributes;
import ghidra.debug.api.ValStr;
import ghidra.program.model.listing.Program;
/**
@@ -32,6 +33,8 @@ import ghidra.program.model.listing.Program;
* {@link ScriptAttributesParser}.
*/
public class UnixShellScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLaunchOffer {
public static final String HASH = "#";
public static final int HASH_LEN = HASH.length();
public static final String SHEBANG = "#!";
/**
@@ -56,10 +59,10 @@ public class UnixShellScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLa
@Override
protected String removeDelimiter(String line) {
String stripped = line.stripLeading();
if (!stripped.startsWith("#")) {
if (!stripped.startsWith(HASH)) {
return null;
}
return stripped.substring(1);
return stripped.substring(HASH_LEN);
}
};
ScriptAttributes attrs = parser.parseFile(script);
@@ -68,15 +71,7 @@ public class UnixShellScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLa
}
private UnixShellScriptTraceRmiLaunchOffer(TraceRmiLauncherServicePlugin plugin,
Program program,
File script, String configName, ScriptAttributes attrs) {
Program program, File script, String configName, ScriptAttributes attrs) {
super(plugin, program, script, configName, attrs);
}
@Override
protected void prepareSubprocess(List<String> commandLine, Map<String, String> env,
Map<String, ?> args, SocketAddress address) {
ScriptAttributesParser.processArguments(commandLine, env, script, attrs.parameters(), args,
address);
}
}
@@ -54,7 +54,6 @@ import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService;
import ghidra.rmi.trace.TraceRmi.*;
import ghidra.rmi.trace.TraceRmi.Compiler;
import ghidra.rmi.trace.TraceRmi.Language;
import ghidra.trace.database.DBTrace;
import ghidra.trace.model.Lifespan;
@@ -129,11 +128,9 @@ public class TraceRmiHandler implements TraceRmiConnection {
}
}
protected record Tid(DoId doId, int txId) {
}
protected record Tid(DoId doId, int txId) {}
protected record OpenTx(Tid txId, Transaction tx, boolean undoable) {
}
protected record OpenTx(Tid txId, Transaction tx, boolean undoable) {}
protected class OpenTraceMap {
private final Map<DoId, OpenTrace> byId = new HashMap<>();
@@ -388,7 +385,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
protected static void sendDelimited(OutputStream out, RootMessage msg, long dbgSeq)
throws IOException {
ByteBuffer buf = ByteBuffer.allocate(4);
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
buf.putInt(msg.getSerializedSize());
out.write(buf.array());
msg.writeTo(out);
@@ -867,6 +864,9 @@ public class TraceRmiHandler implements TraceRmiConnection {
throws InvalidNameException, IOException, CancelledException {
DomainFolder traces = getOrCreateNewTracesFolder();
List<String> path = sanitizePath(req.getPath().getPath());
if (path.isEmpty()) {
throw new IllegalArgumentException("CreateTrace: path (name) cannot be empty");
}
DomainFolder folder = createFolders(traces, path.subList(0, path.size() - 1));
CompilerSpec cs = requireCompilerSpec(req.getLanguage(), req.getCompiler());
DBTrace trace = new DBTrace(path.get(path.size() - 1), cs, this);
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -37,6 +37,7 @@ import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.util.PathMatcher;
import ghidra.dbg.util.PathPredicates;
import ghidra.dbg.util.PathPredicates.Align;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.model.DebuggerObjectActionContext;
import ghidra.debug.api.model.DebuggerSingleObjectPathActionContext;
import ghidra.debug.api.target.ActionName;
@@ -345,25 +346,15 @@ public class TraceRmiTarget extends AbstractTarget {
}
private Map<String, Object> promptArgs(RemoteMethod method, Map<String, Object> defaults) {
SchemaContext ctx = getSchemaContext();
/**
* TODO: RemoteMethod parameter descriptions should also use ValStr. This map conversion
* stuff is getting onerous and hacky.
*/
Map<String, ValStr<?>> defs = ValStr.fromPlainMap(defaults);
RemoteMethodInvocationDialog dialog = new RemoteMethodInvocationDialog(tool,
method.display(), method.display(), null);
while (true) {
for (RemoteParameter param : method.parameters().values()) {
Object val = defaults.get(param.name());
if (val != null) {
Class<?> type = ctx.getSchema(param.type()).getType();
dialog.setMemorizedArgument(param.name(), type.asSubclass(Object.class),
val);
}
}
Map<String, Object> args = dialog.promptArguments(ctx, method.parameters(), defaults);
if (args == null) {
// Cancelled
return null;
}
return args;
}
getSchemaContext(), method.display(), method.display(), null);
Map<String, ValStr<?>> args = dialog.promptArguments(method.parameters(), defs, defs);
return args == null ? null : ValStr.toPlainMap(args);
}
private CompletableFuture<?> invokeMethod(boolean prompt, RemoteMethod method,
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -30,7 +30,7 @@ import org.junit.Test;
import generic.Unique;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.gui.objects.components.InvocationDialogHelper;
import ghidra.app.plugin.core.debug.gui.InvocationDialogHelper;
import ghidra.app.plugin.core.debug.gui.tracermi.connection.tree.*;
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiClient;
@@ -60,13 +60,17 @@ public class TraceRmiConnectionManagerProviderTest extends AbstractGhidraHeadedD
provider = waitForComponentProvider(TraceRmiConnectionManagerProvider.class);
}
InvocationDialogHelper<?, ?> waitDialog() {
return InvocationDialogHelper.waitFor(TraceRmiConnectDialog.class);
}
@Test
public void testActionAccept() throws Exception {
performEnabledAction(provider, provider.actionConnectAccept, false);
InvocationDialogHelper helper = InvocationDialogHelper.waitFor();
InvocationDialogHelper<?, ?> helper = waitDialog();
helper.dismissWithArguments(Map.ofEntries(
Map.entry("address", "localhost"),
Map.entry("port", 0)));
helper.entry("address", "localhost"),
helper.entry("port", 0)));
waitForPass(() -> Unique.assertOne(traceRmiService.getAllAcceptors()));
}
@@ -78,10 +82,10 @@ public class TraceRmiConnectionManagerProviderTest extends AbstractGhidraHeadedD
throw new AssertionError();
}
performEnabledAction(provider, provider.actionConnectOutbound, false);
InvocationDialogHelper helper = InvocationDialogHelper.waitFor();
InvocationDialogHelper<?, ?> helper = waitDialog();
helper.dismissWithArguments(Map.ofEntries(
Map.entry("address", sockaddr.getHostString()),
Map.entry("port", sockaddr.getPort())));
helper.entry("address", sockaddr.getHostString()),
helper.entry("port", sockaddr.getPort())));
try (SocketChannel channel = server.accept()) {
TestTraceRmiClient client = new TestTraceRmiClient(channel);
client.sendNegotiate("Test client");
@@ -94,10 +98,10 @@ public class TraceRmiConnectionManagerProviderTest extends AbstractGhidraHeadedD
@Test
public void testActionStartServer() throws Exception {
performEnabledAction(provider, provider.actionStartServer, false);
InvocationDialogHelper helper = InvocationDialogHelper.waitFor();
InvocationDialogHelper<?, ?> helper = waitDialog();
helper.dismissWithArguments(Map.ofEntries(
Map.entry("address", "localhost"),
Map.entry("port", 0)));
helper.entry("address", "localhost"),
helper.entry("port", 0)));
waitForPass(() -> assertTrue(traceRmiService.isServerStarted()));
waitForPass(() -> assertFalse(provider.actionStartServer.isEnabled()));
@@ -40,6 +40,18 @@ import ghidra.util.task.TaskMonitor;
public class ByModuleAutoMapSpec implements AutoMapSpec {
public static final String CONFIG_NAME = "1_MAP_BY_MODULE";
/**
* Get the instance.
*
* <p>
* Note this will not work until after the class searcher is done.
*
* @return the instance
*/
public static ByModuleAutoMapSpec instance() {
return (ByModuleAutoMapSpec) AutoMapSpec.fromConfigName(CONFIG_NAME);
}
@Override
public String getConfigName() {
return CONFIG_NAME;
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -26,6 +26,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.swing.JComponent;
import javax.swing.JPanel;
@@ -63,6 +64,7 @@ import ghidra.dbg.target.TargetMethod.TargetParameterMap;
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
import ghidra.dbg.util.DebuggerCallbackReorderer;
import ghidra.dbg.util.PathUtils;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.model.DebuggerMemoryMapper;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
@@ -135,8 +137,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
private final AutoService.Wiring autoServiceWiring;
@AutoOptionDefined(
name = "Default Extended Step",
description = "The default string for the extended step command")
name = "Default Extended Step",
description = "The default string for the extended step command")
String extendedStep = "";
@SuppressWarnings("unused")
@@ -1367,13 +1369,14 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
boolean prompt = p;
};
return AsyncUtils.loop(TypeSpec.VOID, (loop) -> {
Map<String, ?> args = launchOffer.getLauncherArgs(launcher, locals.prompt);
Map<String, ValStr<?>> args = launchOffer.getLauncherArgs(launcher, locals.prompt);
if (args == null) {
// Cancelled
loop.exit();
}
else {
launcher.launch(args).thenAccept(loop::exit).exceptionally(ex -> {
Map<String, ?> a = ValStr.toPlainMap(args);
launcher.launch(a).thenAccept(loop::exit).exceptionally(ex -> {
loop.repeat();
return null;
});
@@ -1428,7 +1431,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
}
return;
}
Map<String, ?> args = methodDialog.promptArguments(parameters);
Map<String, ?> args = methodDialog.promptArguments(parameters, Map.of(), Map.of());
if (args != null) {
String script = (String) args.get("Script");
if (script != null && !script.isEmpty()) {
@@ -1623,7 +1626,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
if (configParameters.isEmpty()) {
return AsyncUtils.nil();
}
Map<String, ?> args = configDialog.promptArguments(configParameters);
Map<String, ?> args =
configDialog.promptArguments(configParameters, Map.of(), Map.of());
if (args == null) {
// User cancelled
return AsyncUtils.nil();
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,10 +16,12 @@
package ghidra.app.plugin.core.debug.service.model;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import docking.ActionContext;
import ghidra.app.context.ProgramLocationActionContext;
@@ -38,6 +40,7 @@ import ghidra.dbg.target.TargetMethod.TargetParameterMap;
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
import ghidra.dbg.util.PathMatcher;
import ghidra.dbg.util.PathPredicates;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.model.*;
import ghidra.debug.api.target.ActionName;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
@@ -237,26 +240,12 @@ public class TraceRecorderTarget extends AbstractTarget {
}
private Map<String, ?> promptArgs(TargetMethod method, Map<String, ?> defaults) {
Map<String, ValStr<?>> defs = ValStr.fromPlainMap(defaults);
DebuggerMethodInvocationDialog dialog = new DebuggerMethodInvocationDialog(tool,
method.getDisplay(), method.getDisplay(), null);
while (true) {
for (ParameterDescription<?> param : method.getParameters().values()) {
Object val = defaults.get(param.name);
if (val != null) {
dialog.setMemorizedArgument(param.name, param.type.asSubclass(Object.class),
val);
}
}
Map<String, ?> args = dialog.promptArguments(method.getParameters());
if (args == null) {
// Cancelled
return null;
}
if (dialog.isResetRequested()) {
continue;
}
return args;
}
Map<String, ValStr<?>> args = dialog.promptArguments(method.getParameters(), defs, defs);
return args == null ? null : ValStr.toPlainMap(args);
}
private CompletableFuture<?> invokeMethod(boolean prompt, TargetMethod method,
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.debug.service.model.launch;
import static ghidra.async.AsyncUtils.*;
import static ghidra.async.AsyncUtils.loop;
import java.io.File;
import java.io.IOException;
@@ -44,6 +44,7 @@ import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.PathUtils;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.model.DebuggerProgramLaunchOffer;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.debug.api.modules.*;
@@ -281,14 +282,14 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
return proposal;
}
private void saveLauncherArgs(Map<String, ?> args,
private void saveLauncherArgs(Map<String, ValStr<?>> args,
Map<String, ParameterDescription<?>> params) {
SaveState state = new SaveState();
for (ParameterDescription<?> param : params.values()) {
Object val = args.get(param.name);
ValStr<?> val = args.get(param.name);
if (val != null) {
ConfigStateField.putState(state, param.type.asSubclass(Object.class), param.name,
val);
val.val());
}
}
if (program != null) {
@@ -316,19 +317,19 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
* @param params the parameters
* @return the default arguments
*/
protected Map<String, ?> generateDefaultLauncherArgs(
protected Map<String, ValStr<?>> generateDefaultLauncherArgs(
Map<String, ParameterDescription<?>> params) {
if (program == null) {
return Map.of();
}
Map<String, Object> map = new LinkedHashMap<String, Object>();
Map<String, ValStr<?>> map = new LinkedHashMap<>();
for (Entry<String, ParameterDescription<?>> entry : params.entrySet()) {
map.put(entry.getKey(), entry.getValue().defaultValue);
map.put(entry.getKey(), ValStr.from(entry.getValue().defaultValue));
}
String almostExecutablePath = program.getExecutablePath();
File f = new File(almostExecutablePath);
map.put(TargetCmdLineLauncher.CMDLINE_ARGS_NAME,
TargetCmdLineLauncher.quoteImagePathIfSpaces(f.getAbsolutePath()));
ValStr.from(TargetCmdLineLauncher.quoteImagePathIfSpaces(f.getAbsolutePath())));
return map;
}
@@ -338,36 +339,19 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
* @param params the parameters of the model's launcher
* @return the arguments given by the user, or null if cancelled
*/
protected Map<String, ?> promptLauncherArgs(TargetLauncher launcher,
protected Map<String, ValStr<?>> promptLauncherArgs(TargetLauncher launcher,
LaunchConfigurator configurator) {
TargetParameterMap params = launcher.getParameters();
DebuggerMethodInvocationDialog dialog =
new DebuggerMethodInvocationDialog(tool, getButtonTitle(), "Launch", getIcon());
// NB. Do not invoke read/writeConfigState
Map<String, ?> args;
boolean reset = false;
do {
args = configurator.configureLauncher(launcher,
loadLastLauncherArgs(launcher, true), RelPrompt.BEFORE);
for (ParameterDescription<?> param : params.values()) {
Object val = args.get(param.name);
if (val != null) {
dialog.setMemorizedArgument(param.name, param.type.asSubclass(Object.class),
val);
}
}
args = dialog.promptArguments(params);
if (args == null) {
// Cancelled
return null;
}
reset = dialog.isResetRequested();
if (reset) {
args = generateDefaultLauncherArgs(params);
}
saveLauncherArgs(args, params);
}
while (reset);
Map<String, ValStr<?>> defaultArgs = generateDefaultLauncherArgs(params);
Map<String, ValStr<?>> lastArgs = configurator.configureLauncher(launcher,
loadLastLauncherArgs(launcher, true), RelPrompt.BEFORE);
Map<String, ValStr<?>> args = dialog.promptArguments(params, lastArgs, defaultArgs);
saveLauncherArgs(args, params);
return args;
}
@@ -386,7 +370,8 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
* @param forPrompt true if the user will be confirming the arguments
* @return the loaded arguments, or defaults
*/
protected Map<String, ?> loadLastLauncherArgs(TargetLauncher launcher, boolean forPrompt) {
protected Map<String, ValStr<?>> loadLastLauncherArgs(TargetLauncher launcher,
boolean forPrompt) {
/**
* TODO: Supposedly, per-program, per-user config stuff is being generalized for analyzers.
* Re-examine this if/when that gets merged
@@ -401,13 +386,13 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
Element element = XmlUtilities.fromString(property);
SaveState state = new SaveState(element);
List<String> names = List.of(state.getNames());
Map<String, Object> args = new LinkedHashMap<>();
Map<String, ValStr<?>> args = new LinkedHashMap<>();
for (ParameterDescription<?> param : params.values()) {
if (names.contains(param.name)) {
Object configState =
ConfigStateField.getState(state, param.type, param.name);
if (configState != null) {
args.put(param.name, configState);
args.put(param.name, ValStr.from(configState));
}
}
}
@@ -426,7 +411,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
e);
}
}
Map<String, ?> args = generateDefaultLauncherArgs(params);
Map<String, ValStr<?>> args = generateDefaultLauncherArgs(params);
saveLauncherArgs(args, params);
return args;
}
@@ -447,7 +432,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
* @param configurator a means of configuring the launcher
* @return the chosen arguments, or null if the user cancels at the prompt
*/
public Map<String, ?> getLauncherArgs(TargetLauncher launcher, boolean prompt,
public Map<String, ValStr<?>> getLauncherArgs(TargetLauncher launcher, boolean prompt,
LaunchConfigurator configurator) {
return prompt
? configurator.configureLauncher(launcher,
@@ -456,7 +441,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
RelPrompt.NONE);
}
public Map<String, ?> getLauncherArgs(TargetLauncher launcher, boolean prompt) {
public Map<String, ValStr<?>> getLauncherArgs(TargetLauncher launcher, boolean prompt) {
return getLauncherArgs(launcher, prompt, LaunchConfigurator.NOP);
}
@@ -541,13 +526,14 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
// Eww.
protected CompletableFuture<Void> launch(TargetLauncher launcher,
boolean prompt, LaunchConfigurator configurator, TaskMonitor monitor) {
Map<String, ?> args = getLauncherArgs(launcher, prompt, configurator);
Map<String, ValStr<?>> args = getLauncherArgs(launcher, prompt, configurator);
if (args == null) {
throw new CancellationException();
}
Map<String, ?> a = ValStr.toPlainMap(args);
return AsyncTimer.DEFAULT_TIMER.mark()
.timeOut(
launcher.launch(args), getTimeoutMillis(), () -> onTimedOutLaunch(monitor));
launcher.launch(a), getTimeoutMillis(), () -> onTimedOutLaunch(monitor));
}
protected void checkCancelled(TaskMonitor monitor) {
@@ -33,8 +33,9 @@ public enum MiscellaneousUtils {
* Obtain a swing component which may be used to edit the property.
*
* <p>
* This has been shamelessly stolen from {@link EditorState#getEditorComponent()}, which seems
* entangled with Ghidra's whole options system. I think this portion could be factored out.
* This has was originally stolen from {@link EditorState#getEditorComponent()}, which seems
* entangled with Ghidra's whole options system. Can that be factored out? Since then, the two
* have drifted apart.
*
* @param editor the editor for which to obtain an interactive component for editing
* @return the component
@@ -53,16 +54,11 @@ public enum MiscellaneousUtils {
return new PropertyText(editor);
}
Class<? extends PropertyEditor> clazz = editor.getClass();
String clazzName = clazz.getSimpleName();
if (clazzName.startsWith("String")) {
// Most likely some kind of string editor with a null value. Just use a string
// property and let the value be empty.
return new PropertyText(editor);
}
throw new IllegalStateException(
"Ghidra does not know how to use PropertyEditor: " + editor.getClass().getName());
/**
* TODO: Would be nice to know the actual type, but alas! Just default to a PropertyText and
* hope all goes well.
*/
return new PropertyText(editor);
}
public static void rigFocusAndEnter(Component c, Runnable runnable) {
@@ -0,0 +1,94 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui;
import java.beans.PropertyEditor;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import org.apache.commons.lang3.exception.ExceptionUtils;
import docking.test.AbstractDockingTest;
import ghidra.async.SwingExecutorService;
import ghidra.debug.api.ValStr;
import ghidra.framework.options.SaveState;
public class InvocationDialogHelper<P, D extends AbstractDebuggerParameterDialog<P>> {
public static <P, D extends AbstractDebuggerParameterDialog<P>> InvocationDialogHelper<P, D> waitFor(
Class<D> cls) {
D dialog = AbstractDockingTest.waitForDialogComponent(cls);
return new InvocationDialogHelper<>(dialog);
}
private final AbstractDebuggerParameterDialog<P> dialog;
public InvocationDialogHelper(AbstractDebuggerParameterDialog<P> dialog) {
this.dialog = dialog;
}
public void dismissWithArguments(Map<String, ValStr<?>> args) {
dialog.setMemorizedArguments(args);
invoke();
}
public <T> Map.Entry<String, ValStr<T>> entry(String key, T value) {
return Map.entry(key, ValStr.from(value));
}
public void setArg(P param, Object value) {
PropertyEditor editor = dialog.getEditor(param);
runSwing(() -> editor.setValue(value));
}
protected void runSwing(Runnable r) {
try {
CompletableFuture.runAsync(r, SwingExecutorService.LATER).get();
}
catch (ExecutionException e) {
switch (e.getCause()) {
case RuntimeException t -> throw t;
case Exception t -> throw new RuntimeException(t);
default -> ExceptionUtils.rethrow(e.getCause());
}
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public void setArgAsString(P param, String value) {
PropertyEditor editor = dialog.getEditor(param);
runSwing(() -> editor.setAsText(value));
}
public void invoke() {
runSwing(() -> dialog.invoke(null));
}
public SaveState saveState() {
SaveState parent = new SaveState();
runSwing(() -> dialog.writeConfigState(parent));
return parent.getSaveState(AbstractDebuggerParameterDialog.KEY_MEMORIZED_ARGUMENTS);
}
public void loadState(SaveState state) {
SaveState parent = new SaveState();
parent.putSaveState(AbstractDebuggerParameterDialog.KEY_MEMORIZED_ARGUMENTS, state);
runSwing(() -> dialog.readConfigState(parent));
}
}
@@ -1,48 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.objects.components;
import static org.junit.Assert.assertNotNull;
import java.util.Map;
import docking.test.AbstractDockingTest;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.util.Swing;
public class InvocationDialogHelper {
public static InvocationDialogHelper waitFor() {
DebuggerMethodInvocationDialog dialog =
AbstractDockingTest.waitForDialogComponent(DebuggerMethodInvocationDialog.class);
return new InvocationDialogHelper(dialog);
}
private final DebuggerMethodInvocationDialog dialog;
public InvocationDialogHelper(DebuggerMethodInvocationDialog dialog) {
this.dialog = dialog;
}
public void dismissWithArguments(Map<String, Object> args) {
for (Map.Entry<String, Object> a : args.entrySet()) {
ParameterDescription<?> p = dialog.parameters.get(a.getKey());
assertNotNull(p);
dialog.setMemorizedArgument(a.getKey(), p.type.asSubclass(Object.class), a.getValue());
}
Swing.runNow(() -> dialog.invoke(null));
}
}
@@ -33,6 +33,8 @@ public final class NumericUtilities {
private final static String HEX_PREFIX_X = "0X";
private final static String HEX_PREFIX_x = "0x";
private final static String BIN_PREFIX = "0B";
private final static String OCT_PREFIX = "0";
private final static Set<Class<? extends Number>> INTEGER_TYPES = new HashSet<>();
static {
@@ -235,6 +237,49 @@ public final class NumericUtilities {
return new BigInteger(s, 16);
}
private static BigInteger decodeMagnitude(int p, String s) {
// Special case, so it doesn't get chewed by octal parser
if ("0".equals(s)) {
return BigInteger.ZERO;
}
if (s.regionMatches(true, p, HEX_PREFIX_X, 0, HEX_PREFIX_X.length())) {
return new BigInteger(s.substring(p + HEX_PREFIX_X.length()), 16);
}
if (s.regionMatches(true, p, BIN_PREFIX, 0, BIN_PREFIX.length())) {
return new BigInteger(s.substring(p + BIN_PREFIX.length()), 2);
}
// Check last, because prefix is shortest.
if (s.regionMatches(true, p, OCT_PREFIX, 0, OCT_PREFIX.length())) {
return new BigInteger(s.substring(p + OCT_PREFIX.length()), 8);
}
return new BigInteger(s.substring(p), 10);
}
/**
* Decode a big integer in hex, binary, octal, or decimal, based on the prefix 0x, 0b, or 0.
*
* <p>
* This checks for the presence of a case-insensitive prefix. 0x denotes hex, 0b denotes binary,
* 0 denotes octal. If no prefix is given, decimal is assumed. A sign +/- may immediately
* precede the prefix. If no sign is given, a positive value is assumed.
*
* @param s the string to parse
* @return the decoded value
*/
public static BigInteger decodeBigInteger(String s) {
int p = 0;
boolean negative = false;
if (s.startsWith("+")) {
p = 1;
}
else if (s.startsWith("-")) {
p = 1;
negative = true;
}
BigInteger mag = decodeMagnitude(p, s);
return negative ? mag.negate() : mag;
}
/**
* returns the value of the specified long as hexadecimal, prefixing with the
* {@link #HEX_PREFIX_x} string.
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -18,6 +18,7 @@ package ghidra.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;
@@ -332,4 +333,89 @@ public class NumericUtilitiesTest {
assertEquals(errorMessage, expected[i], actual[i]);
}
}
@Test
public void testDecodeBigInteger() {
// Zero special cases
assertEquals(BigInteger.ZERO, NumericUtilities.decodeBigInteger("0"));
assertEquals(BigInteger.ZERO, NumericUtilities.decodeBigInteger("000"));
// Decimal
assertEquals(BigInteger.valueOf(99), NumericUtilities.decodeBigInteger("99"));
assertEquals(BigInteger.valueOf(99), NumericUtilities.decodeBigInteger("+99"));
assertEquals(BigInteger.valueOf(-99), NumericUtilities.decodeBigInteger("-99"));
// Hex
assertEquals(BigInteger.valueOf(0x99), NumericUtilities.decodeBigInteger("0x99"));
assertEquals(BigInteger.valueOf(0x99), NumericUtilities.decodeBigInteger("+0x99"));
assertEquals(BigInteger.valueOf(-0x99), NumericUtilities.decodeBigInteger("-0x99"));
// Binary
assertEquals(BigInteger.valueOf(0b110), NumericUtilities.decodeBigInteger("0b110"));
assertEquals(BigInteger.valueOf(0b110), NumericUtilities.decodeBigInteger("+0b110"));
assertEquals(BigInteger.valueOf(-0b110), NumericUtilities.decodeBigInteger("-0b110"));
// Octal
assertEquals(BigInteger.valueOf(0755), NumericUtilities.decodeBigInteger("0755"));
assertEquals(BigInteger.valueOf(0755), NumericUtilities.decodeBigInteger("+0755"));
assertEquals(BigInteger.valueOf(-0755), NumericUtilities.decodeBigInteger("-0755"));
// Errors
try {
NumericUtilities.decodeBigInteger("");
fail();
}
catch (NumberFormatException e) {
}
try {
NumericUtilities.decodeBigInteger("+");
fail();
}
catch (NumberFormatException e) {
}
try {
NumericUtilities.decodeBigInteger("-");
fail();
}
catch (NumberFormatException e) {
}
try {
NumericUtilities.decodeBigInteger("0x");
fail();
}
catch (NumberFormatException e) {
}
try {
NumericUtilities.decodeBigInteger("0b");
fail();
}
catch (NumberFormatException e) {
}
try {
NumericUtilities.decodeBigInteger("a01");
fail();
}
catch (NumberFormatException e) {
}
try {
NumericUtilities.decodeBigInteger("081");
fail();
}
catch (NumberFormatException e) {
}
try {
NumericUtilities.decodeBigInteger("0x9g");
fail();
}
catch (NumberFormatException e) {
}
try {
NumericUtilities.decodeBigInteger(" 10");
fail();
}
catch (NumberFormatException e) {
}
try {
NumericUtilities.decodeBigInteger("10 ");
fail();
}
catch (NumberFormatException e) {
}
}
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -22,6 +22,7 @@ import org.junit.Test;
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
import ghidra.app.plugin.core.terminal.TerminalProvider;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
import ghidra.framework.plugintool.AutoConfigState.PathIsFile;
import ghidra.test.ToyProgramBuilder;
@@ -30,7 +31,8 @@ import help.screenshot.GhidraScreenShotGenerator;
public class TraceRmiLauncherServicePluginScreenShots extends GhidraScreenShotGenerator {
TraceRmiLauncherServicePlugin servicePlugin;
protected void captureLauncherByTitle(String title, Map<String, ?> args) throws Throwable {
protected void captureLauncherByTitle(String title, Map<String, ValStr<?>> args)
throws Throwable {
servicePlugin = addPlugin(tool, TraceRmiLauncherServicePlugin.class);
ToyProgramBuilder pb = new ToyProgramBuilder("demo", false);
@@ -49,10 +51,13 @@ public class TraceRmiLauncherServicePluginScreenShots extends GhidraScreenShotGe
captureDialog(DebuggerMethodInvocationDialog.class);
}
protected ValStr<PathIsFile> fileArg(String path) {
return new ValStr<>(new PathIsFile(Paths.get(path)), path);
}
@Test
public void testCaptureGdbLauncher() throws Throwable {
captureLauncherByTitle("gdb",
Map.of("arg:1", new PathIsFile(Paths.get("/home/user/demo"))));
captureLauncherByTitle("gdb", Map.of("arg:1", fileArg("/home/user/demo")));
}
@Test
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -20,9 +20,8 @@ import static org.junit.Assert.assertEquals;
import java.net.SocketAddress;
import java.util.*;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.debug.api.tracermi.TerminalSession;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.tracermi.*;
import ghidra.debug.spi.tracermi.TraceRmiLaunchOpinion;
import ghidra.program.model.listing.Program;
import ghidra.util.task.TaskMonitor;
@@ -30,9 +29,9 @@ import ghidra.util.task.TaskMonitor;
public class TestTraceRmiLaunchOpinion implements TraceRmiLaunchOpinion {
public static class TestTraceRmiLaunchOffer extends AbstractTraceRmiLaunchOffer {
private static final ParameterDescription<String> PARAM_DESC_IMAGE =
ParameterDescription.create(String.class, "image", true, "",
PARAM_DISPLAY_IMAGE, "Image to execute");
private static final LaunchParameter<String> PARAM_IMAGE =
LaunchParameter.create(String.class, "image", PARAM_DISPLAY_IMAGE, "Image to execute",
true, ValStr.str(""), str -> str);
public TestTraceRmiLaunchOffer(TraceRmiLauncherServicePlugin plugin, Program program) {
super(plugin, program);
@@ -58,8 +57,8 @@ public class TestTraceRmiLaunchOpinion implements TraceRmiLaunchOpinion {
}
@Override
public Map<String, ParameterDescription<?>> getParameters() {
return Map.ofEntries(Map.entry(PARAM_DESC_IMAGE.name, PARAM_DESC_IMAGE));
public Map<String, LaunchParameter<?>> getParameters() {
return LaunchParameter.mapOf(PARAM_IMAGE);
}
@Override
@@ -69,19 +68,19 @@ public class TestTraceRmiLaunchOpinion implements TraceRmiLaunchOpinion {
@Override
protected void launchBackEnd(TaskMonitor monitor, Map<String, TerminalSession> sessions,
Map<String, ?> args, SocketAddress address) throws Exception {
Map<String, ValStr<?>> args, SocketAddress address) throws Exception {
}
@Override
public LaunchResult launchProgram(TaskMonitor monitor, LaunchConfigurator configurator) {
assertEquals(PromptMode.NEVER, configurator.getPromptMode());
Map<String, ?> args =
Map<String, ValStr<?>> args =
configurator.configureLauncher(this, loadLastLauncherArgs(false), RelPrompt.NONE);
return new LaunchResult(program, null, null, null, null,
new RuntimeException("Test launcher cannot launch " + args.get("image")));
new RuntimeException("Test launcher cannot launch " + PARAM_IMAGE.get(args).val()));
}
public void saveLauncherArgs(Map<String, ?> args) {
public void saveLauncherArgs(Map<String, ValStr<?>> args) {
super.saveLauncherArgs(args, getParameters());
}
}
@@ -0,0 +1,255 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.math.BigInteger;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import org.junit.*;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.gui.InvocationDialogHelper;
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.BaseType;
import ghidra.async.SwingExecutorService;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.tracermi.LaunchParameter;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.AutoConfigState.PathIsDir;
import ghidra.framework.plugintool.AutoConfigState.PathIsFile;
public class TraceRmiLaunchDialogTest extends AbstractGhidraHeadedDebuggerTest {
private static final LaunchParameter<String> PARAM_STRING =
BaseType.STRING.createParameter("some_string", "A String", "A string",
true, ValStr.str("Hello"));
private static final LaunchParameter<BigInteger> PARAM_INT =
BaseType.INT.createParameter("some_int", "An Int", "An integer",
true, intVal(99));
private static final LaunchParameter<Boolean> PARAM_BOOL =
BaseType.BOOL.createParameter("some_bool", "A Bool", "A boolean",
true, ValStr.from(true));
private static final LaunchParameter<Path> PARAM_PATH =
BaseType.PATH.createParameter("some_path", "A Path", "A path",
true, pathVal("my_path"));
private static final LaunchParameter<PathIsDir> PARAM_DIR =
BaseType.DIR.createParameter("some_dir", "A Dir", "A directory",
true, dirVal("my_dir"));
private static final LaunchParameter<PathIsFile> PARAM_FILE =
BaseType.FILE.createParameter("some_file", "A File", "A file",
true, fileVal("my_file"));
private TraceRmiLaunchDialog dialog;
@Before
public void setupRmiLaunchDialogTest() throws Exception {
dialog = new TraceRmiLaunchDialog(tool, "Launch Test", "Launch", null);
}
record PromptResult(CompletableFuture<Map<String, ValStr<?>>> args,
InvocationDialogHelper<LaunchParameter<?>, ?> h) {}
protected PromptResult prompt(LaunchParameter<?>... params) {
CompletableFuture<Map<String, ValStr<?>>> args = CompletableFuture.supplyAsync(
() -> dialog.promptArguments(LaunchParameter.mapOf(params), Map.of(), Map.of()),
SwingExecutorService.LATER);
InvocationDialogHelper<LaunchParameter<?>, ?> helper =
InvocationDialogHelper.waitFor(TraceRmiLaunchDialog.class);
return new PromptResult(args, helper);
}
static ValStr<BigInteger> intVal(long val, String str) {
return new ValStr<>(BigInteger.valueOf(val), str);
}
static ValStr<BigInteger> intVal(long val) {
return ValStr.from(BigInteger.valueOf(val));
}
static ValStr<Path> pathVal(String path) {
return new ValStr<>(Paths.get(path), path);
}
static ValStr<PathIsDir> dirVal(String path) {
return new ValStr<>(PathIsDir.fromString(path), path);
}
static ValStr<PathIsFile> fileVal(String path) {
return new ValStr<>(PathIsFile.fromString(path), path);
}
@Test
public void testStringDefaultValue() throws Throwable {
PromptResult result = prompt(PARAM_STRING);
result.h.invoke();
Map<String, ValStr<?>> args = waitOn(result.args);
assertEquals(Map.of("some_string", ValStr.str("Hello")), args);
}
@Test
public void testStringInputValue() throws Throwable {
PromptResult result = prompt(PARAM_STRING);
result.h.setArgAsString(PARAM_STRING, "World");
result.h.invoke();
Map<String, ValStr<?>> args = waitOn(result.args);
assertEquals(Map.of("some_string", ValStr.str("World")), args);
}
@Test
public void testIntDefaultValue() throws Throwable {
PromptResult result = prompt(PARAM_INT);
result.h.invoke();
Map<String, ValStr<?>> args = waitOn(result.args);
assertEquals(Map.of("some_int", intVal(99)), args);
}
@Test
public void testIntInputHexValue() throws Throwable {
PromptResult result = prompt(PARAM_INT);
result.h.setArgAsString(PARAM_INT, "0x11");
result.h.invoke();
Map<String, ValStr<?>> args = waitOn(result.args);
assertEquals(Map.of("some_int", intVal(17, "0x11")), args);
}
@Test
public void testIntInputHexValueIncomplete() throws Throwable {
PromptResult result = prompt(PARAM_INT);
try {
result.h.setArgAsString(PARAM_INT, "0x");
fail();
}
catch (NumberFormatException e) {
// pass
}
result.h.invoke();
}
@Test
public void testIntSaveHexValue() throws Throwable {
PromptResult result = prompt(PARAM_INT);
result.h.setArgAsString(PARAM_INT, "0x11");
result.h.invoke();
SaveState state = result.h.saveState();
assertEquals("0x11", state.getString("some_int,java.math.BigInteger", null));
}
@Test
@Ignore
public void testIntLoadHexValue() throws Throwable {
/**
* TODO: This is a bit out of order. However, the dialog cannot load/decode from the state
* until it has the parameters. Worse, to check that user input was valid, the dialog
* verifies that the value it gets back matches the text in the box, because if it doesn't,
* then the editor must have failed to parse/decode the value. Currently, loading the state
* while the dialog box has already populated its values, does not modify the contents of
* any editor, so the text <em>will not</em> match, causing this test to fail.
*/
PromptResult result = prompt(PARAM_INT);
SaveState state = new SaveState();
state.putString("some_int,java.math.BigInteger", "0x11");
result.h.loadState(state);
result.h.invoke();
Map<String, ValStr<?>> args = waitOn(result.args);
assertEquals(Map.of("some_int", intVal(17, "0x11")), args);
}
@Test
public void testBoolDefaultValue() throws Throwable {
PromptResult result = prompt(PARAM_BOOL);
result.h.invoke();
Map<String, ValStr<?>> args = waitOn(result.args);
assertEquals(Map.of("some_bool", ValStr.from(true)), args);
}
@Test
public void testBoolInputValue() throws Throwable {
PromptResult result = prompt(PARAM_BOOL);
result.h.setArg(PARAM_BOOL, false);
result.h.invoke();
Map<String, ValStr<?>> args = waitOn(result.args);
assertEquals(Map.of("some_bool", ValStr.from(false)), args);
}
@Test
public void testPathDefaultValue() throws Throwable {
PromptResult result = prompt(PARAM_PATH);
result.h.invoke();
Map<String, ValStr<?>> args = waitOn(result.args);
assertEquals(Map.of("some_path", pathVal("my_path")), args);
}
@Test
public void testPathInputValue() throws Throwable {
PromptResult result = prompt(PARAM_PATH);
result.h.setArgAsString(PARAM_PATH, "your_path");
result.h.invoke();
Map<String, ValStr<?>> args = waitOn(result.args);
assertEquals(Map.of("some_path", pathVal("your_path")), args);
}
@Test
public void testDirDefaultValue() throws Throwable {
PromptResult result = prompt(PARAM_DIR);
result.h.invoke();
Map<String, ValStr<?>> args = waitOn(result.args);
assertEquals(Map.of("some_dir", dirVal("my_dir")), args);
}
@Test
public void testDirInputValue() throws Throwable {
PromptResult result = prompt(PARAM_DIR);
result.h.setArgAsString(PARAM_DIR, "your_dir");
result.h.invoke();
Map<String, ValStr<?>> args = waitOn(result.args);
assertEquals(Map.of("some_dir", dirVal("your_dir")), args);
}
@Test
public void testFileDefaultValue() throws Throwable {
PromptResult result = prompt(PARAM_FILE);
result.h.invoke();
Map<String, ValStr<?>> args = waitOn(result.args);
assertEquals(Map.of("some_file", fileVal("my_file")), args);
}
@Test
public void testFileInputValue() throws Throwable {
PromptResult result = prompt(PARAM_FILE);
result.h.setArgAsString(PARAM_FILE, "your_file");
result.h.invoke();
Map<String, ValStr<?>> args = waitOn(result.args);
assertEquals(Map.of("some_file", fileVal("your_file")), args);
}
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -28,6 +28,7 @@ import org.junit.Test;
import db.Transaction;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.services.TraceRmiLauncherService;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.*;
import ghidra.framework.OperatingSystem;
@@ -57,11 +58,11 @@ public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebug
protected LaunchConfigurator gdbFileOnly(String file) {
return new LaunchConfigurator() {
@Override
public Map<String, ?> configureLauncher(TraceRmiLaunchOffer offer,
Map<String, ?> arguments, RelPrompt relPrompt) {
Map<String, Object> args = new HashMap<>(arguments);
args.put("arg:1", new PathIsFile(Paths.get(file)));
args.put("env:OPT_START_CMD", "starti");
public Map<String, ValStr<?>> configureLauncher(TraceRmiLaunchOffer offer,
Map<String, ValStr<?>> arguments, RelPrompt relPrompt) {
Map<String, ValStr<?>> args = new HashMap<>(arguments);
args.put("arg:1", new ValStr<>(new PathIsFile(Paths.get(file)), file));
args.put("env:OPT_START_CMD", ValStr.str("starti"));
return args;
}
};
@@ -93,10 +94,10 @@ public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebug
protected LaunchConfigurator dbgengFileOnly(String file) {
return new LaunchConfigurator() {
@Override
public Map<String, ?> configureLauncher(TraceRmiLaunchOffer offer,
Map<String, ?> arguments, RelPrompt relPrompt) {
Map<String, Object> args = new HashMap<>(arguments);
args.put("env:OPT_TARGET_IMG", new PathIsFile(Paths.get(file)));
public Map<String, ValStr<?>> configureLauncher(TraceRmiLaunchOffer offer,
Map<String, ValStr<?>> arguments, RelPrompt relPrompt) {
Map<String, ValStr<?>> args = new HashMap<>(arguments);
args.put("env:OPT_TARGET_IMG", new ValStr<>(new PathIsFile(Paths.get(file)), file));
return args;
}
};
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -31,6 +31,7 @@ import ghidra.app.plugin.core.debug.gui.tracermi.launcher.TestTraceRmiLaunchOpin
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.TraceRmiLauncherServicePlugin;
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.TestRemoteMethod;
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiTarget;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.LaunchResult;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
@@ -182,7 +183,7 @@ public class FlatDebuggerRmiAPITest extends AbstractLiveFlatDebuggerAPITest<Flat
TestTraceRmiLaunchOffer offer =
Unique.assertOne(filter(api.getLaunchOffers(), TestTraceRmiLaunchOffer.class));
offer.saveLauncherArgs(Map.of("image", "/test/image"));
offer.saveLauncherArgs(Map.of("image", ValStr.str("/test/image")));
assertEquals(List.of(offer), api.getSavedLaunchOffers());
}
@@ -191,7 +192,7 @@ public class FlatDebuggerRmiAPITest extends AbstractLiveFlatDebuggerAPITest<Flat
public void testLaunchCustomCommandLine() throws Throwable {
TestTraceRmiLaunchOffer offer =
Unique.assertOne(filter(api.getLaunchOffers(), TestTraceRmiLaunchOffer.class));
offer.saveLauncherArgs(Map.of("image", "/test/image"));
offer.saveLauncherArgs(Map.of("image", ValStr.str("/test/image")));
LaunchResult result = api.launch(monitor);
assertEquals("Test launcher cannot launch /test/image", result.exception().getMessage());