Merge branch 'master' into fix/bsim_results_no_address_space
@ -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]
|
||||
|
@ -1,18 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
#@title remote gdb
|
||||
#@no-image
|
||||
@ -29,7 +29,7 @@
|
||||
#@enum TargetType:str remote extended-remote
|
||||
#@env OPT_TARGET_TYPE:TargetType="remote" "Target" "The type of remote target"
|
||||
#@env OPT_HOST:str="localhost" "Host" "The hostname of the target"
|
||||
#@env OPT_PORT:str="9999" "Port" "The host's listening port"
|
||||
#@env OPT_PORT:int=9999 "Port" "The host's listening port"
|
||||
#@env OPT_ARCH:str="" "Architecture (optional)" "Target architecture override"
|
||||
#@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb on the local system. Omit the full path to resolve using the system PATH."
|
||||
|
||||
@ -60,6 +60,7 @@ fi
|
||||
-ex "show version" \
|
||||
-ex "python import ghidragdb" \
|
||||
$archcmd \
|
||||
-ex "echo Connecting to $OPT_HOST:$OPT_PORT... " \
|
||||
-ex "target $OPT_TARGET_TYPE $OPT_HOST:$OPT_PORT" \
|
||||
-ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
|
||||
-ex "ghidra trace start" \
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -0,0 +1,59 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.debug.api;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public record ValStr<T>(T val, String str) {
|
||||
|
||||
public interface Decoder<T> {
|
||||
default ValStr<T> decodeValStr(String string) {
|
||||
return new ValStr<>(decode(string), string);
|
||||
}
|
||||
|
||||
T decode(String string);
|
||||
}
|
||||
|
||||
public static ValStr<String> str(String value) {
|
||||
return new ValStr<>(value, value);
|
||||
}
|
||||
|
||||
public static <T> ValStr<T> from(T value) {
|
||||
return new ValStr<>(value, value == null ? "" : value.toString());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> ValStr<T> cast(Class<T> cls, ValStr<?> value) {
|
||||
if (cls.isInstance(value.val)) {
|
||||
return (ValStr<T>) value;
|
||||
}
|
||||
return new ValStr<>(cls.cast(value.val), value.str);
|
||||
}
|
||||
|
||||
public static Map<String, ValStr<?>> fromPlainMap(Map<String, ?> map) {
|
||||
return map.entrySet()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(Entry::getKey, e -> ValStr.from(e.getValue())));
|
||||
}
|
||||
|
||||
public static Map<String, ? super Object> toPlainMap(Map<String, ValStr<?>> map) {
|
||||
return map.entrySet()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(Entry::getKey, e -> e.getValue().val()));
|
||||
}
|
||||
}
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -24,6 +24,7 @@ import ghidra.dbg.DebuggerModelFactory;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.target.TargetLauncher;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
@ -117,8 +118,8 @@ public interface DebuggerProgramLaunchOffer {
|
||||
* @param relPrompt describes the timing of this callback relative to prompting the user
|
||||
* @return the adjusted arguments
|
||||
*/
|
||||
default Map<String, ?> configureLauncher(TargetLauncher launcher,
|
||||
Map<String, ?> arguments, RelPrompt relPrompt) {
|
||||
default Map<String, ValStr<?>> configureLauncher(TargetLauncher launcher,
|
||||
Map<String, ValStr<?>> arguments, RelPrompt relPrompt) {
|
||||
return arguments;
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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();
|
||||
|
@ -0,0 +1,106 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.debug.api.tracermi;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.debug.api.ValStr;
|
||||
|
||||
public record LaunchParameter<T>(Class<T> type, String name, String display, String description,
|
||||
boolean required, List<T> choices, ValStr<T> defaultValue, ValStr.Decoder<T> decoder) {
|
||||
|
||||
public static <T> LaunchParameter<T> create(Class<T> type, String name, String display,
|
||||
String description, boolean required, ValStr<T> defaultValue,
|
||||
ValStr.Decoder<T> decoder) {
|
||||
return new LaunchParameter<>(type, name, display, description, required, List.of(),
|
||||
defaultValue, decoder);
|
||||
}
|
||||
|
||||
public static <T> LaunchParameter<T> choices(Class<T> type, String name, String display,
|
||||
String description, Collection<T> choices, ValStr<T> defaultValue) {
|
||||
return new LaunchParameter<>(type, name, display, description, false,
|
||||
List.copyOf(new LinkedHashSet<>(choices)), defaultValue, str -> {
|
||||
for (T t : choices) {
|
||||
if (t.toString().equals(str)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static Map<String, LaunchParameter<?>> mapOf(Collection<LaunchParameter<?>> parameters) {
|
||||
Map<String, LaunchParameter<?>> result = new LinkedHashMap<>();
|
||||
for (LaunchParameter<?> param : parameters) {
|
||||
LaunchParameter<?> exists = result.put(param.name(), param);
|
||||
if (exists != null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Duplicate names in parameter map: first=%s, second=%s".formatted(exists,
|
||||
param));
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableMap(result);
|
||||
}
|
||||
|
||||
public static Map<String, ValStr<?>> validateArguments(
|
||||
Map<String, LaunchParameter<?>> parameters, Map<String, ValStr<?>> arguments) {
|
||||
if (!parameters.keySet().containsAll(arguments.keySet())) {
|
||||
Set<String> extraneous = new TreeSet<>(arguments.keySet());
|
||||
extraneous.removeAll(parameters.keySet());
|
||||
throw new IllegalArgumentException("Extraneous parameters: " + extraneous);
|
||||
}
|
||||
|
||||
Map<String, String> typeErrors = null;
|
||||
for (Map.Entry<String, ValStr<?>> ent : arguments.entrySet()) {
|
||||
String name = ent.getKey();
|
||||
ValStr<?> val = ent.getValue();
|
||||
LaunchParameter<?> param = parameters.get(name);
|
||||
if (val.val() != null && !param.type.isAssignableFrom(val.val().getClass())) {
|
||||
if (typeErrors == null) {
|
||||
typeErrors = new LinkedHashMap<>();
|
||||
}
|
||||
typeErrors.put(name, "val '%s' is not a %s".formatted(val.val(), param.type()));
|
||||
}
|
||||
}
|
||||
if (typeErrors != null) {
|
||||
throw new IllegalArgumentException("Type errors: " + typeErrors);
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
|
||||
public static Map<String, LaunchParameter<?>> mapOf(LaunchParameter<?>... parameters) {
|
||||
return mapOf(Arrays.asList(parameters));
|
||||
}
|
||||
|
||||
public ValStr<T> decode(String string) {
|
||||
return decoder.decodeValStr(string);
|
||||
}
|
||||
|
||||
public ValStr<T> get(Map<String, ValStr<?>> arguments) {
|
||||
if (arguments.containsKey(name)) {
|
||||
return ValStr.cast(type, arguments.get(name));
|
||||
}
|
||||
if (required) {
|
||||
throw new IllegalArgumentException(
|
||||
"Missing required parameter '%s' (%s)".formatted(display, name));
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public void set(Map<String, ValStr<?>> arguments, ValStr<T> value) {
|
||||
arguments.put(name, value);
|
||||
}
|
||||
}
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -20,7 +20,7 @@ import java.util.Map;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.HelpLocation;
|
||||
@ -150,8 +150,8 @@ public interface TraceRmiLaunchOffer {
|
||||
* @param relPrompt describes the timing of this callback relative to prompting the user
|
||||
* @return the adjusted arguments
|
||||
*/
|
||||
default Map<String, ?> configureLauncher(TraceRmiLaunchOffer offer,
|
||||
Map<String, ?> arguments, RelPrompt relPrompt) {
|
||||
default Map<String, ValStr<?>> configureLauncher(TraceRmiLaunchOffer offer,
|
||||
Map<String, ValStr<?>> arguments, RelPrompt relPrompt) {
|
||||
return arguments;
|
||||
}
|
||||
}
|
||||
@ -293,7 +293,7 @@ public interface TraceRmiLaunchOffer {
|
||||
*
|
||||
* @return the parameters
|
||||
*/
|
||||
Map<String, ParameterDescription<?>> getParameters();
|
||||
Map<String, LaunchParameter<?>> getParameters();
|
||||
|
||||
/**
|
||||
* Check if this offer requires an open program
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -28,6 +28,7 @@ import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.debug.api.model.DebuggerProgramLaunchOffer;
|
||||
import ghidra.debug.api.model.DebuggerProgramLaunchOffer.*;
|
||||
import ghidra.debug.api.model.TraceRecorder;
|
||||
@ -578,10 +579,10 @@ public interface FlatDebuggerRecorderAPI extends FlatDebuggerAPI {
|
||||
try {
|
||||
return waitOn(offer.launchProgram(monitor, PromptMode.NEVER, new LaunchConfigurator() {
|
||||
@Override
|
||||
public Map<String, ?> configureLauncher(TargetLauncher launcher,
|
||||
Map<String, ?> arguments, RelPrompt relPrompt) {
|
||||
Map<String, Object> adjusted = new HashMap<>(arguments);
|
||||
adjusted.put(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, commandLine);
|
||||
public Map<String, ValStr<?>> configureLauncher(TargetLauncher launcher,
|
||||
Map<String, ValStr<?>> arguments, RelPrompt relPrompt) {
|
||||
Map<String, ValStr<?>> adjusted = new HashMap<>(arguments);
|
||||
adjusted.put(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, ValStr.str(commandLine));
|
||||
return adjusted;
|
||||
}
|
||||
}));
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -16,8 +16,10 @@
|
||||
package ghidra.debug.flatapi;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import ghidra.app.services.TraceRmiLauncherService;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
@ -116,13 +118,15 @@ public interface FlatDebuggerRmiAPI extends FlatDebuggerAPI {
|
||||
TaskMonitor monitor) {
|
||||
return offer.launchProgram(monitor, new LaunchConfigurator() {
|
||||
@Override
|
||||
public Map<String, ?> configureLauncher(TraceRmiLaunchOffer offer,
|
||||
Map<String, ?> arguments, RelPrompt relPrompt) {
|
||||
public Map<String, ValStr<?>> configureLauncher(TraceRmiLaunchOffer offer,
|
||||
Map<String, ValStr<?>> arguments, RelPrompt relPrompt) {
|
||||
if (arguments.isEmpty()) {
|
||||
return arguments;
|
||||
}
|
||||
Map<String, Object> args = new HashMap<>(arguments);
|
||||
args.putAll(overrideArgs);
|
||||
Map<String, ValStr<?>> args = new HashMap<>(arguments);
|
||||
for (Entry<String, ?> ent : overrideArgs.entrySet()) {
|
||||
args.put(ent.getKey(), ValStr.from(ent.getValue()));
|
||||
}
|
||||
return args;
|
||||
}
|
||||
});
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -15,397 +15,128 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyEditor;
|
||||
import java.beans.PropertyEditorManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.beans.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import org.apache.commons.collections4.BidiMap;
|
||||
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
import org.jdom.Element;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiTarget;
|
||||
import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractDebuggerParameterDialog;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiTarget.Missing;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.SchemaContext;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.debug.api.tracermi.RemoteParameter;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.AutoConfigState.ConfigStateField;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.layout.PairLayout;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
|
||||
public class RemoteMethodInvocationDialog extends DialogComponentProvider
|
||||
implements PropertyChangeListener {
|
||||
private static final String KEY_MEMORIZED_ARGUMENTS = "memorizedArguments";
|
||||
public class RemoteMethodInvocationDialog extends AbstractDebuggerParameterDialog<RemoteParameter> {
|
||||
|
||||
static class ChoicesPropertyEditor implements PropertyEditor {
|
||||
private final List<?> choices;
|
||||
private final String[] tags;
|
||||
|
||||
private final List<PropertyChangeListener> listeners = new ArrayList<>();
|
||||
|
||||
private Object value;
|
||||
|
||||
public ChoicesPropertyEditor(Set<?> choices) {
|
||||
this.choices = List.copyOf(choices);
|
||||
this.tags = choices.stream().map(Objects::toString).toArray(String[]::new);
|
||||
}
|
||||
/**
|
||||
* TODO: Make this a proper editor which can browse and select objects of a required schema.
|
||||
*/
|
||||
public static class TraceObjectEditor extends PropertyEditorSupport {
|
||||
private final JLabel unmodifiableField = new JLabel();
|
||||
|
||||
@Override
|
||||
public void setValue(Object value) {
|
||||
if (Objects.equals(value, this.value)) {
|
||||
super.setValue(value);
|
||||
if (value == null) {
|
||||
unmodifiableField.setText("");
|
||||
return;
|
||||
}
|
||||
if (!choices.contains(value)) {
|
||||
throw new IllegalArgumentException("Unsupported value: " + value);
|
||||
if (!(value instanceof TraceObject obj)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
Object oldValue;
|
||||
List<PropertyChangeListener> listeners;
|
||||
synchronized (this.listeners) {
|
||||
oldValue = this.value;
|
||||
this.value = value;
|
||||
if (this.listeners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
listeners = List.copyOf(this.listeners);
|
||||
}
|
||||
PropertyChangeEvent evt = new PropertyChangeEvent(this, null, oldValue, value);
|
||||
for (PropertyChangeListener l : listeners) {
|
||||
l.propertyChange(evt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPaintable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintValue(Graphics gfx, Rectangle box) {
|
||||
// Not paintable
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJavaInitializationString() {
|
||||
if (value == null) {
|
||||
return "null";
|
||||
}
|
||||
if (value instanceof String str) {
|
||||
return "\"" + StringEscapeUtils.escapeJava(str) + "\"";
|
||||
}
|
||||
return Objects.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAsText() {
|
||||
return Objects.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAsText(String text) throws IllegalArgumentException {
|
||||
int index = ArrayUtils.indexOf(tags, text);
|
||||
if (index < 0) {
|
||||
throw new IllegalArgumentException("Unsupported value: " + text);
|
||||
}
|
||||
setValue(choices.get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getTags() {
|
||||
return tags.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getCustomEditor() {
|
||||
return null;
|
||||
unmodifiableField.setText(obj.getCanonicalPath().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCustomEditor() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
public Component getCustomEditor() {
|
||||
return unmodifiableField;
|
||||
}
|
||||
}
|
||||
|
||||
record NameTypePair(String name, Class<?> type) {
|
||||
public static NameTypePair fromParameter(SchemaContext ctx, RemoteParameter parameter) {
|
||||
return new NameTypePair(parameter.name(), ctx.getSchema(parameter.type()).getType());
|
||||
}
|
||||
|
||||
public static NameTypePair fromString(String name) throws ClassNotFoundException {
|
||||
String[] parts = name.split(",", 2);
|
||||
if (parts.length != 2) {
|
||||
// This appears to be a bad assumption - empty fields results in solitary labels
|
||||
return new NameTypePair(parts[0], String.class);
|
||||
//throw new IllegalArgumentException("Could not parse name,type");
|
||||
}
|
||||
return new NameTypePair(parts[0], Class.forName(parts[1]));
|
||||
}
|
||||
static {
|
||||
PropertyEditorManager.registerEditor(TraceObject.class, TraceObjectEditor.class);
|
||||
}
|
||||
|
||||
private final BidiMap<RemoteParameter, PropertyEditor> paramEditors =
|
||||
new DualLinkedHashBidiMap<>();
|
||||
private final SchemaContext ctx;
|
||||
|
||||
private JPanel panel;
|
||||
private JLabel descriptionLabel;
|
||||
private JPanel pairPanel;
|
||||
private PairLayout layout;
|
||||
|
||||
protected JButton invokeButton;
|
||||
protected JButton resetButton;
|
||||
|
||||
private final PluginTool tool;
|
||||
private SchemaContext ctx;
|
||||
private Map<String, RemoteParameter> parameters;
|
||||
private Map<String, Object> defaults;
|
||||
|
||||
// TODO: Not sure this is the best keying, but I think it works.
|
||||
private Map<NameTypePair, Object> memorized = new HashMap<>();
|
||||
private Map<String, Object> arguments;
|
||||
|
||||
public RemoteMethodInvocationDialog(PluginTool tool, String title, String buttonText,
|
||||
Icon buttonIcon) {
|
||||
super(title, true, true, true, false);
|
||||
this.tool = tool;
|
||||
|
||||
populateComponents(buttonText, buttonIcon);
|
||||
setRememberSize(false);
|
||||
}
|
||||
|
||||
protected Object computeMemorizedValue(RemoteParameter parameter) {
|
||||
return memorized.computeIfAbsent(NameTypePair.fromParameter(ctx, parameter),
|
||||
ntp -> parameter.getDefaultValue());
|
||||
}
|
||||
|
||||
public Map<String, Object> promptArguments(SchemaContext ctx,
|
||||
Map<String, RemoteParameter> parameterMap, Map<String, Object> defaults) {
|
||||
setParameters(ctx, parameterMap);
|
||||
setDefaults(defaults);
|
||||
tool.showDialog(this);
|
||||
|
||||
return getArguments();
|
||||
}
|
||||
|
||||
public void setParameters(SchemaContext ctx, Map<String, RemoteParameter> parameterMap) {
|
||||
public RemoteMethodInvocationDialog(PluginTool tool, SchemaContext ctx, String title,
|
||||
String buttonText, Icon buttonIcon) {
|
||||
super(tool, title, buttonText, buttonIcon);
|
||||
this.ctx = ctx;
|
||||
this.parameters = parameterMap;
|
||||
populateOptions();
|
||||
}
|
||||
|
||||
public void setDefaults(Map<String, Object> defaults) {
|
||||
this.defaults = defaults;
|
||||
}
|
||||
|
||||
private void populateComponents(String buttonText, Icon buttonIcon) {
|
||||
panel = new JPanel(new BorderLayout());
|
||||
panel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||
|
||||
layout = new PairLayout(5, 5);
|
||||
pairPanel = new JPanel(layout);
|
||||
|
||||
JPanel centering = new JPanel(new FlowLayout(FlowLayout.CENTER));
|
||||
JScrollPane scrolling = new JScrollPane(centering, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
|
||||
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
//scrolling.setPreferredSize(new Dimension(100, 130));
|
||||
panel.add(scrolling, BorderLayout.CENTER);
|
||||
centering.add(pairPanel);
|
||||
|
||||
descriptionLabel = new JLabel();
|
||||
descriptionLabel.setMaximumSize(new Dimension(300, 100));
|
||||
panel.add(descriptionLabel, BorderLayout.NORTH);
|
||||
|
||||
addWorkPanel(panel);
|
||||
|
||||
invokeButton = new JButton(buttonText, buttonIcon);
|
||||
addButton(invokeButton);
|
||||
resetButton = new JButton("Reset", DebuggerResources.ICON_REFRESH);
|
||||
addButton(resetButton);
|
||||
addCancelButton();
|
||||
|
||||
invokeButton.addActionListener(this::invoke);
|
||||
resetButton.addActionListener(this::reset);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelCallback() {
|
||||
this.arguments = null;
|
||||
close();
|
||||
protected String parameterName(RemoteParameter parameter) {
|
||||
return parameter.name();
|
||||
}
|
||||
|
||||
protected void invoke(ActionEvent evt) {
|
||||
this.arguments = collectArguments();
|
||||
close();
|
||||
}
|
||||
|
||||
private void reset(ActionEvent evt) {
|
||||
this.arguments = new HashMap<>();
|
||||
for (RemoteParameter param : parameters.values()) {
|
||||
if (defaults.containsKey(param.name())) {
|
||||
arguments.put(param.name(), defaults.get(param.name()));
|
||||
}
|
||||
else {
|
||||
arguments.put(param.name(), param.getDefaultValue());
|
||||
}
|
||||
@Override
|
||||
protected Class<?> parameterType(RemoteParameter parameter) {
|
||||
Class<?> type = ctx.getSchema(parameter.type()).getType();
|
||||
if (TargetObject.class.isAssignableFrom(type)) {
|
||||
return TraceObject.class;
|
||||
}
|
||||
populateValues();
|
||||
return type;
|
||||
}
|
||||
|
||||
protected PropertyEditor createEditor(RemoteParameter param) {
|
||||
Class<?> type = ctx.getSchema(param.type()).getType();
|
||||
PropertyEditor editor = PropertyEditorManager.findEditor(type);
|
||||
if (editor != null) {
|
||||
return editor;
|
||||
}
|
||||
Msg.warn(this, "No editor for " + type + "? Trying String instead");
|
||||
return PropertyEditorManager.findEditor(String.class);
|
||||
@Override
|
||||
protected String parameterLabel(RemoteParameter parameter) {
|
||||
return "".equals(parameter.display()) ? parameter.name() : parameter.display();
|
||||
}
|
||||
|
||||
void populateOptions() {
|
||||
pairPanel.removeAll();
|
||||
paramEditors.clear();
|
||||
for (RemoteParameter param : parameters.values()) {
|
||||
String text = param.display().equals("") ? param.name() : param.display();
|
||||
JLabel label = new JLabel(text);
|
||||
label.setToolTipText(param.description());
|
||||
pairPanel.add(label);
|
||||
|
||||
PropertyEditor editor = createEditor(param);
|
||||
Object val = computeMemorizedValue(param);
|
||||
if (val == null || val.equals(TraceRmiTarget.Missing.MISSING)) {
|
||||
editor.setValue("");
|
||||
} else {
|
||||
editor.setValue(val);
|
||||
}
|
||||
editor.addPropertyChangeListener(this);
|
||||
pairPanel.add(MiscellaneousUtils.getEditorComponent(editor));
|
||||
paramEditors.put(param, editor);
|
||||
}
|
||||
@Override
|
||||
protected String parameterToolTip(RemoteParameter parameter) {
|
||||
return parameter.description();
|
||||
}
|
||||
|
||||
void populateValues() {
|
||||
for (Map.Entry<String, Object> ent : arguments.entrySet()) {
|
||||
RemoteParameter param = parameters.get(ent.getKey());
|
||||
if (param == null) {
|
||||
Msg.warn(this, "No parameter for argument: " + ent);
|
||||
continue;
|
||||
}
|
||||
PropertyEditor editor = paramEditors.get(param);
|
||||
editor.setValue(ent.getValue());
|
||||
}
|
||||
@Override
|
||||
protected ValStr<?> parameterDefault(RemoteParameter parameter) {
|
||||
return ValStr.from(parameter.getDefaultValue());
|
||||
}
|
||||
|
||||
protected Map<String, Object> collectArguments() {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
for (RemoteParameter param : paramEditors.keySet()) {
|
||||
Object val = memorized.get(NameTypePair.fromParameter(ctx, param));
|
||||
if (val != null) {
|
||||
map.put(param.name(), val);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
@Override
|
||||
protected Collection<?> parameterChoices(RemoteParameter parameter) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
public Map<String, Object> getArguments() {
|
||||
@Override
|
||||
protected Map<String, ValStr<?>> validateArguments(Map<String, RemoteParameter> parameters,
|
||||
Map<String, ValStr<?>> arguments) {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
public <T> void setMemorizedArgument(String name, Class<T> type, T value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
memorized.put(new NameTypePair(name, type), value);
|
||||
}
|
||||
|
||||
public <T> T getMemorizedArgument(String name, Class<T> type) {
|
||||
return type.cast(memorized.get(new NameTypePair(name, type)));
|
||||
@Override
|
||||
protected void parameterSaveValue(RemoteParameter parameter, SaveState state, String key,
|
||||
ValStr<?> value) {
|
||||
ConfigStateField.putState(state, parameterType(parameter).asSubclass(Object.class), key,
|
||||
value.val());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
PropertyEditor editor = (PropertyEditor) evt.getSource();
|
||||
RemoteParameter param = paramEditors.getKey(editor);
|
||||
memorized.put(NameTypePair.fromParameter(ctx, param), editor.getValue());
|
||||
protected ValStr<?> parameterLoadValue(RemoteParameter parameter, SaveState state, String key) {
|
||||
return ValStr.from(
|
||||
ConfigStateField.getState(state, parameterType(parameter), key));
|
||||
}
|
||||
|
||||
public void writeConfigState(SaveState saveState) {
|
||||
SaveState subState = new SaveState();
|
||||
for (Map.Entry<NameTypePair, Object> ent : memorized.entrySet()) {
|
||||
NameTypePair ntp = ent.getKey();
|
||||
ConfigStateField.putState(subState, ntp.type().asSubclass(Object.class), ntp.name(),
|
||||
ent.getValue());
|
||||
}
|
||||
saveState.putXmlElement(KEY_MEMORIZED_ARGUMENTS, subState.saveToXml());
|
||||
}
|
||||
|
||||
public void readConfigState(SaveState saveState) {
|
||||
Element element = saveState.getXmlElement(KEY_MEMORIZED_ARGUMENTS);
|
||||
if (element == null) {
|
||||
return;
|
||||
}
|
||||
SaveState subState = new SaveState(element);
|
||||
for (String name : subState.getNames()) {
|
||||
try {
|
||||
NameTypePair ntp = NameTypePair.fromString(name);
|
||||
memorized.put(ntp, ConfigStateField.getState(subState, ntp.type(), ntp.name()));
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Error restoring memorized parameter " + name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setDescription(String htmlDescription) {
|
||||
if (htmlDescription == null) {
|
||||
descriptionLabel.setBorder(BorderFactory.createEmptyBorder());
|
||||
descriptionLabel.setText("");
|
||||
}
|
||||
else {
|
||||
descriptionLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
|
||||
descriptionLabel.setText(htmlDescription);
|
||||
}
|
||||
@Override
|
||||
protected void setEditorValue(PropertyEditor editor, RemoteParameter param, ValStr<?> val) {
|
||||
ValStr<?> v = switch (val.val()) {
|
||||
case Missing __ -> new ValStr<>(null, "");
|
||||
case TraceObject obj -> new ValStr<>(obj, obj.getCanonicalPath().toString());
|
||||
default -> val;
|
||||
};
|
||||
super.setEditorValue(editor, param, v);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi.connection;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.TraceRmiLaunchDialog;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.debug.api.tracermi.LaunchParameter;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
|
||||
public class TraceRmiConnectDialog extends TraceRmiLaunchDialog {
|
||||
|
||||
static final LaunchParameter<String> PARAM_ADDRESS =
|
||||
LaunchParameter.create(String.class, "address",
|
||||
"Host/Address", "Address or hostname for interface(s) to listen on",
|
||||
true, ValStr.str("localhost"), str -> str);
|
||||
static final LaunchParameter<Integer> PARAM_PORT =
|
||||
LaunchParameter.create(Integer.class, "port",
|
||||
"Port", "TCP port number, 0 for ephemeral",
|
||||
true, ValStr.from(0), Integer::decode);
|
||||
private static final Map<String, LaunchParameter<?>> PARAMETERS =
|
||||
LaunchParameter.mapOf(PARAM_ADDRESS, PARAM_PORT);
|
||||
|
||||
public TraceRmiConnectDialog(PluginTool tool, String title, String buttonText) {
|
||||
super(tool, title, buttonText, DebuggerResources.ICON_CONNECTION);
|
||||
}
|
||||
|
||||
public Map<String, ValStr<?>> promptArguments() {
|
||||
return promptArguments(PARAMETERS, Map.of(), Map.of());
|
||||
}
|
||||
}
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -34,11 +34,9 @@ import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.tree.*;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.tree.*;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.debug.api.control.ControlMode;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
@ -62,16 +60,6 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter
|
||||
private static final String GROUP_CONNECT = "1. Connect";
|
||||
private static final String GROUP_MAINTENANCE = "3. Maintenance";
|
||||
|
||||
private static final ParameterDescription<String> PARAM_ADDRESS =
|
||||
ParameterDescription.create(String.class, "address", true, "localhost",
|
||||
"Host/Address", "Address or hostname for interface(s) to listen on");
|
||||
private static final ParameterDescription<Integer> PARAM_PORT =
|
||||
ParameterDescription.create(Integer.class, "port", true, 0,
|
||||
"Port", "TCP port number, 0 for ephemeral");
|
||||
private static final TargetParameterMap PARAMETERS = TargetParameterMap.ofEntries(
|
||||
Map.entry(PARAM_ADDRESS.name, PARAM_ADDRESS),
|
||||
Map.entry(PARAM_PORT.name, PARAM_PORT));
|
||||
|
||||
interface StartServerAction {
|
||||
String NAME = "Start Server";
|
||||
String DESCRIPTION = "Start a TCP server for incoming connections (indefinitely)";
|
||||
@ -344,25 +332,24 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter
|
||||
return traceRmiService != null && !traceRmiService.isServerStarted();
|
||||
}
|
||||
|
||||
private InetSocketAddress promptSocketAddress(String title, String okText) {
|
||||
DebuggerMethodInvocationDialog dialog = new DebuggerMethodInvocationDialog(tool,
|
||||
title, okText, DebuggerResources.ICON_CONNECTION);
|
||||
Map<String, ?> arguments;
|
||||
do {
|
||||
dialog.forgetMemorizedArguments();
|
||||
arguments = dialog.promptArguments(PARAMETERS);
|
||||
}
|
||||
while (dialog.isResetRequested());
|
||||
private InetSocketAddress promptSocketAddress(String title, String okText,
|
||||
HelpLocation helpLocation) {
|
||||
TraceRmiConnectDialog dialog = new TraceRmiConnectDialog(tool, title, okText);
|
||||
dialog.setHelpLocation(helpLocation);
|
||||
Map<String, ValStr<?>> arguments = dialog.promptArguments();
|
||||
|
||||
if (arguments == null) {
|
||||
// Cancelled
|
||||
return null;
|
||||
}
|
||||
String address = PARAM_ADDRESS.get(arguments);
|
||||
int port = PARAM_PORT.get(arguments);
|
||||
String address = TraceRmiConnectDialog.PARAM_ADDRESS.get(arguments).val();
|
||||
int port = TraceRmiConnectDialog.PARAM_PORT.get(arguments).val();
|
||||
return new InetSocketAddress(address, port);
|
||||
}
|
||||
|
||||
private void doActionStartServerActivated(ActionContext __) {
|
||||
InetSocketAddress sockaddr = promptSocketAddress("Start Trace RMI Server", "Start");
|
||||
InetSocketAddress sockaddr = promptSocketAddress("Start Trace RMI Server", "Start",
|
||||
actionStartServer.getHelpLocation());
|
||||
if (sockaddr == null) {
|
||||
return;
|
||||
}
|
||||
@ -395,7 +382,8 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter
|
||||
}
|
||||
|
||||
private void doActionConnectAcceptActivated(ActionContext __) {
|
||||
InetSocketAddress sockaddr = promptSocketAddress("Accept Trace RMI Connection", "Listen");
|
||||
InetSocketAddress sockaddr = promptSocketAddress("Accept Trace RMI Connection", "Listen",
|
||||
actionConnectAccept.getHelpLocation());
|
||||
if (sockaddr == null) {
|
||||
return;
|
||||
}
|
||||
@ -420,7 +408,8 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter
|
||||
}
|
||||
|
||||
private void doActionConnectOutboundActivated(ActionContext __) {
|
||||
InetSocketAddress sockaddr = promptSocketAddress("Connect to Trace RMI", "Connect");
|
||||
InetSocketAddress sockaddr = promptSocketAddress("Connect to Trace RMI", "Connect",
|
||||
actionConnectOutbound.getHelpLocation());
|
||||
if (sockaddr == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -23,7 +23,8 @@ import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.ScriptAttributes;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.TtyCondition;
|
||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.debug.api.tracermi.LaunchParameter;
|
||||
import ghidra.debug.api.tracermi.TerminalSession;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.HelpLocation;
|
||||
@ -84,7 +85,7 @@ public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmi
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ParameterDescription<?>> getParameters() {
|
||||
public Map<String, LaunchParameter<?>> getParameters() {
|
||||
return attrs.parameters();
|
||||
}
|
||||
|
||||
@ -93,12 +94,15 @@ public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmi
|
||||
return attrs.timeoutMillis();
|
||||
}
|
||||
|
||||
protected abstract void prepareSubprocess(List<String> commandLine, Map<String, String> env,
|
||||
Map<String, ?> args, SocketAddress address);
|
||||
protected void prepareSubprocess(List<String> commandLine, Map<String, String> env,
|
||||
Map<String, ValStr<?>> args, SocketAddress address) {
|
||||
ScriptAttributesParser.processArguments(commandLine, env, script, attrs.parameters(), args,
|
||||
address);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void launchBackEnd(TaskMonitor monitor, Map<String, TerminalSession> sessions,
|
||||
Map<String, ?> args, SocketAddress address) throws Exception {
|
||||
Map<String, ValStr<?>> args, SocketAddress address) throws Exception {
|
||||
List<String> commandLine = new ArrayList<>();
|
||||
Map<String, String> env = new HashMap<>(System.getenv());
|
||||
prepareSubprocess(commandLine, env, args, address);
|
||||
@ -112,7 +116,7 @@ public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmi
|
||||
}
|
||||
NullPtyTerminalSession ns = nullPtyTerminal();
|
||||
env.put(ent.getKey(), ns.name());
|
||||
sessions.put(ns.name(), ns);
|
||||
sessions.put(ent.getKey(), ns);
|
||||
}
|
||||
|
||||
sessions.put("Shell",
|
||||
|
@ -28,7 +28,7 @@ import java.util.concurrent.*;
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
|
||||
import ghidra.app.plugin.core.debug.gui.action.ByModuleAutoMapSpec;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.LaunchFailureDialog.ErrPromptResponse;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.DefaultTraceRmiAcceptor;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler;
|
||||
@ -36,8 +36,8 @@ import ghidra.app.plugin.core.terminal.TerminalListener;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.dbg.util.ShellUtils;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.debug.api.action.AutoMapSpec;
|
||||
import ghidra.debug.api.modules.DebuggerMissingProgramActionContext;
|
||||
import ghidra.debug.api.modules.DebuggerStaticMappingChangeListener;
|
||||
@ -212,14 +212,13 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
return mappingService.getOpenMappedLocation(trace, probe, snap) != null;
|
||||
}
|
||||
|
||||
protected SaveState saveLauncherArgsToState(Map<String, ?> args,
|
||||
Map<String, ParameterDescription<?>> params) {
|
||||
protected SaveState saveLauncherArgsToState(Map<String, ValStr<?>> args,
|
||||
Map<String, LaunchParameter<?>> params) {
|
||||
SaveState state = new SaveState();
|
||||
for (ParameterDescription<?> param : params.values()) {
|
||||
Object val = args.get(param.name);
|
||||
for (LaunchParameter<?> param : params.values()) {
|
||||
ValStr<?> val = args.get(param.name());
|
||||
if (val != null) {
|
||||
ConfigStateField.putState(state, param.type.asSubclass(Object.class),
|
||||
"param_" + param.name, val);
|
||||
state.putString("param_" + param.name(), val.str());
|
||||
}
|
||||
}
|
||||
return state;
|
||||
@ -233,56 +232,56 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
plugin.writeProgramLaunchConfig(program, getConfigName(), state);
|
||||
}
|
||||
|
||||
protected void saveLauncherArgs(Map<String, ?> args,
|
||||
Map<String, ParameterDescription<?>> params) {
|
||||
protected void saveLauncherArgs(Map<String, ValStr<?>> args,
|
||||
Map<String, LaunchParameter<?>> params) {
|
||||
saveState(saveLauncherArgsToState(args, params));
|
||||
}
|
||||
|
||||
interface ImageParamSetter {
|
||||
@SuppressWarnings("unchecked")
|
||||
static ImageParamSetter get(ParameterDescription<?> param) {
|
||||
if (param.type == String.class) {
|
||||
return new StringImageParamSetter((ParameterDescription<String>) param);
|
||||
static ImageParamSetter get(LaunchParameter<?> param) {
|
||||
if (param.type() == String.class) {
|
||||
return new StringImageParamSetter((LaunchParameter<String>) param);
|
||||
}
|
||||
if (param.type == PathIsFile.class) {
|
||||
return new FileImageParamSetter((ParameterDescription<PathIsFile>) param);
|
||||
if (param.type() == PathIsFile.class) {
|
||||
return new FileImageParamSetter((LaunchParameter<PathIsFile>) param);
|
||||
}
|
||||
Msg.warn(ImageParamSetter.class,
|
||||
"'Image' parameter has unsupported type: " + param.type);
|
||||
"'Image' parameter has unsupported type: " + param.type());
|
||||
return null;
|
||||
}
|
||||
|
||||
void setImage(Map<String, Object> map, Program program);
|
||||
void setImage(Map<String, ValStr<?>> map, Program program);
|
||||
}
|
||||
|
||||
static class StringImageParamSetter implements ImageParamSetter {
|
||||
private final ParameterDescription<String> param;
|
||||
private final LaunchParameter<String> param;
|
||||
|
||||
public StringImageParamSetter(ParameterDescription<String> param) {
|
||||
public StringImageParamSetter(LaunchParameter<String> param) {
|
||||
this.param = param;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(Map<String, Object> map, Program program) {
|
||||
public void setImage(Map<String, ValStr<?>> map, Program program) {
|
||||
// str-type Image is a hint that the launcher is remote
|
||||
String value = TraceRmiLauncherServicePlugin.getProgramPath(program, false);
|
||||
param.set(map, value);
|
||||
param.set(map, ValStr.str(value));
|
||||
}
|
||||
}
|
||||
|
||||
static class FileImageParamSetter implements ImageParamSetter {
|
||||
private final ParameterDescription<PathIsFile> param;
|
||||
private final LaunchParameter<PathIsFile> param;
|
||||
|
||||
public FileImageParamSetter(ParameterDescription<PathIsFile> param) {
|
||||
public FileImageParamSetter(LaunchParameter<PathIsFile> param) {
|
||||
this.param = param;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(Map<String, Object> map, Program program) {
|
||||
public void setImage(Map<String, ValStr<?>> map, Program program) {
|
||||
// file-type Image is a hint that the launcher is local
|
||||
String str = TraceRmiLauncherServicePlugin.getProgramPath(program, true);
|
||||
PathIsFile value = str == null ? null : new PathIsFile(Paths.get(str));
|
||||
param.set(map, value);
|
||||
param.set(map, new ValStr<>(value, str));
|
||||
}
|
||||
}
|
||||
|
||||
@ -297,33 +296,34 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
* @return the default arguments
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Map<String, ?> generateDefaultLauncherArgs(
|
||||
Map<String, ParameterDescription<?>> params) {
|
||||
Map<String, Object> map = new LinkedHashMap<String, Object>();
|
||||
protected Map<String, ValStr<?>> generateDefaultLauncherArgs(
|
||||
Map<String, LaunchParameter<?>> params) {
|
||||
Map<String, ValStr<?>> map = new LinkedHashMap<>();
|
||||
ImageParamSetter imageSetter = null;
|
||||
for (Entry<String, ParameterDescription<?>> entry : params.entrySet()) {
|
||||
ParameterDescription<?> param = entry.getValue();
|
||||
map.put(entry.getKey(), param.defaultValue);
|
||||
if (PARAM_DISPLAY_IMAGE.equals(param.display)) {
|
||||
for (Entry<String, LaunchParameter<?>> entry : params.entrySet()) {
|
||||
LaunchParameter<?> param = entry.getValue();
|
||||
map.put(entry.getKey(), ValStr.cast(Object.class, param.defaultValue()));
|
||||
if (PARAM_DISPLAY_IMAGE.equals(param.display())) {
|
||||
imageSetter = ImageParamSetter.get(param);
|
||||
// May still be null if type is not supported
|
||||
}
|
||||
else if (param.name.startsWith(PREFIX_PARAM_EXTTOOL)) {
|
||||
String tool = param.name.substring(PREFIX_PARAM_EXTTOOL.length());
|
||||
else if (param.name().startsWith(PREFIX_PARAM_EXTTOOL)) {
|
||||
String tool = param.name().substring(PREFIX_PARAM_EXTTOOL.length());
|
||||
List<String> names =
|
||||
program.getLanguage().getLanguageDescription().getExternalNames(tool);
|
||||
if (names != null && !names.isEmpty()) {
|
||||
if (param.type == String.class) {
|
||||
var paramStr = (ParameterDescription<String>) param;
|
||||
paramStr.set(map, names.get(0));
|
||||
String toolName = names.get(0);
|
||||
if (param.type() == String.class) {
|
||||
var paramStr = (LaunchParameter<String>) param;
|
||||
paramStr.set(map, ValStr.str(toolName));
|
||||
}
|
||||
else if (param.type == PathIsFile.class) {
|
||||
var paramPIF = (ParameterDescription<PathIsFile>) param;
|
||||
paramPIF.set(map, PathIsFile.fromString(names.get(0)));
|
||||
else if (param.type() == PathIsFile.class) {
|
||||
var paramPIF = (LaunchParameter<PathIsFile>) param;
|
||||
paramPIF.set(map, new ValStr<>(PathIsFile.fromString(toolName), toolName));
|
||||
}
|
||||
else if (param.type == PathIsDir.class) {
|
||||
var paramPID = (ParameterDescription<PathIsDir>) param;
|
||||
paramPID.set(map, PathIsDir.fromString(names.get(0)));
|
||||
else if (param.type() == PathIsDir.class) {
|
||||
var paramPID = (LaunchParameter<PathIsDir>) param;
|
||||
paramPID.set(map, new ValStr<>(PathIsDir.fromString(toolName), toolName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -337,50 +337,33 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
/**
|
||||
* Prompt the user for arguments, showing those last used or defaults
|
||||
*
|
||||
* @param lastExc
|
||||
*
|
||||
* @param params the parameters of the model's launcher
|
||||
* @param configurator a thing to generate/modify the (default) arguments
|
||||
* @param lastExc if re-prompting, an error to display
|
||||
* @return the arguments given by the user, or null if cancelled
|
||||
*/
|
||||
protected Map<String, ?> promptLauncherArgs(LaunchConfigurator configurator,
|
||||
protected Map<String, ValStr<?>> promptLauncherArgs(LaunchConfigurator configurator,
|
||||
Throwable lastExc) {
|
||||
Map<String, ParameterDescription<?>> params = getParameters();
|
||||
DebuggerMethodInvocationDialog dialog =
|
||||
new DebuggerMethodInvocationDialog(tool, getTitle(), "Launch", getIcon());
|
||||
Map<String, LaunchParameter<?>> params = getParameters();
|
||||
TraceRmiLaunchDialog dialog =
|
||||
new TraceRmiLaunchDialog(tool, getTitle(), "Launch", getIcon());
|
||||
dialog.setDescription(getDescription());
|
||||
dialog.setHelpLocation(getHelpLocation());
|
||||
if (lastExc != null) {
|
||||
dialog.setStatusText(lastExc.toString(), MessageType.ERROR);
|
||||
}
|
||||
else {
|
||||
dialog.setStatusText("");
|
||||
}
|
||||
|
||||
// NB. Do not invoke read/writeConfigState
|
||||
Map<String, ?> args;
|
||||
boolean reset = false;
|
||||
do {
|
||||
args =
|
||||
configurator.configureLauncher(this, loadLastLauncherArgs(true), RelPrompt.BEFORE);
|
||||
for (ParameterDescription<?> param : params.values()) {
|
||||
Object val = args.get(param.name);
|
||||
if (val != null) {
|
||||
dialog.setMemorizedArgument(param.name, param.type.asSubclass(Object.class),
|
||||
val);
|
||||
}
|
||||
}
|
||||
if (lastExc != null) {
|
||||
dialog.setStatusText(lastExc.toString(), MessageType.ERROR);
|
||||
}
|
||||
else {
|
||||
dialog.setStatusText("");
|
||||
}
|
||||
args = dialog.promptArguments(params);
|
||||
if (args == null) {
|
||||
// Cancelled
|
||||
return null;
|
||||
}
|
||||
reset = dialog.isResetRequested();
|
||||
if (reset) {
|
||||
args = generateDefaultLauncherArgs(params);
|
||||
}
|
||||
|
||||
Map<String, ValStr<?>> defaultArgs = generateDefaultLauncherArgs(params);
|
||||
Map<String, ValStr<?>> lastArgs =
|
||||
configurator.configureLauncher(this, loadLastLauncherArgs(true), RelPrompt.BEFORE);
|
||||
Map<String, ValStr<?>> args = dialog.promptArguments(params, lastArgs, defaultArgs);
|
||||
if (args != null) {
|
||||
saveLauncherArgs(args, params);
|
||||
}
|
||||
while (reset);
|
||||
return args;
|
||||
}
|
||||
|
||||
@ -398,31 +381,40 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
* @param forPrompt true if the user will be confirming the arguments
|
||||
* @return the loaded arguments, or defaults
|
||||
*/
|
||||
protected Map<String, ?> loadLastLauncherArgs(boolean forPrompt) {
|
||||
Map<String, ParameterDescription<?>> params = getParameters();
|
||||
Map<String, ?> args = loadLauncherArgsFromState(loadState(forPrompt), params);
|
||||
protected Map<String, ValStr<?>> loadLastLauncherArgs(boolean forPrompt) {
|
||||
Map<String, LaunchParameter<?>> params = getParameters();
|
||||
Map<String, ValStr<?>> args = loadLauncherArgsFromState(loadState(forPrompt), params);
|
||||
saveLauncherArgs(args, params);
|
||||
return args;
|
||||
}
|
||||
|
||||
protected Map<String, ?> loadLauncherArgsFromState(SaveState state,
|
||||
Map<String, ParameterDescription<?>> params) {
|
||||
Map<String, ?> defaultArgs = generateDefaultLauncherArgs(params);
|
||||
protected Map<String, ValStr<?>> loadLauncherArgsFromState(SaveState state,
|
||||
Map<String, LaunchParameter<?>> params) {
|
||||
Map<String, ValStr<?>> defaultArgs = generateDefaultLauncherArgs(params);
|
||||
if (state == null) {
|
||||
return defaultArgs;
|
||||
}
|
||||
List<String> names = List.of(state.getNames());
|
||||
Map<String, Object> args = new LinkedHashMap<>();
|
||||
for (ParameterDescription<?> param : params.values()) {
|
||||
String key = "param_" + param.name;
|
||||
Object configState =
|
||||
names.contains(key) ? ConfigStateField.getState(state, param.type, key) : null;
|
||||
if (configState != null) {
|
||||
args.put(param.name, configState);
|
||||
Map<String, ValStr<?>> args = new LinkedHashMap<>();
|
||||
Set<String> names = Set.of(state.getNames());
|
||||
for (LaunchParameter<?> param : params.values()) {
|
||||
String key = "param_" + param.name();
|
||||
if (!names.contains(key)) {
|
||||
args.put(param.name(), defaultArgs.get(param.name()));
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
args.put(param.name, defaultArgs.get(param.name));
|
||||
String str = state.getString(key, null);
|
||||
if (str != null) {
|
||||
args.put(param.name(), param.decode(str));
|
||||
continue;
|
||||
}
|
||||
// Perhaps wrong type; was saved in older version.
|
||||
Object fallback = ConfigStateField.getState(state, param.type(), param.name());
|
||||
if (fallback != null) {
|
||||
args.put(param.name(), ValStr.from(fallback));
|
||||
continue;
|
||||
}
|
||||
Msg.warn(this, "Could not load saved launcher arg '%s' (%s)".formatted(param.name(),
|
||||
param.display()));
|
||||
}
|
||||
return args;
|
||||
}
|
||||
@ -435,7 +427,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the launcher args
|
||||
* Obtain the launcher arguments
|
||||
*
|
||||
* <p>
|
||||
* This should either call {@link #promptLauncherArgs(LaunchConfigurator, Throwable)} or
|
||||
@ -447,7 +439,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
* @param lastExc if retrying, the last exception to display as an error message
|
||||
* @return the chosen arguments, or null if the user cancels at the prompt
|
||||
*/
|
||||
public Map<String, ?> getLauncherArgs(boolean prompt, LaunchConfigurator configurator,
|
||||
public Map<String, ValStr<?>> getLauncherArgs(boolean prompt, LaunchConfigurator configurator,
|
||||
Throwable lastExc) {
|
||||
return prompt
|
||||
? configurator.configureLauncher(this, promptLauncherArgs(configurator, lastExc),
|
||||
@ -543,8 +535,8 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
}
|
||||
|
||||
protected abstract void launchBackEnd(TaskMonitor monitor,
|
||||
Map<String, TerminalSession> sessions, Map<String, ?> args, SocketAddress address)
|
||||
throws Exception;
|
||||
Map<String, TerminalSession> sessions, Map<String, ValStr<?>> args,
|
||||
SocketAddress address) throws Exception;
|
||||
|
||||
static class NoStaticMappingException extends Exception {
|
||||
public NoStaticMappingException(String message) {
|
||||
@ -557,9 +549,18 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
}
|
||||
}
|
||||
|
||||
protected void initializeMonitor(TaskMonitor monitor) {
|
||||
protected AutoMapSpec getAutoMapSpec() {
|
||||
DebuggerAutoMappingService auto = tool.getService(DebuggerAutoMappingService.class);
|
||||
AutoMapSpec spec = auto.getAutoMapSpec();
|
||||
return auto == null ? ByModuleAutoMapSpec.instance() : auto.getAutoMapSpec();
|
||||
}
|
||||
|
||||
protected AutoMapSpec getAutoMapSpec(Trace trace) {
|
||||
DebuggerAutoMappingService auto = tool.getService(DebuggerAutoMappingService.class);
|
||||
return auto == null ? ByModuleAutoMapSpec.instance() : auto.getAutoMapSpec(trace);
|
||||
}
|
||||
|
||||
protected void initializeMonitor(TaskMonitor monitor) {
|
||||
AutoMapSpec spec = getAutoMapSpec();
|
||||
if (requiresImage() && spec.hasTask()) {
|
||||
monitor.setMaximum(6);
|
||||
}
|
||||
@ -574,8 +575,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
if (!requiresImage()) {
|
||||
return;
|
||||
}
|
||||
DebuggerAutoMappingService auto = tool.getService(DebuggerAutoMappingService.class);
|
||||
AutoMapSpec spec = auto.getAutoMapSpec(trace);
|
||||
AutoMapSpec spec = getAutoMapSpec(trace);
|
||||
if (!spec.hasTask()) {
|
||||
return;
|
||||
}
|
||||
@ -625,7 +625,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
while (true) {
|
||||
try {
|
||||
monitor.setMessage("Gathering arguments");
|
||||
Map<String, ?> args = getLauncherArgs(prompt, configurator, lastExc);
|
||||
Map<String, ValStr<?>> args = getLauncherArgs(prompt, configurator, lastExc);
|
||||
if (args == null) {
|
||||
if (lastExc == null) {
|
||||
lastExc = new CancelledException();
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -22,18 +22,31 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.ScriptAttributes;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
/**
|
||||
* A launcher implemented by a simple DOS/Windows batch file.
|
||||
*
|
||||
* <p>
|
||||
* The script must start with an attributes header in a comment block.
|
||||
* The script must start with an attributes header in a comment block. See
|
||||
* {@link ScriptAttributesParser}.
|
||||
*/
|
||||
public class BatchScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLaunchOffer {
|
||||
public static final String REM = "::";
|
||||
public static final int REM_LEN = REM.length();
|
||||
|
||||
/**
|
||||
* Create a launch offer from the given batch file.
|
||||
*
|
||||
* @param plugin the launcher service plugin
|
||||
* @param program the current program, usually the target image. In general, this should be used
|
||||
* for at least two purposes. 1) To populate the default command line. 2) To ensure
|
||||
* the target image is mapped in the resulting target trace.
|
||||
* @param script the batch file that implements this offer
|
||||
* @return the offer
|
||||
* @throws FileNotFoundException if the batch file does not exist
|
||||
*/
|
||||
public static BatchScriptTraceRmiLaunchOffer create(TraceRmiLauncherServicePlugin plugin,
|
||||
Program program, File script) throws FileNotFoundException {
|
||||
ScriptAttributesParser parser = new ScriptAttributesParser() {
|
||||
@ -60,11 +73,4 @@ public class BatchScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLaunch
|
||||
File script, String configName, ScriptAttributes attrs) {
|
||||
super(plugin, program, script, configName, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepareSubprocess(List<String> commandLine, Map<String, String> env,
|
||||
Map<String, ?> args, SocketAddress address) {
|
||||
ScriptAttributesParser.processArguments(commandLine, env, script, attrs.parameters(), args,
|
||||
address);
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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;
|
||||
}
|
||||
TypeAndDefault<?> tad =
|
||||
TypeAndDefault.parse(loc, tadParts[0].trim(), tadParts[1].trim(), userTypes);
|
||||
ParameterDescription<?> 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));
|
||||
try {
|
||||
TypeAndDefault<?> tad =
|
||||
TypeAndDefault.parse(loc, tadParts[0].trim(), tadParts[1].trim(), userTypes);
|
||||
LaunchParameter<?> param = tad.createParameter(name, parts.get(1), parts.get(2));
|
||||
if (parameters.put(name, param) != null) {
|
||||
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));
|
||||
reportError(MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG));
|
||||
return;
|
||||
}
|
||||
OptType<?> type = OptType.parse(loc, colonType.substring(1), userTypes);
|
||||
if (type == null) {
|
||||
return;
|
||||
OptType<?> type;
|
||||
try {
|
||||
type = OptType.parse(loc, colonType.substring(1), userTypes);
|
||||
String name = PREFIX_ARG + argNum;
|
||||
parameters.put(name,
|
||||
type.createParameter(name, parts.get(1), parts.get(2), true,
|
||||
new ValStr<>(null, "")));
|
||||
}
|
||||
catch (ParseException e) {
|
||||
reportError(e.getMessage());
|
||||
}
|
||||
String name = PREFIX_ARG + argNum;
|
||||
parameters.put(name, ParameterDescription.create(type.cls(), name, true, null,
|
||||
parts.get(1), parts.get(2)));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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:
|
||||
}
|
||||
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)));
|
||||
return;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractDebuggerParameterDialog;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.debug.api.tracermi.LaunchParameter;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
|
||||
public class TraceRmiLaunchDialog extends AbstractDebuggerParameterDialog<LaunchParameter<?>> {
|
||||
|
||||
public TraceRmiLaunchDialog(PluginTool tool, String title, String buttonText, Icon buttonIcon) {
|
||||
super(tool, title, buttonText, buttonIcon);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String parameterName(LaunchParameter<?> parameter) {
|
||||
return parameter.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> parameterType(LaunchParameter<?> parameter) {
|
||||
return parameter.type();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String parameterLabel(LaunchParameter<?> parameter) {
|
||||
return parameter.display();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String parameterToolTip(LaunchParameter<?> parameter) {
|
||||
return parameter.description();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValStr<?> parameterDefault(LaunchParameter<?> parameter) {
|
||||
return parameter.defaultValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<?> parameterChoices(LaunchParameter<?> parameter) {
|
||||
return parameter.choices();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, ValStr<?>> validateArguments(Map<String, LaunchParameter<?>> parameters,
|
||||
Map<String, ValStr<?>> arguments) {
|
||||
return LaunchParameter.validateArguments(parameters, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parameterSaveValue(LaunchParameter<?> parameter, SaveState state, String key,
|
||||
ValStr<?> value) {
|
||||
state.putString(key, value.str());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValStr<?> parameterLoadValue(LaunchParameter<?> parameter, SaveState state,
|
||||
String key) {
|
||||
String str = state.getString(key, null);
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
return parameter.decode(str);
|
||||
}
|
||||
}
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -22,6 +22,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.ScriptAttributes;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
/**
|
||||
@ -32,6 +33,8 @@ import ghidra.program.model.listing.Program;
|
||||
* {@link ScriptAttributesParser}.
|
||||
*/
|
||||
public class UnixShellScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLaunchOffer {
|
||||
public static final String HASH = "#";
|
||||
public static final int HASH_LEN = HASH.length();
|
||||
public static final String SHEBANG = "#!";
|
||||
|
||||
/**
|
||||
@ -56,10 +59,10 @@ public class UnixShellScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLa
|
||||
@Override
|
||||
protected String removeDelimiter(String line) {
|
||||
String stripped = line.stripLeading();
|
||||
if (!stripped.startsWith("#")) {
|
||||
if (!stripped.startsWith(HASH)) {
|
||||
return null;
|
||||
}
|
||||
return stripped.substring(1);
|
||||
return stripped.substring(HASH_LEN);
|
||||
}
|
||||
};
|
||||
ScriptAttributes attrs = parser.parseFile(script);
|
||||
@ -68,15 +71,7 @@ public class UnixShellScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLa
|
||||
}
|
||||
|
||||
private UnixShellScriptTraceRmiLaunchOffer(TraceRmiLauncherServicePlugin plugin,
|
||||
Program program,
|
||||
File script, String configName, ScriptAttributes attrs) {
|
||||
Program program, File script, String configName, ScriptAttributes attrs) {
|
||||
super(plugin, program, script, configName, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepareSubprocess(List<String> commandLine, Map<String, String> env,
|
||||
Map<String, ?> args, SocketAddress address) {
|
||||
ScriptAttributesParser.processArguments(commandLine, env, script, attrs.parameters(), args,
|
||||
address);
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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);
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -37,6 +37,7 @@ import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.dbg.util.PathMatcher;
|
||||
import ghidra.dbg.util.PathPredicates;
|
||||
import ghidra.dbg.util.PathPredicates.Align;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.debug.api.model.DebuggerObjectActionContext;
|
||||
import ghidra.debug.api.model.DebuggerSingleObjectPathActionContext;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
@ -345,25 +346,15 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||
}
|
||||
|
||||
private Map<String, Object> promptArgs(RemoteMethod method, Map<String, Object> defaults) {
|
||||
SchemaContext ctx = getSchemaContext();
|
||||
/**
|
||||
* TODO: RemoteMethod parameter descriptions should also use ValStr. This map conversion
|
||||
* stuff is getting onerous and hacky.
|
||||
*/
|
||||
Map<String, ValStr<?>> defs = ValStr.fromPlainMap(defaults);
|
||||
RemoteMethodInvocationDialog dialog = new RemoteMethodInvocationDialog(tool,
|
||||
method.display(), method.display(), null);
|
||||
while (true) {
|
||||
for (RemoteParameter param : method.parameters().values()) {
|
||||
Object val = defaults.get(param.name());
|
||||
if (val != null) {
|
||||
Class<?> type = ctx.getSchema(param.type()).getType();
|
||||
dialog.setMemorizedArgument(param.name(), type.asSubclass(Object.class),
|
||||
val);
|
||||
}
|
||||
}
|
||||
Map<String, Object> args = dialog.promptArguments(ctx, method.parameters(), defaults);
|
||||
if (args == null) {
|
||||
// Cancelled
|
||||
return null;
|
||||
}
|
||||
return args;
|
||||
}
|
||||
getSchemaContext(), method.display(), method.display(), null);
|
||||
Map<String, ValStr<?>> args = dialog.promptArguments(method.parameters(), defs, defs);
|
||||
return args == null ? null : ValStr.toPlainMap(args);
|
||||
}
|
||||
|
||||
private CompletableFuture<?> invokeMethod(boolean prompt, RemoteMethod method,
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "ghidratrace"
|
||||
version = "11.1.2"
|
||||
version = "11.2"
|
||||
authors = [
|
||||
{ name="Ghidra Development Team" },
|
||||
]
|
||||
|
@ -1,17 +1,17 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
from collections import deque, namedtuple
|
||||
from concurrent.futures import Future
|
||||
@ -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):
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -30,7 +30,7 @@ import org.junit.Test;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.InvocationDialogHelper;
|
||||
import ghidra.app.plugin.core.debug.gui.InvocationDialogHelper;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.tree.*;
|
||||
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiClient;
|
||||
@ -60,13 +60,17 @@ public class TraceRmiConnectionManagerProviderTest extends AbstractGhidraHeadedD
|
||||
provider = waitForComponentProvider(TraceRmiConnectionManagerProvider.class);
|
||||
}
|
||||
|
||||
InvocationDialogHelper<?, ?> waitDialog() {
|
||||
return InvocationDialogHelper.waitFor(TraceRmiConnectDialog.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionAccept() throws Exception {
|
||||
performEnabledAction(provider, provider.actionConnectAccept, false);
|
||||
InvocationDialogHelper helper = InvocationDialogHelper.waitFor();
|
||||
InvocationDialogHelper<?, ?> helper = waitDialog();
|
||||
helper.dismissWithArguments(Map.ofEntries(
|
||||
Map.entry("address", "localhost"),
|
||||
Map.entry("port", 0)));
|
||||
helper.entry("address", "localhost"),
|
||||
helper.entry("port", 0)));
|
||||
waitForPass(() -> Unique.assertOne(traceRmiService.getAllAcceptors()));
|
||||
}
|
||||
|
||||
@ -78,10 +82,10 @@ public class TraceRmiConnectionManagerProviderTest extends AbstractGhidraHeadedD
|
||||
throw new AssertionError();
|
||||
}
|
||||
performEnabledAction(provider, provider.actionConnectOutbound, false);
|
||||
InvocationDialogHelper helper = InvocationDialogHelper.waitFor();
|
||||
InvocationDialogHelper<?, ?> helper = waitDialog();
|
||||
helper.dismissWithArguments(Map.ofEntries(
|
||||
Map.entry("address", sockaddr.getHostString()),
|
||||
Map.entry("port", sockaddr.getPort())));
|
||||
helper.entry("address", sockaddr.getHostString()),
|
||||
helper.entry("port", sockaddr.getPort())));
|
||||
try (SocketChannel channel = server.accept()) {
|
||||
TestTraceRmiClient client = new TestTraceRmiClient(channel);
|
||||
client.sendNegotiate("Test client");
|
||||
@ -94,10 +98,10 @@ public class TraceRmiConnectionManagerProviderTest extends AbstractGhidraHeadedD
|
||||
@Test
|
||||
public void testActionStartServer() throws Exception {
|
||||
performEnabledAction(provider, provider.actionStartServer, false);
|
||||
InvocationDialogHelper helper = InvocationDialogHelper.waitFor();
|
||||
InvocationDialogHelper<?, ?> helper = waitDialog();
|
||||
helper.dismissWithArguments(Map.ofEntries(
|
||||
Map.entry("address", "localhost"),
|
||||
Map.entry("port", 0)));
|
||||
helper.entry("address", "localhost"),
|
||||
helper.entry("port", 0)));
|
||||
waitForPass(() -> assertTrue(traceRmiService.isServerStarted()));
|
||||
waitForPass(() -> assertFalse(provider.actionStartServer.isEnabled()));
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -40,6 +40,18 @@ import ghidra.util.task.TaskMonitor;
|
||||
public class ByModuleAutoMapSpec implements AutoMapSpec {
|
||||
public static final String CONFIG_NAME = "1_MAP_BY_MODULE";
|
||||
|
||||
/**
|
||||
* Get the instance.
|
||||
*
|
||||
* <p>
|
||||
* Note this will not work until after the class searcher is done.
|
||||
*
|
||||
* @return the instance
|
||||
*/
|
||||
public static ByModuleAutoMapSpec instance() {
|
||||
return (ByModuleAutoMapSpec) AutoMapSpec.fromConfigName(CONFIG_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfigName() {
|
||||
return CONFIG_NAME;
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -34,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);
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -26,6 +26,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JPanel;
|
||||
@ -63,6 +64,7 @@ import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
|
||||
import ghidra.dbg.util.DebuggerCallbackReorderer;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.debug.api.model.DebuggerMemoryMapper;
|
||||
import ghidra.debug.api.model.TraceRecorder;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
@ -135,8 +137,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||
private final AutoService.Wiring autoServiceWiring;
|
||||
|
||||
@AutoOptionDefined(
|
||||
name = "Default Extended Step",
|
||||
description = "The default string for the extended step command")
|
||||
name = "Default Extended Step",
|
||||
description = "The default string for the extended step command")
|
||||
String extendedStep = "";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@ -1367,13 +1369,14 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||
boolean prompt = p;
|
||||
};
|
||||
return AsyncUtils.loop(TypeSpec.VOID, (loop) -> {
|
||||
Map<String, ?> args = launchOffer.getLauncherArgs(launcher, locals.prompt);
|
||||
Map<String, ValStr<?>> args = launchOffer.getLauncherArgs(launcher, locals.prompt);
|
||||
if (args == null) {
|
||||
// Cancelled
|
||||
loop.exit();
|
||||
}
|
||||
else {
|
||||
launcher.launch(args).thenAccept(loop::exit).exceptionally(ex -> {
|
||||
Map<String, ?> a = ValStr.toPlainMap(args);
|
||||
launcher.launch(a).thenAccept(loop::exit).exceptionally(ex -> {
|
||||
loop.repeat();
|
||||
return null;
|
||||
});
|
||||
@ -1428,7 +1431,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||
}
|
||||
return;
|
||||
}
|
||||
Map<String, ?> args = methodDialog.promptArguments(parameters);
|
||||
Map<String, ?> args = methodDialog.promptArguments(parameters, Map.of(), Map.of());
|
||||
if (args != null) {
|
||||
String script = (String) args.get("Script");
|
||||
if (script != null && !script.isEmpty()) {
|
||||
@ -1623,7 +1626,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||
if (configParameters.isEmpty()) {
|
||||
return AsyncUtils.nil();
|
||||
}
|
||||
Map<String, ?> args = configDialog.promptArguments(configParameters);
|
||||
Map<String, ?> args =
|
||||
configDialog.promptArguments(configParameters, Map.of(), Map.of());
|
||||
if (args == null) {
|
||||
// User cancelled
|
||||
return AsyncUtils.nil();
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -65,7 +65,9 @@ class TraceBreakpointSet {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("<at %s in %s: %s>", address, trace.getName(), breakpoints);
|
||||
synchronized (breakpoints) {
|
||||
return String.format("<at %s in %s: %s>", address, trace.getName(), breakpoints);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,22 +128,24 @@ class TraceBreakpointSet {
|
||||
*/
|
||||
public TraceMode computeMode() {
|
||||
TraceMode mode = TraceMode.NONE;
|
||||
if (getControlMode().useEmulatedBreakpoints()) {
|
||||
synchronized (breakpoints) {
|
||||
if (getControlMode().useEmulatedBreakpoints()) {
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
mode = mode.combine(computeEmuMode(bpt.obj));
|
||||
if (mode == TraceMode.MISSING) {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
mode = mode.combine(computeEmuMode(bpt.obj));
|
||||
mode = mode.combine(computeTargetMode(bpt.obj));
|
||||
if (mode == TraceMode.MISSING) {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
mode = mode.combine(computeTargetMode(bpt.obj));
|
||||
if (mode == TraceMode.MISSING) {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -188,14 +192,16 @@ class TraceBreakpointSet {
|
||||
*/
|
||||
public String computeSleigh() {
|
||||
String sleigh = null;
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
String s = bpt.obj.getEmuSleigh();
|
||||
if (sleigh != null && !sleigh.equals(s)) {
|
||||
return null;
|
||||
synchronized (breakpoints) {
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
String s = bpt.obj.getEmuSleigh();
|
||||
if (sleigh != null && !sleigh.equals(s)) {
|
||||
return null;
|
||||
}
|
||||
sleigh = s;
|
||||
}
|
||||
sleigh = s;
|
||||
return sleigh;
|
||||
}
|
||||
return sleigh;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -206,8 +212,10 @@ class TraceBreakpointSet {
|
||||
public void setEmuSleigh(String emuSleigh) {
|
||||
this.emuSleigh = emuSleigh;
|
||||
try (Transaction tx = trace.openTransaction("Set breakpoint Sleigh")) {
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
bpt.obj.setEmuSleigh(emuSleigh);
|
||||
synchronized (breakpoints) {
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
bpt.obj.setEmuSleigh(emuSleigh);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -218,7 +226,9 @@ class TraceBreakpointSet {
|
||||
* @return true if empty, false otherwise
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return breakpoints.isEmpty();
|
||||
synchronized (breakpoints) {
|
||||
return breakpoints.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -227,7 +237,9 @@ class TraceBreakpointSet {
|
||||
* @return the breakpoints
|
||||
*/
|
||||
public Set<TraceBreakpoint> getBreakpoints() {
|
||||
return breakpoints.stream().map(e -> e.obj).collect(Collectors.toUnmodifiableSet());
|
||||
synchronized (breakpoints) {
|
||||
return breakpoints.stream().map(e -> e.obj).collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -246,7 +258,9 @@ class TraceBreakpointSet {
|
||||
bpt.setEmuSleigh(emuSleigh);
|
||||
}
|
||||
}
|
||||
return breakpoints.add(new IDHashed<>(bpt));
|
||||
synchronized (breakpoints) {
|
||||
return breakpoints.add(new IDHashed<>(bpt));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -275,7 +289,9 @@ class TraceBreakpointSet {
|
||||
* @return true if the set actually changes as a result
|
||||
*/
|
||||
public boolean remove(TraceBreakpoint bpt) {
|
||||
return breakpoints.remove(new IDHashed<>(bpt));
|
||||
synchronized (breakpoints) {
|
||||
return breakpoints.remove(new IDHashed<>(bpt));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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,14 +355,18 @@ class TraceBreakpointSet {
|
||||
}
|
||||
|
||||
private void planEnableTarget(BreakpointActionSet actions) {
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
actions.planEnableTarget(target, bpt.obj);
|
||||
synchronized (breakpoints) {
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
actions.planEnableTarget(target, bpt.obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void planEnableEmu(BreakpointActionSet actions) {
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
actions.planEnableEmu(bpt.obj);
|
||||
synchronized (breakpoints) {
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
actions.planEnableEmu(bpt.obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -369,14 +389,18 @@ class TraceBreakpointSet {
|
||||
|
||||
private void planDisableTarget(BreakpointActionSet actions, long length,
|
||||
Collection<TraceBreakpointKind> kinds) {
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
actions.planDisableTarget(target, bpt.obj);
|
||||
synchronized (breakpoints) {
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
actions.planDisableTarget(target, bpt.obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void planDisableEmu(BreakpointActionSet actions) {
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
actions.planDisableEmu(bpt.obj);
|
||||
synchronized (breakpoints) {
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
actions.planDisableEmu(bpt.obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -399,14 +423,18 @@ class TraceBreakpointSet {
|
||||
|
||||
private void planDeleteTarget(BreakpointActionSet actions, long length,
|
||||
Set<TraceBreakpointKind> kinds) {
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
actions.planDeleteTarget(target, bpt.obj);
|
||||
synchronized (breakpoints) {
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
actions.planDeleteTarget(target, bpt.obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void planDeleteEmu(BreakpointActionSet actions) {
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
actions.planDeleteEmu(bpt.obj);
|
||||
synchronized (breakpoints) {
|
||||
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
|
||||
actions.planDeleteEmu(bpt.obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -16,10 +16,12 @@
|
||||
package ghidra.app.plugin.core.debug.service.model;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import docking.ActionContext;
|
||||
import ghidra.app.context.ProgramLocationActionContext;
|
||||
@ -38,6 +40,7 @@ import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
|
||||
import ghidra.dbg.util.PathMatcher;
|
||||
import ghidra.dbg.util.PathPredicates;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.debug.api.model.*;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
@ -237,26 +240,12 @@ public class TraceRecorderTarget extends AbstractTarget {
|
||||
}
|
||||
|
||||
private Map<String, ?> promptArgs(TargetMethod method, Map<String, ?> defaults) {
|
||||
Map<String, ValStr<?>> defs = ValStr.fromPlainMap(defaults);
|
||||
DebuggerMethodInvocationDialog dialog = new DebuggerMethodInvocationDialog(tool,
|
||||
method.getDisplay(), method.getDisplay(), null);
|
||||
while (true) {
|
||||
for (ParameterDescription<?> param : method.getParameters().values()) {
|
||||
Object val = defaults.get(param.name);
|
||||
if (val != null) {
|
||||
dialog.setMemorizedArgument(param.name, param.type.asSubclass(Object.class),
|
||||
val);
|
||||
}
|
||||
}
|
||||
Map<String, ?> args = dialog.promptArguments(method.getParameters());
|
||||
if (args == null) {
|
||||
// Cancelled
|
||||
return null;
|
||||
}
|
||||
if (dialog.isResetRequested()) {
|
||||
continue;
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
Map<String, ValStr<?>> args = dialog.promptArguments(method.getParameters(), defs, defs);
|
||||
return args == null ? null : ValStr.toPlainMap(args);
|
||||
}
|
||||
|
||||
private CompletableFuture<?> invokeMethod(boolean prompt, TargetMethod method,
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -15,7 +15,7 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.model.launch;
|
||||
|
||||
import static ghidra.async.AsyncUtils.*;
|
||||
import static ghidra.async.AsyncUtils.loop;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@ -44,6 +44,7 @@ import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.debug.api.model.DebuggerProgramLaunchOffer;
|
||||
import ghidra.debug.api.model.TraceRecorder;
|
||||
import ghidra.debug.api.modules.*;
|
||||
@ -281,14 +282,14 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||
return proposal;
|
||||
}
|
||||
|
||||
private void saveLauncherArgs(Map<String, ?> args,
|
||||
private void saveLauncherArgs(Map<String, ValStr<?>> args,
|
||||
Map<String, ParameterDescription<?>> params) {
|
||||
SaveState state = new SaveState();
|
||||
for (ParameterDescription<?> param : params.values()) {
|
||||
Object val = args.get(param.name);
|
||||
ValStr<?> val = args.get(param.name);
|
||||
if (val != null) {
|
||||
ConfigStateField.putState(state, param.type.asSubclass(Object.class), param.name,
|
||||
val);
|
||||
val.val());
|
||||
}
|
||||
}
|
||||
if (program != null) {
|
||||
@ -316,19 +317,19 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||
* @param params the parameters
|
||||
* @return the default arguments
|
||||
*/
|
||||
protected Map<String, ?> generateDefaultLauncherArgs(
|
||||
protected Map<String, ValStr<?>> generateDefaultLauncherArgs(
|
||||
Map<String, ParameterDescription<?>> params) {
|
||||
if (program == null) {
|
||||
return Map.of();
|
||||
}
|
||||
Map<String, Object> map = new LinkedHashMap<String, Object>();
|
||||
Map<String, ValStr<?>> map = new LinkedHashMap<>();
|
||||
for (Entry<String, ParameterDescription<?>> entry : params.entrySet()) {
|
||||
map.put(entry.getKey(), entry.getValue().defaultValue);
|
||||
map.put(entry.getKey(), ValStr.from(entry.getValue().defaultValue));
|
||||
}
|
||||
String almostExecutablePath = program.getExecutablePath();
|
||||
File f = new File(almostExecutablePath);
|
||||
map.put(TargetCmdLineLauncher.CMDLINE_ARGS_NAME,
|
||||
TargetCmdLineLauncher.quoteImagePathIfSpaces(f.getAbsolutePath()));
|
||||
ValStr.from(TargetCmdLineLauncher.quoteImagePathIfSpaces(f.getAbsolutePath())));
|
||||
return map;
|
||||
}
|
||||
|
||||
@ -338,36 +339,19 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||
* @param params the parameters of the model's launcher
|
||||
* @return the arguments given by the user, or null if cancelled
|
||||
*/
|
||||
protected Map<String, ?> promptLauncherArgs(TargetLauncher launcher,
|
||||
protected Map<String, ValStr<?>> promptLauncherArgs(TargetLauncher launcher,
|
||||
LaunchConfigurator configurator) {
|
||||
TargetParameterMap params = launcher.getParameters();
|
||||
DebuggerMethodInvocationDialog dialog =
|
||||
new DebuggerMethodInvocationDialog(tool, getButtonTitle(), "Launch", getIcon());
|
||||
|
||||
// NB. Do not invoke read/writeConfigState
|
||||
Map<String, ?> args;
|
||||
boolean reset = false;
|
||||
do {
|
||||
args = configurator.configureLauncher(launcher,
|
||||
loadLastLauncherArgs(launcher, true), RelPrompt.BEFORE);
|
||||
for (ParameterDescription<?> param : params.values()) {
|
||||
Object val = args.get(param.name);
|
||||
if (val != null) {
|
||||
dialog.setMemorizedArgument(param.name, param.type.asSubclass(Object.class),
|
||||
val);
|
||||
}
|
||||
}
|
||||
args = dialog.promptArguments(params);
|
||||
if (args == null) {
|
||||
// Cancelled
|
||||
return null;
|
||||
}
|
||||
reset = dialog.isResetRequested();
|
||||
if (reset) {
|
||||
args = generateDefaultLauncherArgs(params);
|
||||
}
|
||||
saveLauncherArgs(args, params);
|
||||
}
|
||||
while (reset);
|
||||
|
||||
Map<String, ValStr<?>> defaultArgs = generateDefaultLauncherArgs(params);
|
||||
Map<String, ValStr<?>> lastArgs = configurator.configureLauncher(launcher,
|
||||
loadLastLauncherArgs(launcher, true), RelPrompt.BEFORE);
|
||||
Map<String, ValStr<?>> args = dialog.promptArguments(params, lastArgs, defaultArgs);
|
||||
saveLauncherArgs(args, params);
|
||||
return args;
|
||||
}
|
||||
|
||||
@ -386,7 +370,8 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||
* @param forPrompt true if the user will be confirming the arguments
|
||||
* @return the loaded arguments, or defaults
|
||||
*/
|
||||
protected Map<String, ?> loadLastLauncherArgs(TargetLauncher launcher, boolean forPrompt) {
|
||||
protected Map<String, ValStr<?>> loadLastLauncherArgs(TargetLauncher launcher,
|
||||
boolean forPrompt) {
|
||||
/**
|
||||
* TODO: Supposedly, per-program, per-user config stuff is being generalized for analyzers.
|
||||
* Re-examine this if/when that gets merged
|
||||
@ -401,13 +386,13 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||
Element element = XmlUtilities.fromString(property);
|
||||
SaveState state = new SaveState(element);
|
||||
List<String> names = List.of(state.getNames());
|
||||
Map<String, Object> args = new LinkedHashMap<>();
|
||||
Map<String, ValStr<?>> args = new LinkedHashMap<>();
|
||||
for (ParameterDescription<?> param : params.values()) {
|
||||
if (names.contains(param.name)) {
|
||||
Object configState =
|
||||
ConfigStateField.getState(state, param.type, param.name);
|
||||
if (configState != null) {
|
||||
args.put(param.name, configState);
|
||||
args.put(param.name, ValStr.from(configState));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -426,7 +411,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||
e);
|
||||
}
|
||||
}
|
||||
Map<String, ?> args = generateDefaultLauncherArgs(params);
|
||||
Map<String, ValStr<?>> args = generateDefaultLauncherArgs(params);
|
||||
saveLauncherArgs(args, params);
|
||||
return args;
|
||||
}
|
||||
@ -447,7 +432,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||
* @param configurator a means of configuring the launcher
|
||||
* @return the chosen arguments, or null if the user cancels at the prompt
|
||||
*/
|
||||
public Map<String, ?> getLauncherArgs(TargetLauncher launcher, boolean prompt,
|
||||
public Map<String, ValStr<?>> getLauncherArgs(TargetLauncher launcher, boolean prompt,
|
||||
LaunchConfigurator configurator) {
|
||||
return prompt
|
||||
? configurator.configureLauncher(launcher,
|
||||
@ -456,7 +441,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||
RelPrompt.NONE);
|
||||
}
|
||||
|
||||
public Map<String, ?> getLauncherArgs(TargetLauncher launcher, boolean prompt) {
|
||||
public Map<String, ValStr<?>> getLauncherArgs(TargetLauncher launcher, boolean prompt) {
|
||||
return getLauncherArgs(launcher, prompt, LaunchConfigurator.NOP);
|
||||
}
|
||||
|
||||
@ -541,13 +526,14 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||
// Eww.
|
||||
protected CompletableFuture<Void> launch(TargetLauncher launcher,
|
||||
boolean prompt, LaunchConfigurator configurator, TaskMonitor monitor) {
|
||||
Map<String, ?> args = getLauncherArgs(launcher, prompt, configurator);
|
||||
Map<String, ValStr<?>> args = getLauncherArgs(launcher, prompt, configurator);
|
||||
if (args == null) {
|
||||
throw new CancellationException();
|
||||
}
|
||||
Map<String, ?> a = ValStr.toPlainMap(args);
|
||||
return AsyncTimer.DEFAULT_TIMER.mark()
|
||||
.timeOut(
|
||||
launcher.launch(args), getTimeoutMillis(), () -> onTimedOutLaunch(monitor));
|
||||
launcher.launch(a), getTimeoutMillis(), () -> onTimedOutLaunch(monitor));
|
||||
}
|
||||
|
||||
protected void checkCancelled(TaskMonitor monitor) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -15,7 +15,7 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.tracemgr;
|
||||
|
||||
import static ghidra.framework.main.DataTreeDialogType.*;
|
||||
import static ghidra.framework.main.DataTreeDialogType.OPEN;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
@ -656,7 +656,9 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
|
||||
@Override
|
||||
public synchronized Collection<Trace> getOpenTraces() {
|
||||
return Set.copyOf(tracesView);
|
||||
synchronized (listenersByTrace) {
|
||||
return Set.copyOf(tracesView);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -982,24 +984,28 @@ 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());
|
||||
}
|
||||
if (current.getTrace() == trace) {
|
||||
activate(DebuggerCoordinates.NOWHERE, ActivationCause.ACTIVATE_DEFAULT);
|
||||
try {
|
||||
if (current.getTrace() == trace) {
|
||||
activate(DebuggerCoordinates.NOWHERE, ActivationCause.ACTIVATE_DEFAULT);
|
||||
}
|
||||
else {
|
||||
contextChanged();
|
||||
}
|
||||
}
|
||||
else {
|
||||
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));
|
||||
|
@ -33,8 +33,9 @@ public enum MiscellaneousUtils {
|
||||
* Obtain a swing component which may be used to edit the property.
|
||||
*
|
||||
* <p>
|
||||
* This has been shamelessly stolen from {@link EditorState#getEditorComponent()}, which seems
|
||||
* entangled with Ghidra's whole options system. I think this portion could be factored out.
|
||||
* This has was originally stolen from {@link EditorState#getEditorComponent()}, which seems
|
||||
* entangled with Ghidra's whole options system. Can that be factored out? Since then, the two
|
||||
* have drifted apart.
|
||||
*
|
||||
* @param editor the editor for which to obtain an interactive component for editing
|
||||
* @return the component
|
||||
@ -53,16 +54,11 @@ public enum MiscellaneousUtils {
|
||||
return new PropertyText(editor);
|
||||
}
|
||||
|
||||
Class<? extends PropertyEditor> clazz = editor.getClass();
|
||||
String clazzName = clazz.getSimpleName();
|
||||
if (clazzName.startsWith("String")) {
|
||||
// Most likely some kind of string editor with a null value. Just use a string
|
||||
// property and let the value be empty.
|
||||
return new PropertyText(editor);
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
"Ghidra does not know how to use PropertyEditor: " + editor.getClass().getName());
|
||||
/**
|
||||
* TODO: Would be nice to know the actual type, but alas! Just default to a PropertyText and
|
||||
* hope all goes well.
|
||||
*/
|
||||
return new PropertyText(editor);
|
||||
}
|
||||
|
||||
public static void rigFocusAndEnter(Component c, Runnable runnable) {
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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();
|
||||
}
|
||||
|
@ -0,0 +1,94 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui;
|
||||
|
||||
import java.beans.PropertyEditor;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import docking.test.AbstractDockingTest;
|
||||
import ghidra.async.SwingExecutorService;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.framework.options.SaveState;
|
||||
|
||||
public class InvocationDialogHelper<P, D extends AbstractDebuggerParameterDialog<P>> {
|
||||
|
||||
public static <P, D extends AbstractDebuggerParameterDialog<P>> InvocationDialogHelper<P, D> waitFor(
|
||||
Class<D> cls) {
|
||||
D dialog = AbstractDockingTest.waitForDialogComponent(cls);
|
||||
return new InvocationDialogHelper<>(dialog);
|
||||
}
|
||||
|
||||
private final AbstractDebuggerParameterDialog<P> dialog;
|
||||
|
||||
public InvocationDialogHelper(AbstractDebuggerParameterDialog<P> dialog) {
|
||||
this.dialog = dialog;
|
||||
}
|
||||
|
||||
public void dismissWithArguments(Map<String, ValStr<?>> args) {
|
||||
dialog.setMemorizedArguments(args);
|
||||
invoke();
|
||||
}
|
||||
|
||||
public <T> Map.Entry<String, ValStr<T>> entry(String key, T value) {
|
||||
return Map.entry(key, ValStr.from(value));
|
||||
}
|
||||
|
||||
public void setArg(P param, Object value) {
|
||||
PropertyEditor editor = dialog.getEditor(param);
|
||||
runSwing(() -> editor.setValue(value));
|
||||
}
|
||||
|
||||
protected void runSwing(Runnable r) {
|
||||
try {
|
||||
CompletableFuture.runAsync(r, SwingExecutorService.LATER).get();
|
||||
}
|
||||
catch (ExecutionException e) {
|
||||
switch (e.getCause()) {
|
||||
case RuntimeException t -> throw t;
|
||||
case Exception t -> throw new RuntimeException(t);
|
||||
default -> ExceptionUtils.rethrow(e.getCause());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setArgAsString(P param, String value) {
|
||||
PropertyEditor editor = dialog.getEditor(param);
|
||||
runSwing(() -> editor.setAsText(value));
|
||||
}
|
||||
|
||||
public void invoke() {
|
||||
runSwing(() -> dialog.invoke(null));
|
||||
}
|
||||
|
||||
public SaveState saveState() {
|
||||
SaveState parent = new SaveState();
|
||||
runSwing(() -> dialog.writeConfigState(parent));
|
||||
return parent.getSaveState(AbstractDebuggerParameterDialog.KEY_MEMORIZED_ARGUMENTS);
|
||||
}
|
||||
|
||||
public void loadState(SaveState state) {
|
||||
SaveState parent = new SaveState();
|
||||
parent.putSaveState(AbstractDebuggerParameterDialog.KEY_MEMORIZED_ARGUMENTS, state);
|
||||
runSwing(() -> dialog.readConfigState(parent));
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.objects.components;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import docking.test.AbstractDockingTest;
|
||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
public class InvocationDialogHelper {
|
||||
|
||||
public static InvocationDialogHelper waitFor() {
|
||||
DebuggerMethodInvocationDialog dialog =
|
||||
AbstractDockingTest.waitForDialogComponent(DebuggerMethodInvocationDialog.class);
|
||||
return new InvocationDialogHelper(dialog);
|
||||
}
|
||||
|
||||
private final DebuggerMethodInvocationDialog dialog;
|
||||
|
||||
public InvocationDialogHelper(DebuggerMethodInvocationDialog dialog) {
|
||||
this.dialog = dialog;
|
||||
}
|
||||
|
||||
public void dismissWithArguments(Map<String, Object> args) {
|
||||
for (Map.Entry<String, Object> a : args.entrySet()) {
|
||||
ParameterDescription<?> p = dialog.parameters.get(a.getKey());
|
||||
assertNotNull(p);
|
||||
dialog.setMemorizedArgument(a.getKey(), p.type.asSubclass(Object.class), a.getValue());
|
||||
}
|
||||
Swing.runNow(() -> dialog.invoke(null));
|
||||
}
|
||||
}
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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();
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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:
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -26,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);
|
||||
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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));
|
||||
|
@ -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|
|
||||
|
@ -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]
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -71,22 +71,17 @@ public class AddProgramToH2BSimDatabaseScript extends GhidraScript {
|
||||
askValues("Select Database File", null, values);
|
||||
|
||||
File h2DbFile = values.getFile(DATABASE);
|
||||
BSimServerInfo serverInfo =
|
||||
new BSimServerInfo(DBType.file, null, 0, h2DbFile.getAbsolutePath());
|
||||
|
||||
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) {
|
||||
popup("There is an existing connection to the database.");
|
||||
return;
|
||||
}
|
||||
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,11 +164,13 @@ 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());
|
||||
bds.dispose();
|
||||
BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
|
||||
if (bds != null) {
|
||||
bds.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -31,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,31 +79,27 @@ 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);
|
||||
String functionTagsCSV = values.getString(FUNCTION_TAGS);
|
||||
List<String> tags = parseCSV(functionTagsCSV);
|
||||
String databaseName = values.getString(NAME);
|
||||
File dbDir = values.getFile(DIRECTORY);
|
||||
String template = values.getChoice(DATABASE_TEMPLATE);
|
||||
String functionTagsCSV = values.getString(FUNCTION_TAGS);
|
||||
List<String> tags = parseCSV(functionTagsCSV);
|
||||
|
||||
String exeCatCSV = values.getString(EXECUTABLE_CATEGORIES);
|
||||
List<String> cats = parseCSV(exeCatCSV);
|
||||
String exeCatCSV = values.getString(EXECUTABLE_CATEGORIES);
|
||||
List<String> cats = parseCSV(exeCatCSV);
|
||||
|
||||
File dbFile = new File(dbDir, databaseName);
|
||||
File dbFile = new File(dbDir, databaseName);
|
||||
BSimServerInfo serverInfo =
|
||||
new BSimServerInfo(DBType.file, null, 0, dbFile.getAbsolutePath());
|
||||
|
||||
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!");
|
||||
return;
|
||||
}
|
||||
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();
|
||||
@ -140,11 +135,13 @@ 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());
|
||||
bds.dispose();
|
||||
BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
|
||||
if (bds != null) {
|
||||
bds.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"> Add a new database/server definition - a
|
||||
Define Server Dialog will be shown.</LI>
|
||||
<LI><IMG alt="" src="Icons.ADD_ICON"> Add a new BSim database/server definition - an
|
||||
Add BSim Server Dialog will be shown.</LI>
|
||||
|
||||
<LI><IMG alt="" src="Icons.DELETE_ICON"> 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">
|
||||
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"> 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>
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 20 KiB |
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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;
|
||||
|
@ -4,25 +4,25 @@
|
||||
* 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;
|
||||
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);
|
||||
int active = ds.getActiveConnections();
|
||||
if (active != 0) {
|
||||
if (!force) {
|
||||
BSimH2FileDataSource ds = BSimH2FileDBConnectionManager.getDataSourceIfExists(info);
|
||||
if (ds != null) {
|
||||
int active = ds.getActiveConnections();
|
||||
if (active != 0 && !force) {
|
||||
return false;
|
||||
}
|
||||
ds.dispose();
|
||||
}
|
||||
}
|
||||
else if (dbType == DBType.postgres) {
|
||||
BSimPostgresDataSource ds = BSimPostgresDBConnectionManager.getDataSource(info);
|
||||
int active = ds.getActiveConnections();
|
||||
if (active != 0) {
|
||||
if (!force) {
|
||||
BSimPostgresDataSource ds = BSimPostgresDBConnectionManager.getDataSourceIfExists(info);
|
||||
if (ds != null) {
|
||||
int active = ds.getActiveConnections();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -30,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;
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -16,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;
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -30,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;
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -16,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;
|
||||
@ -60,7 +63,7 @@ public class BSimServerDialog extends DialogComponentProvider {
|
||||
addDismissButton();
|
||||
setPreferredSize(600, 400);
|
||||
notifyContextChanged(); // kick actions to initialized enabled state
|
||||
setHelpLocation(new HelpLocation("BSimSearchPlugin","BSim_Servers_Dialog" ));
|
||||
setHelpLocation(new HelpLocation("BSimSearchPlugin", "BSim_Servers_Dialog"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -70,34 +73,101 @@ public class BSimServerDialog extends DialogComponentProvider {
|
||||
}
|
||||
|
||||
private void createToolbarActions() {
|
||||
HelpLocation help = new HelpLocation("BSimSearchPlugin","Manage_Servers_Actions" );
|
||||
|
||||
HelpLocation help = new HelpLocation("BSimSearchPlugin", "Manage_Servers_Actions");
|
||||
|
||||
DockingAction addServerAction =
|
||||
new ActionBuilder("Add Server", "Dialog").toolBarIcon(Icons.ADD_ICON)
|
||||
.helpLocation(help)
|
||||
.onAction(e -> defineBsimServer())
|
||||
.build();
|
||||
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)
|
||||
.helpLocation(help)
|
||||
.onAction(e -> deleteBsimServer())
|
||||
.enabledWhen(c -> hasSelection())
|
||||
.build();
|
||||
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)
|
||||
.toolBarIcon(new GIcon("icon.bsim.change.password"))
|
||||
.onAction(e -> changePassword())
|
||||
.enabledWhen(c -> hasSelection())
|
||||
.build();
|
||||
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 -> 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() {
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -16,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,
|
||||
ServiceProvider provider) throws IllegalArgumentException {
|
||||
|
||||
// FIXME: Get cell tooltip to show full getDBName which includes file path
|
||||
|
||||
public String getValue(BSimServerInfo serverInfo, Settings settings, Object data,
|
||||
ServiceProvider provider) throws IllegalArgumentException {
|
||||
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,8 +174,8 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(BSimServerInfo serverInfo, Settings settings, Program data,
|
||||
ServiceProvider provider) throws IllegalArgumentException {
|
||||
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,
|
||||
ServiceProvider provider) throws IllegalArgumentException {
|
||||
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 String getColumnName() {
|
||||
return "Active Connections";
|
||||
}
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
|
||||
@Override
|
||||
public Integer getValue(BSimServerInfo serverInfo, Settings settings, Program data,
|
||||
ServiceProvider provider) throws IllegalArgumentException {
|
||||
int activeConnections = BSimServerManager.getActiveConnections(serverInfo);
|
||||
if (activeConnections < 0) {
|
||||
return null;
|
||||
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);
|
||||
}
|
||||
return activeConnections;
|
||||
c.setText(text);
|
||||
c.setIcon(icon);
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 80;
|
||||
public String getFilterString(ConnectionPoolStatus t, Settings settings) {
|
||||
return null; // Filtering not supported
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class TypeColumn
|
||||
extends AbstractProgramBasedDynamicTableColumn<BSimServerInfo, String> {
|
||||
private class ConnectionStatusColumn
|
||||
extends AbstractDynamicTableColumn<BSimServerInfo, ConnectionPoolStatus, Object> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Type";
|
||||
return "Active/Idle Connections";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(BSimServerInfo serverInfo, Settings settings, Program data,
|
||||
ServiceProvider provider) throws IllegalArgumentException {
|
||||
|
||||
return serverInfo.getDBType().toString();
|
||||
public ConnectionPoolStatus getValue(BSimServerInfo serverInfo, Settings settings,
|
||||
Object data, ServiceProvider provider) throws IllegalArgumentException {
|
||||
return getConnectionPoolStatus(serverInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 80;
|
||||
return 150;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<ConnectionPoolStatus> getColumnRenderer() {
|
||||
return ConnectionStatusColumnRenderer.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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;
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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();
|
||||
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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.
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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,
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -28,6 +28,7 @@ import ghidra.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
|
||||
|
BIN
Ghidra/Features/BSim/src/main/resources/images/connect.png
Normal file
After Width: | Height: | Size: 748 B |
BIN
Ghidra/Features/BSim/src/main/resources/images/disconnect.png
Normal file
After Width: | Height: | Size: 796 B |
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -23,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.*;
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -15,7 +15,6 @@
|
||||
*/
|
||||
package ghidra.features.bsim.gui;
|
||||
|
||||
import ghidra.features.bsim.gui.search.dialog.BSimServerManager;
|
||||
import ghidra.features.bsim.query.facade.SFQueryServiceFactory;
|
||||
|
||||
public class BSimSearchPluginTestHelper {
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -15,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;
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -18,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);
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -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;
|
||||
|
@ -4,16 +4,16 @@
|
||||
* 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.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() {
|
||||
@ -77,7 +76,7 @@ public class BSimH2DatabaseManagerTest extends AbstractGhidraHeadedIntegrationTe
|
||||
}
|
||||
|
||||
private BSimServerInfo createDatabase(String databaseName, List<String> tags,
|
||||
List<String> execats, String expectedError) {
|
||||
List<String> execats, String expectedError) {
|
||||
|
||||
BSimServerInfo h2DbInfo = getBsimServerInfo(databaseName);
|
||||
Msg.debug(this, "Creating H2 File DB: " + h2DbInfo);
|
@ -4,16 +4,16 @@
|
||||
* 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.query.test;
|
||||
package ghidra.features.bsim.query.test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
@ -4,16 +4,16 @@
|
||||
* 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.query.test;
|
||||
package ghidra.features.bsim.query.test;
|
||||
|
||||
import java.io.*;
|
||||
|
34
Ghidra/Features/Base/.launch/JShell.launch
Normal 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="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <sourceLookupDirector> <sourceContainers duplicates="false"> <container memento="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;classpathContainer path=&quot;org.eclipse.jdt.launching.JRE_CONTAINER&quot;/&gt;&#10;" typeId="org.eclipse.jdt.launching.sourceContainer.classpathContainer"/> </sourceContainers> </sourceLookupDirector> "/>
|
||||
<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="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry path="5" projectName="Framework Utility" type="1"/> "/>
|
||||
</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 -Djava.system.class.loader=ghidra.GhidraClassLoader -Xshare:off -Dfile.encoding=UTF8 -Duser.country=US -Duser.language=en -Dsun.java2d.pmoffscreen=false -Dsun.java2d.xrender=true -Dsun.java2d.d3d=false -Xdock:name="Ghidra" -Dvisualvm.display.name=Ghidra -Dpython.console.encoding=UTF-8"/>
|
||||
</launchConfiguration>
|
@ -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|
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -31,6 +31,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++;
|
||||
|
@ -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()));
|
||||
|
@ -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: </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>
|
@ -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>
|
||||
|
@ -12,467 +12,525 @@
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
<H1>Search Memory</H1>
|
||||
|
||||
<P>Search Memory locates sequences of bytes in program memory. The search is based on a
|
||||
value entered as hex numbers, decimal numbers or strings. 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>To Search Memory:</P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<OL>
|
||||
<LI>From the Tool, select <B>Search</B><IMG alt="" border="0" src="help/shared/arrow.gif">
|
||||
<B>Memory</B></LI>
|
||||
<A name="Memory Search"></A>
|
||||
|
||||
<LI>Enter a Hex String in the Value field<BR>
|
||||
This will create a Hex Sequence for searching.</LI>
|
||||
<H1>Search Memory</H1>
|
||||
|
||||
<LI>Choose "Next" to find the next occurrence<BR>
|
||||
|
||||
- or -<BR>
|
||||
Choose "Previous" to find the previous occurrence<BR>
|
||||
|
||||
- or -<BR>
|
||||
Choose "Search All" to find all occurrences.</LI>
|
||||
</OL>
|
||||
</BLOCKQUOTE>
|
||||
<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>
|
||||
|
||||
|
||||
<H2>Search Formats</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<UL type="disc">
|
||||
<LI><A href="#Hex">Hex</A></LI>
|
||||
|
||||
<LI><A href="#String">String</A></LI>
|
||||
|
||||
<LI><A href="#Decimal">Decimal</A></LI>
|
||||
|
||||
<LI><A href="#Binary">Binary</A></LI>
|
||||
|
||||
<LI><A href="#Regular_Expression">Regular Expression</A></LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P align="center"><IMG src="images/SearchMemoryHex.png" border="0" alt=""> </P>
|
||||
|
||||
<H2>Search Options</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<H3>Search</H3>
|
||||
<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>
|
||||
<H4>Search Value</H4>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<UL>
|
||||
<LI>The value to search. The values entered will be interpreted based on the
|
||||
<I>Format</I> options.</LI>
|
||||
</UL>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<H4>Hex Sequence</H4>
|
||||
</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>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<H4><A name="Hex"></A><B>Hex:</B></H4>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<UL>
|
||||
<LI>Value is interpreted as a sequence of hex numbers, separated by spaces. Wildcard
|
||||
characters can be used to match any single hex digit (i.e. any 4 bit value). 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>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<BLOCKQUOTE>
|
||||
<P><B>Example:</B></P>
|
||||
|
||||
<TABLE border="1" width="526">
|
||||
<TBODY>
|
||||
<TR>
|
||||
<TD width="260">
|
||||
<P><B>Value: </B></P>
|
||||
</TD>
|
||||
|
||||
<TD width="252">
|
||||
<P> "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 </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 </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>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P> </P>
|
||||
|
||||
<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. 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>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H4><A name="Decimal"></A><B>Decimal:</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>
|
||||
</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
|
||||
number.</LI>
|
||||
|
||||
<LI>
|
||||
Valid decimal numbers are:
|
||||
<A href="#Memory_Search_Window">Memory Search Window</A>
|
||||
|
||||
<UL>
|
||||
<LI><B>Byte</B> - any fixed point 8 bit number (-128 to 255)</LI>
|
||||
<LI><A href="#Search_Controls">Search Controls</A></LI>
|
||||
|
||||
<LI><B>Word</B> - any fixed point 16 bit number (-32768 to 65535)</LI>
|
||||
<LI><A href="#Scan_Controls">Scan Controls</A></LI>
|
||||
|
||||
<LI><B>DWord</B> - any fixed point 32 bit number (you get the idea.....)</LI>
|
||||
<LI><A href="#Results_Table">Results Table</A></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>
|
||||
<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>
|
||||
|
||||
<H4><A name="Binary"></A><B>Binary:</B></H4>
|
||||
<H2>Memory Search Window</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Value is interpreted as a sequence of binary numbers, separated by spaces.
|
||||
Wildcard characters ('x' or '?' or '.') can be used to match any bit.</P>
|
||||
</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 border="0" src="images/SearchMemoryBinary.png" alt=""></P>
|
||||
<P align="center"><IMG src="images/MemorySearchProvider.png" border="0" alt=""> </P>
|
||||
|
||||
<UL>
|
||||
<LI>Only binary digits (0 or 1) or wildcard characters (*?.) are allowed to be
|
||||
entered.</LI>
|
||||
<P align="center"><I>Memory Search Window</I></P>
|
||||
<A name="Search_Controls"></A>
|
||||
|
||||
<LI>The byte search pattern is formed by concatenating the bytes from each
|
||||
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>
|
||||
|
||||
<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>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P align="center"><IMG border="0" src="images/SearchMemoryRegex.png" alt=""></P>
|
||||
|
||||
<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> </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> </P>
|
||||
|
||||
<H3><B>Code Unit Scope</B></H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Filters the matches based upon the code unit containing a given address.</P>
|
||||
|
||||
<UL type="disc">
|
||||
<LI><B>Instructions</B> - includes instruction code units in the search.</LI>
|
||||
|
||||
<LI><B>Defined Data</B> - includes defined data in the search.</LI>
|
||||
|
||||
<LI><B>Undefined Data</B> - includes undefined data in the search.</LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><B>Byte Order</B></H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Sets the byte ordering for multi-byte values. 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> </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. </P>
|
||||
|
||||
<P><IMG alt="" border="0" src="help/shared/note.png"> Dismissing the search dialog
|
||||
automatically cancels the search operation.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3> </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
|
||||
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. </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>
|
||||
</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: </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>
|
||||
<H3>Search Controls</H3>
|
||||
|
||||
<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>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>
|
||||
|
||||
<P align="center"><IMG border="1" src="images/SearchInstructionsIncludeOperands.png" alt=
|
||||
""></P>
|
||||
<H4>Search Format Field:</H4>
|
||||
|
||||
<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>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>If the <B>Exclude Operands</B> option is chosen then the search will find all
|
||||
instances of the following instructions only.</P>
|
||||
<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 border="1" src="images/SearchInstructionsExcludeOperands.png" alt=
|
||||
""></P>
|
||||
<P align="center"><IMG src="images/MemorySearchProviderWithScanPanelOn.png" border="0"
|
||||
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>
|
||||
<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>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>
|
||||
<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=
|
||||
""> </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>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<H2>Actions</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<A name="Search_Next"></A>
|
||||
|
||||
<H3><IMG alt="" border="0" src="icon.down"> 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"> 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"> 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"> 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"> 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"> Toggle
|
||||
Options Panel</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>This action toggles the <A href="#Options">options display</A> on or off.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><IMG alt="" border="0" src="icon.make.selection"> Make Selection</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>This action will create a selection in the associated view from all the currently
|
||||
selected match table rows.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><IMG alt="" border="0" src="icon.navigate.in"> Toggle Single Click
|
||||
Navigation</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<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>
|
||||
|
||||
<H3><IMG alt="" border="0" src="icon.plugin.table.delete.row"> Delete Selected
|
||||
Table Rows</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<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>
|
||||
|
||||
<H4>A union B</H4>
|
||||
|
||||
<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>
|
||||
|
||||
<H4>A intersect B</H4>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
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 xor B</H4>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
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>
|
||||
|
||||
<H4>A - B</H4>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
Subtracts the new results from the existing results. When
|
||||
this option is selected, the button text will show "A-B Search".
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H4>B - A</H4>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
Subtracts the existing results from the new results. When this option is
|
||||
selected, the button text will show "B-A Search".
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<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>
|
||||
|
||||
<H2>Search Forward/Backwards Using Global Actions</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<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>
|
||||
|
||||
<H3>Search Memory Forwards:</H3>
|
||||
|
||||
<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>
|
||||
|
||||
<H3>Search Memory Backwards:</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><Shift>F3</B> (the default keybinding.)</P>
|
||||
</BLOCKQUOTE><A name="Highlight_Options"></A>
|
||||
|
||||
<H2>Highlight Search Options</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<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. </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>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P>Highlights are removed when you close the search window.</P>
|
||||
<BR>
|
||||
<BR>
|
||||
</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 class="providedbyplugin">Provided by: <I>Memory Search Plugin</I></P>
|
||||
|
||||
<P align="center"><IMG border="1" src="images/MultipleSelectionError.png" alt=""></P>
|
||||
<BR>
|
||||
<P class="relatedtopic">Related Topics:</P>
|
||||
|
||||
<UL>
|
||||
<LI><A href="Searching.htm">Searching Program Text</A></LI>
|
||||
|
||||
<LI><A href="Regular_Expressions.htm">Regular Expressions</A></LI>
|
||||
</UL><BR>
|
||||
<BR>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P class="providedbyplugin">Provided by: the <B><I>MemSearchPlugin</I></B> </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>
|
||||
<BR>
|
||||
</BODY>
|
||||
</HTML>
|
||||
|
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 12 KiB |