mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
Merge remote-tracking branch
'origin/GP-4324_Dan_moreGdbLaunchers--SQUASHED'
This commit is contained in:
commit
fea1243894
20 changed files with 396 additions and 114 deletions
|
@ -19,17 +19,18 @@
|
||||||
#@desc <h3>Launch with <tt>gdb</tt></h3>
|
#@desc <h3>Launch with <tt>gdb</tt></h3>
|
||||||
#@desc <p>This will launch the target on the local machine using <tt>gdb</tt>. GDB must already
|
#@desc <p>This will launch the target on the local machine using <tt>gdb</tt>. GDB must already
|
||||||
#@desc be installed on your system, and it must embed the Python 3 interpreter. You will also
|
#@desc be installed on your system, and it must embed the Python 3 interpreter. You will also
|
||||||
#@desc need <tt>protobuf</tt> and <tt>psutil</tt> installed for Python 3.
|
#@desc need <tt>protobuf</tt> and <tt>psutil</tt> installed for Python 3.</p>
|
||||||
#@desc </body></html>
|
#@desc </body></html>
|
||||||
#@menu-group local
|
#@menu-group local
|
||||||
#@icon icon.debugger
|
#@icon icon.debugger
|
||||||
#@help TraceRmiLauncherServicePlugin#gdb
|
#@help TraceRmiLauncherServicePlugin#gdb
|
||||||
#@enum StartCmd:str run start starti
|
#@enum StartCmd:str run start starti
|
||||||
#@env OPT_GDB_PATH:str="gdb" "Path to gdb" "The path to gdb. Omit the full path to resolve using the system PATH."
|
|
||||||
#@env OPT_START_CMD:StartCmd="start" "Run command" "The gdb command to actually run the target."
|
|
||||||
#@arg :str "Image" "The target binary executable image"
|
#@arg :str "Image" "The target binary executable image"
|
||||||
#@args "Arguments" "Command-line arguments to pass to the target"
|
#@args "Arguments" "Command-line arguments to pass to the target"
|
||||||
#@tty TTY_TARGET
|
#@env OPT_GDB_PATH:str="gdb" "Path to gdb" "The path to gdb. Omit the full path to resolve using the system PATH."
|
||||||
|
#@env OPT_START_CMD:StartCmd="start" "Run command" "The gdb command to actually run the target."
|
||||||
|
#@env OPT_EXTRA_TTY:bool=false "Inferior TTY" "Provide a separate terminal emulator for the target."
|
||||||
|
#@tty TTY_TARGET if env:OPT_EXTRA_TTY
|
||||||
|
|
||||||
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||||
then
|
then
|
||||||
|
@ -48,6 +49,8 @@ target_image="$1"
|
||||||
shift
|
shift
|
||||||
target_args="$@"
|
target_args="$@"
|
||||||
|
|
||||||
|
# NOTE: Ghidra will leave TTY_TARGET empty, which gdb takes for the same terminal.
|
||||||
|
|
||||||
"$OPT_GDB_PATH" \
|
"$OPT_GDB_PATH" \
|
||||||
-q \
|
-q \
|
||||||
-ex "set pagination off" \
|
-ex "set pagination off" \
|
||||||
|
|
64
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/ssh-gdb.sh
Executable file
64
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/ssh-gdb.sh
Executable file
|
@ -0,0 +1,64 @@
|
||||||
|
#!/usr/bin/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.
|
||||||
|
##
|
||||||
|
#@timeout 60000
|
||||||
|
#@title gdb via ssh
|
||||||
|
#@desc <html><body width="300px">
|
||||||
|
#@desc <h3>Launch with <tt>gdb</tt> via <tt>ssh</tt></h3>
|
||||||
|
#@desc <p>This will launch the target on a remote machine using <tt>gdb</tt> via <tt>ssh</tt>.
|
||||||
|
#@desc GDB and an SSH server must already be installed and operational on the remote system, and
|
||||||
|
#@desc GDB must embed the Python 3 interpreter. The remote SSH server must be configured to allow
|
||||||
|
#@desc remote port forwarding. You will also need to install the following for GDB's embedded
|
||||||
|
#@desc version of Python:</p>
|
||||||
|
#@desc <ul>
|
||||||
|
#@desc <li><tt>ghidragdb</tt> - Ghidra plugin for GDB, available from the Debugger-agent-gdb
|
||||||
|
#@desc directory in Ghidra</li>
|
||||||
|
#@desc <li><tt>ghidratrace</tt> - Ghidra Trace RMI client for Python, available from the
|
||||||
|
#@desc Debugger-rmi-trace directory in Ghidra</li>
|
||||||
|
#@desc <li><tt>protobuf</tt> - available from PyPI</li>
|
||||||
|
#@desc <li><tt>psutil</tt> - available from PyPI</li>
|
||||||
|
#@desc </ul>
|
||||||
|
#@desc </body></html>
|
||||||
|
#@menu-group remote
|
||||||
|
#@icon icon.debugger
|
||||||
|
#@help TraceRmiLauncherServicePlugin#gdb
|
||||||
|
#@enum StartCmd:str run start starti
|
||||||
|
#@arg :str "Image" "The target binary executable image on the remote system"
|
||||||
|
#@args "Arguments" "Command-line arguments to pass to the target"
|
||||||
|
#@env OPT_HOST:str="localhost" "[User@]Host" "The hostname or user@host"
|
||||||
|
#@env OPT_REMOTE_PORT:int=12345 "Remote Trace RMI Port" "A free port on the remote end to receive and forward the Trace RMI connection."
|
||||||
|
#@env OPT_EXTRA_SSH_ARGS:str="" "Extra ssh arguments" "Extra arguments to pass to ssh. Use with care."
|
||||||
|
#@env OPT_GDB_PATH:str="gdb" "Path to gdb" "The path to gdb on the remote system. Omit the full path to resolve using the system PATH."
|
||||||
|
#@env OPT_START_CMD:StartCmd="start" "Run command" "The gdb command to actually run the target."
|
||||||
|
|
||||||
|
target_image="$1"
|
||||||
|
shift
|
||||||
|
target_args="$@"
|
||||||
|
|
||||||
|
ssh "-R$OPT_REMOTE_PORT:$GHIDRA_TRACE_RMI_ADDR" -t $OPT_EXTRA_SSH_ARGS "$OPT_HOST" "TERM='$TERM' '$OPT_GDB_PATH' \
|
||||||
|
-q \
|
||||||
|
-ex 'set pagination off' \
|
||||||
|
-ex 'set confirm off' \
|
||||||
|
-ex 'show version' \
|
||||||
|
-ex 'python import ghidragdb' \
|
||||||
|
-ex 'file \"$target_image\"' \
|
||||||
|
-ex 'set args $target_args' \
|
||||||
|
-ex 'ghidra trace connect \"localhost:$OPT_REMOTE_PORT\"' \
|
||||||
|
-ex 'ghidra trace start' \
|
||||||
|
-ex 'ghidra trace sync-enable' \
|
||||||
|
-ex '$OPT_START_CMD' \
|
||||||
|
-ex 'set confirm on' \
|
||||||
|
-ex 'set pagination on'"
|
69
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/ssh-gdbserver.sh
Executable file
69
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/ssh-gdbserver.sh
Executable file
|
@ -0,0 +1,69 @@
|
||||||
|
#!/usr/bin/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.
|
||||||
|
##
|
||||||
|
#@timeout 60000
|
||||||
|
#@title gdb + gdbserver via ssh
|
||||||
|
#@desc <html><body width="300px">
|
||||||
|
#@desc <h3>Launch with local <tt>gdb</tt> and <tt>gdbserver</tt> via <tt>ssh</tt></h3>
|
||||||
|
#@desc <p>This will start <tt>gdb</tt> on the local system and then use it to connect and launch
|
||||||
|
#@desc the target in <tt>gdbserver</tt> on the remote system via <tt>ssh</tt>. The actual command
|
||||||
|
#@desc used is, e.g:</p>
|
||||||
|
#@desc <pre>target remote | ssh user@host gdbserver - /path/to/image</pre>
|
||||||
|
#@desc <p>It may be worth testing this manually to ensure everything is configured correctly. An
|
||||||
|
#@desc SSH server and <tt>gdbserver</tt> must already be installed and operational on the remote
|
||||||
|
#@desc system. GDB must be installed on your local system, it must be compatible with the
|
||||||
|
#@desc <tt>gdbserver</tt> on the remote system, and it must embed the Python 3 interpreter. You
|
||||||
|
#@desc will also need <tt>protobuf</tt> installed for Python 3 on the local system. There are no
|
||||||
|
#@desc Python requirements for the remote system.</p>
|
||||||
|
#@desc </body></html>
|
||||||
|
#@menu-group remote
|
||||||
|
#@icon icon.debugger
|
||||||
|
#@help TraceRmiLauncherServicePlugin#gdb
|
||||||
|
#@arg :str "Image" "The target binary executable image on the remote system"
|
||||||
|
#@args "Arguments" "Command-line arguments to pass to the target"
|
||||||
|
#@env OPT_HOST:str="localhost" "[User@]Host" "The hostname or user@host"
|
||||||
|
#@env OPT_EXTRA_SSH_ARGS:str="" "Extra ssh arguments" "Extra arguments to pass to ssh. Use with care."
|
||||||
|
#@env OPT_GDBSERVER_PATH:str="gdbserver" "Path to gdbserver (remote)" "The path to gdbserver on the remote system. Omit the full path to resolve using the system PATH."
|
||||||
|
#@env OPT_EXTRA_GDBSERVER_ARGS:str="" "Extra gdbserver arguments" "Extra arguments to pass to gdbserver. Use with care."
|
||||||
|
#@env OPT_GDB_PATH:str="gdb" "Path to gdb" "The path to gdb on the local system. Omit the full path to resolve using the system PATH."
|
||||||
|
|
||||||
|
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||||
|
then
|
||||||
|
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$PYTHONPATH
|
||||||
|
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||||
|
elif [ -d ${GHIDRA_HOME}/.git ]
|
||||||
|
then
|
||||||
|
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$PYTHONPATH
|
||||||
|
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||||
|
else
|
||||||
|
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/pypkg/src:$PYTHONPATH
|
||||||
|
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/pypkg/src:$PYTHONPATH
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$OPT_GDB_PATH" \
|
||||||
|
-q \
|
||||||
|
-ex "set pagination off" \
|
||||||
|
-ex "set confirm off" \
|
||||||
|
-ex "show version" \
|
||||||
|
-ex "python import ghidragdb" \
|
||||||
|
-ex "set inferior-tty $TTY_TARGET" \
|
||||||
|
-ex "target remote | ssh $OPT_EXTRA_SSH_ARGS '$OPT_HOST' '$OPT_GDBSERVER_PATH' $OPT_EXTRA_GDBSERVER_ARGS - $@" \
|
||||||
|
-ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
|
||||||
|
-ex "ghidra trace start" \
|
||||||
|
-ex "ghidra trace sync-enable" \
|
||||||
|
-ex "ghidra trace sync-synth-stopped" \
|
||||||
|
-ex "set confirm on" \
|
||||||
|
-ex "set pagination on"
|
|
@ -1510,6 +1510,19 @@ def ghidra_trace_sync_disable(*, is_mi, **kwargs):
|
||||||
hooks.disable_current_inferior()
|
hooks.disable_current_inferior()
|
||||||
|
|
||||||
|
|
||||||
|
@cmd('ghidra trace sync-synth-stopped', '-ghidra-trace-sync-synth-stopped',
|
||||||
|
gdb.COMMAND_SUPPORT, False)
|
||||||
|
def ghidra_trace_sync_synth_stopped(*, is_mi, **kwargs):
|
||||||
|
"""
|
||||||
|
Act as though the target has just stopped.
|
||||||
|
|
||||||
|
This may need to be invoked immediately after 'ghidra trace sync-enable',
|
||||||
|
to ensure the first snapshot displays the initial/current target state.
|
||||||
|
"""
|
||||||
|
|
||||||
|
hooks.on_stop(object()) # Pass a fake event
|
||||||
|
|
||||||
|
|
||||||
@cmd('ghidra util wait-stopped', '-ghidra-util-wait-stopped', gdb.COMMAND_NONE, False)
|
@cmd('ghidra util wait-stopped', '-ghidra-util-wait-stopped', gdb.COMMAND_NONE, False)
|
||||||
def ghidra_util_wait_stopped(timeout='1', *, is_mi, **kwargs):
|
def ghidra_util_wait_stopped(timeout='1', *, is_mi, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -31,26 +31,51 @@ public interface DebuggerConsoleService {
|
||||||
* Log a message to the console
|
* Log a message to the console
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* <b>WARNING:</b> See {@link #log(Icon, String, ActionContext)} regarding HTML.
|
* <b>WARNING:</b> See {@link #log(Icon, String, Throwable, ActionContext)} regarding HTML.
|
||||||
*
|
*
|
||||||
* @param icon an icon for the message
|
* @param icon an icon for the message
|
||||||
* @param message the HTML-formatted message
|
* @param message the HTML-formatted message
|
||||||
*/
|
*/
|
||||||
void log(Icon icon, String message);
|
void log(Icon icon, String message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an error message to the console
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* <b>WARNING:</b> See {@link #log(Icon, String, Throwable, ActionContext)} regarding HTML.
|
||||||
|
*
|
||||||
|
* @param icon an icon for the message
|
||||||
|
* @param message the HTML-formatted message
|
||||||
|
* @param error an exception, if applicable
|
||||||
|
*/
|
||||||
|
void log(Icon icon, String message, Throwable error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log an actionable message to the console
|
* Log an actionable message to the console
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
|
* <b>WARNING:</b> See {@link #log(Icon, String, Throwable, ActionContext)} regarding HTML.
|
||||||
|
*
|
||||||
|
* @param icon an icon for the message
|
||||||
|
* @param message the HTML-formatted message
|
||||||
|
* @param context an (immutable) context for actions
|
||||||
|
*/
|
||||||
|
void log(Icon icon, String message, ActionContext context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an actionable error message to the console
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
* <b>WARNING:</b> The log accepts and will interpret HTML in its messages, allowing a rich and
|
* <b>WARNING:</b> The log accepts and will interpret HTML in its messages, allowing a rich and
|
||||||
* flexible display; however, you MUST sanitize any content derived from the user or target. We
|
* flexible display; however, you MUST sanitize any content derived from the user or target. We
|
||||||
* recommend using {@link HTMLUtilities#escapeHTML(String)}.
|
* recommend using {@link HTMLUtilities#escapeHTML(String)}.
|
||||||
*
|
*
|
||||||
* @param icon an icon for the message
|
* @param icon an icon for the message
|
||||||
* @param message the HTML-formatted message
|
* @param message the HTML-formatted message
|
||||||
|
* @param error an exception, if applicable
|
||||||
* @param context an (immutable) context for actions
|
* @param context an (immutable) context for actions
|
||||||
*/
|
*/
|
||||||
void log(Icon icon, String message, ActionContext context);
|
void log(Icon icon, String message, Throwable error, ActionContext context);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an actionable message from the console
|
* Remove an actionable message from the console
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.util.*;
|
||||||
import javax.swing.Icon;
|
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.ScriptAttributes;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.TtyCondition;
|
||||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||||
import ghidra.debug.api.tracermi.TerminalSession;
|
import ghidra.debug.api.tracermi.TerminalSession;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
|
@ -87,6 +88,11 @@ public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmi
|
||||||
return attrs.parameters();
|
return attrs.parameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getConnectionTimeoutMillis() {
|
||||||
|
return attrs.timeoutMillis();
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract void prepareSubprocess(List<String> commandLine, Map<String, String> env,
|
protected abstract void prepareSubprocess(List<String> commandLine, Map<String, String> env,
|
||||||
Map<String, ?> args, SocketAddress address);
|
Map<String, ?> args, SocketAddress address);
|
||||||
|
|
||||||
|
@ -97,9 +103,12 @@ public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmi
|
||||||
Map<String, String> env = new HashMap<>(System.getenv());
|
Map<String, String> env = new HashMap<>(System.getenv());
|
||||||
prepareSubprocess(commandLine, env, args, address);
|
prepareSubprocess(commandLine, env, args, address);
|
||||||
|
|
||||||
for (String tty : attrs.extraTtys()) {
|
for (Map.Entry<String, TtyCondition> ent : attrs.extraTtys().entrySet()) {
|
||||||
|
if (!ent.getValue().isActive(args)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
NullPtyTerminalSession ns = nullPtyTerminal();
|
NullPtyTerminalSession ns = nullPtyTerminal();
|
||||||
env.put(tty, ns.name());
|
env.put(ent.getKey(), ns.name());
|
||||||
sessions.put(ns.name(), ns);
|
sessions.put(ns.name(), ns);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
|
|
||||||
public static final String PREFIX_DBGLAUNCH = "DBGLAUNCH_";
|
public static final String PREFIX_DBGLAUNCH = "DBGLAUNCH_";
|
||||||
public static final String PARAM_DISPLAY_IMAGE = "Image";
|
public static final String PARAM_DISPLAY_IMAGE = "Image";
|
||||||
|
public static final int DEFAULT_TIMEOUT_MILLIS = 10000;
|
||||||
|
|
||||||
protected record PtyTerminalSession(Terminal terminal, Pty pty, PtySession session,
|
protected record PtyTerminalSession(Terminal terminal, Pty pty, PtySession session,
|
||||||
Thread waiter) implements TerminalSession {
|
Thread waiter) implements TerminalSession {
|
||||||
|
@ -149,7 +150,11 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int getTimeoutMillis() {
|
protected int getTimeoutMillis() {
|
||||||
return 10000;
|
return DEFAULT_TIMEOUT_MILLIS;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getConnectionTimeoutMillis() {
|
||||||
|
return getTimeoutMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -550,7 +555,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
launchBackEnd(monitor, sessions, args, acceptor.getAddress());
|
launchBackEnd(monitor, sessions, args, acceptor.getAddress());
|
||||||
monitor.setMessage("Waiting for connection");
|
monitor.setMessage("Waiting for connection");
|
||||||
monitor.increment();
|
monitor.increment();
|
||||||
acceptor.setTimeout(getTimeoutMillis());
|
acceptor.setTimeout(getConnectionTimeoutMillis());
|
||||||
connection = acceptor.accept();
|
connection = acceptor.accept();
|
||||||
connection.registerTerminals(sessions.values());
|
connection.registerTerminals(sessions.values());
|
||||||
monitor.setMessage("Waiting for trace");
|
monitor.setMessage("Waiting for trace");
|
||||||
|
|
|
@ -51,6 +51,7 @@ public abstract class ScriptAttributesParser {
|
||||||
public static final String AT_ARG = "@arg";
|
public static final String AT_ARG = "@arg";
|
||||||
public static final String AT_ARGS = "@args";
|
public static final String AT_ARGS = "@args";
|
||||||
public static final String AT_TTY = "@tty";
|
public static final String AT_TTY = "@tty";
|
||||||
|
public static final String AT_TIMEOUT = "@timeout";
|
||||||
|
|
||||||
public static final String PREFIX_ENV = "env:";
|
public static final String PREFIX_ENV = "env:";
|
||||||
public static final String PREFIX_ARG = "arg:";
|
public static final String PREFIX_ARG = "arg:";
|
||||||
|
@ -66,6 +67,10 @@ public abstract class ScriptAttributesParser {
|
||||||
"%s: Invalid %s syntax. Use :type \"Display\" \"Tool Tip\"";
|
"%s: Invalid %s syntax. Use :type \"Display\" \"Tool Tip\"";
|
||||||
public static final String MSGPAT_INVALID_ARGS_SYNTAX =
|
public static final String MSGPAT_INVALID_ARGS_SYNTAX =
|
||||||
"%s: Invalid %s syntax. Use \"Display\" \"Tool Tip\"";
|
"%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]";
|
||||||
|
public static final String MSGPAT_INVALID_TIMEOUT_SYNTAX = "" +
|
||||||
|
"%s: Invalid %s syntax. Use [milliseconds]";
|
||||||
|
|
||||||
protected record Location(String fileName, int lineNo) {
|
protected record Location(String fileName, int lineNo) {
|
||||||
@Override
|
@Override
|
||||||
|
@ -228,6 +233,33 @@ public abstract class ScriptAttributesParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface TtyCondition {
|
||||||
|
boolean isActive(Map<String, ?> args);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ConstTtyCondition implements TtyCondition {
|
||||||
|
ALWAYS {
|
||||||
|
@Override
|
||||||
|
public boolean isActive(Map<String, ?> args) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
record EqualsTtyCondition(String key, String repr) implements TtyCondition {
|
||||||
|
@Override
|
||||||
|
public boolean isActive(Map<String, ?> args) {
|
||||||
|
return Objects.toString(args.get(key)).equals(repr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record BoolTtyCondition(String key) implements TtyCondition {
|
||||||
|
@Override
|
||||||
|
public boolean isActive(Map<String, ?> args) {
|
||||||
|
return args.get(key) instanceof Boolean b && b.booleanValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected static String addrToString(InetAddress address) {
|
protected static String addrToString(InetAddress address) {
|
||||||
if (address.isAnyLocalAddress()) {
|
if (address.isAnyLocalAddress()) {
|
||||||
return "127.0.0.1"; // Can't connect to 0.0.0.0 as such. Choose localhost.
|
return "127.0.0.1"; // Can't connect to 0.0.0.0 as such. Choose localhost.
|
||||||
|
@ -244,7 +276,8 @@ public abstract class ScriptAttributesParser {
|
||||||
|
|
||||||
public record ScriptAttributes(String title, String description, List<String> menuPath,
|
public record ScriptAttributes(String title, String description, List<String> menuPath,
|
||||||
String menuGroup, String menuOrder, Icon icon, HelpLocation helpLocation,
|
String menuGroup, String menuOrder, Icon icon, HelpLocation helpLocation,
|
||||||
Map<String, ParameterDescription<?>> parameters, Collection<String> extraTtys) {
|
Map<String, ParameterDescription<?>> parameters, Map<String, TtyCondition> extraTtys,
|
||||||
|
int timeoutMillis) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -302,7 +335,8 @@ public abstract class ScriptAttributesParser {
|
||||||
private HelpLocation helpLocation;
|
private HelpLocation helpLocation;
|
||||||
private final Map<String, UserType<?>> userTypes = new HashMap<>();
|
private final Map<String, UserType<?>> userTypes = new HashMap<>();
|
||||||
private final Map<String, ParameterDescription<?>> parameters = new LinkedHashMap<>();
|
private final Map<String, ParameterDescription<?>> parameters = new LinkedHashMap<>();
|
||||||
private final Set<String> extraTtys = new LinkedHashSet<>();
|
private final Map<String, TtyCondition> extraTtys = new LinkedHashMap<>();
|
||||||
|
private int timeoutMillis = AbstractTraceRmiLaunchOffer.DEFAULT_TIMEOUT_MILLIS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a line should just be ignored, e.g., blank lines, or the "shebang" line on UNIX.
|
* Check if a line should just be ignored, e.g., blank lines, or the "shebang" line on UNIX.
|
||||||
|
@ -352,7 +386,8 @@ public abstract class ScriptAttributesParser {
|
||||||
/**
|
/**
|
||||||
* Process a line in the metadata comment block
|
* Process a line in the metadata comment block
|
||||||
*
|
*
|
||||||
* @param line the line, excluding any comment delimiters
|
* @param loc the location, for error reporting
|
||||||
|
* @param comment the comment, excluding any comment delimiters
|
||||||
*/
|
*/
|
||||||
public void parseComment(Location loc, String comment) {
|
public void parseComment(Location loc, String comment) {
|
||||||
if (comment.isBlank()) {
|
if (comment.isBlank()) {
|
||||||
|
@ -379,6 +414,7 @@ public abstract class ScriptAttributesParser {
|
||||||
case AT_ARG -> parseArg(loc, parts[1], ++argc);
|
case AT_ARG -> parseArg(loc, parts[1], ++argc);
|
||||||
case AT_ARGS -> parseArgs(loc, parts[1]);
|
case AT_ARGS -> parseArgs(loc, parts[1]);
|
||||||
case AT_TTY -> parseTty(loc, parts[1]);
|
case AT_TTY -> parseTty(loc, parts[1]);
|
||||||
|
case AT_TIMEOUT -> parseTimeout(loc, parts[1]);
|
||||||
default -> parseUnrecognized(loc, comment);
|
default -> parseUnrecognized(loc, comment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -531,12 +567,41 @@ public abstract class ScriptAttributesParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void parseTty(Location loc, String str) {
|
protected void putTty(Location loc, String name, TtyCondition condition) {
|
||||||
if (!extraTtys.add(str)) {
|
if (extraTtys.put(name, condition) != null) {
|
||||||
Msg.warn(this, "%s: Duplicate %s. Ignored".formatted(loc, AT_TTY));
|
Msg.warn(this, "%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:
|
||||||
|
putTty(loc, parts.get(0), ConstTtyCondition.ALWAYS);
|
||||||
|
return;
|
||||||
|
case 3:
|
||||||
|
if ("if".equals(parts.get(1))) {
|
||||||
|
putTty(loc, parts.get(0), new BoolTtyCondition(parts.get(2)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 5:
|
||||||
|
if ("if".equals(parts.get(1)) && "==".equals(parts.get(3))) {
|
||||||
|
putTty(loc, parts.get(0), new EqualsTtyCondition(parts.get(2), parts.get(4)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Msg.error(this, MSGPAT_INVALID_TTY_SYNTAX.formatted(loc, AT_TTY));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void parseTimeout(Location loc, String str) {
|
||||||
|
try {
|
||||||
|
timeoutMillis = Integer.parseInt(str);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
Msg.error(this, MSGPAT_INVALID_TIMEOUT_SYNTAX.formatted(loc, AT_TIMEOUT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void parseUnrecognized(Location loc, String line) {
|
protected void parseUnrecognized(Location loc, String line) {
|
||||||
Msg.warn(this, "%s: Unrecognized metadata: %s".formatted(loc, line));
|
Msg.warn(this, "%s: Unrecognized metadata: %s".formatted(loc, line));
|
||||||
}
|
}
|
||||||
|
@ -560,7 +625,8 @@ public abstract class ScriptAttributesParser {
|
||||||
}
|
}
|
||||||
return new ScriptAttributes(title, getDescription(), List.copyOf(menuPath), menuGroup,
|
return new ScriptAttributes(title, getDescription(), List.copyOf(menuPath), menuGroup,
|
||||||
menuOrder, new GIcon(iconId), helpLocation,
|
menuOrder, new GIcon(iconId), helpLocation,
|
||||||
Collections.unmodifiableMap(new LinkedHashMap<>(parameters)), List.copyOf(extraTtys));
|
Collections.unmodifiableMap(new LinkedHashMap<>(parameters)),
|
||||||
|
Collections.unmodifiableMap(new LinkedHashMap<>(extraTtys)), timeoutMillis);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getDescription() {
|
private String getDescription() {
|
||||||
|
|
|
@ -190,7 +190,7 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
Msg.trace(this, "No root schema, yet: " + trace);
|
Msg.trace(this, "No root schema, yet: " + trace);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
TargetObjectSchema schema = ctx.getSchema(type);
|
TargetObjectSchema schema = ctx.getSchemaOrNull(type);
|
||||||
if (schema == null) {
|
if (schema == null) {
|
||||||
Msg.error(this, "Schema " + type + " not in trace! " + trace);
|
Msg.error(this, "Schema " + type + " not in trace! " + trace);
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -1145,6 +1145,6 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Msg.error(this, message, ex);
|
Msg.error(this, message, ex);
|
||||||
consoleService.log(DebuggerResources.ICON_LOG_ERROR, message + " (" + ex + ")");
|
consoleService.log(DebuggerResources.ICON_LOG_ERROR, message, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1379,6 +1379,6 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Msg.error(this, message, ex);
|
Msg.error(this, message, ex);
|
||||||
consoleService.log(DebuggerResources.ICON_LOG_ERROR, message + " (" + ex + ")");
|
consoleService.log(DebuggerResources.ICON_LOG_ERROR, message, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,11 +107,21 @@ public class DebuggerConsolePlugin extends Plugin implements DebuggerConsoleServ
|
||||||
provider.log(icon, message);
|
provider.log(icon, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(Icon icon, String message, Throwable error) {
|
||||||
|
provider.log(icon, message, error);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void log(Icon icon, String message, ActionContext context) {
|
public void log(Icon icon, String message, ActionContext context) {
|
||||||
provider.log(icon, message, context);
|
provider.log(icon, message, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(Icon icon, String message, Throwable error, ActionContext context) {
|
||||||
|
provider.log(icon, message, error, context);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeFromLog(ActionContext context) {
|
public void removeFromLog(ActionContext context) {
|
||||||
provider.removeFromLog(context);
|
provider.removeFromLog(context);
|
||||||
|
|
|
@ -17,7 +17,7 @@ package ghidra.app.plugin.core.debug.gui.console;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -64,14 +64,14 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
static final int MIN_ROW_HEIGHT = 16;
|
static final int MIN_ROW_HEIGHT = 16;
|
||||||
|
|
||||||
protected enum LogTableColumns implements EnumeratedTableColumn<LogTableColumns, LogRow<?>> {
|
protected enum LogTableColumns implements EnumeratedTableColumn<LogTableColumns, LogRow<?>> {
|
||||||
ICON("Icon", Icon.class, LogRow::getIcon, SortDirection.ASCENDING, false),
|
ICON("Icon", Icon.class, LogRow::icon, SortDirection.ASCENDING, false),
|
||||||
MESSAGE("Message", Object.class, LogRow::getMessage, SortDirection.ASCENDING, false) {
|
MESSAGE("Message", Object.class, LogRow::message, SortDirection.ASCENDING, false) {
|
||||||
@Override
|
@Override
|
||||||
public GColumnRenderer<?> getRenderer() {
|
public GColumnRenderer<?> getRenderer() {
|
||||||
return HtmlOrProgressCellRenderer.INSTANCE;
|
return HtmlOrProgressCellRenderer.INSTANCE;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ACTIONS("Actions", ActionList.class, LogRow::getActions, SortDirection.DESCENDING, true) {
|
ACTIONS("Actions", ActionList.class, LogRow::actions, SortDirection.DESCENDING, true) {
|
||||||
private static final ConsoleActionsCellRenderer RENDERER =
|
private static final ConsoleActionsCellRenderer RENDERER =
|
||||||
new ConsoleActionsCellRenderer();
|
new ConsoleActionsCellRenderer();
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
return RENDERER;
|
return RENDERER;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
TIME("Time", Date.class, LogRow::getDate, SortDirection.DESCENDING, false) {
|
TIME("Time", Date.class, LogRow::date, SortDirection.DESCENDING, false) {
|
||||||
@Override
|
@Override
|
||||||
public GColumnRenderer<?> getRenderer() {
|
public GColumnRenderer<?> getRenderer() {
|
||||||
return CustomToStringCellRenderer.TIME_24HMSms;
|
return CustomToStringCellRenderer.TIME_24HMSms;
|
||||||
|
@ -191,99 +191,57 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
* @param <T> the type of the message
|
* @param <T> the type of the message
|
||||||
*/
|
*/
|
||||||
public interface LogRow<T> {
|
public interface LogRow<T> {
|
||||||
Icon getIcon();
|
Icon icon();
|
||||||
|
|
||||||
T getMessage();
|
T message();
|
||||||
|
|
||||||
ActionList getActions();
|
ActionList actions();
|
||||||
|
|
||||||
Date getDate();
|
Date date();
|
||||||
|
|
||||||
ActionContext getActionContext();
|
ActionContext actionContext();
|
||||||
|
|
||||||
|
default boolean activated() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class MessageLogRow implements LogRow<String> {
|
record MessageLogRow(Icon icon, String message, Date date, Throwable error,
|
||||||
private final Icon icon;
|
ActionContext actionContext, ActionList actions) implements LogRow<String> {
|
||||||
private final String message;
|
public MessageLogRow(Icon icon, String message, Date date, Throwable error,
|
||||||
private final Date date;
|
ActionContext actionContext, ActionList actions) {
|
||||||
private final ActionContext context;
|
|
||||||
private final ActionList actions;
|
|
||||||
|
|
||||||
public MessageLogRow(Icon icon, String message, Date date, ActionContext context,
|
|
||||||
ActionList actions) {
|
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
this.date = date;
|
this.date = date;
|
||||||
this.context = context;
|
this.error = error;
|
||||||
|
this.actionContext = actionContext;
|
||||||
this.actions = Objects.requireNonNull(actions);
|
this.actions = Objects.requireNonNull(actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Icon getIcon() {
|
public boolean activated() {
|
||||||
return icon;
|
Msg.showError(this, null, "Inspect error", message, error);
|
||||||
}
|
return true;
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMessage() {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Date getDate() {
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ActionContext getActionContext() {
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ActionList getActions() {
|
|
||||||
return actions;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class MonitorLogRow implements LogRow<MonitorReceiver> {
|
record MonitorLogRow(MonitorReceiver message, Date date, ActionContext actionContext,
|
||||||
|
ActionList actions) implements LogRow<MonitorReceiver> {
|
||||||
|
|
||||||
static final GIcon ICON = new GIcon("icon.pending");
|
static final GIcon ICON = new GIcon("icon.pending");
|
||||||
|
|
||||||
private final MonitorReceiver monitor;
|
public MonitorLogRow(MonitorReceiver message, Date date, ActionContext actionContext,
|
||||||
private final Date date;
|
|
||||||
private final ActionContext context;
|
|
||||||
private final ActionList actions;
|
|
||||||
|
|
||||||
public MonitorLogRow(MonitorReceiver monitor, Date date, ActionContext context,
|
|
||||||
ActionList actions) {
|
ActionList actions) {
|
||||||
this.monitor = monitor;
|
this.message = message;
|
||||||
this.date = date;
|
this.date = date;
|
||||||
this.context = context;
|
this.actionContext = actionContext;
|
||||||
this.actions = Objects.requireNonNull(actions);
|
this.actions = Objects.requireNonNull(actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Icon getIcon() {
|
public Icon icon() {
|
||||||
return ICON;
|
return ICON;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public MonitorReceiver getMessage() {
|
|
||||||
return monitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ActionList getActions() {
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Date getDate() {
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ActionContext getActionContext() {
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ListenerForProgress implements ProgressListener {
|
private class ListenerForProgress implements ProgressListener {
|
||||||
|
@ -323,7 +281,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void errorReported(MonitorReceiver monitor, Throwable error) {
|
public void errorReported(MonitorReceiver monitor, Throwable error) {
|
||||||
log(DebuggerResources.ICON_LOG_ERROR, error.getMessage());
|
log(DebuggerResources.ICON_LOG_ERROR, error.getMessage(), error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -371,7 +329,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
LogTableColumns, ActionContext, LogRow<?>, LogRow<?>> {
|
LogTableColumns, ActionContext, LogRow<?>, LogRow<?>> {
|
||||||
|
|
||||||
public LogTableModel(PluginTool tool) {
|
public LogTableModel(PluginTool tool) {
|
||||||
super(tool, "Log", LogTableColumns.class, r -> r == null ? null : r.getActionContext(),
|
super(tool, "Log", LogTableColumns.class, r -> r == null ? null : r.actionContext(),
|
||||||
r -> r, r -> r);
|
r -> r, r -> r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -499,6 +457,27 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
logFilterPanel = new GhidraTableFilterPanel<>(logTable, logTableModel);
|
logFilterPanel = new GhidraTableFilterPanel<>(logTable, logTableModel);
|
||||||
mainPanel.add(logFilterPanel, BorderLayout.NORTH);
|
mainPanel.add(logFilterPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
logTable.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
if (e.getButton() == MouseEvent.BUTTON1 & e.getClickCount() == 2) {
|
||||||
|
if (activateSelectedRow()) {
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
logTable.addKeyListener(new KeyAdapter() {
|
||||||
|
@Override
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||||
|
if (activateSelectedRow()) {
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
logTable.setRowHeight(ACTION_BUTTON_SIZE + 2);
|
logTable.setRowHeight(ACTION_BUTTON_SIZE + 2);
|
||||||
TableColumnModel columnModel = logTable.getColumnModel();
|
TableColumnModel columnModel = logTable.getColumnModel();
|
||||||
|
|
||||||
|
@ -517,6 +496,14 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
timeCol.setPreferredWidth(15);
|
timeCol.setPreferredWidth(15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean activateSelectedRow() {
|
||||||
|
LogRow<?> row = logFilterPanel.getSelectedItem();
|
||||||
|
if (row == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return row.activated();
|
||||||
|
}
|
||||||
|
|
||||||
protected void createActions() {
|
protected void createActions() {
|
||||||
actionClear = ClearAction.builder(plugin)
|
actionClear = ClearAction.builder(plugin)
|
||||||
.onAction(this::activatedClear)
|
.onAction(this::activatedClear)
|
||||||
|
@ -548,7 +535,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
// I guess this can happen because of timing?
|
// I guess this can happen because of timing?
|
||||||
return super.getActionContext(event);
|
return super.getActionContext(event);
|
||||||
}
|
}
|
||||||
return sel.getActionContext();
|
return sel.actionContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_LOG_BUFFER_LIMIT)
|
@AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_LOG_BUFFER_LIMIT)
|
||||||
|
@ -570,17 +557,25 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void log(Icon icon, String message) {
|
protected void log(Icon icon, String message) {
|
||||||
log(icon, message, new LogRowConsoleActionContext());
|
log(icon, message, null, new LogRowConsoleActionContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void log(Icon icon, String message, ActionContext context) {
|
protected void log(Icon icon, String message, ActionContext context) {
|
||||||
logRow(
|
log(icon, message, null, context);
|
||||||
new MessageLogRow(icon, message, new Date(), context, computeToolbarActions(context)));
|
}
|
||||||
|
|
||||||
|
protected void log(Icon icon, String message, Throwable error) {
|
||||||
|
log(icon, message, error, new LogRowConsoleActionContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void log(Icon icon, String message, Throwable error, ActionContext context) {
|
||||||
|
logRow(new MessageLogRow(icon, message, new Date(), error, context,
|
||||||
|
computeToolbarActions(context)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void logRow(LogRow<?> row) {
|
protected void logRow(LogRow<?> row) {
|
||||||
synchronized (buffer) {
|
synchronized (buffer) {
|
||||||
LogRow<?> old = logTableModel.deleteKey(row.getActionContext());
|
LogRow<?> old = logTableModel.deleteKey(row.actionContext());
|
||||||
if (old != null) {
|
if (old != null) {
|
||||||
buffer.remove(old);
|
buffer.remove(old);
|
||||||
}
|
}
|
||||||
|
@ -608,7 +603,8 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
ActionContext context = new LogRowConsoleActionContext();
|
ActionContext context = new LogRowConsoleActionContext();
|
||||||
logRow(new MessageLogRow(iconForLevel(event.getLevel()),
|
logRow(new MessageLogRow(iconForLevel(event.getLevel()),
|
||||||
"<html>" + HTMLUtilities.escapeHTML(event.getMessage().getFormattedMessage()),
|
"<html>" + HTMLUtilities.escapeHTML(event.getMessage().getFormattedMessage()),
|
||||||
new Date(event.getTimeMillis()), context, computeToolbarActions(context)));
|
new Date(event.getTimeMillis()), event.getThrown(), context,
|
||||||
|
computeToolbarActions(context)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void removeFromLog(ActionContext context) {
|
protected void removeFromLog(ActionContext context) {
|
||||||
|
@ -686,7 +682,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
synchronized (buffer) {
|
synchronized (buffer) {
|
||||||
return logTableModel.getModelData()
|
return logTableModel.getModelData()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(r -> ctxCls.isInstance(r.getActionContext()))
|
.filter(r -> ctxCls.isInstance(r.actionContext()))
|
||||||
.count();
|
.count();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,7 +118,7 @@ public class TargetActionTask extends Task {
|
||||||
private void reportError(Throwable error) {
|
private void reportError(Throwable error) {
|
||||||
DebuggerConsoleService consoleService = tool.getService(DebuggerConsoleService.class);
|
DebuggerConsoleService consoleService = tool.getService(DebuggerConsoleService.class);
|
||||||
if (consoleService != null) {
|
if (consoleService != null) {
|
||||||
consoleService.log(DebuggerResources.ICON_LOG_ERROR, error.getMessage());
|
consoleService.log(DebuggerResources.ICON_LOG_ERROR, error.getMessage(), error);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Msg.showError(this, null, "Control Error", error.getMessage(), error);
|
Msg.showError(this, null, "Control Error", error.getMessage(), error);
|
||||||
|
|
|
@ -269,12 +269,12 @@ public class DebuggerObjectsPlugin extends AbstractDebuggerPlugin
|
||||||
providers.get(0).readConfigState(saveState);
|
providers.get(0).readConfigState(saveState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void objectError(String message) {
|
public void objectError(String message, Throwable ex) {
|
||||||
if (consoleService == null) {
|
if (consoleService == null) {
|
||||||
Msg.error(this, message);
|
Msg.error(this, message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
consoleService.log(DebuggerResources.ICON_LOG_ERROR, message);
|
consoleService.log(DebuggerResources.ICON_LOG_ERROR, message, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -351,7 +351,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||||
if (pane != null) {
|
if (pane != null) {
|
||||||
if (currentModel != null) {
|
if (currentModel != null) {
|
||||||
currentModel.fetchModelRoot().thenAccept(this::refresh).exceptionally(ex -> {
|
currentModel.fetchModelRoot().thenAccept(this::refresh).exceptionally(ex -> {
|
||||||
plugin.objectError("Error refreshing model root");
|
plugin.objectError("Error refreshing model root", ex);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -554,7 +554,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||||
table.setColumns();
|
table.setColumns();
|
||||||
// TODO: What with attrs?
|
// TODO: What with attrs?
|
||||||
}).exceptionally(ex -> {
|
}).exceptionally(ex -> {
|
||||||
plugin.objectError("Failed to fetch attributes");
|
plugin.objectError("Failed to fetch attributes", ex);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -569,7 +569,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||||
public void addTargetToMap(ObjectContainer container) {
|
public void addTargetToMap(ObjectContainer container) {
|
||||||
DebuggerObjectsProvider provider = container.getProvider();
|
DebuggerObjectsProvider provider = container.getProvider();
|
||||||
if (!this.equals(provider)) {
|
if (!this.equals(provider)) {
|
||||||
plugin.objectError("TargetMap corrupted");
|
plugin.objectError("TargetMap corrupted", null);
|
||||||
}
|
}
|
||||||
TargetObject targetObject = container.getTargetObject();
|
TargetObject targetObject = container.getTargetObject();
|
||||||
if (targetObject != null && !container.isLink()) {
|
if (targetObject != null && !container.isLink()) {
|
||||||
|
|
|
@ -35,7 +35,7 @@ public class ObjectElementRow {
|
||||||
map = attributes;
|
map = attributes;
|
||||||
}).exceptionally(ex -> {
|
}).exceptionally(ex -> {
|
||||||
DebuggerObjectsPlugin plugin = provider.getPlugin();
|
DebuggerObjectsPlugin plugin = provider.getPlugin();
|
||||||
plugin.objectError("Failed to fetch attributes");
|
plugin.objectError("Failed to fetch attributes", ex);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ public class DefaultMonitorReceiver implements MonitorReceiver {
|
||||||
|
|
||||||
private boolean valid = true;
|
private boolean valid = true;
|
||||||
|
|
||||||
private String message;
|
private String message = "";
|
||||||
private long maximum;
|
private long maximum;
|
||||||
private long progress;
|
private long progress;
|
||||||
|
|
||||||
|
@ -70,9 +70,14 @@ public class DefaultMonitorReceiver implements MonitorReceiver {
|
||||||
|
|
||||||
void setMessage(String message) {
|
void setMessage(String message) {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
this.message = message;
|
if (message == null) {
|
||||||
|
this.message = "";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
plugin.listeners.invoke().messageUpdated(this, message);
|
plugin.listeners.invoke().messageUpdated(this, this.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reportError(Throwable error) {
|
void reportError(Throwable error) {
|
||||||
|
|
|
@ -1636,7 +1636,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||||
|
|
||||||
DebuggerOpenProgramActionContext ctx = new DebuggerOpenProgramActionContext(df);
|
DebuggerOpenProgramActionContext ctx = new DebuggerOpenProgramActionContext(df);
|
||||||
waitForPass(() -> assertTrue(consolePlugin.logContains(ctx)));
|
waitForPass(() -> assertTrue(consolePlugin.logContains(ctx)));
|
||||||
assertTrue(consolePlugin.getLogRow(ctx).getMessage() instanceof String message &&
|
assertTrue(consolePlugin.getLogRow(ctx).message() instanceof String message &&
|
||||||
message.contains("recovery"));
|
message.contains("recovery"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1665,7 +1665,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||||
|
|
||||||
DebuggerOpenProgramActionContext ctx = new DebuggerOpenProgramActionContext(df);
|
DebuggerOpenProgramActionContext ctx = new DebuggerOpenProgramActionContext(df);
|
||||||
waitForPass(() -> assertTrue(consolePlugin.logContains(ctx)));
|
waitForPass(() -> assertTrue(consolePlugin.logContains(ctx)));
|
||||||
assertTrue(consolePlugin.getLogRow(ctx).getMessage() instanceof String message &&
|
assertTrue(consolePlugin.getLogRow(ctx).message() instanceof String message &&
|
||||||
message.contains("version"));
|
message.contains("version"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1684,8 +1684,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
LogRow<?> row = consolePlugin.getLogRow(ctx);
|
LogRow<?> row = consolePlugin.getLogRow(ctx);
|
||||||
assertEquals(1, row.getActions().size());
|
assertEquals(1, row.actions().size());
|
||||||
BoundAction boundAction = row.getActions().get(0);
|
BoundAction boundAction = row.actions().get(0);
|
||||||
assertEquals(listingProvider.actionOpenProgram, boundAction.action);
|
assertEquals(listingProvider.actionOpenProgram, boundAction.action);
|
||||||
|
|
||||||
boundAction.perform();
|
boundAction.perform();
|
||||||
|
|
|
@ -18,6 +18,7 @@ package ghidra.framework.plugintool;
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
import java.lang.invoke.MethodHandles.Lookup;
|
import java.lang.invoke.MethodHandles.Lookup;
|
||||||
import java.lang.reflect.*;
|
import java.lang.reflect.*;
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
|
@ -258,6 +259,20 @@ public interface AutoConfigState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class BigIntegerConfigFieldCodec implements ConfigFieldCodec<BigInteger> {
|
||||||
|
public static final BigIntegerConfigFieldCodec INSTANCE = new BigIntegerConfigFieldCodec();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigInteger read(SaveState state, String name, BigInteger current) {
|
||||||
|
return new BigInteger(state.getBytes(name, new byte[] { 0 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(SaveState state, String name, BigInteger value) {
|
||||||
|
state.putBytes(name, value == null ? null : value.toByteArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static class EnumConfigFieldCodec implements ConfigFieldCodec<Enum<?>> {
|
static class EnumConfigFieldCodec implements ConfigFieldCodec<Enum<?>> {
|
||||||
public static final EnumConfigFieldCodec INSTANCE = new EnumConfigFieldCodec();
|
public static final EnumConfigFieldCodec INSTANCE = new EnumConfigFieldCodec();
|
||||||
|
|
||||||
|
@ -301,6 +316,8 @@ public interface AutoConfigState {
|
||||||
addCodec(float[].class, FloatArrayConfigFieldCodec.INSTANCE);
|
addCodec(float[].class, FloatArrayConfigFieldCodec.INSTANCE);
|
||||||
addCodec(double[].class, DoubleArrayConfigFieldCodec.INSTANCE);
|
addCodec(double[].class, DoubleArrayConfigFieldCodec.INSTANCE);
|
||||||
addCodec(String[].class, StringArrayConfigFieldCodec.INSTANCE);
|
addCodec(String[].class, StringArrayConfigFieldCodec.INSTANCE);
|
||||||
|
|
||||||
|
addCodec(BigInteger.class, BigIntegerConfigFieldCodec.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> void addCodec(Class<T> cls, ConfigFieldCodec<T> codec) {
|
private static <T> void addCodec(Class<T> cls, ConfigFieldCodec<T> codec) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue