Merge branch 'master' into fix/bsim_results_no_address_space

This commit is contained in:
Jeffrey 2024-09-08 18:34:45 -04:00
commit 5d553c9045
418 changed files with 34373 additions and 7604 deletions

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "ghidradbg"
version = "11.1.2"
version = "11.2"
authors = [
{ name="Ghidra Development Team" },
]
@ -17,8 +17,8 @@ classifiers = [
"Operating System :: OS Independent",
]
dependencies = [
"ghidratrace==11.1.2",
"pybag>=2.2.10"
"ghidratrace==11.2",
"pybag>=2.2.12"
]
[project.urls]

View File

@ -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" \

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "ghidragdb"
version = "11.1.2"
version = "11.2"
authors = [
{ name="Ghidra Development Team" },
]
@ -17,7 +17,7 @@ classifiers = [
"Operating System :: OS Independent",
]
dependencies = [
"ghidratrace==11.1.2",
"ghidratrace==11.2",
]
[project.urls]

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "ghidralldb"
version = "11.1.2"
version = "11.2"
authors = [
{ name="Ghidra Development Team" },
]
@ -17,7 +17,7 @@ classifiers = [
"Operating System :: OS Independent",
]
dependencies = [
"ghidratrace==11.1.2",
"ghidratrace==11.2",
]
[project.urls]

View File

@ -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()));
}
}

View File

@ -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;
}
}

View File

@ -778,10 +778,18 @@ public class DebuggerCoordinates {
return coords;
}
public boolean isAlive() {
public static boolean isAlive(Target target) {
return target != null && target.isValid();
}
public boolean isAlive() {
return isAlive(target);
}
public static boolean isAliveAndPresent(TraceProgramView view, Target target) {
return isAlive(target) && target.getSnap() == view.getSnap();
}
protected boolean isPresent() {
TraceSchedule defaultedTime = getTime();
return target.getSnap() == defaultedTime.getSnap() && defaultedTime.isSnapOnly();

View File

@ -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);
}
}

View File

@ -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

View File

@ -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;
}
}));

View File

@ -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;
}
});

View File

@ -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)) {
return;
}
if (!choices.contains(value)) {
throw new IllegalArgumentException("Unsupported value: " + value);
}
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() {
super.setValue(value);
if (value == null) {
return "null";
unmodifiableField.setText("");
return;
}
if (value instanceof String str) {
return "\"" + StringEscapeUtils.escapeJava(str) + "\"";
if (!(value instanceof TraceObject obj)) {
throw new IllegalArgumentException();
}
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);
public Component getCustomEditor() {
return unmodifiableField;
}
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
static {
PropertyEditorManager.registerEditor(TraceObject.class, TraceObjectEditor.class);
}
record NameTypePair(String name, Class<?> type) {
public static NameTypePair fromParameter(SchemaContext ctx, RemoteParameter parameter) {
return new NameTypePair(parameter.name(), ctx.getSchema(parameter.type()).getType());
}
private final SchemaContext ctx;
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]));
}
}
private final BidiMap<RemoteParameter, PropertyEditor> paramEditors =
new DualLinkedHashBidiMap<>();
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();
@Override
protected Class<?> parameterType(RemoteParameter parameter) {
Class<?> type = ctx.getSchema(parameter.type()).getType();
if (TargetObject.class.isAssignableFrom(type)) {
return TraceObject.class;
}
return type;
}
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());
}
}
populateValues();
@Override
protected String parameterLabel(RemoteParameter parameter) {
return "".equals(parameter.display()) ? parameter.name() : parameter.display();
}
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 parameterToolTip(RemoteParameter parameter) {
return parameter.description();
}
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 ValStr<?> parameterDefault(RemoteParameter parameter) {
return ValStr.from(parameter.getDefaultValue());
}
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 Collection<?> parameterChoices(RemoteParameter parameter) {
return Set.of();
}
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;
}
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);
}
}

View File

@ -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());
}
}

View File

@ -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;
}

View File

@ -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",

View File

@ -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());
// 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);
}
// NB. Do not invoke read/writeConfigState
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();

View File

@ -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);
}
}

View File

@ -27,18 +27,23 @@ import javax.swing.Icon;
import generic.theme.GIcon;
import generic.theme.Gui;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.dbg.util.ShellUtils;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.tracermi.LaunchParameter;
import ghidra.framework.Application;
import ghidra.framework.plugintool.AutoConfigState.PathIsDir;
import ghidra.framework.plugintool.AutoConfigState.PathIsFile;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.*;
/**
* A parser for reading attributes from a script header
*/
public abstract class ScriptAttributesParser {
public static final String ENV_GHIDRA_HOME = "GHIDRA_HOME";
public static final String ENV_GHIDRA_TRACE_RMI_ADDR = "GHIDRA_TRACE_RMI_ADDR";
public static final String ENV_GHIDRA_TRACE_RMI_HOST = "GHIDRA_TRACE_RMI_HOST";
public static final String ENV_GHIDRA_TRACE_RMI_PORT = "GHIDRA_TRACE_RMI_PORT";
public static final String AT_TITLE = "@title";
public static final String AT_DESC = "@desc";
public static final String AT_MENU_PATH = "@menu-path";
@ -69,10 +74,29 @@ public abstract class ScriptAttributesParser {
public static final String MSGPAT_INVALID_ARGS_SYNTAX =
"%s: Invalid %s syntax. Use \"Display\" \"Tool Tip\"";
public static final String MSGPAT_INVALID_TTY_SYNTAX =
"%s: Invalid %s syntax. Use TTY_TARGET [if env:OPT_EXTRA_TTY]";
"%s: Invalid %s syntax. Use TTY_TARGET [if env:OPT [== VAL]]";
public static final String MSGPAT_INVALID_TTY_NO_PARAM =
"%s: In %s: No such parameter '%s'";
public static final String MSGPAT_INVALID_TTY_NOT_BOOL =
"%s: In %s: Parameter '%s' must have bool type";
public static final String MSGPAT_INVALID_TTY_BAD_VAL =
"%s: In %s: Parameter '%s' has type %s, but '%s' cannot be parsed as such";
public static final String MSGPAT_INVALID_TIMEOUT_SYNTAX = "" +
"%s: Invalid %s syntax. Use [milliseconds]";
public static class ParseException extends Exception {
private Location loc;
public ParseException(Location loc, String message) {
super(message);
this.loc = loc;
}
public Location getLocation() {
return loc;
}
}
protected record Location(String fileName, int lineNo) {
@Override
public String toString() {
@ -80,31 +104,36 @@ public abstract class ScriptAttributesParser {
}
}
protected interface OptType<T> {
static OptType<?> parse(Location loc, String typeName,
Map<String, UserType<?>> userEnums) {
protected interface OptType<T> extends ValStr.Decoder<T> {
static OptType<?> parse(Location loc, String typeName, Map<String, UserType<?>> userEnums)
throws ParseException {
OptType<?> type = BaseType.parseNoErr(typeName);
if (type == null) {
type = userEnums.get(typeName);
}
if (type == null) { // still
Msg.error(ScriptAttributesParser.class,
"%s: Invalid type %s".formatted(loc, typeName));
return null;
throw new ParseException(loc, "%s: Invalid type %s".formatted(loc, typeName));
}
return type;
}
default TypeAndDefault<T> withCastDefault(Object defaultValue) {
return new TypeAndDefault<>(this, cls().cast(defaultValue));
default TypeAndDefault<T> withCastDefault(ValStr<Object> defaultValue) {
return new TypeAndDefault<>(this, ValStr.cast(cls(), defaultValue));
}
Class<T> cls();
T decode(Location loc, String str);
default T decode(Location loc, String str) throws ParseException {
try {
return decode(str);
}
catch (Exception e) {
throw new ParseException(loc, "%s: %s".formatted(loc, e.getMessage()));
}
}
ParameterDescription<T> createParameter(String name, T defaultValue, String display,
String description);
LaunchParameter<T> createParameter(String name, String display, String description,
boolean required, ValStr<T> defaultValue);
}
protected interface BaseType<T> extends OptType<T> {
@ -120,12 +149,10 @@ public abstract class ScriptAttributesParser {
};
}
public static BaseType<?> parse(Location loc, String typeName) {
public static BaseType<?> parse(Location loc, String typeName) throws ParseException {
BaseType<?> type = parseNoErr(typeName);
if (type == null) {
Msg.error(ScriptAttributesParser.class,
"%s: Invalid base type %s".formatted(loc, typeName));
return null;
throw new ParseException(loc, "%s: Invalid base type %s".formatted(loc, typeName));
}
return type;
}
@ -137,7 +164,7 @@ public abstract class ScriptAttributesParser {
}
@Override
public String decode(Location loc, String str) {
public String decode(String str) {
return str;
}
};
@ -149,18 +176,14 @@ public abstract class ScriptAttributesParser {
}
@Override
public BigInteger decode(Location loc, String str) {
public BigInteger decode(String str) {
try {
if (str.startsWith("0x")) {
return new BigInteger(str.substring(2), 16);
}
return new BigInteger(str);
return NumericUtilities.decodeBigInteger(str);
}
catch (NumberFormatException e) {
Msg.error(ScriptAttributesParser.class,
("%s: Invalid int for %s: %s. You may prefix with 0x for hexadecimal. " +
"Otherwise, decimal is used.").formatted(loc, AT_ENV, str));
return null;
throw new IllegalArgumentException(
"Invalid int %s. Prefixes 0x, 0b, and 0 (octal) are allowed."
.formatted(str));
}
}
};
@ -172,17 +195,16 @@ public abstract class ScriptAttributesParser {
}
@Override
public Boolean decode(Location loc, String str) {
Boolean result = switch (str) {
public Boolean decode(String str) {
Boolean result = switch (str.trim().toLowerCase()) {
case "true" -> true;
case "false" -> false;
default -> null;
};
if (result == null) {
Msg.error(ScriptAttributesParser.class,
"%s: Invalid bool for %s: %s. Only true or false (in lower case) is allowed."
.formatted(loc, AT_ENV, str));
return null;
throw new IllegalArgumentException(
"Invalid bool for %s: %s. Only true or false is allowed."
.formatted(AT_ENV, str));
}
return result;
}
@ -195,7 +217,7 @@ public abstract class ScriptAttributesParser {
}
@Override
public Path decode(Location loc, String str) {
public Path decode(String str) {
return Paths.get(str);
}
};
@ -207,7 +229,7 @@ public abstract class ScriptAttributesParser {
}
@Override
public PathIsDir decode(Location loc, String str) {
public PathIsDir decode(String str) {
return new PathIsDir(Paths.get(str));
}
};
@ -219,7 +241,7 @@ public abstract class ScriptAttributesParser {
}
@Override
public PathIsFile decode(Location loc, String str) {
public PathIsFile decode(String str) {
return new PathIsFile(Paths.get(str));
}
};
@ -228,11 +250,15 @@ public abstract class ScriptAttributesParser {
return new UserType<>(this, choices.stream().map(cls()::cast).toList());
}
default UserType<T> withChoices(List<T> choices) {
return new UserType<>(this, choices);
}
@Override
default ParameterDescription<T> createParameter(String name, T defaultValue, String display,
String description) {
return ParameterDescription.create(cls(), name, false, defaultValue, display,
description);
default LaunchParameter<T> createParameter(String name, String display, String description,
boolean required, ValStr<T> defaultValue) {
return LaunchParameter.create(cls(), name, display, description, required, defaultValue,
this);
}
}
@ -243,62 +269,57 @@ public abstract class ScriptAttributesParser {
}
@Override
public T decode(Location loc, String str) {
return base.decode(loc, str);
public T decode(String str) {
return base.decode(str);
}
@Override
public ParameterDescription<T> createParameter(String name, T defaultValue, String display,
String description) {
return ParameterDescription.choices(cls(), name, choices, defaultValue, display,
description);
public LaunchParameter<T> createParameter(String name, String display, String description,
boolean required, ValStr<T> defaultValue) {
return LaunchParameter.choices(cls(), name, display, description, choices,
defaultValue);
}
}
protected record TypeAndDefault<T>(OptType<T> type, T defaultValue) {
protected record TypeAndDefault<T>(OptType<T> type, ValStr<T> defaultValue) {
public static TypeAndDefault<?> parse(Location loc, String typeName, String defaultString,
Map<String, UserType<?>> userEnums) {
Map<String, UserType<?>> userEnums) throws ParseException {
OptType<?> tac = OptType.parse(loc, typeName, userEnums);
if (tac == null) {
return null;
}
Object value = tac.decode(loc, defaultString);
if (value == null) {
return null;
}
return tac.withCastDefault(value);
return tac.withCastDefault(new ValStr<>(value, defaultString));
}
public ParameterDescription<T> createParameter(String name, String display,
String description) {
return type.createParameter(name, defaultValue, display, description);
public LaunchParameter<T> createParameter(String name, String display, String description) {
return type.createParameter(name, display, description, false, defaultValue);
}
}
public interface TtyCondition {
boolean isActive(Map<String, ?> args);
boolean isActive(Map<String, ValStr<?>> args);
}
enum ConstTtyCondition implements TtyCondition {
ALWAYS {
@Override
public boolean isActive(Map<String, ?> args) {
public boolean isActive(Map<String, ValStr<?>> args) {
return true;
}
},
}
record EqualsTtyCondition(String key, String repr) implements TtyCondition {
record EqualsTtyCondition(LaunchParameter<?> param, Object value) implements TtyCondition {
@Override
public boolean isActive(Map<String, ?> args) {
return Objects.toString(args.get(key)).equals(repr);
public boolean isActive(Map<String, ValStr<?>> args) {
ValStr<?> valStr = param.get(args);
return Objects.equals(valStr == null ? null : valStr.val(), value);
}
}
record BoolTtyCondition(String key) implements TtyCondition {
record BoolTtyCondition(LaunchParameter<Boolean> param) implements TtyCondition {
@Override
public boolean isActive(Map<String, ?> args) {
return args.get(key) instanceof Boolean b && b.booleanValue();
public boolean isActive(Map<String, ValStr<?>> args) {
ValStr<Boolean> valStr = param.get(args);
return valStr != null && valStr.val();
}
}
@ -318,9 +339,8 @@ public abstract class ScriptAttributesParser {
public record ScriptAttributes(String title, String description, List<String> menuPath,
String menuGroup, String menuOrder, Icon icon, HelpLocation helpLocation,
Map<String, ParameterDescription<?>> parameters, Map<String, TtyCondition> extraTtys,
int timeoutMillis, boolean noImage) {
}
Map<String, LaunchParameter<?>> parameters, Map<String, TtyCondition> extraTtys,
int timeoutMillis, boolean noImage) {}
/**
* Convert an arguments map into a command line and environment variables
@ -335,34 +355,35 @@ public abstract class ScriptAttributesParser {
* @param address the address of the listening TraceRmi socket
*/
public static void processArguments(List<String> commandLine, Map<String, String> env,
File script, Map<String, ParameterDescription<?>> parameters, Map<String, ?> args,
File script, Map<String, LaunchParameter<?>> parameters, Map<String, ValStr<?>> args,
SocketAddress address) {
commandLine.add(script.getAbsolutePath());
env.put("GHIDRA_HOME", Application.getInstallationDirectory().getAbsolutePath());
env.put(ENV_GHIDRA_HOME, Application.getInstallationDirectory().getAbsolutePath());
if (address != null) {
env.put("GHIDRA_TRACE_RMI_ADDR", sockToString(address));
env.put(ENV_GHIDRA_TRACE_RMI_ADDR, sockToString(address));
if (address instanceof InetSocketAddress tcp) {
env.put("GHIDRA_TRACE_RMI_HOST", tcp.getAddress().getHostAddress());
env.put("GHIDRA_TRACE_RMI_PORT", Integer.toString(tcp.getPort()));
env.put(ENV_GHIDRA_TRACE_RMI_HOST, tcp.getAddress().getHostAddress());
env.put(ENV_GHIDRA_TRACE_RMI_PORT, Integer.toString(tcp.getPort()));
}
}
ParameterDescription<?> paramDesc;
for (int i = 1; (paramDesc = parameters.get("arg:" + i)) != null; i++) {
commandLine.add(Objects.toString(paramDesc.get(args)));
LaunchParameter<?> param;
for (int i = 1; (param = parameters.get("arg:" + i)) != null; i++) {
// Don't use ValStr.str here. I'd like the script's input normalized
commandLine.add(Objects.toString(param.get(args).val()));
}
paramDesc = parameters.get("args");
if (paramDesc != null) {
commandLine.addAll(ShellUtils.parseArgs((String) paramDesc.get(args)));
param = parameters.get("args");
if (param != null) {
commandLine.addAll(ShellUtils.parseArgs(param.get(args).str()));
}
for (Entry<String, ParameterDescription<?>> ent : parameters.entrySet()) {
for (Entry<String, LaunchParameter<?>> ent : parameters.entrySet()) {
String key = ent.getKey();
if (key.startsWith(PREFIX_ENV)) {
String varName = key.substring(PREFIX_ENV.length());
env.put(varName, Objects.toString(ent.getValue().get(args)));
env.put(varName, Objects.toString(ent.getValue().get(args).val()));
}
}
}
@ -376,7 +397,7 @@ public abstract class ScriptAttributesParser {
private String iconId;
private HelpLocation helpLocation;
private final Map<String, UserType<?>> userTypes = new HashMap<>();
private final Map<String, ParameterDescription<?>> parameters = new LinkedHashMap<>();
private final Map<String, LaunchParameter<?>> parameters = new LinkedHashMap<>();
private final Map<String, TtyCondition> extraTtys = new LinkedHashMap<>();
private int timeoutMillis = AbstractTraceRmiLaunchOffer.DEFAULT_TIMEOUT_MILLIS;
private boolean noImage = false;
@ -401,9 +422,17 @@ public abstract class ScriptAttributesParser {
*/
protected abstract String removeDelimiter(String line);
public ScriptAttributes parseFile(File script) throws FileNotFoundException {
/**
* Parse the header from the give input stream
*
* @param stream the stream from of the input stream file
* @param scriptName the name of the script file
* @return the parsed attributes
* @throws IOException if there was an issue reading the stream
*/
public ScriptAttributes parseStream(InputStream stream, String scriptName) throws IOException {
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(new FileInputStream(script)))) {
new BufferedReader(new InputStreamReader(stream))) {
String line;
for (int lineNo = 1; (line = reader.readLine()) != null; lineNo++) {
if (ignoreLine(lineNo, line)) {
@ -413,9 +442,22 @@ public abstract class ScriptAttributesParser {
if (comment == null) {
break;
}
parseComment(new Location(script.getName(), lineNo), comment);
parseComment(new Location(scriptName, lineNo), comment);
}
return validate(script.getName());
return validate(scriptName);
}
}
/**
* Parse the header of the given script file
*
* @param script the file
* @return the parsed attributes
* @throws FileNotFoundException if the script file could not be found
*/
public ScriptAttributes parseFile(File script) throws FileNotFoundException {
try {
return parseStream(new FileInputStream(script), script.getName());
}
catch (FileNotFoundException e) {
// Avoid capture by IOException
@ -468,7 +510,7 @@ public abstract class ScriptAttributesParser {
protected void parseTitle(Location loc, String str) {
if (title != null) {
Msg.warn(this, "%s: Duplicate @title".formatted(loc));
reportWarning("%s: Duplicate %s".formatted(loc, AT_TITLE));
}
title = str;
}
@ -483,161 +525,222 @@ public abstract class ScriptAttributesParser {
protected void parseMenuPath(Location loc, String str) {
if (menuPath != null) {
Msg.warn(this, "%s: Duplicate %s".formatted(loc, AT_MENU_PATH));
reportWarning("%s: Duplicate %s".formatted(loc, AT_MENU_PATH));
}
menuPath = List.of(str.trim().split("\\."));
if (menuPath.isEmpty()) {
Msg.error(this,
reportError(
"%s: Empty %s. Ignoring.".formatted(loc, AT_MENU_PATH));
}
}
protected void parseMenuGroup(Location loc, String str) {
if (menuGroup != null) {
Msg.warn(this, "%s: Duplicate %s".formatted(loc, AT_MENU_GROUP));
reportWarning("%s: Duplicate %s".formatted(loc, AT_MENU_GROUP));
}
menuGroup = str;
}
protected void parseMenuOrder(Location loc, String str) {
if (menuOrder != null) {
Msg.warn(this, "%s: Duplicate %s".formatted(loc, AT_MENU_ORDER));
reportWarning("%s: Duplicate %s".formatted(loc, AT_MENU_ORDER));
}
menuOrder = str;
}
protected void parseIcon(Location loc, String str) {
if (iconId != null) {
Msg.warn(this, "%s: Duplicate %s".formatted(loc, AT_ICON));
reportWarning("%s: Duplicate %s".formatted(loc, AT_ICON));
}
iconId = str.trim();
if (!Gui.hasIcon(iconId)) {
Msg.error(this,
reportError(
"%s: Icon id %s not registered in the theme".formatted(loc, iconId));
}
}
protected void parseHelp(Location loc, String str) {
if (helpLocation != null) {
Msg.warn(this, "%s: Duplicate %s".formatted(loc, AT_HELP));
reportWarning("%s: Duplicate %s".formatted(loc, AT_HELP));
}
String[] parts = str.trim().split("#", 2);
if (parts.length != 2) {
Msg.error(this, MSGPAT_INVALID_HELP_SYNTAX.formatted(loc, AT_HELP));
reportError(MSGPAT_INVALID_HELP_SYNTAX.formatted(loc, AT_HELP));
return;
}
helpLocation = new HelpLocation(parts[0].trim(), parts[1].trim());
}
protected <T> UserType<T> parseEnumChoices(Location loc, BaseType<T> baseType,
List<String> choiceParts) {
List<T> choices = new ArrayList<>();
boolean err = false;
for (String s : choiceParts) {
try {
choices.add(baseType.decode(loc, s));
}
catch (ParseException e) {
reportError(e.getMessage());
}
}
if (err) {
return null;
}
return baseType.withChoices(choices);
}
protected void parseEnum(Location loc, String str) {
List<String> parts = ShellUtils.parseArgs(str);
if (parts.size() < 2) {
Msg.error(this, MSGPAT_INVALID_ENUM_SYNTAX.formatted(loc, AT_ENUM));
reportError(MSGPAT_INVALID_ENUM_SYNTAX.formatted(loc, AT_ENUM));
return;
}
String[] nameParts = parts.get(0).split(":", 2);
if (nameParts.length != 2) {
Msg.error(this, MSGPAT_INVALID_ENUM_SYNTAX.formatted(loc, AT_ENUM));
reportError(MSGPAT_INVALID_ENUM_SYNTAX.formatted(loc, AT_ENUM));
return;
}
String name = nameParts[0].trim();
BaseType<?> baseType = BaseType.parse(loc, nameParts[1]);
if (baseType == null) {
BaseType<?> baseType;
try {
baseType = BaseType.parse(loc, nameParts[1]);
}
catch (ParseException e) {
reportError(e.getMessage());
return;
}
List<?> choices = parts.stream().skip(1).map(s -> baseType.decode(loc, s)).toList();
if (choices.contains(null)) {
return;
UserType<?> userType = parseEnumChoices(loc, baseType, parts.subList(1, parts.size()));
if (userType == null) {
return; // errors already reported
}
UserType<?> userType = baseType.withCastChoices(choices);
if (userTypes.put(name, userType) != null) {
Msg.warn(this, "%s: Duplicate %s %s. Replaced.".formatted(loc, AT_ENUM, name));
reportWarning("%s: Duplicate %s %s. Replaced.".formatted(loc, AT_ENUM, name));
}
}
protected void parseEnv(Location loc, String str) {
List<String> parts = ShellUtils.parseArgs(str);
if (parts.size() != 3) {
Msg.error(this, MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV));
reportError(MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV));
return;
}
String[] nameParts = parts.get(0).split(":", 2);
if (nameParts.length != 2) {
Msg.error(this, MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV));
reportError(MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV));
return;
}
String trimmed = nameParts[0].trim();
String name = PREFIX_ENV + trimmed;
String[] tadParts = nameParts[1].split("=", 2);
if (tadParts.length != 2) {
Msg.error(this, MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV));
reportError(MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV));
return;
}
try {
TypeAndDefault<?> tad =
TypeAndDefault.parse(loc, tadParts[0].trim(), tadParts[1].trim(), userTypes);
ParameterDescription<?> param = tad.createParameter(name, parts.get(1), parts.get(2));
LaunchParameter<?> param = tad.createParameter(name, parts.get(1), parts.get(2));
if (parameters.put(name, param) != null) {
Msg.warn(this, "%s: Duplicate %s %s. Replaced.".formatted(loc, AT_ENV, trimmed));
reportWarning("%s: Duplicate %s %s. Replaced.".formatted(loc, AT_ENV, trimmed));
}
}
catch (ParseException e) {
reportError(e.getMessage());
}
}
protected void parseArg(Location loc, String str, int argNum) {
List<String> parts = ShellUtils.parseArgs(str);
if (parts.size() != 3) {
Msg.error(this, MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG));
reportError(MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG));
return;
}
String colonType = parts.get(0).trim();
if (!colonType.startsWith(":")) {
Msg.error(this, MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG));
return;
}
OptType<?> type = OptType.parse(loc, colonType.substring(1), userTypes);
if (type == null) {
reportError(MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG));
return;
}
OptType<?> type;
try {
type = OptType.parse(loc, colonType.substring(1), userTypes);
String name = PREFIX_ARG + argNum;
parameters.put(name, ParameterDescription.create(type.cls(), name, true, null,
parts.get(1), parts.get(2)));
parameters.put(name,
type.createParameter(name, parts.get(1), parts.get(2), true,
new ValStr<>(null, "")));
}
catch (ParseException e) {
reportError(e.getMessage());
}
}
protected void parseArgs(Location loc, String str) {
List<String> parts = ShellUtils.parseArgs(str);
if (parts.size() != 2) {
Msg.error(this, MSGPAT_INVALID_ARGS_SYNTAX.formatted(loc, AT_ARGS));
reportError(MSGPAT_INVALID_ARGS_SYNTAX.formatted(loc, AT_ARGS));
return;
}
ParameterDescription<String> parameter = ParameterDescription.create(String.class,
"args", false, "", parts.get(0), parts.get(1));
LaunchParameter<String> parameter = BaseType.STRING.createParameter(
"args", parts.get(0), parts.get(1), false, ValStr.str(""));
if (parameters.put(KEY_ARGS, parameter) != null) {
Msg.warn(this, "%s: Duplicate %s. Replaced".formatted(loc, AT_ARGS));
reportWarning("%s: Duplicate %s. Replaced".formatted(loc, AT_ARGS));
}
}
protected void putTty(Location loc, String name, TtyCondition condition) {
if (extraTtys.put(name, condition) != null) {
Msg.warn(this, "%s: Duplicate %s. Ignored".formatted(loc, AT_TTY));
reportWarning("%s: Duplicate %s. Ignored".formatted(loc, AT_TTY));
}
}
protected void parseTty(Location loc, String str) {
List<String> parts = ShellUtils.parseArgs(str);
switch (parts.size()) {
case 1:
case 1 -> {
putTty(loc, parts.get(0), ConstTtyCondition.ALWAYS);
return;
case 3:
}
case 3 -> {
if ("if".equals(parts.get(1))) {
putTty(loc, parts.get(0), new BoolTtyCondition(parts.get(2)));
LaunchParameter<?> param = parameters.get(parts.get(2));
if (param == null) {
reportError(
MSGPAT_INVALID_TTY_NO_PARAM.formatted(loc, AT_TTY, parts.get(2)));
return;
}
case 5:
if (param.type() != Boolean.class) {
reportError(
MSGPAT_INVALID_TTY_NOT_BOOL.formatted(loc, AT_TTY, param.name()));
return;
}
@SuppressWarnings("unchecked")
LaunchParameter<Boolean> asBoolParam = (LaunchParameter<Boolean>) param;
putTty(loc, parts.get(0), new BoolTtyCondition(asBoolParam));
return;
}
}
case 5 -> {
if ("if".equals(parts.get(1)) && "==".equals(parts.get(3))) {
putTty(loc, parts.get(0), new EqualsTtyCondition(parts.get(2), parts.get(4)));
LaunchParameter<?> param = parameters.get(parts.get(2));
if (param == null) {
reportError(
MSGPAT_INVALID_TTY_NO_PARAM.formatted(loc, AT_TTY, parts.get(2)));
return;
}
try {
Object value = param.decode(parts.get(4)).val();
putTty(loc, parts.get(0), new EqualsTtyCondition(param, value));
return;
}
catch (Exception e) {
reportError(MSGPAT_INVALID_TTY_BAD_VAL.formatted(loc, AT_TTY,
param.name(), param.type(), parts.get(4)));
return;
}
}
Msg.error(this, MSGPAT_INVALID_TTY_SYNTAX.formatted(loc, AT_TTY));
}
}
reportError(MSGPAT_INVALID_TTY_SYNTAX.formatted(loc, AT_TTY));
}
protected void parseTimeout(Location loc, String str) {
@ -645,7 +748,7 @@ public abstract class ScriptAttributesParser {
timeoutMillis = Integer.parseInt(str);
}
catch (NumberFormatException e) {
Msg.error(this, MSGPAT_INVALID_TIMEOUT_SYNTAX.formatted(loc, AT_TIMEOUT));
reportError(MSGPAT_INVALID_TIMEOUT_SYNTAX.formatted(loc, AT_TIMEOUT));
}
}
@ -654,12 +757,13 @@ public abstract class ScriptAttributesParser {
}
protected void parseUnrecognized(Location loc, String line) {
Msg.warn(this, "%s: Unrecognized metadata: %s".formatted(loc, line));
reportWarning("%s: Unrecognized metadata: %s".formatted(loc, line));
}
protected ScriptAttributes validate(String fileName) {
if (title == null) {
Msg.error(this, "%s is required. Using script file name.".formatted(AT_TITLE));
reportError(
"%s is required. Using script file name: '%s'".formatted(AT_TITLE, fileName));
title = fileName;
}
if (menuPath == null) {
@ -683,4 +787,12 @@ public abstract class ScriptAttributesParser {
private String getDescription() {
return description == null ? null : description.toString();
}
protected void reportWarning(String message) {
Msg.warn(this, message);
}
protected void reportError(String message) {
Msg.error(this, message);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
@ -69,7 +68,7 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateFileException;
public class TraceRmiHandler implements TraceRmiConnection {
public static final String VERSION = "11.1";
public static final String VERSION = "11.2";
protected static class VersionMismatchError extends TraceRmiError {
public VersionMismatchError(String remote) {
@ -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);

View File

@ -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,

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "ghidratrace"
version = "11.1.2"
version = "11.2"
authors = [
{ name="Ghidra Development Team" },
]

View File

@ -34,7 +34,7 @@ from .util import send_delimited, recv_delimited
# Other places to change:
# * every pyproject.toml file (incl. deps)
# * TraceRmiHandler.VERSION
VERSION = '11.1'
VERSION = '11.2'
class RemoteResult(Future):

View File

@ -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()));

View File

@ -0,0 +1,767 @@
/* ###
* 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.awt.*;
import java.awt.event.ActionEvent;
import java.beans.*;
import java.io.File;
import java.math.BigInteger;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.List;
import java.util.Map.Entry;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
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 docking.DialogComponentProvider;
import docking.options.editor.FileChooserEditor;
import docking.widgets.button.BrowseButton;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode;
import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils;
import ghidra.debug.api.ValStr;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.AutoConfigState.PathIsDir;
import ghidra.framework.plugintool.AutoConfigState.PathIsFile;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.*;
import ghidra.util.layout.PairLayout;
public abstract class AbstractDebuggerParameterDialog<P> extends DialogComponentProvider
implements PropertyChangeListener {
static final String KEY_MEMORIZED_ARGUMENTS = "memorizedArguments";
public static class BigIntEditor extends PropertyEditorSupport {
String asText = "";
@Override
public String getJavaInitializationString() {
Object value = getValue();
return value == null
? "null"
: "new BigInteger(\"%s\")".formatted(value);
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
/**
* Set asText first, since setValue will fire change listener. It will call getAsText().
*/
asText = text;
setValueNoAsText(text == null
? null
: NumericUtilities.decodeBigInteger(text));
}
public void setValueNoAsText(Object value) {
super.setValue(value);
}
@Override
public void setValue(Object value) {
super.setValue(value);
asText = value == null ? "" : value.toString();
}
@Override
public String getAsText() {
return asText;
}
}
public static class FileChooserPanel extends JPanel {
private final static int NUMBER_OF_COLUMNS = 20;
private final JTextField textField = new JTextField(NUMBER_OF_COLUMNS);
private final JButton browseButton = new BrowseButton();
private final Runnable propertyChange;
private GhidraFileChooser fileChooser; // lazy
public FileChooserPanel(Runnable propertyChange) {
this.propertyChange = propertyChange;
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
add(textField);
add(Box.createHorizontalStrut(5));
add(browseButton);
setBorder(BorderFactory.createEmptyBorder());
textField.addActionListener(e -> propertyChange.run());
textField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
propertyChange.run();
}
@Override
public void insertUpdate(DocumentEvent e) {
propertyChange.run();
}
@Override
public void changedUpdate(DocumentEvent e) {
propertyChange.run();
}
});
browseButton.addActionListener(e -> displayFileChooser());
}
public void setValue(File file) {
textField.setText(file == null ? "" : file.getAbsolutePath());
}
private void displayFileChooser() {
if (fileChooser == null) {
fileChooser = createFileChooser();
}
String path = textField.getText().trim();
if (!path.isEmpty()) {
File f = new File(path);
if (f.isDirectory()) {
fileChooser.setCurrentDirectory(f);
}
else {
File pf = f.getParentFile();
if (pf != null && pf.isDirectory()) {
fileChooser.setSelectedFile(f);
}
}
}
File chosen = fileChooser.getSelectedFile(true);
if (chosen != null) {
textField.setText(chosen.getAbsolutePath());
propertyChange.run();
}
}
protected String getTitle() {
return "Choose Path";
}
protected GhidraFileChooserMode getSelectionMode() {
return GhidraFileChooserMode.FILES_AND_DIRECTORIES;
}
private GhidraFileChooser createFileChooser() {
GhidraFileChooser chooser = new GhidraFileChooser(browseButton);
chooser.setTitle(getTitle());
chooser.setApproveButtonText(getTitle());
chooser.setFileSelectionMode(getSelectionMode());
// No way for script to specify filter....
return chooser;
}
}
/**
* Compared to {@link FileChooserEditor}, this does not require the user to enter a full path.
* Nor will it resolve file names against the working directory. It's just a text box with a
* file browser assist.
*/
public static class PathEditor extends PropertyEditorSupport {
private final FileChooserPanel panel = newChooserPanel();
protected FileChooserPanel newChooserPanel() {
return new FileChooserPanel(this::firePropertyChange);
}
@Override
public String getAsText() {
return panel.textField.getText().trim();
}
@Override
public Object getValue() {
String text = panel.textField.getText().trim();
if (text.isEmpty()) {
return null;
}
return Paths.get(text);
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (text == null || text.isBlank()) {
panel.textField.setText("");
}
else {
panel.textField.setText(text);
}
}
@Override
public void setValue(Object value) {
if (value == null) {
panel.textField.setText("");
}
else if (value instanceof String s) {
panel.textField.setText(s);
}
else if (value instanceof Path p) {
panel.textField.setText(p.toString());
}
else {
throw new IllegalArgumentException("value=" + value);
}
}
@Override
public boolean supportsCustomEditor() {
return true;
}
@Override
public Component getCustomEditor() {
return panel;
}
}
public static class PathIsDirEditor extends PathEditor {
@Override
protected FileChooserPanel newChooserPanel() {
return new FileChooserPanel(this::firePropertyChange) {
@Override
protected String getTitle() {
return "Choose Directory";
}
@Override
protected GhidraFileChooserMode getSelectionMode() {
return GhidraFileChooserMode.DIRECTORIES_ONLY;
}
};
}
@Override
public Object getValue() {
Object value = super.getValue();
if (value == null) {
return null;
}
if (value instanceof Path p) {
return new PathIsDir(p);
}
throw new AssertionError();
}
@Override
public void setValue(Object value) {
if (value instanceof PathIsDir dir) {
super.setValue(dir.path());
}
else {
super.setValue(value);
}
}
}
public static class PathIsFileEditor extends PathEditor {
@Override
protected FileChooserPanel newChooserPanel() {
return new FileChooserPanel(this::firePropertyChange) {
@Override
protected String getTitle() {
return "Choose File";
}
@Override
protected GhidraFileChooserMode getSelectionMode() {
return GhidraFileChooserMode.FILES_ONLY;
}
};
}
@Override
public Object getValue() {
Object value = super.getValue();
if (value == null) {
return null;
}
if (value instanceof Path p) {
return new PathIsFile(p);
}
throw new AssertionError();
}
@Override
public void setValue(Object value) {
if (value instanceof PathIsFile file) {
super.setValue(file.path());
}
else {
super.setValue(value);
}
}
}
static {
PropertyEditorManager.registerEditor(BigInteger.class, BigIntEditor.class);
PropertyEditorManager.registerEditor(Path.class, PathEditor.class);
PropertyEditorManager.registerEditor(PathIsDir.class, PathIsDirEditor.class);
PropertyEditorManager.registerEditor(PathIsFile.class, PathIsFileEditor.class);
}
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(Collection<?> choices) {
this.choices = choices.stream().distinct().toList();
this.tags = choices.stream().map(Objects::toString).toArray(String[]::new);
}
@Override
public void setValue(Object value) {
if (Objects.equals(value, this.value)) {
return;
}
if (!choices.contains(value)) {
throw new IllegalArgumentException("Unsupported value: " + value);
}
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;
}
@Override
public boolean supportsCustomEditor() {
return false;
}
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
synchronized (listeners) {
listeners.add(listener);
}
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
}
protected record NameTypePair(String name, Class<?> type) {
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]));
}
public final String encodeString() {
return name + "," + type.getName();
}
}
private final BidiMap<P, PropertyEditor> paramEditors = new DualLinkedHashBidiMap<>();
private JPanel panel;
private JLabel descriptionLabel;
private JPanel pairPanel;
private PairLayout layout;
protected JButton invokeButton;
protected JButton resetButton;
private final PluginTool tool;
// package access for testing
Map<String, P> parameters;
private Map<String, ValStr<?>> defaults = Map.of();
// TODO: Not sure this is the best keying, but I think it works.
private Map<NameTypePair, ValStr<?>> memorized = new HashMap<>();
private Map<String, ValStr<?>> arguments;
public AbstractDebuggerParameterDialog(PluginTool tool, String title, String buttonText,
Icon buttonIcon) {
super(title, true, true, true, false);
this.tool = tool;
populateComponents(buttonText, buttonIcon);
setRememberSize(false);
}
protected abstract String parameterName(P parameter);
protected abstract Class<?> parameterType(P parameter);
protected NameTypePair parameterNameAndType(P parameter) {
return new NameTypePair(parameterName(parameter), parameterType(parameter));
}
protected abstract String parameterLabel(P parameter);
protected abstract String parameterToolTip(P parameter);
protected abstract ValStr<?> parameterDefault(P parameter);
protected abstract Collection<?> parameterChoices(P parameter);
protected abstract Map<String, ValStr<?>> validateArguments(Map<String, P> parameters,
Map<String, ValStr<?>> arguments);
protected abstract void parameterSaveValue(P parameter, SaveState state, String key,
ValStr<?> value);
protected abstract ValStr<?> parameterLoadValue(P parameter, SaveState state, String key);
protected ValStr<?> computeInitialValue(P parameter) {
ValStr<?> val = memorized.computeIfAbsent(parameterNameAndType(parameter),
ntp -> defaults.get(parameterName(parameter)));
return val;
}
/**
* Prompt the user for the given arguments, all at once
*
* <p>
* This displays a single dialog with each option listed. The parameter map contains the
* description of each parameter to be displayed. The {@code initial} values are the values to
* pre-populate the options with, e.g., because they are saved from a previous session, or
* because they are the suggested values. If the user clicks the "Reset" button, the values are
* revered to the defaults given in each parameter's description, unless that value is
* overridden in {@code defaults}. This may be appropriate if a value is suggested for a
* (perhaps required) option that otherwise has no default.
*
* @param parameterMap the map of parameters, keyed by {@link #parameterName(Object)}. This map
* may be ordered to control the order of options displayed.
* @param initial the initial values of the options. If a key is not provided, the initial value
* is its default value. Extraneous keys are ignored.
* @param defaults the default values to use upon reset. If a key is not provided, the default
* is taken from the parameter description. Extraneous keys are ignored.
* @return the arguments provided by the user
*/
public Map<String, ValStr<?>> promptArguments(Map<String, P> parameterMap,
Map<String, ValStr<?>> initial, Map<String, ValStr<?>> defaults) {
setDefaults(defaults);
setParameters(parameterMap);
setMemorizedArguments(initial);
populateValues(initial);
tool.showDialog(this);
return getArguments();
}
protected void setParameters(Map<String, P> parameterMap) {
this.parameters = parameterMap;
for (P param : parameterMap.values()) {
if (!defaults.containsKey(parameterName(param))) {
defaults.put(parameterName(param), parameterDefault(param));
}
}
populateOptions();
}
protected void setMemorizedArguments(Map<String, ValStr<?>> initial) {
for (P param : parameters.values()) {
ValStr<?> val = initial.get(parameterName(param));
if (val != null) {
setMemorizedArgument(param, val);
}
}
}
protected void setDefaults(Map<String, ValStr<?>> defaults) {
this.defaults = new HashMap<>(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();
}
void invoke(ActionEvent evt) {
try {
this.arguments = validateArguments(parameters, collectArguments());
close();
}
catch (IllegalStateException e) {
setStatusText(e.getMessage(), MessageType.ERROR, true);
}
}
void reset(ActionEvent evt) {
this.arguments = null;
populateValues(defaults);
}
protected PropertyEditor createEditor(P parameter) {
Collection<?> choices = parameterChoices(parameter);
if (!choices.isEmpty()) {
return new ChoicesPropertyEditor(choices);
}
Class<?> type = parameterType(parameter);
PropertyEditor editor = PropertyEditorManager.findEditor(type);
if (editor != null) {
return editor;
}
Msg.warn(this, "No editor for " + type + "? Trying String instead");
editor = PropertyEditorManager.findEditor(String.class);
return editor;
}
// test access
PropertyEditor getEditor(P parameter) {
return paramEditors.get(parameter);
}
protected void setEditorValue(PropertyEditor editor, P param, ValStr<?> val) {
switch (val.val()) {
case null -> {
}
case BigInteger bi -> editor.setAsText(val.str());
default -> editor.setValue(val.val());
}
}
void populateOptions() {
pairPanel.removeAll();
paramEditors.clear();
for (P param : parameters.values()) {
JLabel label = new JLabel(parameterLabel(param));
label.setToolTipText(parameterToolTip(param));
pairPanel.add(label);
PropertyEditor editor = createEditor(param);
ValStr<?> val = computeInitialValue(param);
setEditorValue(editor, param, val);
editor.addPropertyChangeListener(this);
pairPanel.add(MiscellaneousUtils.getEditorComponent(editor));
paramEditors.put(param, editor);
}
}
void populateValues(Map<String, ValStr<?>> values) {
for (Map.Entry<String, ValStr<?>> ent : values.entrySet()) {
P param = parameters.get(ent.getKey());
if (param == null) {
continue;
}
PropertyEditor editor = paramEditors.get(param);
setEditorValue(editor, param, ent.getValue());
}
}
protected Map<String, ValStr<?>> collectArguments() {
Map<String, ValStr<?>> map = new LinkedHashMap<>();
Set<String> invalid = new LinkedHashSet<>();
for (Entry<P, PropertyEditor> ent : paramEditors.entrySet()) {
P param = ent.getKey();
PropertyEditor editor = ent.getValue();
ValStr<?> val = memorized.get(parameterNameAndType(param));
if (!Objects.equals(editor.getAsText(), val.str())) {
invalid.add(parameterLabel(param));
}
if (val != null) {
map.put(parameterName(param), val);
}
}
if (!invalid.isEmpty()) {
throw new IllegalStateException("Invalid value for " + invalid);
}
return map;
}
public Map<String, ValStr<?>> getArguments() {
return arguments;
}
void setMemorizedArgument(P parameter, ValStr<?> value) {
if (value == null) {
return;
}
memorized.put(parameterNameAndType(parameter), value);
}
public void forgetMemorizedArguments() {
memorized.clear();
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
PropertyEditor editor = (PropertyEditor) evt.getSource();
P param = paramEditors.getKey(editor);
memorized.put(parameterNameAndType(param),
new ValStr<>(editor.getValue(), editor.getAsText()));
}
public void writeConfigState(SaveState saveState) {
SaveState subState = new SaveState();
for (Map.Entry<NameTypePair, ValStr<?>> ent : memorized.entrySet()) {
NameTypePair ntp = ent.getKey();
P param = parameters.get(ntp.name());
if (param == null) {
continue;
}
parameterSaveValue(param, subState, ntp.encodeString(), ent.getValue());
}
saveState.putSaveState(KEY_MEMORIZED_ARGUMENTS, subState);
}
public void readConfigState(SaveState saveState) {
/**
* TODO: This method is defunct. It is only used by the DebuggerObjectsProvider, which is
* now deprecated, but I suspect other providers intend to use this in the same way. If
* those providers don't manually load/compute initial and default values at the time of
* prompting, then this will need to be fixed. The decode of the values will need to be
* delayed until (and repeated every time) parameters are populated.
*/
SaveState subState = saveState.getSaveState(KEY_MEMORIZED_ARGUMENTS);
if (subState == null) {
return;
}
for (String name : subState.getNames()) {
try {
NameTypePair ntp = NameTypePair.fromString(name);
P param = parameters.get(ntp.name());
if (param == null) {
continue;
}
memorized.put(ntp, parameterLoadValue(param, subState, ntp.encodeString()));
}
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);
}
}
}

View File

@ -0,0 +1,112 @@
/* ###
* 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.util.List;
import java.util.concurrent.*;
import java.util.stream.Stream;
import db.Transaction;
import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.core.debug.gui.action.DebuggerReadsMemoryTrait;
import ghidra.debug.api.target.Target;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
import ghidra.features.base.memsearch.bytesource.SearchRegion;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.program.TraceProgramView;
/**
* A byte source for searching the memory of a possibly-live target in the debugger.
*
* <p>
* Because we'd like the search to preserve its state over the lifetime of the target, and the
* target "changes" by navigating snapshots, we need to allow the view to move without requiring a
* new byte source to be constructed. We <em>cannot</em>, however, just blindly follow the
* {@link Navigatable} wherever it goes. This is roughly the equivalent of a {@link Program}, but
* with knowledge of the target to cause a refresh of actual target memory when necessary.
*/
public class DebuggerByteSource implements AddressableByteSource {
private final PluginTool tool;
private final TraceProgramView view;
private final Target target;
private final DebuggerReadsMemoryTrait readsMem;
public DebuggerByteSource(PluginTool tool, TraceProgramView view, Target target,
DebuggerReadsMemoryTrait readsMem) {
this.tool = tool;
this.view = view;
this.target = target;
this.readsMem = readsMem;
}
@Override
public int getBytes(Address address, byte[] bytes, int length) {
AddressSet set = new AddressSet(address, address.add(length - 1));
try {
readsMem.getAutoSpec()
.readMemory(tool, DebuggerCoordinates.NOWHERE.view(view).target(target), set)
.get(Target.TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
return view.getMemory().getBytes(address, bytes, 0, length);
}
catch (AddressOutOfBoundsException | MemoryAccessException | InterruptedException
| ExecutionException | TimeoutException e) {
return 0;
}
}
@Override
public List<SearchRegion> getSearchableRegions() {
AddressFactory factory = view.getTrace().getBaseAddressFactory();
List<AddressSpace> spaces = Stream.of(factory.getPhysicalSpaces())
.filter(s -> s.getType() != AddressSpace.TYPE_OTHER)
.toList();
if (spaces.size() == 1) {
return DebuggerSearchRegionFactory.ALL.stream()
.map(f -> f.createRegion(null))
.toList();
}
Stream<AddressSpace> concat =
Stream.concat(Stream.of((AddressSpace) null), spaces.stream());
return concat
.flatMap(s -> DebuggerSearchRegionFactory.ALL.stream().map(f -> f.createRegion(s)))
.toList();
}
@Override
public void invalidate() {
try (Transaction tx = view.getTrace().openTransaction("Invalidate memory")) {
TraceMemoryManager mm = view.getTrace().getMemoryManager();
for (AddressSpace space : view.getTrace().getBaseAddressFactory().getAddressSpaces()) {
if (!space.isMemorySpace()) {
continue;
}
TraceMemorySpace ms = mm.getMemorySpace(space, false);
if (ms == null) {
continue;
}
ms.setState(view.getSnap(), space.getMinAddress(), space.getMaxAddress(),
TraceMemoryState.UNKNOWN);
}
}
}
}

View File

@ -0,0 +1,130 @@
/* ###
* 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.util.List;
import ghidra.features.base.memsearch.bytesource.SearchRegion;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
public enum DebuggerSearchRegionFactory {
FULL_SPACE("All Addresses", """
Searches all memory in the space, regardless of known validity.""") {
@Override
AddressSetView getAddresses(AddressSpace space, Program program) {
AddressSet set = new AddressSet();
if (space != null) {
set.add(space.getMinAddress(), space.getMaxAddress());
return set;
}
for (AddressSpace s : program.getAddressFactory().getAddressSpaces()) {
set.add(s.getMinAddress(), s.getMaxAddress());
}
return set;
}
},
VALID("Valid Addresses", """
Searches listed memory regions in the space.""") {
@Override
AddressSetView getAddresses(AddressSpace space, Program program) {
AddressSet set = new AddressSet();
for (MemoryBlock block : program.getMemory().getBlocks()) {
if (space == null || space == block.getStart().getAddressSpace()) {
set.add(block.getAddressRange());
}
}
return set;
}
@Override
boolean isDefault(AddressSpace space) {
return space == null;
}
},
WRITABLE("Writable Addresses", """
Searches listed regions marked as writable in the space.""") {
@Override
AddressSetView getAddresses(AddressSpace space, Program program) {
AddressSet set = new AddressSet();
for (MemoryBlock block : program.getMemory().getBlocks()) {
if (block.isWrite() &&
(space == null || space == block.getStart().getAddressSpace())) {
set.add(block.getAddressRange());
}
}
return set;
}
};
public static final List<DebuggerSearchRegionFactory> ALL = List.of(values());
record DebuggerSearchRegion(DebuggerSearchRegionFactory factory, AddressSpace spaces)
implements SearchRegion {
@Override
public String getName() {
return factory.getName(spaces);
}
@Override
public String getDescription() {
return factory.getDescription(spaces);
}
@Override
public AddressSetView getAddresses(Program program) {
return factory.getAddresses(spaces, program);
}
@Override
public boolean isDefault() {
return factory.isDefault(spaces);
}
}
private final String namePrefix;
private final String description;
private DebuggerSearchRegionFactory(String namePrefix, String description) {
this.namePrefix = namePrefix;
this.description = description;
}
public SearchRegion createRegion(AddressSpace space) {
return new DebuggerSearchRegion(this, space);
}
String getName(AddressSpace space) {
if (space == null) {
return namePrefix;
}
return "%s (%s)".formatted(namePrefix, space.getName());
}
String getDescription(AddressSpace spaces) {
return description;
}
abstract AddressSetView getAddresses(AddressSpace space, Program program);
boolean isDefault(AddressSpace space) {
return false;
}
}

View File

@ -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;

View File

@ -51,8 +51,7 @@ import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
import ghidra.app.plugin.core.debug.disassemble.CurrentPlatformTraceDisassembleCommand;
import ghidra.app.plugin.core.debug.disassemble.CurrentPlatformTraceDisassembleCommand.Reqs;
import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin;
import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.OpenProgramAction;
import ghidra.app.plugin.core.debug.gui.action.*;
@ -76,6 +75,8 @@ import ghidra.debug.api.listing.MultiBlendedListingBackgroundColorModel;
import ghidra.debug.api.modules.DebuggerMissingModuleActionContext;
import ghidra.debug.api.modules.DebuggerStaticMappingChangeListener;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
import ghidra.features.base.memsearch.bytesource.EmptyByteSource;
import ghidra.framework.model.DomainFile;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
@ -1358,4 +1359,12 @@ public class DebuggerListingProvider extends CodeViewerProvider {
.setViewerPosition(vp.getIndex(), vp.getXOffset(), vp.getYOffset());
});
}
@Override
public AddressableByteSource getByteSource() {
if (current == DebuggerCoordinates.NOWHERE) {
return EmptyByteSource.INSTANCE;
}
return new DebuggerByteSource(tool, current.getView(), current.getTarget(), readsMemTrait);
}
}

View File

@ -34,8 +34,7 @@ import docking.menu.MultiStateDockingAction;
import docking.widgets.fieldpanel.support.ViewerPosition;
import generic.theme.GThemeDefaults.Colors;
import ghidra.app.plugin.core.byteviewer.*;
import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec;
@ -46,6 +45,8 @@ import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.debug.api.action.GoToInput;
import ghidra.debug.api.action.LocationTrackingSpec;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
import ghidra.features.base.memsearch.bytesource.EmptyByteSource;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
@ -666,4 +667,12 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
newProvider.panel.setViewerPosition(vp);
});
}
@Override
public AddressableByteSource getByteSource() {
if (current == DebuggerCoordinates.NOWHERE) {
return EmptyByteSource.INSTANCE;
}
return new DebuggerByteSource(tool, current.getView(), current.getTarget(), readsMemTrait);
}
}

View File

@ -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;
@ -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();

View File

@ -15,654 +15,75 @@
*/
package ghidra.app.plugin.core.debug.gui.objects.components;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.beans.*;
import java.io.File;
import java.math.BigInteger;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.Icon;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.text.StringEscapeUtils;
import org.jdom.Element;
import docking.DialogComponentProvider;
import docking.options.editor.FileChooserEditor;
import docking.widgets.button.BrowseButton;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils;
import ghidra.app.plugin.core.debug.gui.AbstractDebuggerParameterDialog;
import ghidra.dbg.target.TargetMethod;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.debug.api.ValStr;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.AutoConfigState.*;
import ghidra.framework.plugintool.AutoConfigState.ConfigStateField;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.Msg;
import ghidra.util.layout.PairLayout;
public class DebuggerMethodInvocationDialog extends DialogComponentProvider
implements PropertyChangeListener {
public static class BigIntEditor extends PropertyEditorSupport {
@Override
public String getJavaInitializationString() {
Object value = getValue();
return value == null
? "null"
: "new BigInteger(\"%s\")".formatted(value);
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(text == null
? null
: new BigInteger(text));
}
}
public static class FileChooserPanel extends JPanel {
private final static int NUMBER_OF_COLUMNS = 20;
private final JTextField textField = new JTextField(NUMBER_OF_COLUMNS);
private final JButton browseButton = new BrowseButton();
private final Runnable propertyChange;
private GhidraFileChooser fileChooser; // lazy
public FileChooserPanel(Runnable propertyChange) {
this.propertyChange = propertyChange;
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
add(textField);
add(Box.createHorizontalStrut(5));
add(browseButton);
setBorder(BorderFactory.createEmptyBorder());
textField.addActionListener(e -> propertyChange.run());
textField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
propertyChange.run();
}
@Override
public void insertUpdate(DocumentEvent e) {
propertyChange.run();
}
@Override
public void changedUpdate(DocumentEvent e) {
propertyChange.run();
}
});
browseButton.addActionListener(e -> displayFileChooser());
}
public void setValue(File file) {
textField.setText(file == null ? "" : file.getAbsolutePath());
}
private void displayFileChooser() {
if (fileChooser == null) {
fileChooser = createFileChooser();
}
String path = textField.getText().trim();
if (!path.isEmpty()) {
File f = new File(path);
if (f.isDirectory()) {
fileChooser.setCurrentDirectory(f);
}
else {
File pf = f.getParentFile();
if (pf != null && pf.isDirectory()) {
fileChooser.setSelectedFile(f);
}
}
}
File chosen = fileChooser.getSelectedFile(true);
if (chosen != null) {
textField.setText(chosen.getAbsolutePath());
propertyChange.run();
}
}
protected String getTitle() {
return "Choose Path";
}
protected GhidraFileChooserMode getSelectionMode() {
return GhidraFileChooserMode.FILES_AND_DIRECTORIES;
}
private GhidraFileChooser createFileChooser() {
GhidraFileChooser chooser = new GhidraFileChooser(browseButton);
chooser.setTitle(getTitle());
chooser.setApproveButtonText(getTitle());
chooser.setFileSelectionMode(getSelectionMode());
// No way for script to specify filter....
return chooser;
}
}
/**
* Compared to {@link FileChooserEditor}, this does not require the user to enter a full path.
* Nor will it resolve file names against the working directory. It's just a text box with a
* file browser assist.
*/
public static class PathEditor extends PropertyEditorSupport {
private final FileChooserPanel panel = newChooserPanel();
protected FileChooserPanel newChooserPanel() {
return new FileChooserPanel(this::firePropertyChange);
}
@Override
public String getAsText() {
return panel.textField.getText().trim();
}
@Override
public Object getValue() {
String text = panel.textField.getText().trim();
if (text.isEmpty()) {
return null;
}
return Paths.get(text);
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (text == null || text.isBlank()) {
panel.textField.setText("");
}
else {
panel.textField.setText(text);
}
}
@Override
public void setValue(Object value) {
if (value == null) {
panel.textField.setText("");
}
else if (value instanceof String s) {
panel.textField.setText(s);
}
else if (value instanceof Path p) {
panel.textField.setText(p.toString());
}
else {
throw new IllegalArgumentException("value=" + value);
}
}
@Override
public boolean supportsCustomEditor() {
return true;
}
@Override
public Component getCustomEditor() {
return panel;
}
}
public static class PathIsDirEditor extends PathEditor {
@Override
protected FileChooserPanel newChooserPanel() {
return new FileChooserPanel(this::firePropertyChange) {
@Override
protected String getTitle() {
return "Choose Directory";
}
@Override
protected GhidraFileChooserMode getSelectionMode() {
return GhidraFileChooserMode.DIRECTORIES_ONLY;
}
};
}
@Override
public Object getValue() {
Object value = super.getValue();
if (value == null) {
return null;
}
if (value instanceof Path p) {
return new PathIsDir(p);
}
throw new AssertionError();
}
@Override
public void setValue(Object value) {
if (value instanceof PathIsDir dir) {
super.setValue(dir.path());
}
else {
super.setValue(value);
}
}
}
public static class PathIsFileEditor extends PathEditor {
@Override
protected FileChooserPanel newChooserPanel() {
return new FileChooserPanel(this::firePropertyChange) {
@Override
protected String getTitle() {
return "Choose File";
}
@Override
protected GhidraFileChooserMode getSelectionMode() {
return GhidraFileChooserMode.FILES_ONLY;
}
};
}
@Override
public Object getValue() {
Object value = super.getValue();
if (value == null) {
return null;
}
if (value instanceof Path p) {
return new PathIsFile(p);
}
throw new AssertionError();
}
@Override
public void setValue(Object value) {
if (value instanceof PathIsFile file) {
super.setValue(file.path());
}
else {
super.setValue(value);
}
}
}
static {
PropertyEditorManager.registerEditor(BigInteger.class, BigIntEditor.class);
PropertyEditorManager.registerEditor(Path.class, PathEditor.class);
PropertyEditorManager.registerEditor(PathIsDir.class, PathIsDirEditor.class);
PropertyEditorManager.registerEditor(PathIsFile.class, PathIsFileEditor.class);
}
private static final String KEY_MEMORIZED_ARGUMENTS = "memorizedArguments";
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);
}
@Override
public void setValue(Object value) {
if (Objects.equals(value, this.value)) {
return;
}
if (!choices.contains(value)) {
throw new IllegalArgumentException("Unsupported value: " + value);
}
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;
}
@Override
public boolean supportsCustomEditor() {
return false;
}
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
synchronized (listeners) {
listeners.add(listener);
}
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
}
final static class NameTypePair extends MutablePair<String, Class<?>> {
public static NameTypePair fromParameter(ParameterDescription<?> parameter) {
return new NameTypePair(parameter.name, parameter.type);
}
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]));
}
public NameTypePair(String name, Class<?> type) {
super(name, type);
}
@Override
public String toString() {
return getName() + "," + getType().getName();
}
@Override
public Class<?> setValue(Class<?> value) {
throw new UnsupportedOperationException();
}
public String getName() {
return getLeft();
}
public Class<?> getType() {
return getRight();
}
}
private final BidiMap<ParameterDescription<?>, PropertyEditor> paramEditors =
new DualLinkedHashBidiMap<>();
private JPanel panel;
private JLabel descriptionLabel;
private JPanel pairPanel;
private PairLayout layout;
protected JButton invokeButton;
protected JButton resetButton;
protected boolean resetRequested;
private final PluginTool tool;
Map<String, ParameterDescription<?>> parameters;
// TODO: Not sure this is the best keying, but I think it works.
private Map<NameTypePair, Object> memorized = new HashMap<>();
private Map<String, ?> arguments;
public class DebuggerMethodInvocationDialog
extends AbstractDebuggerParameterDialog<ParameterDescription<?>> {
public DebuggerMethodInvocationDialog(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(ParameterDescription<?> parameter) {
return memorized.computeIfAbsent(NameTypePair.fromParameter(parameter),
ntp -> parameter.defaultValue);
}
public Map<String, ?> promptArguments(Map<String, ParameterDescription<?>> parameterMap) {
setParameters(parameterMap);
tool.showDialog(this);
return getArguments();
}
public void setParameters(Map<String, ParameterDescription<?>> parameterMap) {
this.parameters = parameterMap;
populateOptions();
}
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);
resetRequested = false;
super(tool, title, buttonText, buttonIcon);
}
@Override
protected void cancelCallback() {
this.arguments = null;
this.resetRequested = false;
close();
}
void invoke(ActionEvent evt) {
this.arguments = TargetMethod.validateArguments(parameters, collectArguments(), false);
this.resetRequested = false;
close();
}
void reset(ActionEvent evt) {
this.arguments = new LinkedHashMap<>();
this.resetRequested = true;
close();
}
protected PropertyEditor getEditor(ParameterDescription<?> param) {
if (!param.choices.isEmpty()) {
return new ChoicesPropertyEditor(param.choices);
}
Class<?> type = param.type;
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);
}
void populateOptions() {
pairPanel.removeAll();
paramEditors.clear();
for (ParameterDescription<?> param : parameters.values()) {
JLabel label = new JLabel(param.display);
label.setToolTipText(param.description);
pairPanel.add(label);
PropertyEditor editor = getEditor(param);
Object val = computeMemorizedValue(param);
if (val == null) {
editor.setValue("");
}
else {
editor.setValue(val);
}
editor.addPropertyChangeListener(this);
pairPanel.add(MiscellaneousUtils.getEditorComponent(editor));
paramEditors.put(param, editor);
}
}
protected Map<String, ?> collectArguments() {
Map<String, Object> map = new LinkedHashMap<>();
for (ParameterDescription<?> param : paramEditors.keySet()) {
Object val = memorized.get(NameTypePair.fromParameter(param));
if (val != null) {
map.put(param.name, val);
}
}
return map;
}
public Map<String, ?> getArguments() {
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)));
}
public void forgetMemorizedArguments() {
memorized.clear();
protected String parameterName(ParameterDescription<?> parameter) {
return parameter.name;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
PropertyEditor editor = (PropertyEditor) evt.getSource();
ParameterDescription<?> param = paramEditors.getKey(editor);
memorized.put(NameTypePair.fromParameter(param), editor.getValue());
protected Class<?> parameterType(ParameterDescription<?> parameter) {
return parameter.type;
}
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.getType().asSubclass(Object.class),
ntp.getName(), ent.getValue());
}
saveState.putXmlElement(KEY_MEMORIZED_ARGUMENTS, subState.saveToXml());
@Override
protected String parameterLabel(ParameterDescription<?> parameter) {
return parameter.display;
}
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.getType(), ntp.getName()));
}
catch (Exception e) {
Msg.error(this, "Error restoring memorized parameter " + name, e);
}
}
@Override
protected String parameterToolTip(ParameterDescription<?> parameter) {
return parameter.description;
}
public boolean isResetRequested() {
return resetRequested;
@Override
protected ValStr<?> parameterDefault(ParameterDescription<?> parameter) {
return ValStr.from(parameter.defaultValue);
}
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 Set<?> parameterChoices(ParameterDescription<?> parameter) {
return parameter.choices;
}
@Override
protected Map<String, ValStr<?>> validateArguments(
Map<String, ParameterDescription<?>> parameters, Map<String, ValStr<?>> arguments) {
Map<String, ?> args = ValStr.toPlainMap(arguments);
return ValStr.fromPlainMap(TargetMethod.validateArguments(parameters, args, false));
}
@Override
protected void parameterSaveValue(ParameterDescription<?> parameter, SaveState state,
String key, ValStr<?> value) {
ConfigStateField.putState(state, parameter.type.asSubclass(Object.class), key, value.val());
}
@Override
protected ValStr<?> parameterLoadValue(ParameterDescription<?> parameter, SaveState state,
String key) {
return ValStr.from(ConfigStateField.getState(state, parameter.type, key));
}
}

View File

@ -65,8 +65,10 @@ class TraceBreakpointSet {
@Override
public String toString() {
synchronized (breakpoints) {
return String.format("<at %s in %s: %s>", address, trace.getName(), breakpoints);
}
}
/**
* Set the target when the trace is associated to a live target
@ -126,6 +128,7 @@ class TraceBreakpointSet {
*/
public TraceMode computeMode() {
TraceMode mode = TraceMode.NONE;
synchronized (breakpoints) {
if (getControlMode().useEmulatedBreakpoints()) {
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
mode = mode.combine(computeEmuMode(bpt.obj));
@ -143,6 +146,7 @@ class TraceBreakpointSet {
}
return mode;
}
}
/**
* Compute the mode (enablement) of the given breakpoint
@ -188,6 +192,7 @@ class TraceBreakpointSet {
*/
public String computeSleigh() {
String sleigh = null;
synchronized (breakpoints) {
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
String s = bpt.obj.getEmuSleigh();
if (sleigh != null && !sleigh.equals(s)) {
@ -197,6 +202,7 @@ class TraceBreakpointSet {
}
return sleigh;
}
}
/**
* Set the sleigh injection for all breakpoints in this set
@ -206,11 +212,13 @@ class TraceBreakpointSet {
public void setEmuSleigh(String emuSleigh) {
this.emuSleigh = emuSleigh;
try (Transaction tx = trace.openTransaction("Set breakpoint Sleigh")) {
synchronized (breakpoints) {
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
bpt.obj.setEmuSleigh(emuSleigh);
}
}
}
}
/**
* Check if this set actually contains any trace breakpoints
@ -218,8 +226,10 @@ class TraceBreakpointSet {
* @return true if empty, false otherwise
*/
public boolean isEmpty() {
synchronized (breakpoints) {
return breakpoints.isEmpty();
}
}
/**
* Get the breakpoints in this set
@ -227,8 +237,10 @@ class TraceBreakpointSet {
* @return the breakpoints
*/
public Set<TraceBreakpoint> getBreakpoints() {
synchronized (breakpoints) {
return breakpoints.stream().map(e -> e.obj).collect(Collectors.toUnmodifiableSet());
}
}
/**
* Add a breakpoint to this set
@ -246,8 +258,10 @@ class TraceBreakpointSet {
bpt.setEmuSleigh(emuSleigh);
}
}
synchronized (breakpoints) {
return breakpoints.add(new IDHashed<>(bpt));
}
}
/**
* Check if the given trace breakpoint "fits" in this set
@ -275,8 +289,10 @@ class TraceBreakpointSet {
* @return true if the set actually changes as a result
*/
public boolean remove(TraceBreakpoint bpt) {
synchronized (breakpoints) {
return breakpoints.remove(new IDHashed<>(bpt));
}
}
/**
* Plan to enable the logical breakpoint within this trace
@ -303,7 +319,7 @@ class TraceBreakpointSet {
public void planEnable(BreakpointActionSet actions, long length,
Collection<TraceBreakpointKind> kinds) {
long snap = getSnap();
if (breakpoints.isEmpty()) {
if (isEmpty()) {
if (target == null || getControlMode().useEmulatedBreakpoints()) {
planPlaceEmu(actions, snap, length, kinds);
}
@ -339,16 +355,20 @@ class TraceBreakpointSet {
}
private void planEnableTarget(BreakpointActionSet actions) {
synchronized (breakpoints) {
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
actions.planEnableTarget(target, bpt.obj);
}
}
}
private void planEnableEmu(BreakpointActionSet actions) {
synchronized (breakpoints) {
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
actions.planEnableEmu(bpt.obj);
}
}
}
/**
* Plan to disable the logical breakpoint in this trace
@ -369,16 +389,20 @@ class TraceBreakpointSet {
private void planDisableTarget(BreakpointActionSet actions, long length,
Collection<TraceBreakpointKind> kinds) {
synchronized (breakpoints) {
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
actions.planDisableTarget(target, bpt.obj);
}
}
}
private void planDisableEmu(BreakpointActionSet actions) {
synchronized (breakpoints) {
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
actions.planDisableEmu(bpt.obj);
}
}
}
/**
* Plan to delete the logical breakpoint in this trace
@ -399,14 +423,18 @@ class TraceBreakpointSet {
private void planDeleteTarget(BreakpointActionSet actions, long length,
Set<TraceBreakpointKind> kinds) {
synchronized (breakpoints) {
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
actions.planDeleteTarget(target, bpt.obj);
}
}
}
private void planDeleteEmu(BreakpointActionSet actions) {
synchronized (breakpoints) {
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
actions.planDeleteEmu(bpt.obj);
}
}
}
}

View File

@ -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,

View File

@ -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,
Map<String, ValStr<?>> defaultArgs = generateDefaultLauncherArgs(params);
Map<String, ValStr<?>> lastArgs = 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);
}
Map<String, ValStr<?>> args = dialog.promptArguments(params, lastArgs, defaultArgs);
saveLauncherArgs(args, params);
}
while (reset);
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) {

View File

@ -0,0 +1,180 @@
/* ###
* 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.service.modules;
import java.net.URL;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin.ChangeCollector;
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange;
import ghidra.framework.model.*;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.*;
import ghidra.util.Msg;
class InfoPerProgram implements DomainObjectListener {
static class NavMultiMap<K, V> {
private final TreeMap<K, Set<V>> map = new TreeMap<>();
public boolean put(K k, V v) {
return map.computeIfAbsent(k, __ -> new HashSet<>()).add(v);
}
public boolean remove(K k, V v) {
Set<V> set = map.get(k);
if (set == null) {
return false;
}
if (!set.remove(v)) {
return false;
}
if (set.isEmpty()) {
map.remove(k);
}
return true;
}
}
private final DebuggerStaticMappingServicePlugin plugin;
final Program program;
final NavMultiMap<Address, MappingEntry> inboundByStaticAddress = new NavMultiMap<>();
final URL url;
InfoPerProgram(DebuggerStaticMappingServicePlugin plugin, Program program) {
this.plugin = plugin;
this.program = program;
this.url = ProgramURLUtils.getUrlFromProgram(program);
program.addListener(this);
}
@Override
public void domainObjectChanged(DomainObjectChangedEvent ev) {
if (ev.contains(DomainObjectEvent.FILE_CHANGED) || ev.contains(DomainObjectEvent.RENAMED)) {
if (!urlMatches()) {
CompletableFuture.runAsync(plugin::programsChanged, plugin.executor);
}
}
}
boolean urlMatches() {
return Objects.equals(url, ProgramURLUtils.getUrlFromProgram(program));
}
void clearProgram(ChangeCollector cc, MappingEntry me) {
assert me.program == program;
inboundByStaticAddress.remove(me.getStaticAddress(), me);
me.clearProgram(cc, program);
}
void fillProgram(ChangeCollector cc, MappingEntry me) {
assert me.getStaticProgramUrl().equals(ProgramURLUtils.getUrlFromProgram(program));
me.fillProgram(cc, program);
inboundByStaticAddress.put(me.getStaticAddress(), me);
}
void clearEntries(ChangeCollector cc) {
if (url == null) {
return;
}
for (InfoPerTrace info : plugin.traceInfoByTrace.values()) {
info.clearEntriesForProgram(cc, this);
}
}
void fillEntries(ChangeCollector cc) {
if (url == null) {
return;
}
for (InfoPerTrace info : plugin.traceInfoByTrace.values()) {
info.fillEntriesForProgram(cc, this);
}
}
Set<TraceLocation> getOpenMappedTraceLocations(Address address) {
Set<TraceLocation> result = new HashSet<>();
for (Set<MappingEntry> set : inboundByStaticAddress.map.headMap(address, true).values()) {
for (MappingEntry me : set) {
if (me.mapping.isDeleted()) {
Msg.warn(this, "Encountered deleted mapping: " + me.mapping);
continue;
}
if (!me.isInProgramRange(address)) {
continue;
}
result.add(me.mapProgramAddressToTraceLocation(address));
}
}
return result;
}
TraceLocation getOpenMappedTraceLocation(Trace trace, Address address, long snap) {
// TODO: Map by trace?
for (Set<MappingEntry> set : inboundByStaticAddress.map.headMap(address, true).values()) {
for (MappingEntry me : set) {
if (me.mapping.isDeleted()) {
Msg.warn(this, "Encountered deleted mapping: " + me.mapping);
continue;
}
if (me.getTrace() != trace) {
continue;
}
if (!me.isInProgramRange(address)) {
continue;
}
if (!me.isInTraceLifespan(snap)) {
continue;
}
return me.mapProgramAddressToTraceLocation(address);
}
}
return null;
}
private void collectOpenMappedViews(Map<TraceSpan, Collection<MappedAddressRange>> result,
AddressRange rng) {
for (Set<MappingEntry> set : inboundByStaticAddress.map.headMap(rng.getMaxAddress(), true)
.values()) {
for (MappingEntry me : set) {
if (me.mapping.isDeleted()) {
Msg.warn(this, "Encountered deleted mapping: " + me.mapping);
continue;
}
// NB. No lifespan to consider
if (!me.isInProgramRange(rng)) {
continue;
}
AddressRange srcRange = me.getStaticRange().intersect(rng);
AddressRange dstRange = me.mapProgramRangeToTrace(rng);
result.computeIfAbsent(me.getTraceSpan(), p -> new TreeSet<>())
.add(new MappedAddressRange(srcRange, dstRange));
}
}
}
Map<TraceSpan, Collection<MappedAddressRange>> getOpenMappedViews(AddressSetView set) {
Map<TraceSpan, Collection<MappedAddressRange>> result = new HashMap<>();
for (AddressRange rng : set) {
collectOpenMappedViews(result, rng);
}
return Collections.unmodifiableMap(result);
}
}

View File

@ -0,0 +1,254 @@
/* ###
* 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.service.modules;
import java.net.URL;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin.ChangeCollector;
import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange;
import ghidra.framework.model.DomainObjectChangedEvent;
import ghidra.framework.model.DomainObjectEvent;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.modules.TraceStaticMapping;
import ghidra.trace.util.TraceEvents;
import ghidra.util.Msg;
class InfoPerTrace extends TraceDomainObjectListener {
private final DebuggerStaticMappingServicePlugin plugin;
final Trace trace;
final Map<TraceStaticMapping, MappingEntry> outboundByEntry = new HashMap<>();
final NavigableMap<TraceAddressSnapRange, MappingEntry> outboundByRange =
new TreeMap<>(Comparator.comparing(TraceAddressSnapRange::getX1));
final MultiValuedMap<URL, MappingEntry> outboundByStaticUrl = new HashSetValuedHashMap<>();
private volatile boolean needsResync = false;
InfoPerTrace(DebuggerStaticMappingServicePlugin plugin, Trace trace) {
this.plugin = plugin;
this.trace = trace;
listenForUntyped(DomainObjectEvent.RESTORED, e -> objectRestored());
listenFor(TraceEvents.MAPPING_ADDED, this::staticMappingAdded);
listenFor(TraceEvents.MAPPING_DELETED, this::staticMappingDeleted);
trace.addListener(this);
}
@Override
public void domainObjectChanged(DomainObjectChangedEvent ev) {
super.domainObjectChanged(ev); // Dispatch individual records
// Now do the actual processing
if (needsResync) {
needsResync = false;
CompletableFuture.runAsync(this::resyncEntries, plugin.executor);
}
}
private void objectRestored() {
this.needsResync = true;
}
private void staticMappingAdded(TraceStaticMapping mapping) {
this.needsResync = true;
}
private void staticMappingDeleted(TraceStaticMapping mapping) {
this.needsResync = true;
}
public void dispose() {
trace.removeListener(this);
}
private void resyncEntries() {
try (ChangeCollector cc = new ChangeCollector(plugin)) {
// Invoke change callbacks without the lock! (try must surround sync)
synchronized (plugin.lock) {
resyncEntries(cc);
}
}
}
void resyncEntries(ChangeCollector cc) {
Set<TraceStaticMapping> oldEntries = outboundByEntry.keySet();
Set<TraceStaticMapping> curEntries = trace.getStaticMappingManager()
.getAllEntries()
.stream()
.filter(e -> !e.isDeleted()) // Double-check
.collect(Collectors.toSet());
Set<TraceStaticMapping> removed = ChangeCollector.subtract(oldEntries, curEntries);
Set<TraceStaticMapping> added = ChangeCollector.subtract(curEntries, oldEntries);
processRemovedEntries(cc, removed);
processAddedEntries(cc, added);
}
void removeEntries(ChangeCollector cc) {
processRemovedEntries(cc, Set.copyOf(outboundByEntry.keySet()));
}
private void processRemovedEntries(ChangeCollector cc, Set<TraceStaticMapping> removed) {
for (TraceStaticMapping entry : removed) {
processRemovedEntry(cc, entry);
}
}
private void processRemovedEntry(ChangeCollector cc, TraceStaticMapping entry) {
MappingEntry me = outboundByEntry.remove(entry);
if (me == null) {
return;
}
outboundByRange.remove(me.getTraceAddressSnapRange());
outboundByStaticUrl.removeMapping(me.getStaticProgramUrl(), me);
plugin.checkAndClearProgram(cc, me);
}
private void processAddedEntries(ChangeCollector cc, Set<TraceStaticMapping> added) {
for (TraceStaticMapping entry : added) {
processAddedEntry(cc, entry);
}
}
private void processAddedEntry(ChangeCollector cc, TraceStaticMapping entry) {
MappingEntry me = new MappingEntry(entry);
outboundByEntry.put(entry, me);
outboundByRange.put(me.getTraceAddressSnapRange(), me);
outboundByStaticUrl.put(me.getStaticProgramUrl(), me);
plugin.checkAndFillProgram(cc, me);
}
void clearEntriesForProgram(ChangeCollector cc, InfoPerProgram progInfo) {
for (MappingEntry me : outboundByStaticUrl.get(progInfo.url)) {
progInfo.clearProgram(cc, me);
}
}
void fillEntriesForProgram(ChangeCollector cc, InfoPerProgram progInfo) {
for (MappingEntry me : outboundByStaticUrl.get(progInfo.url)) {
progInfo.fillProgram(cc, me);
}
}
Set<Program> getOpenMappedProgramsAtSnap(long snap) {
Set<Program> result = new HashSet<>();
for (Entry<TraceAddressSnapRange, MappingEntry> out : outboundByRange.entrySet()) {
MappingEntry me = out.getValue();
if (me.mapping.isDeleted()) {
Msg.warn(this, "Encountered deleted mapping: " + me.mapping);
continue;
}
if (!me.isStaticProgramOpen()) {
continue;
}
if (!out.getKey().getLifespan().contains(snap)) {
continue;
}
result.add(me.program);
}
return result;
}
ProgramLocation getOpenMappedProgramLocation(Address address, Lifespan span) {
TraceAddressSnapRange tasr = new ImmutableTraceAddressSnapRange(address, span);
// max is tasr (single address)
for (MappingEntry me : outboundByRange.headMap(tasr, true).values()) {
if (me.mapping.isDeleted()) {
Msg.warn(this, "Encountered deleted mapping: " + me.mapping);
continue;
}
if (!tasr.intersects(me.getTraceAddressSnapRange())) {
continue;
}
if (me.isStaticProgramOpen()) {
return me.mapTraceAddressToProgramLocation(address);
}
}
return null;
}
private void collectOpenMappedViews(Map<Program, Collection<MappedAddressRange>> result,
AddressRange rng, Lifespan span) {
TraceAddressSnapRange tasr = new ImmutableTraceAddressSnapRange(rng, span);
TraceAddressSnapRange max = new ImmutableTraceAddressSnapRange(rng.getMaxAddress(), span);
for (MappingEntry me : outboundByRange.headMap(max, true).values()) {
if (me.mapping.isDeleted()) {
Msg.warn(this, "Encountered deleted mapping: " + me.mapping);
continue;
}
if (me.program == null) {
continue;
}
if (!tasr.intersects(me.getTraceAddressSnapRange())) {
continue;
}
AddressRange srcRng = me.getTraceRange().intersect(rng);
AddressRange dstRng = me.mapTraceRangeToProgram(rng);
result.computeIfAbsent(me.program, p -> new TreeSet<>())
.add(new MappedAddressRange(srcRng, dstRng));
}
}
Map<Program, Collection<MappedAddressRange>> getOpenMappedViews(AddressSetView set,
Lifespan span) {
/**
* NB. Cannot use the OverlappingObjectIterator here. Because of the snap dimension, objects
* may not be disjoint in the address dimension.
*/
Map<Program, Collection<MappedAddressRange>> result = new HashMap<>();
for (AddressRange rng : set) {
collectOpenMappedViews(result, rng, span);
}
return Collections.unmodifiableMap(result);
}
private void collectMappedProgramUrlsInView(Set<URL> result, AddressRange rng, Lifespan span) {
TraceAddressSnapRange tasr = new ImmutableTraceAddressSnapRange(rng, span);
TraceAddressSnapRange max = new ImmutableTraceAddressSnapRange(rng.getMaxAddress(), span);
for (MappingEntry me : outboundByRange.headMap(max, true).values()) {
if (me.mapping.isDeleted()) {
Msg.warn(this, "Encountered deleted mapping: " + me.mapping);
continue;
}
if (!tasr.intersects(me.getTraceAddressSnapRange())) {
continue;
}
result.add(me.getStaticProgramUrl());
}
}
Set<URL> getMappedProgramUrlsInView(AddressSetView set, Lifespan span) {
/**
* NB. Cannot use the OverlappingObjectIterator here. Because of the snap dimension, objects
* may not be disjoint in the address dimension.
*/
Set<URL> result = new HashSet<>();
for (AddressRange rng : set) {
collectMappedProgramUrlsInView(result, rng, span);
}
return Collections.unmodifiableSet(result);
}
}

View File

@ -0,0 +1,202 @@
/* ###
* 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.service.modules;
import java.net.URL;
import java.util.Objects;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin.ChangeCollector;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.modules.TraceStaticMapping;
import ghidra.util.Msg;
class MappingEntry {
final TraceStaticMapping mapping;
final TraceAddressSnapRange tasr;
Program program;
private AddressRange staticRange;
public MappingEntry(TraceStaticMapping mapping) {
this.mapping = mapping;
// Yes, mapping range and lifespan are immutable
this.tasr = new ImmutableTraceAddressSnapRange(mapping.getTraceAddressRange(),
mapping.getLifespan());
}
@Override
public boolean equals(Object o) {
if (!(o instanceof MappingEntry that)) {
return false;
}
// Yes, use identity, since it should be the same trace db records
if (this.mapping != that.mapping) {
return false;
}
if (this.program != that.program) {
return false;
}
if (!Objects.equals(this.staticRange, that.staticRange)) {
return false;
}
return true;
}
public Trace getTrace() {
return mapping.getTrace();
}
Address addrOrMin(Program program, String addr) {
AddressFactory factory = program.getAddressFactory();
Address result = factory.getAddress(addr);
if (result == null) {
Msg.warn(this, "Mapping entry has invalid static address: " + addr);
result = factory.getDefaultAddressSpace().getMinAddress();
}
return result;
}
Address addrOrMax(Address start, long length) {
Address result = start.addWrapSpace(length);
if (result.compareTo(start) < 0) {
Msg.warn(this, "Mapping entry caused overflow in static address space");
return start.getAddressSpace().getMaxAddress();
}
return result;
}
void clearProgram(ChangeCollector cc, Program program) {
this.program = null;
this.staticRange = null;
cc.traceAffected(getTrace());
cc.programAffected(program);
}
void fillProgram(ChangeCollector cc, Program program) {
this.program = program;
Address minAddr = addrOrMin(program, mapping.getStaticAddress());
Address maxAddr = addrOrMax(minAddr, mapping.getLength() - 1);
this.staticRange = new AddressRangeImpl(minAddr, maxAddr);
cc.traceAffected(getTrace());
cc.programAffected(program);
}
public AddressRange getTraceRange() {
return mapping.getTraceAddressRange();
}
public Address getTraceAddress() {
return mapping.getMinTraceAddress();
}
public AddressRange getStaticRange() {
return staticRange;
}
public Address getStaticAddress() {
if (staticRange == null) {
return null;
}
return staticRange.getMinAddress();
}
public TraceSpan getTraceSpan() {
return new DefaultTraceSpan(mapping.getTrace(), mapping.getLifespan());
}
public TraceAddressSnapRange getTraceAddressSnapRange() {
return tasr;
}
public boolean isInTraceRange(Address address, Long snap) {
return mapping.getTraceAddressRange().contains(address) &&
(snap == null || mapping.getLifespan().contains(snap));
}
public boolean isInTraceRange(AddressRange rng, Long snap) {
return mapping.getTraceAddressRange().intersects(rng) &&
(snap == null || mapping.getLifespan().contains(snap));
}
public boolean isInTraceLifespan(long snap) {
return mapping.getLifespan().contains(snap);
}
public boolean isInProgramRange(Address address) {
if (staticRange == null) {
return false;
}
return staticRange.contains(address);
}
public boolean isInProgramRange(AddressRange rng) {
if (staticRange == null) {
return false;
}
return staticRange.intersects(rng);
}
protected Address mapTraceAddressToProgram(Address address) {
assert isInTraceRange(address, null);
long offset = address.subtract(mapping.getMinTraceAddress());
return staticRange.getMinAddress().addWrapSpace(offset);
}
public ProgramLocation mapTraceAddressToProgramLocation(Address address) {
if (program == null) {
throw new IllegalStateException("Static program is not opened");
}
return new ProgramLocation(program, mapTraceAddressToProgram(address));
}
public AddressRange mapTraceRangeToProgram(AddressRange rng) {
assert isInTraceRange(rng, null);
AddressRange part = rng.intersect(mapping.getTraceAddressRange());
Address min = mapTraceAddressToProgram(part.getMinAddress());
Address max = mapTraceAddressToProgram(part.getMaxAddress());
return new AddressRangeImpl(min, max);
}
protected Address mapProgramAddressToTrace(Address address) {
assert isInProgramRange(address);
long offset = address.subtract(staticRange.getMinAddress());
return mapping.getMinTraceAddress().addWrapSpace(offset);
}
protected TraceLocation mapProgramAddressToTraceLocation(Address address) {
return new DefaultTraceLocation(mapping.getTrace(), null, mapping.getLifespan(),
mapProgramAddressToTrace(address));
}
public AddressRange mapProgramRangeToTrace(AddressRange rng) {
assert (rng.intersects(staticRange));
AddressRange part = rng.intersect(staticRange);
Address min = mapProgramAddressToTrace(part.getMinAddress());
Address max = mapProgramAddressToTrace(part.getMaxAddress());
return new AddressRangeImpl(min, max);
}
public boolean isStaticProgramOpen() {
return program != null;
}
public URL getStaticProgramUrl() {
return mapping.getStaticProgramURL();
}
}

View File

@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.debug.service.tracemgr;
import static ghidra.framework.main.DataTreeDialogType.*;
import static ghidra.framework.main.DataTreeDialogType.OPEN;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
@ -656,8 +656,10 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
@Override
public synchronized Collection<Trace> getOpenTraces() {
synchronized (listenersByTrace) {
return Set.copyOf(tracesView);
}
}
@Override
public DebuggerCoordinates getCurrent() {
@ -982,11 +984,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
navigationHistoryService.clear(trace.getProgramView());
}
synchronized (listenersByTrace) {
trace.release(this);
lastCoordsByTrace.remove(trace);
trace.removeListener(listenersByTrace.remove(trace));
//Msg.debug(this, "Remaining Consumers of " + trace + ": " + trace.getConsumerList());
}
try {
if (current.getTrace() == trace) {
activate(DebuggerCoordinates.NOWHERE, ActivationCause.ACTIVATE_DEFAULT);
}
@ -994,12 +996,16 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
contextChanged();
}
}
finally {
trace.release(this);
}
}
protected void doCloseTraces(Collection<Trace> traces, Collection<Target> targets) {
for (Trace t : traces) {
if (t.getConsumerList().contains(this)) {
firePluginEvent(new TraceClosedPluginEvent(getName(), t));
doTraceClosed(t);
firePluginEvent(new TraceClosedPluginEvent(getName(), t));
}
}
TargetActionTask.executeTask(tool, new DisconnectTask(tool, targets));

View File

@ -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,18 +54,13 @@ 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.
/**
* 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);
}
throw new IllegalStateException(
"Ghidra does not know how to use PropertyEditor: " + editor.getClass().getName());
}
public static void rigFocusAndEnter(Component c, Runnable runnable) {
c.addFocusListener(new FocusAdapter() {
@Override

View File

@ -614,8 +614,7 @@ public abstract class AbstractGhidraHeadedDebuggerTest
@BeforeClass
public static void beforeClass() {
// Note: we may decided to move this up to a framework-level base test class
// Note: we decided to move this up to a framework-level base test class
TestDataStructureErrorHandlerInstaller.installConcurrentExceptionErrorHandler();
}
@ -656,7 +655,7 @@ public abstract class AbstractGhidraHeadedDebuggerTest
if (tb != null) {
if (traceManager != null && traceManager.getOpenTraces().contains(tb.trace)) {
traceManager.closeTrace(tb.trace);
traceManager.closeTraceNoConfirm(tb.trace);
}
tb.close();
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -95,6 +95,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
try (ToyDBTraceBuilder r = new ToyDBTraceBuilder(saved)) {
assertNotSame(tb.trace, r.trace);
traceManager.openTrace(r.trace);
waitForDomainObject(r.trace);
return r.trace;
}
}
@ -240,10 +241,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
}
@Test
public void testAddMappingThenCopyAndTranslateStaticToTraceMissWayBefore() throws Exception {
public void testAddMappingThenCopyAndTranslateStaticToTraceMissWayBefore() throws Throwable {
addMapping();
copyTrace();
add2ndMapping();
waitOn(mappingService.changesSettled());
Set<TraceLocation> locations = mappingService.getOpenMappedLocations(
new ProgramLocation(program, stSpace.getAddress(0x00000bad)));
@ -251,10 +253,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
}
@Test
public void testAddMappingThenCopyAndTranslateStaticToTraceMissJustBefore() throws Exception {
public void testAddMappingThenCopyAndTranslateStaticToTraceMissJustBefore() throws Throwable {
addMapping();
copyTrace();
add2ndMapping();
waitOn(mappingService.changesSettled());
Set<TraceLocation> locations = mappingService.getOpenMappedLocations(
new ProgramLocation(program, stSpace.getAddress(0x001fffff)));
@ -262,10 +265,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
}
@Test
public void testAddMappingThenCopyAndTranslateStaticToTraceHitAtStart() throws Exception {
public void testAddMappingThenCopyAndTranslateStaticToTraceHitAtStart() throws Throwable {
addMapping();
Trace copy = copyTrace();
add2ndMapping();
waitOn(mappingService.changesSettled());
Set<TraceLocation> locations = mappingService.getOpenMappedLocations(
new ProgramLocation(program, stSpace.getAddress(0x00200000)));
@ -281,10 +285,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
}
@Test
public void testAddMappingThenCopyAndTranslateStaticToTraceHitInMiddle() throws Exception {
public void testAddMappingThenCopyAndTranslateStaticToTraceHitInMiddle() throws Throwable {
addMapping();
Trace copy = copyTrace();
add2ndMapping();
waitOn(mappingService.changesSettled());
Set<TraceLocation> locations = mappingService.getOpenMappedLocations(
new ProgramLocation(program, stSpace.getAddress(0x00200833)));
@ -298,10 +303,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
}
@Test
public void testAddMappingThenCopyAndTranslateStaticToTraceHitAtEnd() throws Exception {
public void testAddMappingThenCopyAndTranslateStaticToTraceHitAtEnd() throws Throwable {
addMapping();
Trace copy = copyTrace();
add2ndMapping();
waitOn(mappingService.changesSettled());
Set<TraceLocation> locations = mappingService.getOpenMappedLocations(
new ProgramLocation(program, stSpace.getAddress(0x00200fff)));
@ -315,10 +321,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
}
@Test
public void testAddMappingThenCopyAndTranslateStaticToTraceMissJustAfter() throws Exception {
public void testAddMappingThenCopyAndTranslateStaticToTraceMissJustAfter() throws Throwable {
addMapping();
copyTrace();
add2ndMapping();
waitOn(mappingService.changesSettled());
Set<TraceLocation> locations = mappingService.getOpenMappedLocations(
new ProgramLocation(program, stSpace.getAddress(0x00201000)));
@ -326,10 +333,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
}
@Test
public void testAddMappingThenCopyAndTranslateStaticToTraceMissWayAfter() throws Exception {
public void testAddMappingThenCopyAndTranslateStaticToTraceMissWayAfter() throws Throwable {
addMapping();
copyTrace();
add2ndMapping();
waitOn(mappingService.changesSettled());
Set<TraceLocation> locations = mappingService.getOpenMappedLocations(
new ProgramLocation(program, stSpace.getAddress(0xbadbadbadL)));
@ -377,10 +385,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
}
@Test
public void testAddMappingThenTranslateStaticViewToTraceEmpty() throws Exception {
public void testAddMappingThenTranslateStaticViewToTraceEmpty() throws Throwable {
addMapping();
copyTrace();
add2ndMapping();
waitOn(mappingService.changesSettled());
Map<TraceSpan, Collection<MappedAddressRange>> views =
mappingService.getOpenMappedViews(program, new AddressSet());
@ -388,10 +397,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
}
@Test
public void testAddMappingThenTranslateStaticViewToTraceReplete() throws Exception {
public void testAddMappingThenTranslateStaticViewToTraceReplete() throws Throwable {
addMapping();
Trace copy = copyTrace();
add2ndMapping();
waitOn(mappingService.changesSettled());
AddressSet set = new AddressSet();
// Before
@ -438,10 +448,12 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
}
@Test
public void testAddMappingThenCloseStaticAndOpenMappedMissWayBefore() throws Exception {
public void testAddMappingThenCloseStaticAndOpenMappedMissWayBefore() throws Throwable {
// NOTE: Does not make sense to test program->trace, as program has no mapping records
addMapping();
programManager.closeProgram(program, true);
waitForSwing();
waitOn(mappingService.changesSettled());
AddressSet addrSet = new AddressSet(dynSpace.getAddress(0x00000bad));
Set<Program> programSet =
@ -451,9 +463,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
}
@Test
public void testAddMappingThenCloseStaticAndOpenMappedHitInMiddle() throws Exception {
public void testAddMappingThenCloseStaticAndOpenMappedHitInMiddle() throws Throwable {
addMapping();
programManager.closeProgram(program, true);
waitForSwing();
waitOn(mappingService.changesSettled());
AddressSet addrSet = new AddressSet(dynSpace.getAddress(0x00100c0d));
Set<Program> programSet =
@ -465,9 +479,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
}
@Test
public void testAddMappingThenCloseStaticAndOpenMappedMissWayAfter() throws Exception {
public void testAddMappingThenCloseStaticAndOpenMappedMissWayAfter() throws Throwable {
addMapping();
programManager.closeProgram(program, true);
waitForSwing();
waitOn(mappingService.changesSettled());
AddressSet addrSet = new AddressSet(dynSpace.getAddress(0xbadbadbadL));
Set<Program> programSet =
@ -478,14 +494,17 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
@Test
public void testAddMappingThenCloseStaticAndTranslateTraceToStaticHitInMiddle()
throws Exception {
throws Throwable {
addMapping();
waitOn(mappingService.changesSettled());
// pre-check
assertNotNull(mappingService.getOpenMappedLocation(
new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0),
dynSpace.getAddress(0x00100c0d))));
programManager.closeProgram(program, true);
waitForSwing();
waitOn(mappingService.changesSettled());
assertNull(mappingService.getOpenMappedLocation(
new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0),
@ -494,14 +513,16 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
@Test
public void testAddMappingThenCloseTraceAndTranslateStaticToTraceHitInMiddle()
throws Exception {
throws Throwable {
addMapping();
waitOn(mappingService.changesSettled());
// pre-check
assertEquals(1, mappingService.getOpenMappedLocations(
new ProgramLocation(program, stSpace.getAddress(0x00200c0d))).size());
traceManager.closeTrace(tb.trace);
waitForSwing();
waitOn(mappingService.changesSettled());
assertTrue(mappingService.getOpenMappedLocations(
new ProgramLocation(program, stSpace.getAddress(0x00200c0d))).isEmpty());
@ -509,9 +530,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
@Test
public void testAddMappingThenCloseAndReopenStaticAndTranslateTraceToStaticHitInMiddle()
throws Exception {
throws Throwable {
addMapping();
programManager.closeProgram(program, true);
waitForSwing();
waitOn(mappingService.changesSettled());
// pre-check
assertNull(mappingService.getOpenMappedLocation(
new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0),
@ -519,6 +542,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
programManager.openProgram(program);
waitForProgram(program);
waitOn(mappingService.changesSettled());
assertNotNull(mappingService.getOpenMappedLocation(
new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0),
@ -527,17 +551,19 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
@Test
public void testAddMappingThenCloseAndReopenTraceAndTranslateStaticToTraceHitInMiddle()
throws Exception {
throws Throwable {
addMapping();
traceManager.closeTrace(tb.trace);
waitForSwing();
waitOn(mappingService.changesSettled());
// pre-check
assertTrue(mappingService.getOpenMappedLocations(
new ProgramLocation(program, stSpace.getAddress(0x00200c0d))).isEmpty());
traceManager.openTrace(tb.trace);
waitForSwing();
waitForDomainObject(tb.trace);
waitOn(mappingService.changesSettled());
assertEquals(1, mappingService.getOpenMappedLocations(
new ProgramLocation(program, stSpace.getAddress(0x00200c0d))).size());
@ -545,7 +571,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
@Test
public void testAddMappingThenRemoveButAbortThenTranslateTraceToStaticHitInMiddle()
throws Exception {
throws Throwable {
addMapping();
TraceLocation goodLoc =
new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0),
@ -553,19 +579,23 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
try (Transaction tx = tb.startTransaction()) {
mappingManager.findContaining(dynSpace.getAddress(0x00100000), 0).delete();
waitForDomainObject(tb.trace);
waitOn(mappingService.changesSettled());
// pre-check
assertNull(mappingService.getOpenMappedLocation(goodLoc));
tx.abort();
}
waitForDomainObject(tb.trace);
waitOn(mappingService.changesSettled());
assertNotNull(mappingService.getOpenMappedLocation(goodLoc));
}
@Test
public void testAddCorrelationRemoveButUndoThenRequestMappingDynamicToStaticWithin()
throws Exception {
throws Throwable {
addMapping();
waitOn(mappingService.changesSettled());
TraceLocation goodLoc =
new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0),
dynSpace.getAddress(0x00100c0d));
@ -577,11 +607,13 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
mappingManager.findContaining(dynSpace.getAddress(0x00100000), 0).delete();
}
waitForDomainObject(tb.trace);
waitOn(mappingService.changesSettled());
// pre-check
assertNull(mappingService.getOpenMappedLocation(goodLoc));
undo(tb.trace, true);
waitOn(mappingService.changesSettled());
assertNotNull(mappingService.getOpenMappedLocation(goodLoc));
}
@ -619,7 +651,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
}
@Test
public void testMapFullSpace() throws Exception {
public void testMapFullSpace() throws Throwable {
try (Transaction tx = tb.startTransaction()) {
TraceLocation traceLoc =
new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), tb.addr(0));
@ -627,7 +659,10 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
// NB. 0 indicates 1 << 64
mappingService.addMapping(traceLoc, progLoc, 0, true);
}
waitForPass(() -> assertMapsTwoWay(0L, 0L));
waitForSwing();
waitOn(mappingService.changesSettled());
assertMapsTwoWay(0L, 0L);
assertMapsTwoWay(-1L, -1L);
assertMapsTwoWay(Long.MAX_VALUE, Long.MAX_VALUE);
assertMapsTwoWay(Long.MIN_VALUE, Long.MIN_VALUE);

View File

@ -230,11 +230,6 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject
return languageEntry == null ? manager.baseLanguage : languageEntry.getLanguage();
}
@Override
public AddressFactory getAddressFactory() {
return manager.trace.getBaseAddressFactory();
}
@Override
public CompilerSpec getCompilerSpec() {
return compilerSpec;

View File

@ -92,6 +92,11 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana
return trace.getBaseCompilerSpec();
}
@Override
public AddressFactory getAddressFactory() {
return trace.getBaseAddressFactory();
}
@Override
public AddressSetView getHostAddressSet() {
return trace.getBaseAddressFactory().getAddressSet();

View File

@ -33,7 +33,7 @@ import ghidra.util.database.*;
import ghidra.util.database.annot.*;
/**
* The implementation of a stack mapping, directly via a database object
* The implementation of a static mapping, directly via a database object
*
* <p>
* Version history:

View File

@ -26,7 +26,30 @@ import ghidra.program.model.listing.CodeUnit;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.util.AbstractPeekableIterator;
/**
* An iterator of overlapping objects return from two given iterators.
*
* <p>
* The given iterators, named left and right, must return objects each having a range attribute.
* Each iterator must return objects having disjoint ranges, i.e., no two objects from the
* <em>same</em> iterator may intersect. Each iterator must also return the objects sorted by min
* address. This iterator will then discover every case where an object from the left iterator
* overlaps an object from the right iterator, and return a pair for each such instance, in order of
* min address.
*
* <p>
* <b>WARNING:</b> To avoid heap pollution, this iterator re-uses the same {@link Pair} on each call
* to {@link #next()}. If you need to save an overlapping pair, you must copy it.
*
* @param <L> the type of objects returned by the left iterator
* @param <R> the type of objects returned by the right iterator
*/
public class OverlappingObjectIterator<L, R> extends AbstractPeekableIterator<Pair<L, R>> {
/**
* A means of obtaining the range attribute from each object
*
* @param <T> the type of objects returned by an iterator
*/
public interface Ranger<T> {
Address getMinAddress(T t);

View File

@ -960,4 +960,35 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
assertArrayEquals(b.arr(1, 2, 3, 4), read.array());
}
}
@Test
public void testReplicateNpeScenario() throws Exception {
ByteBuffer buf4k = ByteBuffer.allocate(0x1000);
AddressSetView set = b.set(
b.range(0x00400000, 0x00404fff),
b.range(0x00605000, 0x00606fff),
b.range(0x7ffff7a2c000L, 0x7ffff7a33fffL));
Random random = new Random();
for (int i = 0; i < 30; i++) {
try (Transaction tx = b.startTransaction()) {
for (int j = 0; j < 3; j++) {
for (AddressRange r : set) {
for (AddressRange rc : new AddressRangeChunker(r, 0x1000)) {
if (random.nextInt(100) < 20) {
memory.setState(0, rc, TraceMemoryState.ERROR);
continue;
}
buf4k.position(0);
buf4k.limit(0x1000);
memory.putBytes(0, rc.getMinAddress(), buf4k);
}
}
}
}
try (Transaction tx = b.startTransaction()) {
memory.setState(0, b.range(0, -1), TraceMemoryState.UNKNOWN);
}
}
}
}

View File

@ -1097,6 +1097,23 @@ public class RStarTreeMapTest {
assertTrue(obj.map.isEmpty());
}
@Test
public void testAddThenRemove1() {
assertTrue(obj.map.isEmpty());
try (Transaction tx = obj.openTransaction("AddPoint")) {
obj.map.put(new ImmutableIntRect(0, 0, 0, 10), "Test");
}
assertFalse(obj.map.isEmpty());
try (Transaction tx = obj.openTransaction("RemovePoint")) {
assertTrue(obj.map.remove(new ImmutableIntRect(0, 0, 0, 10), "Test"));
}
assertTrue(obj.map.isEmpty());
}
@Test
public void testClear() {
List<Pair<IntRect, String>> points = generatePoints(rect(1, 12, 1, 12));

View File

@ -42,6 +42,8 @@ src/main/help/help/topics/BSimSearchPlugin/images/BSimSearchDialog.png||GHIDRA||
src/main/help/help/topics/BSimSearchPlugin/images/ManageServersDialog.png||GHIDRA||||END|
src/main/resources/bsim.log4j.xml||GHIDRA||||END|
src/main/resources/images/checkmark_yellow.gif||GHIDRA||||END|
src/main/resources/images/connect.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/disconnect.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/flag_green.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/resources/images/preferences-desktop-user-password.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/resources/images/preferences-web-browser-shortcuts-32.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|

View File

@ -14,4 +14,7 @@ icon.bsim.results.status.ignored = checkmark_yellow.gif
icon.bsim.functions.table = FunctionScope.gif
icon.bsim.connected = connect.png
icon.bsim.disconnected = disconnect.png
[Dark Defaults]

View File

@ -71,23 +71,18 @@ public class AddProgramToH2BSimDatabaseScript extends GhidraScript {
askValues("Select Database File", null, values);
File h2DbFile = values.getFile(DATABASE);
FunctionDatabase h2Database = null;
try {
BSimServerInfo serverInfo =
new BSimServerInfo(DBType.file, null, 0, h2DbFile.getAbsolutePath());
h2Database = BSimClientFactory.buildClient(serverInfo, false);
BSimH2FileDataSource bds =
BSimH2FileDBConnectionManager.getDataSourceIfExists(h2Database.getServerInfo());
if (bds == null) {
popup(h2DbFile.getAbsolutePath() + " is not an H2 database file");
return;
}
if (bds.getActiveConnections() > 0) {
BSimH2FileDataSource existingBDS =
BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
if (existingBDS != null && existingBDS.getActiveConnections() > 0) {
popup("There is an existing connection to the database.");
return;
}
try (FunctionDatabase h2Database = BSimClientFactory.buildClient(serverInfo, false)) {
h2Database.initialize();
DatabaseInformation dbInfo = h2Database.getInfo();
@ -169,12 +164,14 @@ public class AddProgramToH2BSimDatabaseScript extends GhidraScript {
}
finally {
if (h2Database != null) {
h2Database.close();
if (existingBDS == null) {
// Dispose database source if it did not previously exist
BSimH2FileDataSource bds =
BSimH2FileDBConnectionManager.getDataSourceIfExists(h2Database.getServerInfo());
BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
if (bds != null) {
bds.dispose();
}
}
}
}
}

View File

@ -31,7 +31,6 @@ import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager;
import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource;
import ghidra.features.bsim.query.protocol.*;
import ghidra.util.MessageType;
import ghidra.util.Msg;
public class CreateH2BSimDatabaseScript extends GhidraScript {
private static final String NAME = "Database Name";
@ -80,8 +79,6 @@ public class CreateH2BSimDatabaseScript extends GhidraScript {
askValues("Enter Database Parameters",
"Enter values required to create a new BSim H2 database.", values);
FunctionDatabase h2Database = null;
try {
String databaseName = values.getString(NAME);
File dbDir = values.getFile(DIRECTORY);
String template = values.getChoice(DATABASE_TEMPLATE);
@ -92,20 +89,18 @@ public class CreateH2BSimDatabaseScript extends GhidraScript {
List<String> cats = parseCSV(exeCatCSV);
File dbFile = new File(dbDir, databaseName);
BSimServerInfo serverInfo =
new BSimServerInfo(DBType.file, null, 0, dbFile.getAbsolutePath());
h2Database = BSimClientFactory.buildClient(serverInfo, false);
BSimH2FileDataSource bds =
BSimH2FileDBConnectionManager.getDataSourceIfExists(h2Database.getServerInfo());
if (bds.getActiveConnections() > 0) {
//if this happens, there is a connection to the database but the
//database file was deleted
Msg.showError(this, null, "Connection Error",
"There is an existing connection to the database!");
BSimH2FileDataSource existingBDS =
BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
if (existingBDS != null && existingBDS.getActiveConnections() > 0) {
popup("There is an existing connection to the database.");
return;
}
try (FunctionDatabase h2Database = BSimClientFactory.buildClient(serverInfo, false)) {
CreateDatabase command = new CreateDatabase();
command.info = new DatabaseInformation();
// Put in fields provided on the command line
@ -140,13 +135,15 @@ public class CreateH2BSimDatabaseScript extends GhidraScript {
popup("Database " + values.getString(NAME) + " created successfully!");
}
finally {
if (h2Database != null) {
h2Database.close();
if (existingBDS == null) {
// Dispose database source if it did not previously exist
BSimH2FileDataSource bds =
BSimH2FileDBConnectionManager.getDataSourceIfExists(h2Database.getServerInfo());
BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
if (bds != null) {
bds.dispose();
}
}
}
}

View File

@ -57,18 +57,24 @@
entry shows a name for the BSim database, its type (postgres, elastic, or file), a host ip
and port (if applicable), and finally the number of active connections.</P>
<P>There are three primary actions for this dialog:</P>
<P>There are four primary actions for this dialog:</P>
<A name="Manage_Servers_Actions"></A>
<UL>
<LI><IMG alt="" src="Icons.ADD_ICON">&nbsp;Add a new database/server definition - a
Define Server Dialog will be shown.</LI>
<LI><IMG alt="" src="Icons.ADD_ICON">&nbsp;Add a new BSim database/server definition - an
Add BSim Server Dialog will be shown.</LI>
<LI><IMG alt="" src="Icons.DELETE_ICON">&nbsp;Delete a database/server definition - The
selected entry will be deleted.</LI>
selected entry will be deleted. This action will force an immediate disconnect for an
active/idle connection and should be used with care.</LI>
<LI><IMG alt="" src="icon.bsim.disconnected"><IMG alt="" src="icon.bsim.connected">&nbsp;
Connect or disconnect an inactive database/server connection. This action is not supported
by Elastic database servers.</LI>
<LI><IMG alt="" src="icon.bsim.change.password">&nbsp;Change password - A change password
dialog will appear for the selected entry</LI>
dialog will appear for the selected database server. Action is disabled for databases
which do not use a password (e.g., Local H2 File database).</LI>
</UL>
<H3><A name="Add_Server_Definition_Dialog">Defining a new BSim server/database</A></H3>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -72,7 +72,7 @@ public class BSimSearchPlugin extends ProgramPlugin {
private Set<BSimSearchResultsProvider> searchResultsProviders = new HashSet<>();
private Set<BSimOverviewProvider> overviewProviders = new HashSet<>();
private BSimServerManager serverManager = new BSimServerManager();
private BSimServerManager serverManager = BSimServerManager.getBSimServerManager();
private BSimSearchService searchService;
private BSimServerCache lastUsedServerCache = null;

View File

@ -13,16 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.bsim.gui.search.dialog;
package ghidra.features.bsim.gui;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import ghidra.features.bsim.query.BSimPostgresDBConnectionManager;
import ghidra.features.bsim.gui.search.dialog.BSimServerManagerListener;
import ghidra.features.bsim.query.*;
import ghidra.features.bsim.query.BSimPostgresDBConnectionManager.BSimPostgresDataSource;
import ghidra.features.bsim.query.BSimServerInfo;
import ghidra.features.bsim.query.BSimServerInfo.DBType;
import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager;
import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource;
@ -36,12 +36,24 @@ import ghidra.util.Swing;
* Managers BSim database server definitions and connections
*/
public class BSimServerManager {
// TODO: Do not allow removal of active server. Dispose data source when removed.
private static BSimServerManager instance;
/**
* Get static singleton instance for BSimServerManager
* @return BSimServerManager instance
*/
static synchronized BSimServerManager getBSimServerManager() {
if (instance == null) {
instance = new BSimServerManager();
}
return instance;
}
private Set<BSimServerInfo> serverInfos = new HashSet<>();
private List<BSimServerManagerListener> listeners = new CopyOnWriteArrayList<>();
public BSimServerManager() {
private BSimServerManager() {
List<File> files = Application.getUserSettingsFiles("bsim", ".server.properties");
for (File file : files) {
BSimServerInfo info = readBsimServerInfoFile(file);
@ -51,6 +63,10 @@ public class BSimServerManager {
}
}
/**
* Get list of defined servers. Method must be invoked from swing thread only.
* @return list of defined servers
*/
public Set<BSimServerInfo> getServerInfos() {
return new HashSet<>(serverInfos);
}
@ -108,6 +124,10 @@ public class BSimServerManager {
return serverFile.delete();
}
/**
* Add server to list. Method must be invoked from swing thread only.
* @param newServerInfo new BSim DB server
*/
public void addServer(BSimServerInfo newServerInfo) {
if (saveBSimServerInfo(newServerInfo)) {
serverInfos.add(newServerInfo);
@ -115,28 +135,42 @@ public class BSimServerManager {
}
}
public boolean removeServer(BSimServerInfo info, boolean force) {
private static boolean disposeServer(BSimServerInfo info, boolean force) {
DBType dbType = info.getDBType();
if (dbType == DBType.file) {
BSimH2FileDataSource ds = BSimH2FileDBConnectionManager.getDataSource(info);
BSimH2FileDataSource ds = BSimH2FileDBConnectionManager.getDataSourceIfExists(info);
if (ds != null) {
int active = ds.getActiveConnections();
if (active != 0) {
if (!force) {
if (active != 0 && !force) {
return false;
}
ds.dispose();
}
}
else if (dbType == DBType.postgres) {
BSimPostgresDataSource ds = BSimPostgresDBConnectionManager.getDataSource(info);
BSimPostgresDataSource ds = BSimPostgresDBConnectionManager.getDataSourceIfExists(info);
if (ds != null) {
int active = ds.getActiveConnections();
if (active != 0) {
if (!force) {
if (active != 0 && !force) {
return false;
}
ds.dispose();
}
}
return true;
}
/**
* Remove BSim DB server from list. Method must be invoked from swing thread only.
* Specified server datasource will be dispose unless it is active or force is true.
* @param info BSim DB server to be removed
* @param force true if server datasource should be disposed even when active.
* @return true if server disposed and removed from list
*/
public boolean removeServer(BSimServerInfo info, boolean force) {
if (!disposeServer(info, force)) {
return false;
}
if (serverInfos.remove(info)) {
removeServerFileFromSettings(info);
notifyServerListChanged();
@ -160,26 +194,38 @@ public class BSimServerManager {
});
}
public static int getActiveConnections(BSimServerInfo serverInfo) {
/**
* Convenience method to get existing BSim JDBC datasource
* @param serverInfo BSim DB server info
* @return BSim DB datasource or null if not instantiated or server does not support a
* {@link BSimJDBCDataSource}.
*/
public static BSimJDBCDataSource getDataSourceIfExists(BSimServerInfo serverInfo) {
switch (serverInfo.getDBType()) {
case postgres:
BSimPostgresDataSource postgresDs =
BSimPostgresDBConnectionManager.getDataSourceIfExists(serverInfo);
if (postgresDs != null) {
return postgresDs.getActiveConnections();
}
break;
return BSimPostgresDBConnectionManager.getDataSourceIfExists(serverInfo);
case file:
BSimH2FileDataSource h2FileDs =
BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
if (h2FileDs != null) {
return h2FileDs.getActiveConnections();
}
break;
return BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
default:
break;
return null;
}
}
/**
* Convenience method to get a new or existing BSim JDBC datasource
* @param serverInfo BSim DB server info
* @return BSim DB datasource or null if server does not support a
* {@link BSimJDBCDataSource}.
*/
public static BSimJDBCDataSource getDataSource(BSimServerInfo serverInfo) {
switch (serverInfo.getDBType()) {
case postgres:
return BSimPostgresDBConnectionManager.getDataSource(serverInfo);
case file:
return BSimH2FileDBConnectionManager.getDataSource(serverInfo);
default:
return null;
}
return -1;
}
}

View File

@ -30,6 +30,7 @@ import docking.widgets.EmptyBorderButton;
import docking.widgets.combobox.GComboBox;
import docking.widgets.textfield.FloatingPointTextField;
import generic.theme.Gui;
import ghidra.features.bsim.gui.BSimServerManager;
import ghidra.features.bsim.query.BSimServerInfo;
import ghidra.features.bsim.query.description.DatabaseInformation;
import ghidra.features.bsim.query.facade.QueryDatabaseException;

View File

@ -16,6 +16,7 @@
package ghidra.features.bsim.gui.search.dialog;
import ghidra.features.bsim.gui.BSimSearchPlugin;
import ghidra.features.bsim.gui.BSimServerManager;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;

View File

@ -30,6 +30,7 @@ import docking.widgets.textfield.IntegerTextField;
import generic.theme.GIcon;
import ghidra.app.services.GoToService;
import ghidra.features.bsim.gui.BSimSearchPlugin;
import ghidra.features.bsim.gui.BSimServerManager;
import ghidra.features.bsim.gui.filters.BSimFilterType;
import ghidra.features.bsim.query.description.DatabaseInformation;
import ghidra.framework.plugintool.PluginTool;

View File

@ -16,21 +16,25 @@
package ghidra.features.bsim.gui.search.dialog;
import java.awt.BorderLayout;
import java.sql.Connection;
import java.sql.SQLException;
import javax.swing.*;
import org.bouncycastle.util.Arrays;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import docking.action.DockingAction;
import docking.*;
import docking.action.*;
import docking.action.builder.ActionBuilder;
import docking.action.builder.ToggleActionBuilder;
import docking.widgets.OptionDialog;
import docking.widgets.PasswordChangeDialog;
import docking.widgets.table.GFilterTable;
import docking.widgets.table.GTable;
import generic.theme.GIcon;
import ghidra.features.bsim.gui.BSimServerManager;
import ghidra.features.bsim.query.*;
import ghidra.features.bsim.query.BSimServerInfo.DBType;
import ghidra.features.bsim.query.FunctionDatabase.Error;
import ghidra.features.bsim.query.FunctionDatabase.ErrorCategory;
import ghidra.framework.plugintool.PluginTool;
@ -42,15 +46,14 @@ import resources.Icons;
*/
public class BSimServerDialog extends DialogComponentProvider {
// TODO: Add connected status indicator (not sure how this relates to elastic case which will likely have a Session concept)
// TODO: Add "Disconnect" action (only works when active connections is 0; does not apply to elastic)
private PluginTool tool;
private BSimServerManager serverManager;
private BSimServerTableModel serverTableModel;
private GFilterTable<BSimServerInfo> filterTable;
private GFilterTable<BSimServerInfo> serverTable;
private BSimServerInfo lastAdded = null;
private ToggleDockingAction dbConnectionAction;
public BSimServerDialog(PluginTool tool, BSimServerManager serverManager) {
super("BSim Server Manager");
this.tool = tool;
@ -73,31 +76,98 @@ public class BSimServerDialog extends DialogComponentProvider {
HelpLocation help = new HelpLocation("BSimSearchPlugin", "Manage_Servers_Actions");
DockingAction addServerAction =
new ActionBuilder("Add Server", "Dialog").toolBarIcon(Icons.ADD_ICON)
new ActionBuilder("Add BSim Database", "Dialog").toolBarIcon(Icons.ADD_ICON)
.helpLocation(help)
.onAction(e -> defineBsimServer())
.build();
addAction(addServerAction);
DockingAction removeServerAction =
new ActionBuilder("Delete Server", "Dialog").toolBarIcon(Icons.DELETE_ICON)
new ActionBuilder("Delete BSim Database", "Dialog").toolBarIcon(Icons.DELETE_ICON)
.helpLocation(help)
.onAction(e -> deleteBsimServer())
.enabledWhen(c -> hasSelection())
.build();
addAction(removeServerAction);
DockingAction changePasswordAction = new ActionBuilder("Change User Password", "Dialog")
.helpLocation(help)
dbConnectionAction =
new ToggleActionBuilder("Toggle Database Connection", "Dialog").helpLocation(help)
.toolBarIcon(new GIcon("icon.bsim.disconnected"))
.onAction(e -> toggleSelectedJDBCDataSourceConnection())
.enabledWhen(c -> isNonActiveJDBCDataSourceSelected(c))
.build();
addAction(dbConnectionAction);
DockingAction changePasswordAction =
new ActionBuilder("Change User Password", "Dialog").helpLocation(help)
.toolBarIcon(new GIcon("icon.bsim.change.password"))
.onAction(e -> changePassword())
.enabledWhen(c -> hasSelection())
.enabledWhen(c -> canChangePassword())
.build();
addAction(changePasswordAction);
}
private void toggleSelectedJDBCDataSourceConnection() {
BSimServerInfo serverInfo = serverTable.getSelectedRowObject();
if (serverInfo == null || serverInfo.getDBType() == DBType.elastic) {
return;
}
BSimJDBCDataSource dataSource = BSimServerManager.getDataSourceIfExists(serverInfo);
if (dataSource == null) {
// connect
dataSource = BSimServerManager.getDataSource(serverInfo);
try (Connection connection = dataSource.getConnection()) {
// do nothing
}
catch (SQLException e) {
Msg.showError(this, rootPanel, "BSim Connection Failure", e.getMessage());
}
}
else {
dataSource.dispose();
}
serverTableModel.fireTableDataChanged();
notifyContextChanged();
}
private boolean isNonActiveJDBCDataSourceSelected(ActionContext c) {
BSimServerInfo serverInfo = serverTable.getSelectedRowObject();
if (serverInfo == null) {
return false;
}
// TODO: May need connection listener on dataSource to facilitate GUI update,
// although modal dialog avoids the issue somewhat
dbConnectionAction.setDescription(dbConnectionAction.getName());
ConnectionPoolStatus status = serverTableModel.getConnectionPoolStatus(serverInfo);
if (status.isActive) {
// Show connected icon
dbConnectionAction
.setToolBarData(new ToolBarData(new GIcon("icon.bsim.connected"), null));
dbConnectionAction.setSelected(true);
dbConnectionAction.setDescription("Disconnect idle BSim Database connection");
// disconnect permitted when no active connections
return status.activeCount == 0;
}
// Show disconnected icon (elastic always shown as disconnected)
dbConnectionAction
.setToolBarData(new ToolBarData(new GIcon("icon.bsim.disconnected"), null));
dbConnectionAction.setSelected(false);
dbConnectionAction.setDescription("Connect BSim Database");
// Action never enabled for elastic DB (i.e., does not use pooled JDBC data source)
return serverInfo.getDBType() != DBType.elastic;
}
private void changePassword() {
BSimServerInfo serverInfo = filterTable.getSelectedRowObject();
BSimServerInfo serverInfo = serverTable.getSelectedRowObject();
if (serverInfo == null) {
return;
}
@ -141,8 +211,13 @@ public class BSimServerDialog extends DialogComponentProvider {
}
}
private boolean canChangePassword() {
BSimServerInfo serverInfo = serverTable.getSelectedRowObject();
return serverInfo != null && serverInfo.getDBType() != DBType.file;
}
private void deleteBsimServer() {
BSimServerInfo selected = filterTable.getSelectedRowObject();
BSimServerInfo selected = serverTable.getSelectedRowObject();
if (selected != null) {
int answer =
OptionDialog.showYesNoDialog(getComponent(), "Delete Server Configuration?",
@ -152,7 +227,7 @@ public class BSimServerDialog extends DialogComponentProvider {
answer = OptionDialog.showOptionDialogWithCancelAsDefaultButton(getComponent(),
"Active Server Configuration!",
"Database connections are still active!\n" +
"Are you sure you want to delete server?",
"Are you sure you want to terminate connections and delete server?",
"Yes", OptionDialog.WARNING_MESSAGE);
if (answer == OptionDialog.YES_OPTION) {
serverManager.removeServer(selected, true);
@ -169,7 +244,7 @@ public class BSimServerDialog extends DialogComponentProvider {
if (newServerInfo != null) {
serverManager.addServer(newServerInfo);
lastAdded = newServerInfo;
Swing.runLater(() -> filterTable.setSelectedRowObject(newServerInfo));
Swing.runLater(() -> serverTable.setSelectedRowObject(newServerInfo));
}
}
@ -178,11 +253,11 @@ public class BSimServerDialog extends DialogComponentProvider {
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
serverTableModel = new BSimServerTableModel(serverManager);
filterTable = new GFilterTable<>(serverTableModel);
GTable table = filterTable.getTable();
serverTable = new GFilterTable<>(serverTableModel);
GTable table = serverTable.getTable();
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.getSelectionModel().addListSelectionListener(e -> notifyContextChanged());
panel.add(filterTable, BorderLayout.CENTER);
panel.add(serverTable, BorderLayout.CENTER);
if (serverTableModel.getRowCount() > 0) {
table.setRowSelectionInterval(0, 0);
@ -192,7 +267,7 @@ public class BSimServerDialog extends DialogComponentProvider {
}
private boolean hasSelection() {
return filterTable.getSelectedRowObject() != null;
return serverTable.getSelectedRowObject() != null;
}
public BSimServerInfo getLastAdded() {

View File

@ -16,27 +16,33 @@
package ghidra.features.bsim.gui.search.dialog;
import java.awt.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import javax.swing.Icon;
import javax.swing.JLabel;
import docking.widgets.table.*;
import ghidra.docking.settings.Settings;
import ghidra.features.bsim.gui.BSimServerManager;
import ghidra.features.bsim.query.BSimServerInfo;
import ghidra.features.bsim.query.BSimServerInfo.DBType;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.framework.plugintool.ServiceProviderStub;
import ghidra.program.model.listing.Program;
import ghidra.util.table.column.AbstractGColumnRenderer;
import ghidra.util.table.column.GColumnRenderer;
import ghidra.util.table.field.AbstractProgramBasedDynamicTableColumn;
/**
* Table model for BSim database server definitions
* Table model for BSim database server definitions.
*
* NOTE: This implementation assumes modal dialog use and non-changing connection state
* while instance is in-use. This was done to avoid adding a conection listener which could
* introduce excessive overhead into the connection pool use.
*/
public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInfo, Object> {
private List<BSimServerInfo> servers;
private Map<BSimServerInfo, ConnectionPoolStatus> statusCache = new HashMap<>();
private BSimServerManager serverManager;
private BSimServerManagerListener listener = new BSimServerManagerListener() {
@Override
@ -63,8 +69,18 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
}
@Override
public boolean isSortable(int columnIndex) {
return columnIndex != 0;
public void fireTableDataChanged() {
statusCache.clear();
super.fireTableDataChanged();
}
/**
* Get DB connection pool status for a specified server
* @param serverInfo server info
* @return connection pool status
*/
ConnectionPoolStatus getConnectionPoolStatus(BSimServerInfo serverInfo) {
return statusCache.computeIfAbsent(serverInfo, s -> new ConnectionPoolStatus(s));
}
@Override
@ -74,7 +90,7 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
descriptor.addVisibleColumn(new TypeColumn());
descriptor.addVisibleColumn(new HostColumn());
descriptor.addVisibleColumn(new PortColumn());
descriptor.addVisibleColumn(new ActiveConnectionColumn());
descriptor.addVisibleColumn(new ConnectionStatusColumn());
return descriptor;
}
@ -84,7 +100,7 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
}
private class DatabaseNameColumn
extends AbstractProgramBasedDynamicTableColumn<BSimServerInfo, String> {
extends AbstractDynamicTableColumn<BSimServerInfo, String, Object> {
private GColumnRenderer<String> renderer = new AbstractGColumnRenderer<>() {
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
@ -112,11 +128,8 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
}
@Override
public String getValue(BSimServerInfo serverInfo, Settings settings, Program data,
public String getValue(BSimServerInfo serverInfo, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
// FIXME: Get cell tooltip to show full getDBName which includes file path
return serverInfo.getShortDBName();
}
@ -131,8 +144,29 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
}
}
private class HostColumn
extends AbstractProgramBasedDynamicTableColumn<BSimServerInfo, String> {
private static class TypeColumn
extends AbstractDynamicTableColumn<BSimServerInfo, String, Object> {
@Override
public String getColumnName() {
return "Type";
}
@Override
public String getValue(BSimServerInfo serverInfo, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
return serverInfo.getDBType().toString();
}
@Override
public int getColumnPreferredWidth() {
return 80;
}
}
private static class HostColumn
extends AbstractDynamicTableColumn<BSimServerInfo, String, Object> {
@Override
public String getColumnName() {
@ -140,7 +174,7 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
}
@Override
public String getValue(BSimServerInfo serverInfo, Settings settings, Program data,
public String getValue(BSimServerInfo serverInfo, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
return serverInfo.getServerName();
@ -152,8 +186,8 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
}
}
private class PortColumn
extends AbstractProgramBasedDynamicTableColumn<BSimServerInfo, Integer> {
private static class PortColumn
extends AbstractDynamicTableColumn<BSimServerInfo, String, Object> {
@Override
public String getColumnName() {
@ -161,64 +195,78 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
}
@Override
public Integer getValue(BSimServerInfo serverInfo, Settings settings, Program data,
public String getValue(BSimServerInfo serverInfo, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
int port = serverInfo.getPort();
if (port <= 0) {
return null;
}
return port;
return Integer.toString(port);
}
@Override
public int getColumnPreferredWidth() {
return 80;
return 60;
}
}
private class ActiveConnectionColumn
extends AbstractProgramBasedDynamicTableColumn<BSimServerInfo, Integer> {
private static class ConnectionStatusColumnRenderer
extends AbstractGColumnRenderer<ConnectionPoolStatus> {
private static final ConnectionStatusColumnRenderer INSTANCE =
new ConnectionStatusColumnRenderer();
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
JLabel c = (JLabel) super.getTableCellRendererComponent(data);
ConnectionPoolStatus status = (ConnectionPoolStatus) data.getValue();
// NOTE: Custom column renderer has neem established with future use of
// status icon in mind (e.g., H2 mixed-mode server enabled)
Icon icon = null; // NOTE: may need default filler icon
String text = null;
if (status.isActive) {
text = Integer.toString(status.activeCount) + " / " +
Integer.toString(status.idleCount);
}
c.setText(text);
c.setIcon(icon);
return c;
}
@Override
public String getFilterString(ConnectionPoolStatus t, Settings settings) {
return null; // Filtering not supported
}
}
private class ConnectionStatusColumn
extends AbstractDynamicTableColumn<BSimServerInfo, ConnectionPoolStatus, Object> {
@Override
public String getColumnName() {
return "Active Connections";
return "Active/Idle Connections";
}
@Override
public Integer getValue(BSimServerInfo serverInfo, Settings settings, Program data,
ServiceProvider provider) throws IllegalArgumentException {
int activeConnections = BSimServerManager.getActiveConnections(serverInfo);
if (activeConnections < 0) {
return null;
}
return activeConnections;
public ConnectionPoolStatus getValue(BSimServerInfo serverInfo, Settings settings,
Object data, ServiceProvider provider) throws IllegalArgumentException {
return getConnectionPoolStatus(serverInfo);
}
@Override
public int getColumnPreferredWidth() {
return 80;
}
}
private class TypeColumn
extends AbstractProgramBasedDynamicTableColumn<BSimServerInfo, String> {
@Override
public String getColumnName() {
return "Type";
return 150;
}
@Override
public String getValue(BSimServerInfo serverInfo, Settings settings, Program data,
ServiceProvider provider) throws IllegalArgumentException {
return serverInfo.getDBType().toString();
}
@Override
public int getColumnPreferredWidth() {
return 80;
public GColumnRenderer<ConnectionPoolStatus> getColumnRenderer() {
return ConnectionStatusColumnRenderer.INSTANCE;
}
}

View File

@ -0,0 +1,44 @@
/* ###
* 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.features.bsim.gui.search.dialog;
import ghidra.features.bsim.gui.BSimServerManager;
import ghidra.features.bsim.query.BSimJDBCDataSource;
import ghidra.features.bsim.query.BSimServerInfo;
class ConnectionPoolStatus {
BSimServerInfo serverInfo;
final boolean isActive;
final int activeCount;
final int idleCount;
ConnectionPoolStatus(BSimServerInfo serverInfo) {
this.serverInfo = serverInfo;
BSimJDBCDataSource dataSource = BSimServerManager.getDataSourceIfExists(serverInfo);
if (dataSource == null) {
isActive = false;
activeCount = 0;
idleCount = 0;
}
else {
isActive = true;
activeCount = dataSource.getActiveConnections();
idleCount = dataSource.getIdleConnections();
}
}
}

View File

@ -91,8 +91,7 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
public boolean acceptServer(BSimServerInfo serverInfo) {
// FIXME: Use task to correct dialog parenting issue caused by password prompt
String errorMessage = null;
try {
FunctionDatabase database = BSimClientFactory.buildClient(serverInfo, true);
try (FunctionDatabase database = BSimClientFactory.buildClient(serverInfo, true)) {
if (database.initialize()) {
return true;
}

View File

@ -118,7 +118,10 @@ public class BSimClientFactory {
}
/**
* Given the URL for a BSim server construct the appropriate BSim client object (implementing FunctionDatabase)
* Given the URL for a BSim server construct the appropriate BSim client object
* (implementing FunctionDatabase). Returned instance must be
* {@link FunctionDatabase#close() closed} when done using it to prevent depletion
* of database connections.
* @param bsimServerInfo BSim server details
* @param async true if database commits should be asynchronous
* @return the database client

View File

@ -48,4 +48,15 @@ public interface BSimJDBCDataSource {
*/
int getActiveConnections();
/**
* Get the number of idle connections in the associated connection pool
* @return number of idle connections
*/
int getIdleConnections();
/**
* Dispose pooled datasource.
*/
void dispose();
}

View File

@ -41,7 +41,8 @@ public class BSimPostgresDBConnectionManager {
private static HashMap<BSimServerInfo, BSimPostgresDataSource> dataSourceMap = new HashMap<>();
public static BSimPostgresDataSource getDataSource(BSimServerInfo postgresServerInfo) {
public static synchronized BSimPostgresDataSource getDataSource(
BSimServerInfo postgresServerInfo) {
if (postgresServerInfo.getDBType() != DBType.postgres) {
throw new IllegalArgumentException("expected postgres server info");
}
@ -54,19 +55,20 @@ public class BSimPostgresDBConnectionManager {
return getDataSource(new BSimServerInfo(postgresUrl));
}
public static BSimPostgresDataSource getDataSourceIfExists(BSimServerInfo serverInfo) {
public static synchronized BSimPostgresDataSource getDataSourceIfExists(
BSimServerInfo serverInfo) {
return dataSourceMap.get(serverInfo);
}
private static synchronized void remove(BSimServerInfo serverInfo) {
private static synchronized void remove(BSimServerInfo serverInfo, boolean force) {
BSimPostgresDataSource ds = dataSourceMap.get(serverInfo);
if (ds == null) {
return;
}
int n = ds.bds.getNumActive();
if (n != 0) {
System.out
.println("Unable to remove data source which has " + n + " active connections");
if (n != 0 && !force) {
Msg.error(BSimPostgresDBConnectionManager.class,
"Unable to remove data source which has " + n + " active connections");
return;
}
ds.close();
@ -113,8 +115,9 @@ public class BSimPostgresDBConnectionManager {
bds.setUsername(userName);
}
@Override
public void dispose() {
remove(serverInfo);
remove(serverInfo, true);
}
private void close() {
@ -143,6 +146,11 @@ public class BSimPostgresDBConnectionManager {
return bds.getNumActive();
}
@Override
public int getIdleConnections() {
return bds.getNumIdle();
}
/**
* Update password on {@link BasicDataSource} for use with future connect attempts.
* Has no affect if username does not match username on data source.

View File

@ -33,7 +33,9 @@ import ghidra.features.bsim.query.facade.SFOverviewInfo;
import ghidra.features.bsim.query.facade.SFQueryInfo;
import ghidra.features.bsim.query.protocol.*;
import ghidra.framework.Application;
import ghidra.program.model.data.DataUtilities;
import ghidra.util.Msg;
import ghidra.util.StringUtilities;
public interface FunctionDatabase extends AutoCloseable {
@ -239,7 +241,15 @@ public interface FunctionDatabase extends AutoCloseable {
if (res == 3) {
throw new LSHException("Query signature data has no setting information");
}
throw new LSHException("Query signature data does not match database");
throw new LSHException("Query signature data " +
getFormattedVersion(manage.getMajorVersion(), manage.getMinorVersion(),
manage.getSettings()) +
" does not match database " +
getFormattedVersion(info.major, info.minor, info.settings));
}
private static String getFormattedVersion(int maj, int min, int settings) {
return String.format("%d.%d:0x%02x", maj, min, settings);
}
public static boolean checkSettingsForInsert(DescriptionManager manage,
@ -262,8 +272,11 @@ public interface FunctionDatabase extends AutoCloseable {
if (res == 3) {
throw new LSHException("Trying to insert signature data with no setting information");
}
throw new LSHException(
"Trying to insert signature data with settings that don't match database");
throw new LSHException("Trying to insert signature data " +
getFormattedVersion(manage.getMajorVersion(), manage.getMinorVersion(),
manage.getSettings()) +
" with settings that don't match database " +
getFormattedVersion(info.major, info.minor, info.settings));
}
public static String constructFatalError(int flags, ExecutableRecord newrec,

View File

@ -28,6 +28,7 @@ import ghidra.features.bsim.query.*;
import ghidra.features.bsim.query.BSimServerInfo.DBType;
import ghidra.features.bsim.query.FunctionDatabase.ConnectionType;
import ghidra.features.bsim.query.FunctionDatabase.Status;
import ghidra.util.Msg;
public class BSimH2FileDBConnectionManager {
@ -44,7 +45,7 @@ public class BSimH2FileDBConnectionManager {
* Get all H2 File DB data sorces which exist in the JVM.
* @return all H2 File DB data sorces
*/
public static Collection<BSimH2FileDataSource> getAllDataSources() {
public static synchronized Collection<BSimH2FileDataSource> getAllDataSources() {
// Create copy to avoid potential concurrent modification
return Collections.unmodifiableCollection(new ArrayList<>(dataSourceMap.values()));
}
@ -57,7 +58,7 @@ public class BSimH2FileDBConnectionManager {
* @throws IllegalArgumentException if {@code fileServerInfo} does not specify an
* H2 File DB type.
*/
public static BSimH2FileDataSource getDataSource(BSimServerInfo fileServerInfo) {
public static synchronized BSimH2FileDataSource getDataSource(BSimServerInfo fileServerInfo) {
if (fileServerInfo.getDBType() != DBType.file) {
throw new IllegalArgumentException("expected file info");
}
@ -79,26 +80,26 @@ public class BSimH2FileDBConnectionManager {
* @return existing H2 File data source or null if server info does not correspond to an
* H2 File or has not be established as an H2 File data source.
*/
public static BSimH2FileDataSource getDataSourceIfExists(BSimServerInfo serverInfo) {
public static synchronized BSimH2FileDataSource getDataSourceIfExists(
BSimServerInfo serverInfo) {
return dataSourceMap.get(serverInfo);
}
private static synchronized void remove(BSimServerInfo serverInfo, boolean force) {
private static synchronized boolean remove(BSimServerInfo serverInfo, boolean force) {
BSimH2FileDataSource ds = dataSourceMap.get(serverInfo);
if (ds == null) {
return;
return true;
}
int n = ds.bds.getNumActive();
if (n != 0) {
System.out
.println("Unable to remove data source which has " + n + " active connections");
if (!force) {
return;
}
if (n != 0 && !force) {
Msg.error(BSimH2FileDBConnectionManager.class,
"Unable to remove data source which has " + n + " active connections");
return false;
}
ds.close();
dataSourceMap.remove(serverInfo);
BSimVectorStoreManager.remove(serverInfo);
return true;
}
/**
@ -123,20 +124,31 @@ public class BSimH2FileDBConnectionManager {
return serverInfo;
}
@Override
public void dispose() {
BSimH2FileDBConnectionManager.remove(serverInfo, true);
}
/**
* Delete the database files associated with this H2 File DB. When complete
* this data source will no longer be valid and should no tbe used.
* Delete the database files associated with this H2 File DB. This will fail immediately
* if active connections exist. Otherwise removal will be attempted and this data source
* will no longer be valid.
* @return true if DB sucessfully removed
*/
public void delete() {
dispose();
public synchronized boolean delete() {
File dbf = new File(serverInfo.getDBName());
// TODO: Should we check for lock on database - could be another process
if (getActiveConnections() != 0) {
Msg.error(this, "Failed to delete active database: " + dbf);
return false;
}
dispose();
if (dbf.isFile()) {
return true;
}
String name = dbf.getName();
int ix = name.lastIndexOf(BSimServerInfo.H2_FILE_EXTENSION);
@ -145,6 +157,13 @@ public class BSimH2FileDBConnectionManager {
}
DeleteDbFiles.execute(dbf.getParent(), name, true);
if (!dbf.isFile()) {
return true;
}
Msg.error(this, "Failed to delete database: " + dbf);
return false;
}
/**
@ -181,6 +200,11 @@ public class BSimH2FileDBConnectionManager {
return bds.getNumActive();
}
@Override
public int getIdleConnections() {
return bds.getNumIdle();
}
private String getH2FileUrl() {
// Remove H2 db file extension if present

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

View File

@ -23,8 +23,7 @@ import org.junit.Test;
import docking.DockingWindowManager;
import docking.action.DockingActionIf;
import ghidra.app.services.ProgramManager;
import ghidra.features.bsim.gui.BSimSearchPlugin;
import ghidra.features.bsim.gui.BSimSearchPluginTestHelper;
import ghidra.features.bsim.gui.*;
import ghidra.features.bsim.gui.overview.BSimOverviewProvider;
import ghidra.features.bsim.gui.overview.BSimOverviewTestHelper;
import ghidra.features.bsim.gui.search.dialog.*;

View File

@ -15,7 +15,6 @@
*/
package ghidra.features.bsim.gui;
import ghidra.features.bsim.gui.search.dialog.BSimServerManager;
import ghidra.features.bsim.query.facade.SFQueryServiceFactory;
public class BSimSearchPluginTestHelper {

View File

@ -65,6 +65,7 @@ public class QueryFilterTest extends AbstractBSimPluginTest {
assertEquals(SQL_TRUTH, sql);
}
@Override
protected void initializeTool() throws Exception {
super.initializeTool();
goTo(FUN1_ADDR);
@ -80,7 +81,6 @@ public class QueryFilterTest extends AbstractBSimPluginTest {
*
* @param ids resolution IDs
* @return the query string
* @throws SQLException if there is a problem creating the filter
*/
private String generateSQL(IDSQLResolution[] ids) {
try {
@ -88,7 +88,8 @@ public class QueryFilterTest extends AbstractBSimPluginTest {
BSimFilter filter = filterSet.getBSimFilter();
BSimSqlClause sql = SQLEffects.createFilter(filter, ids, null);
return sql.whereClause().trim();
} catch (SQLException e) {
}
catch (SQLException e) {
throw new AssertException(e);
}
}

View File

@ -15,8 +15,7 @@
*/
package ghidra.features.bsim.gui.overview;
import ghidra.features.bsim.gui.BSimSearchPlugin;
import ghidra.features.bsim.gui.BSimSearchPluginTestHelper;
import ghidra.features.bsim.gui.*;
import ghidra.features.bsim.gui.search.dialog.*;
import ghidra.features.bsim.query.BSimServerInfo;
import ghidra.features.bsim.query.FunctionDatabase;

View File

@ -18,7 +18,7 @@ package ghidra.features.bsim.gui.search.dialog;
import static org.junit.Assert.*;
import java.sql.SQLException;
import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -30,7 +30,6 @@ import ghidra.features.bsim.query.SQLFunctionDatabase;
import ghidra.features.bsim.query.client.*;
import ghidra.features.bsim.query.facade.FunctionDatabaseTestDouble;
import ghidra.features.bsim.query.protocol.BSimFilter;
import ghidra.program.database.symbol.FunctionSymbol;
/**
* Tests the filtering components of BSim accessible from the UI. This will cover the
@ -44,9 +43,9 @@ import ghidra.program.database.symbol.FunctionSymbol;
*/
public class BSimFilterPanelTest extends AbstractBSimPluginTest {
private Set<FunctionSymbol> selectedFunctions = new HashSet<>();
private BSimFilterPanel filterPanel;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
@ -57,6 +56,7 @@ public class BSimFilterPanelTest extends AbstractBSimPluginTest {
filterPanel = BSimSearchDialogTestHelper.getFilterPanel(searchDialog);
}
@Override
@After
public void tearDown() throws Exception {
close(searchDialog);

View File

@ -17,8 +17,7 @@ package ghidra.features.bsim.gui.search.dialog;
import java.util.Set;
import ghidra.features.bsim.gui.BSimSearchPlugin;
import ghidra.features.bsim.gui.BSimSearchPluginTestHelper;
import ghidra.features.bsim.gui.*;
import ghidra.features.bsim.query.BSimServerInfo;
import ghidra.features.bsim.query.FunctionDatabase;
import ghidra.features.bsim.query.facade.TestBSimServerInfo;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.query.inmemory;
package ghidra.features.bsim.query.file;
import static org.junit.Assert.*;
@ -26,7 +26,6 @@ import ghidra.features.bsim.query.*;
import ghidra.features.bsim.query.BSimServerInfo.DBType;
import ghidra.features.bsim.query.FunctionDatabase.Error;
import ghidra.features.bsim.query.description.DatabaseInformation;
import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager;
import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource;
import ghidra.features.bsim.query.protocol.CreateDatabase;
import ghidra.features.bsim.query.protocol.ResponseInfo;
@ -50,7 +49,7 @@ public class BSimH2DatabaseManagerTest extends AbstractGhidraHeadedIntegrationTe
@After
public void tearDown() {
//cleanup();
cleanup();
}
private File getTempDbDir() {

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.query.test;
package ghidra.features.bsim.query.test;
import static org.junit.Assert.*;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.query.test;
package ghidra.features.bsim.query.test;
import java.io.*;

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
<stringAttribute key="bad_container_name" value="/_Ghidra Support/eclipse"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/Framework Utility/src/main/java/ghidra/Ghidra.java"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="1"/>
</listAttribute>
<mapAttribute key="org.eclipse.debug.core.preferred_launchers">
<mapEntry key="[debug]" value="org.eclipse.jdt.launching.localJavaApplication"/>
<mapEntry key="[run]" value="org.eclipse.jdt.launching.localJavaApplication"/>
</mapAttribute>
<stringAttribute key="org.eclipse.debug.core.source_locator_id" value="org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector"/>
<stringAttribute key="org.eclipse.debug.core.source_locator_memento" value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;sourceLookupDirector&gt;&#10;&lt;sourceContainers duplicates=&quot;false&quot;&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;classpathContainer path=&amp;quot;org.eclipse.jdt.launching.JRE_CONTAINER&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.classpathContainer&quot;/&gt;&#10;&lt;/sourceContainers&gt;&#10;&lt;/sourceLookupDirector&gt;&#10;"/>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_ATTR_USE_ARGFILE" value="false"/>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_SHOW_CODEDETAILS_IN_EXCEPTION_MESSAGES" value="true"/>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_CLASSPATH_ONLY_JAR" value="false"/>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry path=&quot;5&quot; projectName=&quot;Framework Utility&quot; type=&quot;1&quot;/&gt;&#10;"/>
</listAttribute>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="ghidra.Ghidra"/>
<listAttribute key="org.eclipse.jdt.launching.MODULEPATH"/>
<stringAttribute key="org.eclipse.jdt.launching.MODULE_NAME" value="Framework Utility"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="ghidra.JShellRun"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="Framework Utility"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-XX:+IgnoreUnrecognizedVMOptions&#13;&#10;-Djava.system.class.loader=ghidra.GhidraClassLoader&#13;&#10;-Xshare:off&#13;&#10;-Dfile.encoding=UTF8&#13;&#10;-Duser.country=US&#13;&#10;-Duser.language=en&#13;&#10;-Dsun.java2d.pmoffscreen=false&#13;&#10;-Dsun.java2d.xrender=true&#13;&#10;-Dsun.java2d.d3d=false&#13;&#10;-Xdock:name=&quot;Ghidra&quot;&#13;&#10;-Dvisualvm.display.name=Ghidra&#13;&#10;-Dpython.console.encoding=UTF-8"/>
</launchConfiguration>

View File

@ -8,10 +8,12 @@
##MODULE IP: Nuvola Icons - LGPL 2.1
##MODULE IP: Oxygen Icons - LGPL 3.0
##MODULE IP: Tango Icons - Public Domain
##MODULE IP: Crystal Clear Icons - LGPL 2.1
.gitignore||GHIDRA||||END|
.launch/Ghidra Code Coverage.launch||GHIDRA||||END|
.launch/Ghidra.launch||GHIDRA||||END|
.launch/Headless.launch||GHIDRA||||END|
.launch/JShell.launch||GHIDRA||||END|
Module.manifest||GHIDRA||||END|
data/ElfFunctionsThatDoNotReturn||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||||END|
@ -507,8 +509,10 @@ src/main/help/help/topics/RuntimeInfoPlugin/RuntimeInfo.htm||GHIDRA||||END|
src/main/help/help/topics/ScalarSearchPlugin/The_Scalar_Table.htm||GHIDRA||||END|
src/main/help/help/topics/ScalarSearchPlugin/images/ScalarWindow.png||GHIDRA||||END|
src/main/help/help/topics/ScalarSearchPlugin/images/SearchAllScalarsDialog.png||GHIDRA||||END|
src/main/help/help/topics/Search/Instruction_Mnemonic_Search.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Query_Results_Dialog.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Regular_Expressions.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Search_Formats.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Search_Instruction_Patterns.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Search_Memory.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Search_Program_Text.htm||GHIDRA||||END|
@ -519,6 +523,9 @@ src/main/help/help/topics/Search/Searching.htm||GHIDRA||||END|
src/main/help/help/topics/Search/images/DirectReferences.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/EncodedStringsDialog_advancedoptions.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/EncodedStringsDialog_initial.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/MemorySearchProvider.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/MemorySearchProviderWithOptionsOn.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/MemorySearchProviderWithScanPanelOn.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/MultipleSelectionError.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/QueryResultsSearch.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchForAddressTables.png||GHIDRA||||END|
@ -535,11 +542,7 @@ src/main/help/help/topics/Search/images/SearchInstructionsIncludeOperands.png||G
src/main/help/help/topics/Search/images/SearchInstructionsIncludeOperandsNoConsts.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchInstructionsManualSearchDialog.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchLimitExceeded.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchMemoryBinary.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchMemoryDecimal.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchMemoryHex.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchMemoryRegex.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchMemoryString.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchText.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/StringSearchDialog.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/StringSearchResults.png||GHIDRA||||END|
@ -918,6 +921,11 @@ src/main/resources/images/unlock.gif||GHIDRA||||END|
src/main/resources/images/verticalSplit.png||GHIDRA||||END|
src/main/resources/images/view-sort-ascending.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/resources/images/view-sort-descending.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/resources/images/view_bottom.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
src/main/resources/images/view_left_right.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
src/main/resources/images/view_top_bottom.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
src/main/resources/images/viewmag+.png||Crystal Clear Icons - LGPL 2.1||||END|
src/main/resources/images/viewmag.png||GHIDRA||||END|
src/main/resources/images/window.png||GHIDRA||||END|
src/main/resources/images/wizard.png||Nuvola Icons - LGPL 2.1|||nuvola|END|
src/main/resources/images/x-office-document-template.png||Tango Icons - Public Domain|||tango icon set|END|

View File

@ -395,6 +395,11 @@ icon.base.util.xml.functions.bookmark = imported_bookmark.gif
icon.base.util.datatree.version.control.archive.dt.checkout.undo = vcUndoCheckOut.png
icon.base.mem.search.panel.options = view_left_right.png
icon.base.mem.search.panel.scan = view_bottom.png
icon.base.mem.search.panel.search = view_top_bottom.png

View File

@ -31,6 +31,8 @@ import ghidra.program.model.symbol.*;
public class PropagateExternalParametersScript extends GhidraScript {
private List<PushedParamInfo> results = new ArrayList<>();
private static final boolean PRINT_OPTYPE = false;
@Override
public void run() throws Exception {
Listing listing = currentProgram.getListing();
@ -134,8 +136,8 @@ public class PropagateExternalParametersScript extends GhidraScript {
for (Reference extRef : extRefs) {
Address refAddr = extRef.getFromAddress();
String refMnemonic = listing.getCodeUnitAt(refAddr).getMnemonicString();
Function calledFromFunc = listing.getFunctionContaining(refAddr);
if (calledFromFunc == null) {
continue;
@ -147,8 +149,14 @@ public class PropagateExternalParametersScript extends GhidraScript {
while (tempIter.hasNext()) {
Reference thunkRef = tempIter.next();
Address thunkRefAddr = thunkRef.getFromAddress();
String thunkRefMnemonic =
listing.getCodeUnitAt(thunkRefAddr).getMnemonicString();
CodeUnit cu = listing.getCodeUnitAt(thunkRefAddr);
if (cu == null) {
// println("Referenced CodeUnit is null: " + thunkRefAddr);
continue;
}
String thunkRefMnemonic = cu.getMnemonicString();
Function thunkRefFunc = listing.getFunctionContaining(thunkRefAddr);
if ((thunkRefMnemonic.equals(new String("CALL")) && (thunkRefFunc != null))) {
CodeUnitIterator cuIt =
@ -294,10 +302,18 @@ public class PropagateExternalParametersScript extends GhidraScript {
numSkips--;
}
else {
// if option is true add the value of the optype to the EOL comment
String opType = "";
if (PRINT_OPTYPE) {
opType = " " + toHexString(currentProgram.getListing()
.getInstructionAt(cu.getMinAddress())
.getOperandType(0),
false, true);
}
setEOLComment(cu.getMinAddress(), params[index].getDataType().getDisplayName() +
" " + params[index].getName() + " for " + extFuncName);
// add the following to the EOL comment to see the value of the optype
// +" " + toHexString(currentProgram.getListing().getInstructionAt(cu.getMinAddress()).getOperandType(0), false, true)
" " + params[index].getName() + " for " + extFuncName + opType);
addResult(params[index].getName(), params[index].getDataType(),
cu.getMinAddress(), extFuncName);
index++;

View File

@ -268,7 +268,11 @@ public class VSCodeProjectScript extends GhidraScript {
}
// Fix Ghidra installation directory path in build.gradle
File buildTemplateGradleFile = new File(projectDir, "buildTemplate.gradle");
File buildGradleFile = new File(projectDir, "build.gradle");
if (!buildTemplateGradleFile.renameTo(buildGradleFile)) {
throw new IOException("Failed to rename: " + buildTemplateGradleFile);
}
String fileData = FileUtils.readFileToString(buildGradleFile, StandardCharsets.UTF_8);
fileData =
fileData.replaceAll("<REPLACE>", FilenameUtils.separatorsToUnix(installDir.getPath()));

View File

@ -0,0 +1,112 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Search Memory</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
<META name="generator" content="Microsoft FrontPage 4.0">
</HEAD>
<BODY lang="EN-US">
<BLOCKQUOTE>
<H1><A name="Mnemonic_Search"></A>Search for Matching Instructions</H1>
<BLOCKQUOTE>
<P>This action works only on a selection of code. It uses the selected instructions to build
a combined mask/value bit pattern that is then used to populate the search field in a
<A href="Search_Memory.htm">Memory Search Window</A>. This enables searching through memory
for a particular ordering of
instructions. There are three options available:&nbsp;</P>
<UL>
<LI><B>Include Operands</B> - All bits that make up the instruction and all bits that make
up the operands will be included in the search pattern.</LI>
<LI><B>Exclude Operands</B> - All bits that make up the instruction are included in the
search pattern but the bits that make up the operands will be masked off to enable wild
carding for those bits.</LI>
<LI><B>Include Operands (except constants)</B> - All bits that make up the instruction are
included in the search pattern and all bits that make up the operands, except constant
operands, which will be masked off to enable wild carding for those bits.</LI>
</UL>
<BLOCKQUOTE>
<P>Example:</P>
<P>A user first selects the following lines of code. Then, from the Search menu they choose
<B>Search for Matching Instructions</B> and one of the following options:</P>
<P align="center"><IMG border="1" src="images/SearchInstructions.png" alt=""></P>
<B>Option 1:</B>
<BLOCKQUOTE>
<P>If the <B>Include Operands</B> action is chosen then the search will find all
instances of the following instructions and operands.</P>
<P align="center"><IMG border="1" src="images/SearchInstructionsIncludeOperands.png" alt=
""></P>
<P>All of the bytes that make up the selected code will be searched for exactly, with no
wild carding. The bit pattern <B>10000101 11000000 01010110 01101010 00010100
01011110</B> which equates to the byte pattern <B>85 c0 56 6a 14 5e</B> is searched
for.<BR>
<BR>
</P>
</BLOCKQUOTE><B>Option 2:</B>
<BLOCKQUOTE>
<P>If the <B>Exclude Operands</B> option is chosen then the search will find all
instances of the following instructions only.</P>
<P align="center"><IMG border="1" src="images/SearchInstructionsExcludeOperands.png" alt=
""></P>
<P>Only the parts of the byte pattern that make up the instructions will be searched for
with the remaining bits used as wildcards. The bit pattern <B>10000101 11...... 01010...
01101010 ........ 01011...</B> is searched for where the .'s indicate the wild carded
values.<BR>
<BR>
</P>
</BLOCKQUOTE><B>Option 3:</B>
<BLOCKQUOTE>
<P>If the <B>Include Operands (except constants)</B> option is chosen then the search
will find all instances of the instruction and all operands except the 0x14 which is a
constant.</P>
<P align="center"><IMG border="1" src=
"images/SearchInstructionsIncludeOperandsNoConsts.png" alt=""></P>
<P>The bit pattern <B>10000101 11000000 01010110 01101010 ........ 01011110</B> which
equates to the byte pattern <B>85 c0 56 6a xx 5e</B> is searched for where xx can be any
number N between 0x0 and 0xff.<BR>
<BR>
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<P><IMG alt="Note" src="help/shared/note.png">The previous operations can only work on a
<B>single</B> selected region. If multiple regions are selected, the following error dialog
will be shown and the operation will be cancelled.</P>
<P align="center"><IMG border="1" src="images/MultipleSelectionError.png" alt=""></P>
<BR>
<BR>
<P class="providedbyplugin">Provided by: <I>Mnemonic Search Plugin</I></P>
<P class="relatedtopic">Related Topics:</P>
<UL>
<LI><A href="help/topics/Search/Search_Memory.htm">Search Memory</A></LI>
</UL><BR>
<BR>
</BLOCKQUOTE>
</BODY>
</HTML>

View File

@ -19,17 +19,21 @@
same as a regular expression for any standard Java application. Because of restrictions on how
regular expressions are processed, regular expression searches can only be performed in the
forward direction. Unlike standard string searches, case sensitivity and unicode options do not
apply. The <I>Search Memory</I> dialog below shows a sample regular expression entered in the
apply. The <I>Search Memory</I> window below shows a sample regular expression entered in the
<I>Value</I> field.</P>
<TABLE border="0" width="100%">
<BLOCKQUOTE>
<BLOCKQUOTE>
<TABLE border="0" width="80%">
<TBODY>
<TR>
<TD width="100%" align="center"><IMG border="0" src="images/SearchMemoryRegex.png" alt=""></TD>
<TD align="left"><IMG border="0" src="images/SearchMemoryRegex.png" alt=""></TD>
</TR>
</TBODY>
</TABLE>
</BLOCKQUOTE>
<H3>Examples</H3>
<BLOCKQUOTE>

File diff suppressed because it is too large Load Diff

View File

@ -12,467 +12,525 @@
</HEAD>
<BODY lang="EN-US">
<BLOCKQUOTE>
<A name="Memory Search"></A>
<H1>Search Memory</H1>
<P>Search Memory locates sequences of bytes in program memory. &nbsp;The search is based on a
value entered as hex numbers, decimal numbers or strings.&nbsp; The byte sequence may contain
"wildcards" that will match any byte (or possibly nibble). String searching also allows for the
use of <A href="#RegularExpression">regular expression</A> searches.</P>
<P>The memory search feature locates sequences of bytes in program memory. The search is
based on user entered values which can be specified in a variety of formats such as hex,
binary, string, and decimal values. The hex and binary formats support "wildcards" which can
match any bit or nibbles depending on the format. String search also supports the use of <A
href="Search_Formats.htm#RegularExpressions">regular expression</A> searches.</P>
<P>To Search Memory:</P>
<P>To create a new memory search window, select <B>Search</B> <IMG alt="" border="0" src=
"help/shared/arrow.gif"><B>Memory</B> from the main tool menu or use the default keybinding
"S".</P>
<P><IMG alt="" border="0" src="help/shared/tip.png">By default, search windows and their
tabs display "Search Memory:" followed by the search string and the program name. This
can be changed by right-clicking on the title or table to change its name. (This is true
for all transient windows.)</P>
<H2>Contents</H2>
<BLOCKQUOTE>
<OL>
<LI>From the Tool, select <B>Search</B><IMG alt="" border="0" src="help/shared/arrow.gif">
<B>Memory</B></LI>
<UL>
<LI>
<A href="#Memory_Search_Window">Memory Search Window</A>
<LI>Enter a Hex String in the Value field<BR>
This will create a Hex Sequence for searching.</LI>
<UL>
<LI><A href="#Search_Controls">Search Controls</A></LI>
<LI>Choose "Next" to find the next occurrence<BR>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
- or -<BR>
Choose "Previous" to find the previous occurrence<BR>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
- or -<BR>
Choose "Search All" to find all occurrences.</LI>
</OL>
<LI><A href="#Scan_Controls">Scan Controls</A></LI>
<LI><A href="#Results_Table">Results Table</A></LI>
<LI><A href="#Options">Options</A></LI>
</UL>
</LI>
<LI><A href="#Search_Formats">Search Formats</A></LI>
<LI><A href="#Actions">Actions</A></LI>
<LI><A href="#Combining_Searches">Combining Searches</A></LI>
<LI><A href="#Repeating_Searches">Repeating Last Search Forwards/Backwards</A></LI>
<LI><A href="#Highlight_Options">Highlight Search Options</A></LI>
</UL>
</BLOCKQUOTE><A name="Memory_Search_Window"></A>
<H2>Memory Search Window</H2>
<BLOCKQUOTE>
<P>The Memory Search Window provides controls and options for entering search values and
and a table for displaying the search results. It supports both bulk searching and
incremental searching forwards and backwards. Also, users can perform additional searches
and combine those results with the existing results using set operations. The window also
has a "scan" feature which will re-examine the bytes of the current results set, looking
for memory changes at those locations (this is most useful when debugging). Scanning has an
option for reducing the results to those that either changed, didn't change, were
incremented, or were decremented.</P>
<P align="center"><IMG src="images/MemorySearchProvider.png" border="0" alt=""> &nbsp;</P>
<P align="center"><I>Memory Search Window</I></P>
<A name="Search_Controls"></A>
<H3>Search Controls</H3>
<BLOCKQUOTE>
<P>At the top of the window as shown above, there are several GUI elements for initializing and
executing a memory byte search. These controls can be closed from the view after a search
to give more space to view results using the <IMG alt="" border="0"
src="icon.base.mem.search.panel.search"> toolbar button.</P>
<H4>Search Format Field:</H4>
<BLOCKQUOTE>
<P>This is a drop-down list of formats whose selected format determines how to
interpret the text in the <I>Search Input Field</I>. The format will convert the user's
input into a sequence of bytes (and possibly masks). Details on each format are
described in the <A href="Search_Formats.htm"><I>Search Formats</I></A> section.</P>
</BLOCKQUOTE>
<H4>Search Input Field:</H4>
<BLOCKQUOTE>
<P>Next to the <I>Search Format</I> drop-down, there is a text field where users can
enter one or more values to be searched. This field performs validation depending on
the active format. For example, when the format is <I>Hex</I>, users can only enter
hexadecimal values.</P>
</BLOCKQUOTE>
<H4>Previous Search Drop Down:</H4>
<BLOCKQUOTE>
<P>At the end of the input field, there is a drop-down list of previous searches.
Selecting one of these will populate the input field with that previous search input,
as well as the relevant settings that were used in that search such as the search
format.</P>
</BLOCKQUOTE>
<H4>Search Button:</H4>
<BLOCKQUOTE>
<P>Pressing the search button will initiate a search. When the results table is empty,
the only choice is to do an initial search. If there are current results showing in the
table, a drop-down will appear at the back of the button, allowing the user to combine
new search results with the existing results using set operations. See the
<A href="#Combining_Searches"><I>Combining Searches</I></A> section
below for more details.</P>
</BLOCKQUOTE>
<H4>Byte Sequence Field:</H4>
<BLOCKQUOTE>
<P>This field is used to show the user the bytes sequence that will be search for based
on the format and the user input entered. Hovering on this field will also display the
masks that will be used (if applicable).</P>
</BLOCKQUOTE>
<H4>Selection Only Checkbox:</H4>
<BLOCKQUOTE>
<P>If there is a current selection, then this checkbox will be enabled and provide the
user with the option to restrict the search to only the selected addresses. Note that
there is an action that controls whether this option will be selected automatically if
a selection exists.</P>
</BLOCKQUOTE>
</BLOCKQUOTE><A name="Scan_Controls"></A>
<H3>Scan Controls</H3>
<BLOCKQUOTE>
<P>The scan controls are used to re-examine search results, looking for values that have
changed since the search was initiated. This is primary useful when debugging. The
scan controls are not showing by default. Pressing the <IMG alt="" border="0"
src="icon.base.mem.search.panel.scan"> toolbar button will show them along the right side of the
window</P>
<P align="center"><IMG src="images/MemorySearchProviderWithScanPanelOn.png" border="0"
alt=""> &nbsp;</P>
<P align="center"><I>Memory Search Window With Scan Controls Showing</I></P>
<H4>Scan Values Button:</H4>
<BLOCKQUOTE>
<P>This button initiates a scan of the byte values in all the matches in the results
table. Depending on the selected scan option, the set of matches in the table may be
reduced based on the values that changed.</P>
</BLOCKQUOTE>
<H4>Scan Option Radio Buttons</H4>
<BLOCKQUOTE>
<P>One of the following buttons can be selected and they determine how the set of
current matches should be reduced based on changed values.</P>
<UL>
<LI><B>Equals</B> This option will keep all matches whose values haven't changed and
remove any matches whose bytes have changed.</LI>
<LI><B>Not Equals</B> This option will keep all matches whose values have changed and
will remove any matches whose bytes have not changed.</LI>
<LI><B>Increased</B> This option will keep all matches whose values have increased
and will remove any matches whose values decreased or stayed the same.</LI>
<LI><B>Decreased</B> This option will keep all matches whose values have decreased
and will remove any matches whose values increased or stayed the same.</LI>
</UL>
<P><IMG alt="" border="0" src="help/shared/tip.png">The <I>Increased</I> or
<I>Decreased</I> options really only make sense for matches that represent numerical
values such as integers or floats. In other cases it makes the determination based on
the first byte in the sequence that changed, as if they were a sequence of 1 byte
unsigned values.</P>
<P><IMG alt="" border="0" src="help/shared/tip.png">Another way to see changed bytes is
to use the <A href= "#Refresh_Values"><I>Refresh</I></A> toolbar action. This will
update the bytes for each search result and show them in red without reducing the set
of results.</P>
</BLOCKQUOTE>
</BLOCKQUOTE><A name="Results_Table"></A>
<H3>Results Table</H3>
<BLOCKQUOTE>
<P>The bottom part of the window is the search results table. Each row in the table
represents one search match. The table can contain combined results from multiple
searches. At the bottom of the results table, all the standard table filters are
available. The table has the following default columns, but additional columns can be
added by right-clicking on any column header.</P>
</BLOCKQUOTE>
<UL>
<LI><B>Location:</B> Displays the address of the first byte in the matching
sequence.</LI>
<LI><B>Match Bytes:</B> Displays the bytes of the matching sequence. (Note: if you
refresh or scan, the bytes can change. Changed bytes will be displayed in red.)</LI>
<LI><B>Match Value:</B> Displays the matching bytes as a value where the value is
determined by the <I>Search Format</I>. Note that not all formats have a meaningful
value, in which case the column value will be empty.</LI>
<LI><B>Label:</B> Displays any labels that are present at the match address.</LI>
<LI><B>Code Unit:</B> Displays the instruction or data that the match address.</LI>
</UL><A name="Options"></A>
<H3>Options</H3>
<BLOCKQUOTE>
<P>The options panel is not displayed by default. Pressing the <IMG alt="" border="0"
src="icon.base.mem.search.panel.options"> toolbar button will show them along the right side of the
window.</P>
</BLOCKQUOTE>
<P align="center"><IMG src="images/MemorySearchProviderWithOptionsOn.png" border="0" alt=
""> &nbsp;</P>
<P align="center"><I>Memory Search Window With Options Open</I></P>
<H4>Byte Options</H4>
<BLOCKQUOTE>
<P>These are general options that affect most searches.</P>
<UL>
<LI><B>Endianess:</B> This chooses the byte ordering for values that are larger than
one byte. Big Endian orders the bytes most significant first. Little Endian orders the
bytes least significant first.
<LI><B>Alignment:</B> The alignment requires that matches must start on an address that
has an offset that is a multiple of the specified integer field. For example, an
alignment of two would require that the address have an even value.</LI>
</BLOCKQUOTE>
<H4>Decimal Options</H4>
<BLOCKQUOTE>
<P>These options apply when parsing input as decimal values.</P>
<UL>
<LI><B>Size:</B> The size (in bytes) of the decimal values.</LI>
<LI><B>Unsigned:</B> If checked, the values will be interpreted as unsigned values
and the input field won't accept negative values.</LI>
</UL>
</BLOCKQUOTE>
<H4>String Options</H4>
<BLOCKQUOTE>
<P>These options apply when parsing input as string data.</P>
<UL>
<LI><B>Encoding:</B> Specified the char set used to convert between strings and
bytes. (ASCII, UTF8, or UTF 16) </LI>
<LI><B>Case Sensitive:</B> If unselected, causes mask bytes to be generated such that
the search will not be case sensitive. Otherwise, the bytes must match exactly the
input the user entered.</LI>
<LI><B>Escape Sequences:</B> Determines if standard escape sequences are interpreted
literally or not. For example, if checked, and the user enters "\n", the search will
look for an end of line character. If unchecked, the search will look for a "\"
followed by an "n". Supported escape sequences include "\n", "\r", "\b", "\f", "\0",
"\x##", "\u####", "\U#########".</LI>
</UL>
</BLOCKQUOTE>
<H4>Code Type Filters</H4>
<BLOCKQUOTE>
<P>These are filters that can be applied to choose what type(s) of code units to
include in the results. By default, they are all selected. The types are:</P>
<UL>
<LI><B>Instructions:</B> Include matches that start at or in an instruction.</LI>
<LI><B>Defined Data:</B> Include matches that start at or in a define data.</LI>
<LI><B>Undefined Data:</B> Include matches that start at or in undefined data.</LI>
</UL>
</BLOCKQUOTE>
<H4>Memory Regions</H4>
<BLOCKQUOTE>
<P>Choose one or more memory regions to search. The available regions can vary depending
on the context, but the default regions are:</P>
<UL>
<LI><B>Loaded Blocks:</B> These include all the memory blocks defined that are actually
part of a loaded executable binary. On by default.</LI>
<LI><B>Other Blocks:</B> These are other than loaded blocks, typically representing
file header data. Off by default.</LI>
</UL>
</BLOCKQUOTE>
</BLOCKQUOTE><A name="Search_Formats"></A>
<H2>Search Formats</H2>
<BLOCKQUOTE>
<UL type="disc">
<LI><A href="#Hex">Hex</A></LI>
<P>The selected format determines how the user input is used to generate a search byte
sequence (and possibly mask byte sequence). They are also used to format bytes back into
"values" to be displayed in the table, if applicable.</P>
<LI><A href="#String">String</A></LI>
<P>See the page on <A href="Search_Formats.htm">Search Formats</A> for full details on each
format.</P>
</BLOCKQUOTE><A name="Actions"></A>
<LI><A href="#Decimal">Decimal</A></LI>
<H2>Actions</H2>
<LI><A href="#Binary">Binary</A></LI>
<BLOCKQUOTE>
<A name="Search_Next"></A>
<LI><A href="#Regular_Expression">Regular Expression</A></LI>
</UL>
<H3><IMG alt="" border="0" src="icon.down">&nbsp;&nbsp;Incremental Search Forward</H3>
<BLOCKQUOTE>
<P>This action searches forward in memory, starting at the address just after the current
cursor location. It will continue until a match is found or the highest address in the
search space is reached. It does not "wrap". If a match is found, it it is added to the
current table of results.</P>
</BLOCKQUOTE><A name="Search_Previous"></A>
<H3><IMG alt="" border="0" src="icon.up">&nbsp;&nbsp;Incremental Search Backwards</H3>
<BLOCKQUOTE>
<P>This action searches backwards in memory, starting at the address just before the
current cursor location. It will continue until a match is found or the lowest address in
the search space is reached. It does not "wrap". If a match is found, it it is added to
the current table of results.</P>
</BLOCKQUOTE><A name="Refresh_Values"></A>
<H3><IMG alt="" border="0" src="icon.refresh">&nbsp;&nbsp;Refresh</H3>
<BLOCKQUOTE>
<P>This action will read the bytes again from memory for every match in the results
table, looking to see if any of the bytes have changed. If so, the <I>Match Bytes</I> and
<I>Match Value</I> columns will display the changed values in red.</P>
</BLOCKQUOTE><A name="Toggle_Search"></A>
<H3><IMG alt="" border="0" src="icon.base.mem.search.panel.search">&nbsp;&nbsp;Toggle
Search Controls</H3>
<BLOCKQUOTE>
<P>This action toggles the <A href="#Search_Controls">search controls</A> on or off.</P>
</BLOCKQUOTE><A name="Toggle_Scan"></A>
<H3><IMG alt="" border="0" src="icon.base.mem.search.panel.scan">&nbsp;&nbsp;Toggle Scan
Controls</H3>
<BLOCKQUOTE>
<P>This action toggles the <A href="#Scan_Controls">scan controls</A> on or off.</P>
</BLOCKQUOTE><A name="Toggle_Options"></A>
<H3><IMG alt="" border="0" src="icon.base.mem.search.panel.options">&nbsp;&nbsp;Toggle
Options Panel</H3>
<BLOCKQUOTE>
<P>This action toggles the <A href="#Options">options display</A> on or off.</P>
</BLOCKQUOTE>
<P align="center"><IMG src="images/SearchMemoryHex.png" border="0" alt=""> &nbsp;</P>
<H2>Search Options</H2>
<H3><IMG alt="" border="0" src="icon.make.selection">&nbsp;&nbsp;Make Selection</H3>
<BLOCKQUOTE>
<H3>Search</H3>
<BLOCKQUOTE>
<H4>Search Value</H4>
<P>This action will create a selection in the associated view from all the currently
selected match table rows.</P>
</BLOCKQUOTE>
<UL>
<LI>The value to search.&nbsp; The values entered will be interpreted based on the
<I>Format</I> options.</LI>
</UL>
<H3><IMG alt="" border="0" src="icon.navigate.in">&nbsp;&nbsp;Toggle Single Click
Navigation</H3>
<BLOCKQUOTE>
<H4>Hex Sequence</H4>
<P>This action toggles on or off whether a single row selection change triggers
navigation in the associated view. If this option is off, the user must double-click on a
row to navigate in the associated view.</P>
</BLOCKQUOTE>
<UL>
<LI>As the search value is entered, this field will display the exact hex byte sequence
that will be searched for in memory.</LI>
</UL>
<H3>Format</H3>
<H3><IMG alt="" border="0" src="icon.plugin.table.delete.row">&nbsp;&nbsp;Delete Selected
Table Rows</H3>
<BLOCKQUOTE>
<H4><A name="Hex"></A><B>Hex:</B></H4>
<P>This action deletes all selected rows in the results match table.</P>
</BLOCKQUOTE>
</BLOCKQUOTE><A name="Combining_Searches"></A>
<H2>Combining Searches</H2>
<BLOCKQUOTE>
<P>Results from multiple searches can be combined in various ways. These options are only
available once you have results in the table. Once results are present, the <I>Search
Button</I> changes to a button that has a drop down menu that allows you do decide how you
want additional searches to interact with the current results showing in the results table.
The options are as follows:</P>
<H4>New Search</H4>
<BLOCKQUOTE>
This option causes all existing result matches to be replaced with the results of the new
search. When this option is selected, the button text will show "New Search".
<P><IMG alt="" border="0" src="help/shared/tip.png">This does not create a new
search memory window, but re-uses this window. To create a new
search window, you must go back to the search memory action from the main menu.</P>
</BLOCKQUOTE>
<UL>
<LI>Value is interpreted as a sequence of hex numbers, separated by spaces.&nbsp; Wildcard
characters can be used to match any single hex digit (i.e. any 4 bit value). &nbsp;Either
the '.' or '?' character can be used for the wildcard character.</LI>
<LI>Each hex number (separated by spaces) will produce a sequence of bytes that may be
reversed depending on the Byte Order.</LI>
<LI>The byte search pattern is formed by concatenating the bytes from each hex number.</LI>
</UL>
<H4>A union B</H4>
<BLOCKQUOTE>
<BLOCKQUOTE>
<P><B>Example:</B></P>
<TABLE border="1" width="526">
<TBODY>
<TR>
<TD width="260">
<P><B>Value:&nbsp;&nbsp;&nbsp;</B></P>
</TD>
<TD width="252">
<P>&nbsp;"1234 567 89ab"</P>
</TD>
</TR>
<TR>
<TD width="260">
<P><B><A href="help/topics/Glossary/glossary.htm#LittleEndian">Little Endian</A>
Hex Sequence&nbsp;&nbsp;&nbsp;</B></P>
</TD>
<TD width="252">
<P>34 12 67 05 ab 89</P>
</TD>
</TR>
<TR>
<TD width="260">
<P><B><A href="help/topics/Glossary/glossary.htm#BigEndian">Big Endian</A> Hex
Sequence&nbsp;&nbsp;&nbsp;</B></P>
</TD>
<TD width="252">
<P>12 34 05 67 89 ab</P>
</TD>
</TR>
</TBODY>
</TABLE>
<P><IMG alt="" border="0" src="help/shared/tip.png">
As a convenience, if a user enters a single wildcard value within the search text, then
the search string will be interpreted as if 2 consecutive wildcard characters were
entered, meaning to match any byte value.
</P>
<P>
Similarly, if the search string contains an odd number of characters, then a 0 is prepended
to the search string, based on the assumption that a single hex digit implies a leading
0 value.
</P>
</BLOCKQUOTE>
This option adds the results from the new search to all the existing result matches. When
this option is selected, the button text will show "Add To Search".
</BLOCKQUOTE>
<P>&nbsp;</P>
<H4>A intersect B</H4>
<BLOCKQUOTE>
<H4><A name="String"></A><B>String:</B></H4>
<BLOCKQUOTE>
<P>Value is interpreted as the specified character encoding. The center panel of the
Search Memory dialog shows the <I>Format Options</I>, described below.</P>
<P align="center"><IMG border="0" src="images/SearchMemoryString.png" alt=""></P>
<UL>
<LI><I>Encoding</I> - Interprets strings by the specified encoding.&nbsp; Note that
byte ordering determines if the high order byte comes first or last.</LI>
<LI><I>Case Sensitive</I> - Turning off this option will search for the string
regardless of case using the specified character encoding. Only applicable for English
characters.</LI>
<LI><I>Escape Sequences</I> - Enabling this option allows escape sequences in the
search value (i.e., allows \n to be searched for).</LI>
</UL>
This option will combine the results of the new search with the existing search, but only
keep results that are in both the existing result set and the new search result set. When
this option is selected, the button text will show "Intersect Search".
</BLOCKQUOTE>
<H4><A name="Decimal"></A><B>Decimal:</B></H4>
<H4>A xor B</H4>
<BLOCKQUOTE>
<P>Value is interpreted as a sequence of decimal numbers, separated by spaces. The center
panel of the Search Memory dialog shows the <I>Decimal Options</I>, described below.</P>
This option will combine the results of the new search with the existing search, but only
keep results that are in either the new or existing results, but not both.
When this option is selected, the button text will show "Xor Search".
</BLOCKQUOTE>
<P align="center"><IMG border="0" src="images/SearchMemoryDecimal.png" alt=""></P>
<UL>
<LI>Only numbers that fit the specified Decimal Options are allowed to be entered.</LI>
<LI>The byte search pattern is formed by concatenating the bytes from each&nbsp;
number.</LI>
<LI>
Valid decimal numbers are:
<UL>
<LI><B>Byte</B> - any fixed point 8 bit number&nbsp; (-128 to 255)</LI>
<LI><B>Word</B> - any fixed point 16 bit number (-32768 to 65535)</LI>
<LI><B>DWord</B> - any fixed point 32 bit number (you get the idea.....)</LI>
<LI><B>QWord</B> - any fixed point 64 bit number</LI>
<LI><B>Float</B> - any 32 bit floating point number</LI>
<LI><B>Double</B> any 64 bit floating point number</LI>
</UL>
</LI>
</UL>
<H4><A name="Binary"></A><B>Binary:</B></H4>
<H4>A - B</H4>
<BLOCKQUOTE>
<P>Value is interpreted as a sequence of binary numbers, separated by spaces.&nbsp;
Wildcard characters ('x' or '?' or '.') can be used to match any bit.</P>
Subtracts the new results from the existing results. When
this option is selected, the button text will show "A-B Search".
</BLOCKQUOTE>
<P align="center"><IMG border="0" src="images/SearchMemoryBinary.png" alt=""></P>
<UL>
<LI>Only binary digits (0 or 1) or wildcard characters (*?.) are allowed to be
entered.</LI>
<LI>The byte search pattern is formed by concatenating the bytes from each&nbsp;
number.</LI>
<LI>An additional Mask byte which is not shown, is generated for each search byte to handle
the wildcards.</LI>
</UL>
<H4><A name="Regular_Expression"></A><B>Regular Expression:</B></H4>
<H4>B - A</H4>
<BLOCKQUOTE>
<P>Value is interpreted as a Java <A name="RegularExpression"></A><I>Regular Expression</I>
that is matched against memory as if all memory was a string. Help on how to form regular
expressions is available on the <A href="Regular_Expressions.htm">Regular Expression
Help</A> page.</P>
Subtracts the existing results from the new results. When this option is
selected, the button text will show "B-A Search".
</BLOCKQUOTE>
<P align="center"><IMG border="0" src="images/SearchMemoryRegex.png" alt=""></P>
<P><IMG alt="" border="0" src="help/shared/tip.png">Many of these set operations only make
sense if you do advanced searches using wildcards. For example, if you do a search for
integer values of 5, it would make no sense to intersect that with a search for integer
values of 3. The sets are mutually exclusive, so the intersection would be empty.
Explaining how to take advantage of these options is beyond the scope of this document.</P>
</BLOCKQUOTE><A name="Repeating_Searches"></A>
<UL>
<LI>Regular Expressions can only be used to search forward in memory.</LI>
<LI>No Hex Sequence is displayed for regular expressions.</LI>
</UL>
</BLOCKQUOTE>
<H3>Memory Block Types</H3>
<UL>
<LI>Selects which initialized memory blocks are searched. Ghidra now stores external
information from the program's file header in special memory blocks. These blocks do not live
in the program's address space, but instead are stored in the "OTHER" address space. Memory
blocks which would be found in an actual running version of the program are referred to as
"Loaded Memory Blocks."</LI>
<LI style="list-style: none">
<UL>
<LI>Loaded Blocks - will search only "loaded" memory blocks (memory blocks that would
appear in an actual running instance of the program) and not "Other" information memory
blocks.</LI>
<LI>All Blocks - will search all memory blocks including "Other" blocks.</LI>
</UL>
</LI>
</UL>
<P>&nbsp;</P>
<H3>Selection Scope</H3>
<UL type="disc">
<LI><B>Search All</B> - If this option is selected, the search will search all memory in the
tool.</LI>
<LI><B>Search Selection</B> - If this option is selected, the search will be restricted to
the current selection in the tool. This option is only enabled if there is a current
selection in the tool.</LI>
</UL>
<P>&nbsp;</P>
<H3><B>Code Unit Scope</B></H3>
<H2>Search Forward/Backwards Using Global Actions</H2>
<BLOCKQUOTE>
<P>Filters the matches based upon the code unit containing a given address.</P>
<P>Once at least one search has been executed using the <A href=
"#Memory_Search_Window"><I>Memory Search Window</I></A>, the search can be repeated in an
incremental fashion outside a search window using global actions in the main tool menu or
their assigned default keybindings.</P>
<A name="Repeat_Search_Forwards"></A>
<UL type="disc">
<LI><B>Instructions</B> - includes instruction code units in the search.</LI>
<H3>Search Memory Forwards:</H3>
<LI><B>Defined Data</B> - includes defined data in the search.</LI>
<P>This action will use the input data and settings from the last memory search and begin
searching forwards in memory starting at the cursor location in the associated Listing
display. If a match is found, the cursor in the Listing will be placed on the found match
location. To execute this action, select <B>Search</B> <IMG alt="" border="0" src=
"help/shared/arrow.gif"><B>Search Memory Forwards</B> from the main tool menu or press
<B>F3</B> (the default keybinding.)</P>
<A name="Repeat_Search_Backwards"></A>
<LI><B>Undefined Data</B> - includes undefined data in the search.</LI>
</UL>
</BLOCKQUOTE>
<H3>Search Memory Backwards:</H3>
<H3><B>Byte Order</B></H3>
<P>This action will use the input data and settings from the last memory search and begin
searching backwards in memory starting at the cursor location in the associated Listing
display. If a match is found, the cursor in the Listing will be placed on the found match
location. To execute this action, select <B>Search</B> <IMG alt="" border="0" src=
"help/shared/arrow.gif"><B>Search Memory Backwards</B> from the main tool menu or press
<B>&lt;Shift&gt;F3</B> (the default keybinding.)</P>
</BLOCKQUOTE><A name="Highlight_Options"></A>
<H2>Highlight Search Options</H2>
<BLOCKQUOTE>
<P>Sets the byte ordering for multi-byte values.&nbsp; Has no effect on non-Unicode Ascii
values, Binary, or regular expressions.</P>
<P><B>Little Endian</B> - places low-order bytes first.<BR>
For example, the hex number "1234" will generate the bytes "34" , "12".</P>
<P><B>Big Endian</B> - places high-order bytes first.<BR>
For example, the hex number "1234" will generate the bytes "12", "34".</P>
</BLOCKQUOTE>
<H3>Alignment</H3>
<UL type="disc">
<LI>Generally the alignment defaults to 1, but can be set to any number greater than 0. The
search results will be limited to those that begin on the specified byte alignment. In other
words, an alignment of 1 will get all matching results regardless of the address where each
begins. An alignment of 2 will only return matching results that begin on a word aligned
address.</LI>
</UL>
<P>&nbsp;</P>
<H3>Searching</H3>
<UL>
<LI>Next / Previous - Finds the next/previous occurrence of the byte pattern from the current
cursor location; if you mouse click in the Code Browser to move focus there, you can choose
<B><A name="Repeat_Memory_Search"></A>Search</B><IMG alt="" border="0" src=
"help/shared/arrow.gif"> <B>Repeat Memory Search</B> to go to the next/previous match
found.</LI>
<LI>Search All - Finds all occurrences of the byte pattern in a <A href=
"Query_Results_Dialog.htm">Query Results display</A>.</LI>
</UL>
<BLOCKQUOTE>
<P><IMG alt="" border="0" src="help/shared/tip.png"> For very large Programs that may take a
while to search, you can cancel the search at any time. For these situations, a progress bar
is displayed, along with a <B><FONT size="4">Cancel</FONT></B> button. Click on the <B><FONT
size="4">Cancel</FONT></B> button to stop the search.&nbsp;</P>
<P><IMG alt="" border="0" src="help/shared/note.png"> Dismissing the search dialog
automatically cancels the search operation.</P>
</BLOCKQUOTE>
<H3>&nbsp;</H3>
<H3>Highlight Search Option</H3>
<BLOCKQUOTE>
<P>You can specify that the bytes found in the search be highlighted in the Code Browser by
<P>You can control how the bytes found in the search be highlighted in the Code Browser by
selecting the <I>Highlight Search Results</I> checkbox on the Search Options panel. To view
the Search Options, select <B>Edit</B><IMG alt="" border="0" src="help/shared/arrow.gif">
<B>Tool Options...</B> from the tool menu, then select the <I>Search</I> node in the Options
tree in the Options dialog. You can also change the highlight color. Click on the color bar
next to <I>Highlight Color</I> to bring up a color chooser. Choose the new color, click on
the <B>OK</B> button. Apply your changes by clicking on the <B>OK</B> or <B>Apply</B> button
on the Options dialog.&nbsp;</P>
<B>Tool Options...</B> from the tool menu, then select the <I>Search</I> node in the
Options tree in the Options dialog. You can also change the highlight color. Click on the
color bar next to <I>Highlight Color</I> to bring up a color chooser. Choose the new color,
click on the <B>OK</B> button. Apply your changes by clicking on the <B>OK</B> or
<B>Apply</B> button on the Options dialog.&nbsp;</P>
<BLOCKQUOTE>
<P><IMG alt="" border="0" src="help/shared/note.png"> Highlights are displayed for the last
search that you did. For example, if you bring up the Search Program Text dialog and search
for text, that string now becomes the new highlight string. Similarly, if you invoke <A href=
"help/topics/CodeBrowserPlugin/CodeBrowser.htm#cursorTextHighlight">cursor text
highlighting</A>, that becomes the new highlight string.</P>
<P><IMG alt="" border="0" src="help/shared/note.png"> Highlights are displayed for the
last search that you did. For example, if you bring up the Search Program Text dialog and
search for text, that string now becomes the new highlight string. Similarly, if you
invoke <A href="help/topics/CodeBrowserPlugin/CodeBrowser.htm#cursorTextHighlight">cursor
text highlighting</A>, that becomes the new highlight string.</P>
</BLOCKQUOTE>
<P>Highlights are dropped when you close the search dialog, or close the query results window
for your most recent search.</P>
<BR><BR>
</BLOCKQUOTE>
<H3><A name="Mnemonic_Search"></A>Search for Matching Instructions</H3>
<BLOCKQUOTE>
<P>This action works only on a selection of code. It uses the selected instructions to build
a combined mask/value bit pattern that is then used to populate the search field in the
Memory Search Dialog. This enables searching through memory for a particular ordering of
instructions. There are three options available:&nbsp;</P>
<UL>
<LI><B>Include Operands</B> - All bits that make up the instruction and all bits that make
up the operands will be included in the search pattern.</LI>
<LI><B>Exclude Operands</B> - All bits that make up the instruction are included in the
search pattern but the bits that make up the operands will be masked off to enable wild
carding for those bits.</LI>
<LI><B>Include Operands (except constants)</B> - All bits that make up the instruction are
included in the search pattern and all bits that make up the operands, except constant
operands, which will be masked off to enable wild carding for those bits.</LI>
</UL>
<BLOCKQUOTE>
<P>Example:</P>
<P>A user first selects the following lines of code. Then, from the Search menu they choose
<B>Search for Matching Instructions</B> and one of the following options:</P>
<P align="center"><IMG border="1" src="images/SearchInstructions.png" alt=""></P>
<B>Option 1:</B>
<BLOCKQUOTE>
<P>If the <B>Include Operands</B> action is chosen then the search will find all
instances of the following instructions and operands.</P>
<P align="center"><IMG border="1" src="images/SearchInstructionsIncludeOperands.png" alt=
""></P>
<P>All of the bytes that make up the selected code will be searched for exactly, with no
wild carding. The bit pattern <B>10000101 11000000 01010110 01101010 00010100
01011110</B> which equates to the byte pattern <B>85 c0 56 6a 14 5e</B> is searched
for.<BR>
<BR>
</P>
</BLOCKQUOTE><B>Option 2:</B>
<BLOCKQUOTE>
<P>If the <B>Exclude Operands</B> option is chosen then the search will find all
instances of the following instructions only.</P>
<P align="center"><IMG border="1" src="images/SearchInstructionsExcludeOperands.png" alt=
""></P>
<P>Only the parts of the byte pattern that make up the instructions will be searched for
with the remaining bits used as wildcards. The bit pattern <B>10000101 11...... 01010...
01101010 ........ 01011...</B> is searched for where the .'s indicate the wild carded
values.<BR>
<BR>
</P>
</BLOCKQUOTE><B>Option 3:</B>
<BLOCKQUOTE>
<P>If the <B>Include Operands (except constants)</B> option is chosen then the search
will find all instances of the instruction and all operands except the 0x14 which is a
constant.</P>
<P align="center"><IMG border="1" src=
"images/SearchInstructionsIncludeOperandsNoConsts.png" alt=""></P>
<P>The bit pattern <B>10000101 11000000 01010110 01101010 ........ 01011110</B> which
equates to the byte pattern <B>85 c0 56 6a xx 5e</B> is searched for where xx can be any
number N between 0x0 and 0xff.<BR>
<BR>
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<P><IMG alt="Note" src="help/shared/note.png">The previous operations can only work on a
<B>single</B> selected region. If multiple regions are selected, the following error dialog
will be shown and the operation will be cancelled.</P>
<P align="center"><IMG border="1" src="images/MultipleSelectionError.png" alt=""></P>
<P>Highlights are removed when you close the search window.</P>
<BR>
<BR>
</BLOCKQUOTE>
<P class="providedbyplugin">Provided by: the <B><I>MemSearchPlugin</I></B> &nbsp;</P>
&nbsp;
<P class="providedbyplugin">Provided by: <I>Memory Search Plugin</I></P>
<P class="relatedtopic">Related Topics:</P>
<UL>
<LI><A href="Searching.htm">Searching Program Text</A></LI>
<LI><A href="Query_Results_Dialog.htm">Query Results</A></LI>
<LI><A href="Regular_Expressions.htm">Regular Expressions</A></LI>
</UL>
<BR>
</UL><BR>
<BR>
</BLOCKQUOTE>
</BODY>
</HTML>

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Some files were not shown because too many files have changed in this diff Show More