mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-4389: Fixes for Trace RMI lldb on macOS
Create local-lldh.sh launch script Upgrade to JNA-5.14 Fix pty IOCTL numbers for macOS Fix compile-spec mapping Improv error report / clean-up after launch failure. Write ERROR state on memory read failures Convert Python exceptions to LLDB command errors
This commit is contained in:
parent
bb8ec1cbe6
commit
973b9a8d4c
50 changed files with 1247 additions and 723 deletions
|
@ -25,7 +25,7 @@ import agent.gdb.manager.breakpoint.GdbBreakpointInfo;
|
||||||
import agent.gdb.manager.breakpoint.GdbBreakpointInsertions;
|
import agent.gdb.manager.breakpoint.GdbBreakpointInsertions;
|
||||||
import agent.gdb.manager.impl.GdbManagerImpl;
|
import agent.gdb.manager.impl.GdbManagerImpl;
|
||||||
import ghidra.pty.PtyFactory;
|
import ghidra.pty.PtyFactory;
|
||||||
import ghidra.pty.linux.LinuxPty;
|
import ghidra.pty.unix.UnixPty;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The controlling side of a GDB session, using GDB/MI, usually via a pseudo-terminal
|
* The controlling side of a GDB session, using GDB/MI, usually via a pseudo-terminal
|
||||||
|
@ -232,7 +232,7 @@ public interface GdbManager extends AutoCloseable, GdbConsoleOperations, GdbBrea
|
||||||
* Note: depending on the target, its output may not be communicated via this listener. Local
|
* Note: depending on the target, its output may not be communicated via this listener. Local
|
||||||
* targets, e.g., tend to just print output to GDB's controlling TTY. See
|
* targets, e.g., tend to just print output to GDB's controlling TTY. See
|
||||||
* {@link GdbInferior#setTty(String)} for a means to more reliably interact with a target's
|
* {@link GdbInferior#setTty(String)} for a means to more reliably interact with a target's
|
||||||
* input and output. See also {@link LinuxPty} for a means to easily acquire a new TTY from
|
* input and output. See also {@link UnixPty} for a means to easily acquire a new TTY from
|
||||||
* Java.
|
* Java.
|
||||||
*
|
*
|
||||||
* @param listener the listener to add
|
* @param listener the listener to add
|
||||||
|
|
|
@ -551,7 +551,7 @@ def putmem_state(address, length, state, pages=True):
|
||||||
inf = gdb.selected_inferior()
|
inf = gdb.selected_inferior()
|
||||||
base, addr = STATE.trace.memory_mapper.map(inf, start)
|
base, addr = STATE.trace.memory_mapper.map(inf, start)
|
||||||
if base != addr.space:
|
if base != addr.space:
|
||||||
trace.create_overlay_space(base, addr.space)
|
STATE.trace.create_overlay_space(base, addr.space)
|
||||||
STATE.trace.set_memory_state(addr.extend(end - start), state)
|
STATE.trace.set_memory_state(addr.extend(end - start), state)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,10 @@ from concurrent.futures import Future, Executor
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import gdb
|
||||||
from ghidratrace import sch
|
from ghidratrace import sch
|
||||||
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
|
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
|
||||||
|
|
||||||
import gdb
|
|
||||||
|
|
||||||
from . import commands, hooks, util
|
from . import commands, hooks, util
|
||||||
|
|
||||||
|
|
||||||
|
@ -690,8 +689,8 @@ def read_mem(inferior: sch.Schema('Inferior'), range: AddressRange):
|
||||||
gdb.execute(
|
gdb.execute(
|
||||||
f'ghidra trace putmem 0x{offset_start:x} {range.length()}')
|
f'ghidra trace putmem 0x{offset_start:x} {range.length()}')
|
||||||
except:
|
except:
|
||||||
commands.putmem_state(
|
gdb.execute(
|
||||||
offset_start, offset_start+range.length() - 1, 'error')
|
f'ghidra trace putmem-state 0x{offset_start:x} {range.length()} error')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method
|
@REGISTRY.method
|
||||||
|
|
|
@ -23,7 +23,8 @@ import org.junit.Ignore;
|
||||||
|
|
||||||
import agent.gdb.manager.GdbManager;
|
import agent.gdb.manager.GdbManager;
|
||||||
import ghidra.pty.PtySession;
|
import ghidra.pty.PtySession;
|
||||||
import ghidra.pty.linux.LinuxPty;
|
import ghidra.pty.linux.LinuxIoctls;
|
||||||
|
import ghidra.pty.unix.UnixPty;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
@Ignore("Need compatible GDB version for CI")
|
@Ignore("Need compatible GDB version for CI")
|
||||||
|
@ -45,13 +46,13 @@ public class JoinedGdbManagerTest extends AbstractGdbManagerTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected LinuxPty ptyUserGdb;
|
protected UnixPty ptyUserGdb;
|
||||||
protected PtySession gdb;
|
protected PtySession gdb;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CompletableFuture<Void> startManager(GdbManager manager) {
|
protected CompletableFuture<Void> startManager(GdbManager manager) {
|
||||||
try {
|
try {
|
||||||
ptyUserGdb = LinuxPty.openpty();
|
ptyUserGdb = UnixPty.openpty(LinuxIoctls.INSTANCE);
|
||||||
manager.start(null);
|
manager.start(null);
|
||||||
Msg.debug(this, "Starting GDB and invoking new-ui mi2 " + manager.getMi2PtyName());
|
Msg.debug(this, "Starting GDB and invoking new-ui mi2 " + manager.getMi2PtyName());
|
||||||
|
|
||||||
|
|
75
Ghidra/Debug/Debugger-agent-lldb/data/debugger-launchers/local-lldb.sh
Executable file
75
Ghidra/Debug/Debugger-agent-lldb/data/debugger-launchers/local-lldb.sh
Executable file
|
@ -0,0 +1,75 @@
|
||||||
|
#!/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.
|
||||||
|
##
|
||||||
|
#@title lldb
|
||||||
|
#@desc <html><body width="300px">
|
||||||
|
#@desc <h3>Launch with <tt>lldb</tt></h3>
|
||||||
|
#@desc <p>This will launch the target on the local machine using <tt>lldb</tt>. LLDB must already
|
||||||
|
#@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.</p>
|
||||||
|
#@desc </body></html>
|
||||||
|
#@menu-group local
|
||||||
|
#@icon icon.debugger
|
||||||
|
#@help TraceRmiLauncherServicePlugin#lldb
|
||||||
|
#@enum StartCmd:str run "process launch" "process launch --stop-at-entry"
|
||||||
|
#@arg :str "Image" "The target binary executable image"
|
||||||
|
#@args "Arguments" "Command-line arguments to pass to the target"
|
||||||
|
#@env OPT_LLDB_PATH:str="lldb" "Path to lldb" "The path to lldb. Omit the full path to resolve using the system PATH."
|
||||||
|
#@env OPT_START_CMD:StartCmd="process launch" "Run command" "The lldb command to actually run the target."
|
||||||
|
#@env OPT_EXTRA_TTY:bool=false "Target TTY" "Provide a separate terminal emulator for the target."
|
||||||
|
#@tty TTY_TARGET if env:OPT_EXTRA_TTY
|
||||||
|
|
||||||
|
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||||
|
then
|
||||||
|
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-agent-lldb/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-lldb/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-lldb/pypkg/src:$PYTHONPATH
|
||||||
|
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/pypkg/src:$PYTHONPATH
|
||||||
|
fi
|
||||||
|
|
||||||
|
target_image="$1"
|
||||||
|
shift
|
||||||
|
target_args="$@"
|
||||||
|
|
||||||
|
if [ -z "$target_args" ]
|
||||||
|
then
|
||||||
|
argspart=
|
||||||
|
else
|
||||||
|
argspart=-o "settings set target.run-args $target_args"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$TARGET_TTY" ]
|
||||||
|
then
|
||||||
|
ttypart=
|
||||||
|
else
|
||||||
|
ttypart=-o "settings set target.output-path $TTY_TARGET" -o "settings set target.input-path $TTY_TARGET"
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$OPT_LLDB_PATH" \
|
||||||
|
-o "version" \
|
||||||
|
-o "script import ghidralldb" \
|
||||||
|
-o "target create \"$target_image\"" \
|
||||||
|
$argspart \
|
||||||
|
$ttypart \
|
||||||
|
-o "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
|
||||||
|
-o "ghidra trace start" \
|
||||||
|
-o "ghidra trace sync-enable" \
|
||||||
|
-o "$OPT_START_CMD"
|
|
@ -14,20 +14,20 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
##
|
##
|
||||||
from ghidratrace.client import Address, RegVal
|
from ghidratrace.client import Address, RegVal
|
||||||
|
|
||||||
import lldb
|
import lldb
|
||||||
|
|
||||||
from . import util
|
from . import util
|
||||||
|
|
||||||
|
|
||||||
# NOTE: This map is derived from the ldefs using a script
|
# NOTE: This map is derived from the ldefs using a script
|
||||||
language_map = {
|
language_map = {
|
||||||
'aarch64': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A'],
|
'aarch64': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A'],
|
||||||
'armv7': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
'armv7': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||||
'armv7k': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
'armv7k': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||||
'armv7s': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
'armv7s': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||||
'arm64': ['ARM:BE:64:v8', 'ARM:LE:64:v8'],
|
'arm64': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:v8A'],
|
||||||
'arm64_32': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
|
'arm64_32': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
|
||||||
'arm64e': ['ARM:BE:64:v8', 'ARM:LE:64:v8'],
|
'arm64e': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:v8A'],
|
||||||
'i386': ['x86:LE:32:default'],
|
'i386': ['x86:LE:32:default'],
|
||||||
'thumbv7': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
'thumbv7': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||||
'thumbv7k': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
'thumbv7k': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||||
|
@ -40,7 +40,7 @@ data64_compiler_map = {
|
||||||
None: 'pointer64',
|
None: 'pointer64',
|
||||||
}
|
}
|
||||||
|
|
||||||
x86_compiler_map = {
|
default_compiler_map = {
|
||||||
'freebsd': 'gcc',
|
'freebsd': 'gcc',
|
||||||
'linux': 'gcc',
|
'linux': 'gcc',
|
||||||
'netbsd': 'gcc',
|
'netbsd': 'gcc',
|
||||||
|
@ -55,10 +55,12 @@ x86_compiler_map = {
|
||||||
}
|
}
|
||||||
|
|
||||||
compiler_map = {
|
compiler_map = {
|
||||||
'DATA:BE:64:default': data64_compiler_map,
|
'DATA:BE:64:': data64_compiler_map,
|
||||||
'DATA:LE:64:default': data64_compiler_map,
|
'DATA:LE:64:': data64_compiler_map,
|
||||||
'x86:LE:32:default': x86_compiler_map,
|
'x86:LE:32:': default_compiler_map,
|
||||||
'x86:LE:64:default': x86_compiler_map,
|
'x86:LE:64:': default_compiler_map,
|
||||||
|
'ARM:LE:32:': default_compiler_map,
|
||||||
|
'ARM:LE:64:': default_compiler_map,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -132,12 +134,20 @@ def compute_ghidra_compiler(lang):
|
||||||
return comp
|
return comp
|
||||||
|
|
||||||
# Check if the selected lang has specific compiler recommendations
|
# Check if the selected lang has specific compiler recommendations
|
||||||
if not lang in compiler_map:
|
matched_lang = sorted(
|
||||||
|
(l for l in compiler_map if l in lang),
|
||||||
|
key=lambda l: compiler_map[l]
|
||||||
|
)
|
||||||
|
if len(matched_lang) == 0:
|
||||||
return 'default'
|
return 'default'
|
||||||
comp_map = compiler_map[lang]
|
comp_map = compiler_map[matched_lang[0]]
|
||||||
osabi = get_osabi()
|
osabi = get_osabi()
|
||||||
if osabi in comp_map:
|
matched_osabi = sorted(
|
||||||
return comp_map[osabi]
|
(l for l in comp_map if l in osabi),
|
||||||
|
key=lambda l: comp_map[l]
|
||||||
|
)
|
||||||
|
if len(matched_osabi) > 0:
|
||||||
|
return comp_map[matched_osabi[0]]
|
||||||
if None in comp_map:
|
if None in comp_map:
|
||||||
return comp_map[None]
|
return comp_map[None]
|
||||||
return 'default'
|
return 'default'
|
||||||
|
@ -161,7 +171,8 @@ class DefaultMemoryMapper(object):
|
||||||
def map_back(self, proc: lldb.SBProcess, address: Address) -> int:
|
def map_back(self, proc: lldb.SBProcess, address: Address) -> int:
|
||||||
if address.space == self.defaultSpace:
|
if address.space == self.defaultSpace:
|
||||||
return address.offset
|
return address.offset
|
||||||
raise ValueError(f"Address {address} is not in process {proc.GetProcessID()}")
|
raise ValueError(
|
||||||
|
f"Address {address} is not in process {proc.GetProcessID()}")
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_MEMORY_MAPPER = DefaultMemoryMapper('ram')
|
DEFAULT_MEMORY_MAPPER = DefaultMemoryMapper('ram')
|
||||||
|
@ -203,11 +214,11 @@ class DefaultRegisterMapper(object):
|
||||||
|
|
||||||
def map_value(self, proc, name, value):
|
def map_value(self, proc, name, value):
|
||||||
try:
|
try:
|
||||||
### TODO: this seems half-baked
|
# TODO: this seems half-baked
|
||||||
av = value.to_bytes(8, "big")
|
av = value.to_bytes(8, "big")
|
||||||
except e:
|
except e:
|
||||||
raise ValueError("Cannot convert {}'s value: '{}', type: '{}'"
|
raise ValueError("Cannot convert {}'s value: '{}', type: '{}'"
|
||||||
.format(name, value, value.type))
|
.format(name, value, value.type))
|
||||||
return RegVal(self.map_name(proc, name), av)
|
return RegVal(self.map_name(proc, name), av)
|
||||||
|
|
||||||
def map_name_back(self, proc, name):
|
def map_name_back(self, proc, name):
|
||||||
|
@ -258,4 +269,3 @@ def compute_register_mapper(lang):
|
||||||
if ':LE:' in lang:
|
if ':LE:' in lang:
|
||||||
return DEFAULT_LE_REGISTER_MAPPER
|
return DEFAULT_LE_REGISTER_MAPPER
|
||||||
return register_mappers[lang]
|
return register_mappers[lang]
|
||||||
|
|
||||||
|
|
|
@ -14,20 +14,23 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
##
|
##
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import os.path
|
import os.path
|
||||||
|
import shlex
|
||||||
import socket
|
import socket
|
||||||
import time
|
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import psutil
|
||||||
|
|
||||||
from ghidratrace import sch
|
from ghidratrace import sch
|
||||||
from ghidratrace.client import Client, Address, AddressRange, TraceObject
|
from ghidratrace.client import Client, Address, AddressRange, TraceObject
|
||||||
import psutil
|
|
||||||
|
|
||||||
import lldb
|
import lldb
|
||||||
|
|
||||||
from . import arch, hooks, methods, util
|
from . import arch, hooks, methods, util
|
||||||
|
|
||||||
|
|
||||||
PAGE_SIZE = 4096
|
PAGE_SIZE = 4096
|
||||||
|
|
||||||
DEFAULT_REGISTER_BANK = "General Purpose Registers"
|
DEFAULT_REGISTER_BANK = "General Purpose Registers"
|
||||||
|
@ -119,59 +122,124 @@ class State(object):
|
||||||
STATE = State()
|
STATE = State()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
lldb.SBDebugger.InitializeWithErrorHandling();
|
lldb.SBDebugger.InitializeWithErrorHandling()
|
||||||
lldb.debugger = lldb.SBDebugger.Create()
|
lldb.debugger = lldb.SBDebugger.Create()
|
||||||
elif lldb.debugger:
|
elif lldb.debugger:
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_connect "ghidra_trace_connect"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_listen "ghidra_trace_listen"')
|
'command container add -h "Commands for connecting to Ghidra" ghidra')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_disconnect "ghidra_trace_disconnect"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_start "ghidra_trace_start"')
|
'command container add -h "Commands for exporting data to a Ghidra trace" ghidra trace')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_stop "ghidra_trace_stop"')
|
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_restart "ghidra_trace_restart"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_info "ghidra_trace_info"')
|
'command script add -f ghidralldb.commands.ghidra_trace_connect ghidra trace connect')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_info_lcsp "ghidra_trace_info_lcsp"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_txstart "ghidra_trace_txstart"')
|
'command script add -f ghidralldb.commands.ghidra_trace_listen ghidra trace listen')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_txcommit "ghidra_trace_txcommit"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_txabort "ghidra_trace_txabort"')
|
'command script add -f ghidralldb.commands.ghidra_trace_disconnect ghidra trace disconnect')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_txopen "ghidra_trace_txopen"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_save "ghidra_trace_save"')
|
'command script add -f ghidralldb.commands.ghidra_trace_start ghidra trace start')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_new_snap "ghidra_trace_new_snap"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_set_snap "ghidra_trace_set_snap"')
|
'command script add -f ghidralldb.commands.ghidra_trace_stop ghidra trace stop')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_putmem "ghidra_trace_putmem"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_putval "ghidra_trace_putval"')
|
'command script add -f ghidralldb.commands.ghidra_trace_restart ghidra trace restart')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_putmem_state "ghidra_trace_putmem_state"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_delmem "ghidra_trace_delmem"')
|
'command script add -f ghidralldb.commands.ghidra_trace_info ghidra trace info')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_putreg "ghidra_trace_putreg"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_delreg "ghidra_trace_delreg"')
|
'command script add -f ghidralldb.commands.ghidra_trace_info_lcsp ghidra trace info-lcsp')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_create_obj "ghidra_trace_create_obj"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_insert_obj "ghidra_trace_insert_obj"')
|
'command script add -f ghidralldb.commands.ghidra_trace_txstart ghidra trace tx-start')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_remove_obj "ghidra_trace_remove_obj"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_set_value "ghidra_trace_set_value"')
|
'command script add -f ghidralldb.commands.ghidra_trace_txcommit ghidra trace tx-commit')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_retain_values "ghidra_trace_retain_values"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_get_obj "ghidra_trace_get_obj"')
|
'command script add -f ghidralldb.commands.ghidra_trace_txabort ghidra trace tx-abort')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_get_values "ghidra_trace_get_values"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_get_values_rng "ghidra_trace_get_values_rng"')
|
'command script add -f ghidralldb.commands.ghidra_trace_txopen ghidra trace tx-open')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_activate "ghidra_trace_activate"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_disassemble "ghidra_trace_disassemble"')
|
'command script add -f ghidralldb.commands.ghidra_trace_save ghidra trace save')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_put_processes "ghidra_trace_put_processes"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_put_available "ghidra_trace_put_available"')
|
'command script add -f ghidralldb.commands.ghidra_trace_new_snap ghidra trace new-snap')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_put_breakpoints "ghidra_trace_put_breakpoints"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_put_watchpoints "ghidra_trace_put_watchpoints"')
|
'command script add -f ghidralldb.commands.ghidra_trace_set_snap ghidra trace set-snap')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_put_environment "ghidra_trace_put_environment"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_put_regions "ghidra_trace_put_regions"')
|
'command script add -f ghidralldb.commands.ghidra_trace_putmem ghidra trace putmem')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_put_modules "ghidra_trace_put_modules"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_put_threads "ghidra_trace_put_threads"')
|
'command script add -f ghidralldb.commands.ghidra_trace_putval ghidra trace putval')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_put_frames "ghidra_trace_put_frames"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_put_all "ghidra_trace_put_all"')
|
'command script add -f ghidralldb.commands.ghidra_trace_putmem_state ghidra trace putmem-state')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_install_hooks "ghidra_trace_install_hooks"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_remove_hooks "ghidra_trace_remove_hooks"')
|
'command script add -f ghidralldb.commands.ghidra_trace_delmem ghidra trace delmem')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_sync_enable "ghidra_trace_sync_enable"')
|
lldb.debugger.HandleCommand(
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_sync_disable "ghidra_trace_sync_disable"')
|
'command script add -f ghidralldb.commands.ghidra_trace_putreg ghidra trace putreg')
|
||||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_util_mark "_mark_"')
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_delreg ghidra trace delreg')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_create_obj ghidra trace create-obj')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_insert_obj ghidra trace insert-obj')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_remove_obj ghidra trace remove-obj')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_set_value ghidra trace set-value')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_retain_values ghidra trace retain-values')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_get_obj ghidra trace get_obj')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_get_values ghidra trace get-values')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_get_values_rng ghidra trace get-values-rng')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_activate ghidra trace activate')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_disassemble ghidra trace disassemble')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_put_processes ghidra trace put-processes')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_put_available ghidra trace put-available')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_put_breakpoints ghidra trace put-breakpoints')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_put_watchpoints ghidra trace put-watchpoints')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_put_environment ghidra trace put-environment')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_put_regions ghidra trace put-regions')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_put_modules ghidra trace put-modules')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_put_threads ghidra trace put-threads')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_put_frames ghidra trace put-frames')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_put_all ghidra trace put-all')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_install_hooks ghidra trace install-hooks')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_remove_hooks ghidra trace remove-hooks')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_sync_enable ghidra trace sync-enable')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_trace_sync_disable ghidra trace sync-disable')
|
||||||
|
lldb.debugger.HandleCommand(
|
||||||
|
'command script add -f ghidralldb.commands.ghidra_util_mark _mark_')
|
||||||
#lldb.debugger.HandleCommand('target stop-hook add -P ghidralldb.hooks.StopHook')
|
#lldb.debugger.HandleCommand('target stop-hook add -P ghidralldb.hooks.StopHook')
|
||||||
lldb.debugger.SetAsync(True)
|
lldb.debugger.SetAsync(True)
|
||||||
print("Commands loaded.")
|
print("Commands loaded.")
|
||||||
|
|
||||||
|
|
||||||
|
def convert_errors(func):
|
||||||
|
@functools.wraps(func)
|
||||||
|
def _func(debugger, command, result, internal_dict):
|
||||||
|
result.Clear()
|
||||||
|
try:
|
||||||
|
func(debugger, command, result, internal_dict)
|
||||||
|
result.SetStatus(lldb.eReturnStatusSuccessFinishNoResult)
|
||||||
|
except BaseException as e:
|
||||||
|
result.SetError(str(e))
|
||||||
|
return _func
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_connect(debugger, command, result, internal_dict):
|
def ghidra_trace_connect(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Connect LLDB to Ghidra for tracing
|
Connect LLDB to Ghidra for tracing
|
||||||
|
@ -179,10 +247,13 @@ def ghidra_trace_connect(debugger, command, result, internal_dict):
|
||||||
Address must be of the form 'host:port'
|
Address must be of the form 'host:port'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
args = shlex.split(command)
|
||||||
|
|
||||||
STATE.require_no_client()
|
STATE.require_no_client()
|
||||||
address = command if len(command) > 0 else None
|
if len(args) != 1:
|
||||||
if address is None:
|
raise RuntimeError(
|
||||||
raise RuntimeError("'ghidra_trace_connect': missing required argument 'address'")
|
"ghidra trace connect: missing required argument 'address'")
|
||||||
|
address = args[0]
|
||||||
|
|
||||||
parts = address.split(':')
|
parts = address.split(':')
|
||||||
if len(parts) != 2:
|
if len(parts) != 2:
|
||||||
|
@ -191,11 +262,13 @@ def ghidra_trace_connect(debugger, command, result, internal_dict):
|
||||||
try:
|
try:
|
||||||
c = socket.socket()
|
c = socket.socket()
|
||||||
c.connect((host, int(port)))
|
c.connect((host, int(port)))
|
||||||
STATE.client = Client(c, "lldb-" + util.LLDB_VERSION.full, methods.REGISTRY)
|
STATE.client = Client(
|
||||||
|
c, "lldb-" + util.LLDB_VERSION.full, methods.REGISTRY)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise RuntimeError("port must be numeric")
|
raise RuntimeError("port must be numeric")
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_listen(debugger, command, result, internal_dict):
|
def ghidra_trace_listen(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Listen for Ghidra to connect for tracing
|
Listen for Ghidra to connect for tracing
|
||||||
|
@ -224,15 +297,17 @@ def ghidra_trace_listen(debugger, command, result, internal_dict):
|
||||||
s.bind((host, int(port)))
|
s.bind((host, int(port)))
|
||||||
host, port = s.getsockname()
|
host, port = s.getsockname()
|
||||||
s.listen(1)
|
s.listen(1)
|
||||||
print("Listening at {}:{}...\n".format(host, port))
|
print(f"Listening at {host}:{port}...")
|
||||||
c, (chost, cport) = s.accept()
|
c, (chost, cport) = s.accept()
|
||||||
s.close()
|
s.close()
|
||||||
print("Connection from {}:{}\n".format(chost, cport))
|
print(f"Connection from {chost}:{cport}")
|
||||||
STATE.client = Client(c, "lldb-" + util.LLDB_VERSION.full, methods.REGISTRY)
|
STATE.client = Client(
|
||||||
|
c, "lldb-" + util.LLDB_VERSION.full, methods.REGISTRY)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise RuntimeError("port must be numeric")
|
raise RuntimeError("port must be numeric")
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_disconnect(debugger, command, result, internal_dict):
|
def ghidra_trace_disconnect(debugger, command, result, internal_dict):
|
||||||
"""Disconnect LLDB from Ghidra for tracing"""
|
"""Disconnect LLDB from Ghidra for tracing"""
|
||||||
|
|
||||||
|
@ -266,17 +341,19 @@ def start_trace(name):
|
||||||
util.set_convenience_variable('_ghidra_tracing', "true")
|
util.set_convenience_variable('_ghidra_tracing', "true")
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_start(debugger, command, result, internal_dict):
|
def ghidra_trace_start(debugger, command, result, internal_dict):
|
||||||
"""Start a Trace in Ghidra"""
|
"""Start a Trace in Ghidra"""
|
||||||
|
|
||||||
STATE.require_client()
|
STATE.require_client()
|
||||||
name = command if len(command) > 0 else compute_name()
|
name = command if len(command) > 0 else compute_name()
|
||||||
#if name is None:
|
# if name is None:
|
||||||
# name = compute_name()
|
# name = compute_name()
|
||||||
STATE.require_no_trace()
|
STATE.require_no_trace()
|
||||||
start_trace(name)
|
start_trace(name)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_stop(debugger, command, result, internal_dict):
|
def ghidra_trace_stop(debugger, command, result, internal_dict):
|
||||||
"""Stop the Trace in Ghidra"""
|
"""Stop the Trace in Ghidra"""
|
||||||
|
|
||||||
|
@ -284,6 +361,7 @@ def ghidra_trace_stop(debugger, command, result, internal_dict):
|
||||||
STATE.reset_trace()
|
STATE.reset_trace()
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_restart(debugger, command, result, internal_dict):
|
def ghidra_trace_restart(debugger, command, result, internal_dict):
|
||||||
"""Restart or start the Trace in Ghidra"""
|
"""Restart or start the Trace in Ghidra"""
|
||||||
|
|
||||||
|
@ -292,27 +370,29 @@ def ghidra_trace_restart(debugger, command, result, internal_dict):
|
||||||
STATE.trace.close()
|
STATE.trace.close()
|
||||||
STATE.reset_trace()
|
STATE.reset_trace()
|
||||||
name = command if len(command) > 0 else compute_name()
|
name = command if len(command) > 0 else compute_name()
|
||||||
#if name is None:
|
# if name is None:
|
||||||
# name = compute_name()
|
# name = compute_name()
|
||||||
start_trace(name)
|
start_trace(name)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_info(debugger, command, result, internal_dict):
|
def ghidra_trace_info(debugger, command, result, internal_dict):
|
||||||
"""Get info about the Ghidra connection"""
|
"""Get info about the Ghidra connection"""
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
if STATE.client is None:
|
if STATE.client is None:
|
||||||
print("Not connected to Ghidra\n")
|
print("Not connected to Ghidra")
|
||||||
return
|
return
|
||||||
host, port = STATE.client.s.getpeername()
|
host, port = STATE.client.s.getpeername()
|
||||||
print("Connected to Ghidra at {}:{}\n".format(host, port))
|
print(f"Connected to Ghidra at {host}:{port}")
|
||||||
if STATE.trace is None:
|
if STATE.trace is None:
|
||||||
print("No trace\n")
|
print("No trace")
|
||||||
return
|
return
|
||||||
print("Trace active\n")
|
print("Trace active")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_info_lcsp(debugger, command, result, internal_dict):
|
def ghidra_trace_info_lcsp(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Get the selected Ghidra language-compiler-spec pair. Even when
|
Get the selected Ghidra language-compiler-spec pair. Even when
|
||||||
|
@ -321,10 +401,11 @@ def ghidra_trace_info_lcsp(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
language, compiler = arch.compute_ghidra_lcsp()
|
language, compiler = arch.compute_ghidra_lcsp()
|
||||||
print("Selected Ghidra language: {}\n".format(language))
|
print(f"Selected Ghidra language: {language}")
|
||||||
print("Selected Ghidra compiler: {}\n".format(compiler))
|
print(f"Selected Ghidra compiler: {compiler}")
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_txstart(debugger, command, result, internal_dict):
|
def ghidra_trace_txstart(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Start a transaction on the trace
|
Start a transaction on the trace
|
||||||
|
@ -335,6 +416,7 @@ def ghidra_trace_txstart(debugger, command, result, internal_dict):
|
||||||
STATE.tx = STATE.require_trace().start_tx(description, undoable=False)
|
STATE.tx = STATE.require_trace().start_tx(description, undoable=False)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_txcommit(debugger, command, result, internal_dict):
|
def ghidra_trace_txcommit(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Commit the current transaction
|
Commit the current transaction
|
||||||
|
@ -344,6 +426,7 @@ def ghidra_trace_txcommit(debugger, command, result, internal_dict):
|
||||||
STATE.reset_tx()
|
STATE.reset_tx()
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_txabort(debugger, command, result, internal_dict):
|
def ghidra_trace_txabort(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Abort the current transaction
|
Abort the current transaction
|
||||||
|
@ -352,7 +435,7 @@ def ghidra_trace_txabort(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tx = STATE.require_tx()
|
tx = STATE.require_tx()
|
||||||
print("Aborting trace transaction!\n")
|
print("Aborting trace transaction!")
|
||||||
tx.abort()
|
tx.abort()
|
||||||
STATE.reset_tx()
|
STATE.reset_tx()
|
||||||
|
|
||||||
|
@ -365,37 +448,23 @@ def open_tracked_tx(description):
|
||||||
STATE.reset_tx()
|
STATE.reset_tx()
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_txopen(debugger, command, result, internal_dict):
|
def ghidra_trace_txopen(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Run a command with an open transaction
|
Run a command with an open transaction
|
||||||
|
|
||||||
If possible, use this in the following idiom to ensure your transactions
|
This is generally useful only when executing a single 'put' command, or
|
||||||
are closed:
|
when executing a custom command that performs several puts.
|
||||||
|
|
||||||
define my-cmd
|
|
||||||
ghidra_trace_put...
|
|
||||||
ghidra_trace_put...
|
|
||||||
end
|
|
||||||
ghidra_trace_tx-open "My tx" "my-cmd"
|
|
||||||
|
|
||||||
If you instead do:
|
|
||||||
|
|
||||||
ghidra_trace_tx-start "My tx"
|
|
||||||
ghidra_trace_put...
|
|
||||||
ghidra_trace_put...
|
|
||||||
ghidra_trace_tx-commit
|
|
||||||
|
|
||||||
and something goes wrong with one of the puts, the transaction may never be
|
|
||||||
closed, leading to further crashes when trying to start a new transaction.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
items = command.split(" ");
|
items = command.split(" ")
|
||||||
description = items[0]
|
description = items[0]
|
||||||
command = items[1]
|
command = items[1]
|
||||||
with open_tracked_tx(description):
|
with open_tracked_tx(description):
|
||||||
lldb.debugger.HandleCommand(command);
|
lldb.debugger.HandleCommand(command)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_save(debugger, command, result, internal_dict):
|
def ghidra_trace_save(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Save the current trace
|
Save the current trace
|
||||||
|
@ -404,6 +473,7 @@ def ghidra_trace_save(debugger, command, result, internal_dict):
|
||||||
STATE.require_trace().save()
|
STATE.require_trace().save()
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_new_snap(debugger, command, result, internal_dict):
|
def ghidra_trace_new_snap(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Create a new snapshot
|
Create a new snapshot
|
||||||
|
@ -413,15 +483,15 @@ def ghidra_trace_new_snap(debugger, command, result, internal_dict):
|
||||||
|
|
||||||
description = str(command)
|
description = str(command)
|
||||||
STATE.require_tx()
|
STATE.require_tx()
|
||||||
return {'snap': STATE.require_trace().snapshot(description)}
|
|
||||||
|
|
||||||
# TODO: A convenience var for the current snapshot
|
# TODO: A convenience var for the current snapshot
|
||||||
# Will need to update it on:
|
# Will need to update it on:
|
||||||
# ghidra_trace_snapshot/set-snap
|
# ghidra trace snapshot/set-snap
|
||||||
# process ? (only if per-process tracing.... I don't think I'm doing that.)
|
# process ? (only if per-process tracing.... I don't think I'm doing that.)
|
||||||
# ghidra_trace_trace start/stop/restart
|
# ghidra trace trace start/stop/restart
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_set_snap(debugger, command, result, internal_dict):
|
def ghidra_trace_set_snap(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Go to a snapshot
|
Go to a snapshot
|
||||||
|
@ -437,15 +507,18 @@ def ghidra_trace_set_snap(debugger, command, result, internal_dict):
|
||||||
STATE.require_trace().set_snap(int(snap))
|
STATE.require_trace().set_snap(int(snap))
|
||||||
|
|
||||||
|
|
||||||
def put_bytes(start, end, pages, from_tty):
|
def quantize_pages(start, end):
|
||||||
|
return (start // PAGE_SIZE * PAGE_SIZE, (end+PAGE_SIZE-1) // PAGE_SIZE*PAGE_SIZE)
|
||||||
|
|
||||||
|
|
||||||
|
def put_bytes(start, end, result, pages):
|
||||||
trace = STATE.require_trace()
|
trace = STATE.require_trace()
|
||||||
if pages:
|
if pages:
|
||||||
start = start // PAGE_SIZE * PAGE_SIZE
|
start, end = quantize_pages(start, end)
|
||||||
end = (end + PAGE_SIZE - 1) // PAGE_SIZE * PAGE_SIZE
|
|
||||||
proc = util.get_process()
|
proc = util.get_process()
|
||||||
error = lldb.SBError()
|
error = lldb.SBError()
|
||||||
if end - start <= 0:
|
if end - start <= 0:
|
||||||
return {'count': 0}
|
return
|
||||||
buf = proc.ReadMemory(start, end - start, error)
|
buf = proc.ReadMemory(start, end - start, error)
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
|
@ -454,32 +527,34 @@ def put_bytes(start, end, pages, from_tty):
|
||||||
if base != addr.space:
|
if base != addr.space:
|
||||||
trace.create_overlay_space(base, addr.space)
|
trace.create_overlay_space(base, addr.space)
|
||||||
count = trace.put_bytes(addr, buf)
|
count = trace.put_bytes(addr, buf)
|
||||||
if from_tty:
|
if result is not None:
|
||||||
print("Wrote {} bytes\n".format(count))
|
result.PutCString(f"Wrote {count} bytes")
|
||||||
return {'count': count}
|
else:
|
||||||
|
raise RuntimeError(f"Cannot read memory at {start:x}")
|
||||||
|
|
||||||
|
|
||||||
def eval_address(address):
|
def eval_address(address):
|
||||||
try:
|
try:
|
||||||
return util.parse_and_eval(address)
|
return util.parse_and_eval(address)
|
||||||
except e:
|
except BaseException as e:
|
||||||
raise RuntimeError("Cannot convert '{}' to address".format(address))
|
raise RuntimeError(f"Cannot convert '{address}' to address: {e}")
|
||||||
|
|
||||||
|
|
||||||
def eval_range(address, length):
|
def eval_range(address, length):
|
||||||
start = eval_address(address)
|
start = eval_address(address)
|
||||||
try:
|
try:
|
||||||
end = start + util.parse_and_eval(length)
|
end = start + util.parse_and_eval(length)
|
||||||
except e:
|
except BaseException as e:
|
||||||
raise RuntimeError("Cannot convert '{}' to length".format(length))
|
raise RuntimeError(f"Cannot convert '{length}' to length: {e}")
|
||||||
return start, end
|
return start, end
|
||||||
|
|
||||||
|
|
||||||
def putmem(address, length, pages=True, from_tty=True):
|
def putmem(address, length, result, pages=True):
|
||||||
start, end = eval_range(address, length)
|
start, end = eval_range(address, length)
|
||||||
return put_bytes(start, end, pages, from_tty)
|
return put_bytes(start, end, result, pages)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_putmem(debugger, command, result, internal_dict):
|
def ghidra_trace_putmem(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Record the given block of memory into the Ghidra trace.
|
Record the given block of memory into the Ghidra trace.
|
||||||
|
@ -491,9 +566,10 @@ def ghidra_trace_putmem(debugger, command, result, internal_dict):
|
||||||
pages = items[2] if len(items) > 2 else True
|
pages = items[2] if len(items) > 2 else True
|
||||||
|
|
||||||
STATE.require_tx()
|
STATE.require_tx()
|
||||||
return putmem(address, length, pages, True)
|
return putmem(address, length, result, pages)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_putval(debugger, command, result, internal_dict):
|
def ghidra_trace_putval(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Record the given value into the Ghidra trace, if it's in memory.
|
Record the given value into the Ghidra trace, if it's in memory.
|
||||||
|
@ -506,12 +582,25 @@ def ghidra_trace_putval(debugger, command, result, internal_dict):
|
||||||
STATE.require_tx()
|
STATE.require_tx()
|
||||||
try:
|
try:
|
||||||
start = util.parse_and_eval(value)
|
start = util.parse_and_eval(value)
|
||||||
except e:
|
except BaseExcepion as e:
|
||||||
raise RuntimeError("Value '{}' has no address".format(value))
|
raise RuntimeError(f"Value '{value}' has no address: {e}")
|
||||||
end = start + int(start.GetType().GetByteSize())
|
end = start + int(start.GetType().GetByteSize())
|
||||||
return put_bytes(start, end, pages, True)
|
return put_bytes(start, end, pages, True)
|
||||||
|
|
||||||
|
|
||||||
|
def putmem_state(address, length, state, pages=True):
|
||||||
|
STATE.trace.validate_state(state)
|
||||||
|
start, end = eval_range(address, length)
|
||||||
|
if pages:
|
||||||
|
start, end = quantize_pages(start, end)
|
||||||
|
proc = util.get_process()
|
||||||
|
base, addr = STATE.trace.memory_mapper.map(proc, start)
|
||||||
|
if base != addr.space:
|
||||||
|
STATE.trace.create_overlay_space(base, addr.space)
|
||||||
|
STATE.trace.set_memory_state(addr.extend(end - start), state)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_putmem_state(debugger, command, result, internal_dict):
|
def ghidra_trace_putmem_state(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Set the state of the given range of memory in the Ghidra trace.
|
Set the state of the given range of memory in the Ghidra trace.
|
||||||
|
@ -523,15 +612,10 @@ def ghidra_trace_putmem_state(debugger, command, result, internal_dict):
|
||||||
state = items[2]
|
state = items[2]
|
||||||
|
|
||||||
STATE.require_tx()
|
STATE.require_tx()
|
||||||
STATE.trace.validate_state(state)
|
putmem_state(address, length, state)
|
||||||
start, end = eval_range(address, length)
|
|
||||||
proc = util.get_process()
|
|
||||||
base, addr = STATE.trace.memory_mapper.map(proc, start)
|
|
||||||
if base != addr.space:
|
|
||||||
trace.create_overlay_space(base, addr.space)
|
|
||||||
STATE.trace.set_memory_state(addr.extend(end - start), state)
|
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_delmem(debugger, command, result, internal_dict):
|
def ghidra_trace_delmem(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Delete the given range of memory from the Ghidra trace.
|
Delete the given range of memory from the Ghidra trace.
|
||||||
|
@ -558,7 +642,7 @@ def putreg(frame, bank):
|
||||||
space = REGS_PATTERN.format(procnum=proc.GetProcessID(), tnum=util.selected_thread().GetThreadID(),
|
space = REGS_PATTERN.format(procnum=proc.GetProcessID(), tnum=util.selected_thread().GetThreadID(),
|
||||||
level=frame.GetFrameID())
|
level=frame.GetFrameID())
|
||||||
subspace = BANK_PATTERN.format(procnum=proc.GetProcessID(), tnum=util.selected_thread().GetThreadID(),
|
subspace = BANK_PATTERN.format(procnum=proc.GetProcessID(), tnum=util.selected_thread().GetThreadID(),
|
||||||
level=frame.GetFrameID(), bank=bank.name)
|
level=frame.GetFrameID(), bank=bank.name)
|
||||||
STATE.trace.create_overlay_space('register', space)
|
STATE.trace.create_overlay_space('register', space)
|
||||||
robj = STATE.trace.create_object(space)
|
robj = STATE.trace.create_object(space)
|
||||||
robj.insert()
|
robj.insert()
|
||||||
|
@ -568,12 +652,14 @@ def putreg(frame, bank):
|
||||||
values = []
|
values = []
|
||||||
for i in range(0, bank.GetNumChildren()):
|
for i in range(0, bank.GetNumChildren()):
|
||||||
item = bank.GetChildAtIndex(i, lldb.eDynamicCanRunTarget, True)
|
item = bank.GetChildAtIndex(i, lldb.eDynamicCanRunTarget, True)
|
||||||
values.append(mapper.map_value(proc, item.GetName(), item.GetValueAsUnsigned()))
|
values.append(mapper.map_value(
|
||||||
|
proc, item.GetName(), item.GetValueAsUnsigned()))
|
||||||
bobj.set_value(item.GetName(), hex(item.GetValueAsUnsigned()))
|
bobj.set_value(item.GetName(), hex(item.GetValueAsUnsigned()))
|
||||||
# TODO: Memorize registers that failed for this arch, and omit later.
|
# TODO: Memorize registers that failed for this arch, and omit later.
|
||||||
return {'missing': STATE.trace.put_registers(space, values)}
|
STATE.trace.put_registers(space, values)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_putreg(debugger, command, result, internal_dict):
|
def ghidra_trace_putreg(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Record the given register group for the current frame into the Ghidra trace.
|
Record the given register group for the current frame into the Ghidra trace.
|
||||||
|
@ -586,7 +672,7 @@ def ghidra_trace_putreg(debugger, command, result, internal_dict):
|
||||||
STATE.require_tx()
|
STATE.require_tx()
|
||||||
frame = util.selected_frame()
|
frame = util.selected_frame()
|
||||||
regs = frame.GetRegisters()
|
regs = frame.GetRegisters()
|
||||||
if group is not 'all':
|
if group != 'all':
|
||||||
bank = regs.GetFirstValueByName(group)
|
bank = regs.GetFirstValueByName(group)
|
||||||
return putreg(frame, bank)
|
return putreg(frame, bank)
|
||||||
|
|
||||||
|
@ -595,6 +681,7 @@ def ghidra_trace_putreg(debugger, command, result, internal_dict):
|
||||||
putreg(frame, bank)
|
putreg(frame, bank)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_delreg(debugger, command, result, internal_dict):
|
def ghidra_trace_delreg(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Delete the given register group for the curent frame from the Ghidra trace.
|
Delete the given register group for the curent frame from the Ghidra trace.
|
||||||
|
@ -607,9 +694,8 @@ def ghidra_trace_delreg(debugger, command, result, internal_dict):
|
||||||
STATE.require_tx()
|
STATE.require_tx()
|
||||||
proc = util.get_process()
|
proc = util.get_process()
|
||||||
frame = util.selected_frame()
|
frame = util.selected_frame()
|
||||||
space = 'Processes[{}].Threads[{}].Stack[{}].Registers'.format(
|
space = REGS_PATTERN.format(procnum=proc.GetProcessID(), tnum=util.selected_thread().GetThreadID(),
|
||||||
proc.GetProcessID(), util.selected_thread().GetThreadID(), frame.GetFrameID()
|
level=frame.GetFrameID())
|
||||||
)
|
|
||||||
mapper = STATE.trace.register_mapper
|
mapper = STATE.trace.register_mapper
|
||||||
names = []
|
names = []
|
||||||
for desc in frame.registers:
|
for desc in frame.registers:
|
||||||
|
@ -617,6 +703,7 @@ def ghidra_trace_delreg(debugger, command, result, internal_dict):
|
||||||
return STATE.trace.delete_registers(space, names)
|
return STATE.trace.delete_registers(space, names)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_create_obj(debugger, command, result, internal_dict):
|
def ghidra_trace_create_obj(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Create an object in the Ghidra trace.
|
Create an object in the Ghidra trace.
|
||||||
|
@ -631,10 +718,10 @@ def ghidra_trace_create_obj(debugger, command, result, internal_dict):
|
||||||
STATE.require_tx()
|
STATE.require_tx()
|
||||||
obj = STATE.trace.create_object(path)
|
obj = STATE.trace.create_object(path)
|
||||||
obj.insert()
|
obj.insert()
|
||||||
print("Created object: id={}, path='{}'\n".format(obj.id, obj.path))
|
print(f"Created object: id={obj.id}, path='{obj.path}'")
|
||||||
return {'id': obj.id, 'path': obj.path}
|
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_insert_obj(debugger, command, result, internal_dict):
|
def ghidra_trace_insert_obj(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Insert an object into the Ghidra trace.
|
Insert an object into the Ghidra trace.
|
||||||
|
@ -646,10 +733,10 @@ def ghidra_trace_insert_obj(debugger, command, result, internal_dict):
|
||||||
# humans.
|
# humans.
|
||||||
STATE.require_tx()
|
STATE.require_tx()
|
||||||
span = STATE.trace.proxy_object_path(path).insert()
|
span = STATE.trace.proxy_object_path(path).insert()
|
||||||
print("Inserted object: lifespan={}\n".format(span))
|
print(f"Inserted object: lifespan={span}")
|
||||||
return {'lifespan': span}
|
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_remove_obj(debugger, command, result, internal_dict):
|
def ghidra_trace_remove_obj(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Remove an object from the Ghidra trace.
|
Remove an object from the Ghidra trace.
|
||||||
|
@ -668,28 +755,29 @@ def ghidra_trace_remove_obj(debugger, command, result, internal_dict):
|
||||||
|
|
||||||
def to_bytes(value, type):
|
def to_bytes(value, type):
|
||||||
n = value.GetNumChildren()
|
n = value.GetNumChildren()
|
||||||
return bytes(int(value.GetChildAtIndex(i).GetValueAsUnsigned()) for i in range(0,n))
|
return bytes(int(value.GetChildAtIndex(i).GetValueAsUnsigned()) for i in range(0, n))
|
||||||
|
|
||||||
|
|
||||||
def to_string(value, type, encoding, full):
|
def to_string(value, type, encoding, full):
|
||||||
n = value.GetNumChildren()
|
n = value.GetNumChildren()
|
||||||
b = bytes(int(value.GetChildAtIndex(i).GetValueAsUnsigned()) for i in range(0,n))
|
b = bytes(int(value.GetChildAtIndex(i).GetValueAsUnsigned())
|
||||||
|
for i in range(0, n))
|
||||||
return str(b, encoding)
|
return str(b, encoding)
|
||||||
|
|
||||||
|
|
||||||
def to_bool_list(value, type):
|
def to_bool_list(value, type):
|
||||||
n = value.GetNumChildren()
|
n = value.GetNumChildren()
|
||||||
return [bool(int(value.GetChildAtIndex(i).GetValueAsUnsigned())) for i in range(0,n)]
|
return [bool(int(value.GetChildAtIndex(i).GetValueAsUnsigned())) for i in range(0, n)]
|
||||||
|
|
||||||
|
|
||||||
def to_int_list(value, type):
|
def to_int_list(value, type):
|
||||||
n = value.GetNumChildren()
|
n = value.GetNumChildren()
|
||||||
return [int(value.GetChildAtIndex(i).GetValueAsUnsigned()) for i in range(0,n)]
|
return [int(value.GetChildAtIndex(i).GetValueAsUnsigned()) for i in range(0, n)]
|
||||||
|
|
||||||
|
|
||||||
def to_short_list(value, type):
|
def to_short_list(value, type):
|
||||||
n = value.GetNumChildren()
|
n = value.GetNumChildren()
|
||||||
return [int(value.GetChildAtIndex(i).GetValueAsUnsigned()) for i in range(0,n)]
|
return [int(value.GetChildAtIndex(i).GetValueAsUnsigned()) for i in range(0, n)]
|
||||||
|
|
||||||
|
|
||||||
def eval_value(value, schema=None):
|
def eval_value(value, schema=None):
|
||||||
|
@ -763,14 +851,14 @@ def eval_value(value, schema=None):
|
||||||
else:
|
else:
|
||||||
return to_int_list(val, type), sch.LONG_ARR
|
return to_int_list(val, type), sch.LONG_ARR
|
||||||
elif type.IsPointerType():
|
elif type.IsPointerType():
|
||||||
offset = int(val.GetValue(),16)
|
offset = int(val.GetValue(), 16)
|
||||||
proc = util.get_process()
|
proc = util.get_process()
|
||||||
base, addr = STATE.trace.memory_mapper.map(proc, offset)
|
base, addr = STATE.trace.memory_mapper.map(proc, offset)
|
||||||
return (base, addr), sch.ADDRESS
|
return (base, addr), sch.ADDRESS
|
||||||
raise ValueError(
|
raise ValueError(f"Cannot convert ({schema}): '{value}', value='{val}'")
|
||||||
"Cannot convert ({}): '{}', value='{}'".format(schema, value, val))
|
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_set_value(debugger, command, result, internal_dict):
|
def ghidra_trace_set_value(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Set a value (attribute or element) in the Ghidra trace's object tree.
|
Set a value (attribute or element) in the Ghidra trace's object tree.
|
||||||
|
@ -792,7 +880,7 @@ def ghidra_trace_set_value(debugger, command, result, internal_dict):
|
||||||
path = items[0]
|
path = items[0]
|
||||||
key = items[1]
|
key = items[1]
|
||||||
value = items[2]
|
value = items[2]
|
||||||
if len(items) > 3 and items[3] is not "":
|
if len(items) > 3 and items[3] != "":
|
||||||
schema = items[3]
|
schema = items[3]
|
||||||
# This is a horrible hack
|
# This is a horrible hack
|
||||||
if (value.startswith("\"") or value.startswith("L\"")) and schema.endswith("\""):
|
if (value.startswith("\"") or value.startswith("L\"")) and schema.endswith("\""):
|
||||||
|
@ -815,6 +903,7 @@ def ghidra_trace_set_value(debugger, command, result, internal_dict):
|
||||||
STATE.trace.proxy_object_path(path).set_value(key, val, schema)
|
STATE.trace.proxy_object_path(path).set_value(key, val, schema)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_retain_values(debugger, command, result, internal_dict):
|
def ghidra_trace_retain_values(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Retain only those keys listed, settings all others to null.
|
Retain only those keys listed, settings all others to null.
|
||||||
|
@ -851,6 +940,7 @@ def ghidra_trace_retain_values(debugger, command, result, internal_dict):
|
||||||
STATE.trace.proxy_object_path(path).retain_values(keys, kinds=kinds)
|
STATE.trace.proxy_object_path(path).retain_values(keys, kinds=kinds)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_get_obj(debugger, command, result, internal_dict):
|
def ghidra_trace_get_obj(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Get an object descriptor by its canonical path.
|
Get an object descriptor by its canonical path.
|
||||||
|
@ -863,7 +953,6 @@ def ghidra_trace_get_obj(debugger, command, result, internal_dict):
|
||||||
|
|
||||||
trace = STATE.require_trace()
|
trace = STATE.require_trace()
|
||||||
object = trace.get_object(path)
|
object = trace.get_object(path)
|
||||||
print("{}\t{}\n".format(object.id, object.path))
|
|
||||||
return object
|
return object
|
||||||
|
|
||||||
|
|
||||||
|
@ -881,7 +970,7 @@ class TableColumn(object):
|
||||||
|
|
||||||
def print_cell(self, i):
|
def print_cell(self, i):
|
||||||
print(
|
print(
|
||||||
self.contents[i] if self.is_last else self.contents[i].ljust(self.width))
|
self.contents[i] if self.is_last else self.contents[i].ljust(self.width), end='')
|
||||||
|
|
||||||
|
|
||||||
class Tabular(object):
|
class Tabular(object):
|
||||||
|
@ -901,14 +990,14 @@ class Tabular(object):
|
||||||
for rn in range(self.num_rows):
|
for rn in range(self.num_rows):
|
||||||
for c in self.columns:
|
for c in self.columns:
|
||||||
c.print_cell(rn)
|
c.print_cell(rn)
|
||||||
print('\n')
|
print('')
|
||||||
|
|
||||||
|
|
||||||
def val_repr(value):
|
def val_repr(value):
|
||||||
if isinstance(value, TraceObject):
|
if isinstance(value, TraceObject):
|
||||||
return value.path
|
return value.path
|
||||||
elif isinstance(value, Address):
|
elif isinstance(value, Address):
|
||||||
return '{}:{:08x}'.format(value.space, value.offset)
|
return f'{value.space}:{value.offset:08x}'
|
||||||
return repr(value)
|
return repr(value)
|
||||||
|
|
||||||
|
|
||||||
|
@ -920,6 +1009,7 @@ def print_values(values):
|
||||||
table.print_table()
|
table.print_table()
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_get_values(debugger, command, result, internal_dict):
|
def ghidra_trace_get_values(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
List all values matching a given path pattern.
|
List all values matching a given path pattern.
|
||||||
|
@ -933,6 +1023,7 @@ def ghidra_trace_get_values(debugger, command, result, internal_dict):
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_get_values_rng(debugger, command, result, internal_dict):
|
def ghidra_trace_get_values_rng(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
List all values intersecting a given address range.
|
List all values intersecting a given address range.
|
||||||
|
@ -962,13 +1053,15 @@ def activate(path=None):
|
||||||
else:
|
else:
|
||||||
frame = util.selected_frame()
|
frame = util.selected_frame()
|
||||||
if frame is None:
|
if frame is None:
|
||||||
path = THREAD_PATTERN.format(procnum=proc.GetProcessID(), tnum=t.GetThreadID())
|
path = THREAD_PATTERN.format(
|
||||||
|
procnum=proc.GetProcessID(), tnum=t.GetThreadID())
|
||||||
else:
|
else:
|
||||||
path = FRAME_PATTERN.format(
|
path = FRAME_PATTERN.format(
|
||||||
procnum=proc.GetProcessID(), tnum=t.GetThreadID(), level=frame.GetFrameID())
|
procnum=proc.GetProcessID(), tnum=t.GetThreadID(), level=frame.GetFrameID())
|
||||||
trace.proxy_object_path(path).activate()
|
trace.proxy_object_path(path).activate()
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_activate(debugger, command, result, internal_dict):
|
def ghidra_trace_activate(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Activate an object in Ghidra's GUI.
|
Activate an object in Ghidra's GUI.
|
||||||
|
@ -982,6 +1075,7 @@ def ghidra_trace_activate(debugger, command, result, internal_dict):
|
||||||
activate(path)
|
activate(path)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_disassemble(debugger, command, result, internal_dict):
|
def ghidra_trace_disassemble(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Disassemble starting at the given seed.
|
Disassemble starting at the given seed.
|
||||||
|
@ -1000,11 +1094,10 @@ def ghidra_trace_disassemble(debugger, command, result, internal_dict):
|
||||||
trace.create_overlay_space(base, addr.space)
|
trace.create_overlay_space(base, addr.space)
|
||||||
|
|
||||||
length = STATE.trace.disassemble(addr)
|
length = STATE.trace.disassemble(addr)
|
||||||
print("Disassembled {} bytes\n".format(length))
|
print(f"Disassembled {length} bytes")
|
||||||
return {'length': length}
|
|
||||||
|
|
||||||
|
|
||||||
def compute_proc_state(proc = None):
|
def compute_proc_state(proc=None):
|
||||||
if proc.is_running:
|
if proc.is_running:
|
||||||
return 'RUNNING'
|
return 'RUNNING'
|
||||||
return 'STOPPED'
|
return 'STOPPED'
|
||||||
|
@ -1021,6 +1114,7 @@ def put_processes():
|
||||||
procobj.insert()
|
procobj.insert()
|
||||||
STATE.trace.proxy_object_path(PROCESSES_PATH).retain_values(keys)
|
STATE.trace.proxy_object_path(PROCESSES_PATH).retain_values(keys)
|
||||||
|
|
||||||
|
|
||||||
def put_state(event_process):
|
def put_state(event_process):
|
||||||
STATE.require_no_tx()
|
STATE.require_no_tx()
|
||||||
STATE.tx = STATE.require_trace().start_tx("state", undoable=False)
|
STATE.tx = STATE.require_trace().start_tx("state", undoable=False)
|
||||||
|
@ -1033,6 +1127,7 @@ def put_state(event_process):
|
||||||
STATE.reset_tx()
|
STATE.reset_tx()
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_put_processes(debugger, command, result, internal_dict):
|
def ghidra_trace_put_processes(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Put the list of processes into the trace's Processes list.
|
Put the list of processes into the trace's Processes list.
|
||||||
|
@ -1050,11 +1145,12 @@ def put_available():
|
||||||
procobj = STATE.trace.create_object(ppath)
|
procobj = STATE.trace.create_object(ppath)
|
||||||
keys.append(AVAILABLE_KEY_PATTERN.format(pid=proc.pid))
|
keys.append(AVAILABLE_KEY_PATTERN.format(pid=proc.pid))
|
||||||
procobj.set_value('_pid', proc.pid)
|
procobj.set_value('_pid', proc.pid)
|
||||||
procobj.set_value('_display', '{} {}'.format(proc.pid, proc.name))
|
procobj.set_value('_display', f'{proc.pid} {proc.name}')
|
||||||
procobj.insert()
|
procobj.insert()
|
||||||
STATE.trace.proxy_object_path(AVAILABLES_PATH).retain_values(keys)
|
STATE.trace.proxy_object_path(AVAILABLES_PATH).retain_values(keys)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_put_available(debugger, command, result, internal_dict):
|
def ghidra_trace_put_available(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Put the list of available processes into the trace's Available list.
|
Put the list of available processes into the trace's Available list.
|
||||||
|
@ -1078,7 +1174,7 @@ def put_single_breakpoint(b, ibobj, proc, ikeys):
|
||||||
cmdList = lldb.SBStringList()
|
cmdList = lldb.SBStringList()
|
||||||
if b.GetCommandLineCommands(cmdList):
|
if b.GetCommandLineCommands(cmdList):
|
||||||
list = []
|
list = []
|
||||||
for i in range(0,cmdList.GetSize()):
|
for i in range(0, cmdList.GetSize()):
|
||||||
list.append(cmdList.GetStringAtIndex(i))
|
list.append(cmdList.GetStringAtIndex(i))
|
||||||
brkobj.set_value('Commands', list)
|
brkobj.set_value('Commands', list)
|
||||||
if b.GetCondition():
|
if b.GetCondition():
|
||||||
|
@ -1108,17 +1204,19 @@ def put_single_breakpoint(b, ibobj, proc, ikeys):
|
||||||
brkobj.retain_values(keys)
|
brkobj.retain_values(keys)
|
||||||
brkobj.insert()
|
brkobj.insert()
|
||||||
|
|
||||||
|
|
||||||
def put_single_watchpoint(b, ibobj, proc, ikeys):
|
def put_single_watchpoint(b, ibobj, proc, ikeys):
|
||||||
mapper = STATE.trace.memory_mapper
|
mapper = STATE.trace.memory_mapper
|
||||||
bpath = PROC_WATCH_KEY_PATTERN.format(procnum=proc.GetProcessID(), watchnum=b.GetID())
|
bpath = PROC_WATCH_KEY_PATTERN.format(
|
||||||
|
procnum=proc.GetProcessID(), watchnum=b.GetID())
|
||||||
brkobj = STATE.trace.create_object(bpath)
|
brkobj = STATE.trace.create_object(bpath)
|
||||||
desc = util.get_description(b, level=0)
|
desc = util.get_description(b, level=0)
|
||||||
brkobj.set_value('_expression', desc)
|
brkobj.set_value('_expression', desc)
|
||||||
brkobj.set_value('_kinds', 'WRITE')
|
brkobj.set_value('_kinds', 'WRITE')
|
||||||
if "type = r" in desc:
|
if "type = r" in desc:
|
||||||
brkobj.set_value('_kinds', 'READ')
|
brkobj.set_value('_kinds', 'READ')
|
||||||
if "type = rw" in desc:
|
if "type = rw" in desc:
|
||||||
brkobj.set_value('_kinds', 'READ,WRITE')
|
brkobj.set_value('_kinds', 'READ,WRITE')
|
||||||
base, addr = mapper.map(proc, b.GetWatchAddress())
|
base, addr = mapper.map(proc, b.GetWatchAddress())
|
||||||
if base != addr.space:
|
if base != addr.space:
|
||||||
STATE.trace.create_overlay_space(base, addr.space)
|
STATE.trace.create_overlay_space(base, addr.space)
|
||||||
|
@ -1148,6 +1246,7 @@ def put_breakpoints():
|
||||||
STATE.trace.proxy_object_path(BREAKPOINTS_PATH).retain_values(keys)
|
STATE.trace.proxy_object_path(BREAKPOINTS_PATH).retain_values(keys)
|
||||||
ibobj.retain_values(ikeys)
|
ibobj.retain_values(ikeys)
|
||||||
|
|
||||||
|
|
||||||
def put_watchpoints():
|
def put_watchpoints():
|
||||||
target = util.get_target()
|
target = util.get_target()
|
||||||
proc = util.get_process()
|
proc = util.get_process()
|
||||||
|
@ -1163,6 +1262,7 @@ def put_watchpoints():
|
||||||
STATE.trace.proxy_object_path(WATCHPOINTS_PATH).retain_values(keys)
|
STATE.trace.proxy_object_path(WATCHPOINTS_PATH).retain_values(keys)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_put_breakpoints(debugger, command, result, internal_dict):
|
def ghidra_trace_put_breakpoints(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Put the current process's breakpoints into the trace.
|
Put the current process's breakpoints into the trace.
|
||||||
|
@ -1172,6 +1272,8 @@ def ghidra_trace_put_breakpoints(debugger, command, result, internal_dict):
|
||||||
with STATE.client.batch() as b:
|
with STATE.client.batch() as b:
|
||||||
put_breakpoints()
|
put_breakpoints()
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_put_watchpoints(debugger, command, result, internal_dict):
|
def ghidra_trace_put_watchpoints(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Put the current process's watchpoints into the trace.
|
Put the current process's watchpoints into the trace.
|
||||||
|
@ -1193,6 +1295,7 @@ def put_environment():
|
||||||
envobj.insert()
|
envobj.insert()
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_put_environment(debugger, command, result, internal_dict):
|
def ghidra_trace_put_environment(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Put some environment indicators into the Ghidra trace
|
Put some environment indicators into the Ghidra trace
|
||||||
|
@ -1214,7 +1317,8 @@ def put_regions():
|
||||||
mapper = STATE.trace.memory_mapper
|
mapper = STATE.trace.memory_mapper
|
||||||
keys = []
|
keys = []
|
||||||
for r in regions:
|
for r in regions:
|
||||||
rpath = REGION_PATTERN.format(procnum=proc.GetProcessID(), start=r.start)
|
rpath = REGION_PATTERN.format(
|
||||||
|
procnum=proc.GetProcessID(), start=r.start)
|
||||||
keys.append(REGION_KEY_PATTERN.format(start=r.start))
|
keys.append(REGION_KEY_PATTERN.format(start=r.start))
|
||||||
regobj = STATE.trace.create_object(rpath)
|
regobj = STATE.trace.create_object(rpath)
|
||||||
start_base, start_addr = mapper.map(proc, r.start)
|
start_base, start_addr = mapper.map(proc, r.start)
|
||||||
|
@ -1231,6 +1335,7 @@ def put_regions():
|
||||||
MEMORY_PATTERN.format(procnum=proc.GetProcessID())).retain_values(keys)
|
MEMORY_PATTERN.format(procnum=proc.GetProcessID())).retain_values(keys)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_put_regions(debugger, command, result, internal_dict):
|
def ghidra_trace_put_regions(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Read the memory map, if applicable, and write to the trace's Regions
|
Read the memory map, if applicable, and write to the trace's Regions
|
||||||
|
@ -1278,6 +1383,7 @@ def put_modules():
|
||||||
procnum=proc.GetProcessID())).retain_values(mod_keys)
|
procnum=proc.GetProcessID())).retain_values(mod_keys)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_put_modules(debugger, command, result, internal_dict):
|
def ghidra_trace_put_modules(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Gather object files, if applicable, and write to the trace's Modules
|
Gather object files, if applicable, and write to the trace's Modules
|
||||||
|
@ -1289,6 +1395,7 @@ def ghidra_trace_put_modules(debugger, command, result, internal_dict):
|
||||||
|
|
||||||
|
|
||||||
def convert_state(t):
|
def convert_state(t):
|
||||||
|
# TODO: This does not seem to work - currently supplanted by proc.is_running
|
||||||
if t.IsSuspended():
|
if t.IsSuspended():
|
||||||
return 'SUSPENDED'
|
return 'SUSPENDED'
|
||||||
if t.IsStopped():
|
if t.IsStopped():
|
||||||
|
@ -1320,17 +1427,18 @@ def put_threads():
|
||||||
proc = util.get_process()
|
proc = util.get_process()
|
||||||
keys = []
|
keys = []
|
||||||
for t in proc.threads:
|
for t in proc.threads:
|
||||||
tpath = THREAD_PATTERN.format(procnum=proc.GetProcessID(), tnum=t.GetThreadID())
|
tpath = THREAD_PATTERN.format(
|
||||||
|
procnum=proc.GetProcessID(), tnum=t.GetThreadID())
|
||||||
tobj = STATE.trace.create_object(tpath)
|
tobj = STATE.trace.create_object(tpath)
|
||||||
keys.append(THREAD_KEY_PATTERN.format(tnum=t.GetThreadID()))
|
keys.append(THREAD_KEY_PATTERN.format(tnum=t.GetThreadID()))
|
||||||
tobj.set_value('_state', convert_state(t))
|
#tobj.set_value('_state', convert_state(t))
|
||||||
|
tobj.set_value('_state', compute_proc_state(proc))
|
||||||
tobj.set_value('_name', t.GetName())
|
tobj.set_value('_name', t.GetName())
|
||||||
tid = t.GetThreadID()
|
tid = t.GetThreadID()
|
||||||
tobj.set_value('_tid', tid)
|
tobj.set_value('_tid', tid)
|
||||||
tidstr = ('0x{:x}' if radix ==
|
tidstr = f'0x{tid:x}' if radix == 16 else f'0{tid:o}' if radix == 8 else f'{tid}'
|
||||||
16 else '0{:o}' if radix == 8 else '{}').format(tid)
|
tobj.set_value('_short_display',
|
||||||
tobj.set_value('_short_display', '[{}.{}:{}]'.format(
|
f'[{proc.GetProcessID()}.{t.GetThreadID()}:{tidstr}]')
|
||||||
proc.GetProcessID(), t.GetThreadID(), tidstr))
|
|
||||||
tobj.set_value('_display', compute_thread_display(t))
|
tobj.set_value('_display', compute_thread_display(t))
|
||||||
tobj.insert()
|
tobj.insert()
|
||||||
STATE.trace.proxy_object_path(
|
STATE.trace.proxy_object_path(
|
||||||
|
@ -1342,13 +1450,15 @@ def put_event_thread():
|
||||||
# Assumption: Event thread is selected by lldb upon stopping
|
# Assumption: Event thread is selected by lldb upon stopping
|
||||||
t = util.selected_thread()
|
t = util.selected_thread()
|
||||||
if t is not None:
|
if t is not None:
|
||||||
tpath = THREAD_PATTERN.format(procnum=proc.GetProcessID(), tnum=t.GetThreadID())
|
tpath = THREAD_PATTERN.format(
|
||||||
|
procnum=proc.GetProcessID(), tnum=t.GetThreadID())
|
||||||
tobj = STATE.trace.proxy_object_path(tpath)
|
tobj = STATE.trace.proxy_object_path(tpath)
|
||||||
else:
|
else:
|
||||||
tobj = None
|
tobj = None
|
||||||
STATE.trace.proxy_object_path('').set_value('_event_thread', tobj)
|
STATE.trace.proxy_object_path('').set_value('_event_thread', tobj)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_put_threads(debugger, command, result, internal_dict):
|
def ghidra_trace_put_threads(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Put the current process's threads into the Ghidra trace
|
Put the current process's threads into the Ghidra trace
|
||||||
|
@ -1366,7 +1476,7 @@ def put_frames():
|
||||||
if t is None:
|
if t is None:
|
||||||
return
|
return
|
||||||
keys = []
|
keys = []
|
||||||
for i in range(0,t.GetNumFrames()):
|
for i in range(0, t.GetNumFrames()):
|
||||||
f = t.GetFrameAtIndex(i)
|
f = t.GetFrameAtIndex(i)
|
||||||
fpath = FRAME_PATTERN.format(
|
fpath = FRAME_PATTERN.format(
|
||||||
procnum=proc.GetProcessID(), tnum=t.GetThreadID(), level=f.GetFrameID())
|
procnum=proc.GetProcessID(), tnum=t.GetThreadID(), level=f.GetFrameID())
|
||||||
|
@ -1383,6 +1493,7 @@ def put_frames():
|
||||||
procnum=proc.GetProcessID(), tnum=t.GetThreadID())).retain_values(keys)
|
procnum=proc.GetProcessID(), tnum=t.GetThreadID())).retain_values(keys)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_put_frames(debugger, command, result, internal_dict):
|
def ghidra_trace_put_frames(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Put the current thread's frames into the Ghidra trace
|
Put the current thread's frames into the Ghidra trace
|
||||||
|
@ -1393,6 +1504,7 @@ def ghidra_trace_put_frames(debugger, command, result, internal_dict):
|
||||||
put_frames()
|
put_frames()
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_put_all(debugger, command, result, internal_dict):
|
def ghidra_trace_put_all(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Put everything currently selected into the Ghidra trace
|
Put everything currently selected into the Ghidra trace
|
||||||
|
@ -1400,7 +1512,8 @@ def ghidra_trace_put_all(debugger, command, result, internal_dict):
|
||||||
|
|
||||||
STATE.require_tx()
|
STATE.require_tx()
|
||||||
with STATE.client.batch() as b:
|
with STATE.client.batch() as b:
|
||||||
ghidra_trace_putreg(debugger, DEFAULT_REGISTER_BANK, result, internal_dict)
|
ghidra_trace_putreg(debugger, DEFAULT_REGISTER_BANK,
|
||||||
|
result, internal_dict)
|
||||||
ghidra_trace_putmem(debugger, "$pc 1", result, internal_dict)
|
ghidra_trace_putmem(debugger, "$pc 1", result, internal_dict)
|
||||||
ghidra_trace_putmem(debugger, "$sp 1", result, internal_dict)
|
ghidra_trace_putmem(debugger, "$sp 1", result, internal_dict)
|
||||||
put_processes()
|
put_processes()
|
||||||
|
@ -1414,6 +1527,7 @@ def ghidra_trace_put_all(debugger, command, result, internal_dict):
|
||||||
put_available()
|
put_available()
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_install_hooks(debugger, command, result, internal_dict):
|
def ghidra_trace_install_hooks(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Install hooks to trace in Ghidra
|
Install hooks to trace in Ghidra
|
||||||
|
@ -1422,6 +1536,7 @@ def ghidra_trace_install_hooks(debugger, command, result, internal_dict):
|
||||||
hooks.install_hooks()
|
hooks.install_hooks()
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_remove_hooks(debugger, command, result, internal_dict):
|
def ghidra_trace_remove_hooks(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Remove hooks to trace in Ghidra
|
Remove hooks to trace in Ghidra
|
||||||
|
@ -1434,6 +1549,7 @@ def ghidra_trace_remove_hooks(debugger, command, result, internal_dict):
|
||||||
hooks.remove_hooks()
|
hooks.remove_hooks()
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_sync_enable(debugger, command, result, internal_dict):
|
def ghidra_trace_sync_enable(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Synchronize the current process with the Ghidra trace
|
Synchronize the current process with the Ghidra trace
|
||||||
|
@ -1453,6 +1569,7 @@ def ghidra_trace_sync_enable(debugger, command, result, internal_dict):
|
||||||
hooks.enable_current_process()
|
hooks.enable_current_process()
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_trace_sync_disable(debugger, command, result, internal_dict):
|
def ghidra_trace_sync_disable(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Cease synchronizing the current process with the Ghidra trace
|
Cease synchronizing the current process with the Ghidra trace
|
||||||
|
@ -1464,6 +1581,7 @@ def ghidra_trace_sync_disable(debugger, command, result, internal_dict):
|
||||||
hooks.disable_current_process()
|
hooks.disable_current_process()
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_util_wait_stopped(debugger, command, result, internal_dict):
|
def ghidra_util_wait_stopped(debugger, command, result, internal_dict):
|
||||||
"""
|
"""
|
||||||
Spin wait until the selected thread is stopped.
|
Spin wait until the selected thread is stopped.
|
||||||
|
@ -1483,5 +1601,6 @@ def ghidra_util_wait_stopped(debugger, command, result, internal_dict):
|
||||||
raise RuntimeError('Timed out waiting for thread to stop')
|
raise RuntimeError('Timed out waiting for thread to stop')
|
||||||
|
|
||||||
|
|
||||||
|
@convert_errors
|
||||||
def ghidra_util_mark(debugger, command, result, internal_dict):
|
def ghidra_util_mark(debugger, command, result, internal_dict):
|
||||||
print(command)
|
print(command)
|
||||||
|
|
|
@ -13,15 +13,17 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
##
|
##
|
||||||
import time
|
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
import lldb
|
import lldb
|
||||||
|
|
||||||
from . import commands, util
|
from . import commands, util
|
||||||
|
|
||||||
|
|
||||||
ALL_EVENTS = 0xFFFF
|
ALL_EVENTS = 0xFFFF
|
||||||
|
|
||||||
|
|
||||||
class HookState(object):
|
class HookState(object):
|
||||||
__slots__ = ('installed', 'mem_catchpoint')
|
__slots__ = ('installed', 'mem_catchpoint')
|
||||||
|
|
||||||
|
@ -31,7 +33,8 @@ class HookState(object):
|
||||||
|
|
||||||
|
|
||||||
class ProcessState(object):
|
class ProcessState(object):
|
||||||
__slots__ = ('first', 'regions', 'modules', 'threads', 'breaks', 'watches', 'visited')
|
__slots__ = ('first', 'regions', 'modules', 'threads',
|
||||||
|
'breaks', 'watches', 'visited')
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.first = True
|
self.first = True
|
||||||
|
@ -64,9 +67,10 @@ class ProcessState(object):
|
||||||
hashable_frame = (thread.GetThreadID(), frame.GetFrameID())
|
hashable_frame = (thread.GetThreadID(), frame.GetFrameID())
|
||||||
if first or hashable_frame not in self.visited:
|
if first or hashable_frame not in self.visited:
|
||||||
banks = frame.GetRegisters()
|
banks = frame.GetRegisters()
|
||||||
commands.putreg(frame, banks.GetFirstValueByName(commands.DEFAULT_REGISTER_BANK))
|
commands.putreg(frame, banks.GetFirstValueByName(
|
||||||
commands.putmem("$pc", "1", from_tty=False)
|
commands.DEFAULT_REGISTER_BANK))
|
||||||
commands.putmem("$sp", "1", from_tty=False)
|
commands.putmem("$pc", "1", result=None)
|
||||||
|
commands.putmem("$sp", "1", result=None)
|
||||||
self.visited.add(hashable_frame)
|
self.visited.add(hashable_frame)
|
||||||
if first or self.regions or self.threads or self.modules:
|
if first or self.regions or self.threads or self.modules:
|
||||||
# Sections, memory syscalls, or stack allocations
|
# Sections, memory syscalls, or stack allocations
|
||||||
|
@ -117,10 +121,11 @@ HOOK_STATE = HookState()
|
||||||
BRK_STATE = BrkState()
|
BRK_STATE = BrkState()
|
||||||
PROC_STATE = {}
|
PROC_STATE = {}
|
||||||
|
|
||||||
|
|
||||||
def process_event(self, listener, event):
|
def process_event(self, listener, event):
|
||||||
try:
|
try:
|
||||||
desc = util.get_description(event)
|
desc = util.get_description(event)
|
||||||
#event_process = lldb.SBProcess_GetProcessFromEvent(event)
|
# print('Event:', desc)
|
||||||
event_process = util.get_process()
|
event_process = util.get_process()
|
||||||
if event_process not in PROC_STATE:
|
if event_process not in PROC_STATE:
|
||||||
PROC_STATE[event_process.GetProcessID()] = ProcessState()
|
PROC_STATE[event_process.GetProcessID()] = ProcessState()
|
||||||
|
@ -128,35 +133,29 @@ def process_event(self, listener, event):
|
||||||
if rc is False:
|
if rc is False:
|
||||||
print("add listener for process failed")
|
print("add listener for process failed")
|
||||||
|
|
||||||
commands.put_state(event_process)
|
# NB: Calling put_state on running leaves an open transaction
|
||||||
|
if event_process.is_running is False:
|
||||||
|
commands.put_state(event_process)
|
||||||
type = event.GetType()
|
type = event.GetType()
|
||||||
if lldb.SBTarget.EventIsTargetEvent(event):
|
if lldb.SBTarget.EventIsTargetEvent(event):
|
||||||
print('Event:', desc)
|
|
||||||
if (type & lldb.SBTarget.eBroadcastBitBreakpointChanged) != 0:
|
if (type & lldb.SBTarget.eBroadcastBitBreakpointChanged) != 0:
|
||||||
print("eBroadcastBitBreakpointChanged")
|
|
||||||
return on_breakpoint_modified(event)
|
return on_breakpoint_modified(event)
|
||||||
if (type & lldb.SBTarget.eBroadcastBitWatchpointChanged) != 0:
|
if (type & lldb.SBTarget.eBroadcastBitWatchpointChanged) != 0:
|
||||||
print("eBroadcastBitWatchpointChanged")
|
|
||||||
return on_watchpoint_modified(event)
|
return on_watchpoint_modified(event)
|
||||||
if (type & lldb.SBTarget.eBroadcastBitModulesLoaded) != 0:
|
if (type & lldb.SBTarget.eBroadcastBitModulesLoaded) != 0:
|
||||||
print("eBroadcastBitModulesLoaded")
|
|
||||||
return on_new_objfile(event)
|
return on_new_objfile(event)
|
||||||
if (type & lldb.SBTarget.eBroadcastBitModulesUnloaded) != 0:
|
if (type & lldb.SBTarget.eBroadcastBitModulesUnloaded) != 0:
|
||||||
print("eBroadcastBitModulesUnloaded")
|
|
||||||
return on_free_objfile(event)
|
return on_free_objfile(event)
|
||||||
if (type & lldb.SBTarget.eBroadcastBitSymbolsLoaded) != 0:
|
if (type & lldb.SBTarget.eBroadcastBitSymbolsLoaded) != 0:
|
||||||
print("eBroadcastBitSymbolsLoaded")
|
|
||||||
return True
|
return True
|
||||||
if lldb.SBProcess.EventIsProcessEvent(event):
|
if lldb.SBProcess.EventIsProcessEvent(event):
|
||||||
if (type & lldb.SBProcess.eBroadcastBitStateChanged) != 0:
|
if (type & lldb.SBProcess.eBroadcastBitStateChanged) != 0:
|
||||||
print("eBroadcastBitStateChanged")
|
|
||||||
if not event_process.is_alive:
|
if not event_process.is_alive:
|
||||||
return on_exited(event)
|
return on_exited(event)
|
||||||
if event_process.is_stopped:
|
if event_process.is_stopped:
|
||||||
return on_stop(event)
|
return on_stop(event)
|
||||||
return True
|
return True
|
||||||
if (type & lldb.SBProcess.eBroadcastBitInterrupt) != 0:
|
if (type & lldb.SBProcess.eBroadcastBitInterrupt) != 0:
|
||||||
print("eBroadcastBitInterrupt")
|
|
||||||
if event_process.is_stopped:
|
if event_process.is_stopped:
|
||||||
return on_stop(event)
|
return on_stop(event)
|
||||||
if (type & lldb.SBProcess.eBroadcastBitSTDOUT) != 0:
|
if (type & lldb.SBProcess.eBroadcastBitSTDOUT) != 0:
|
||||||
|
@ -164,133 +163,95 @@ def process_event(self, listener, event):
|
||||||
if (type & lldb.SBProcess.eBroadcastBitSTDERR) != 0:
|
if (type & lldb.SBProcess.eBroadcastBitSTDERR) != 0:
|
||||||
return True
|
return True
|
||||||
if (type & lldb.SBProcess.eBroadcastBitProfileData) != 0:
|
if (type & lldb.SBProcess.eBroadcastBitProfileData) != 0:
|
||||||
print("eBroadcastBitProfileData")
|
|
||||||
return True
|
return True
|
||||||
if (type & lldb.SBProcess.eBroadcastBitStructuredData) != 0:
|
if (type & lldb.SBProcess.eBroadcastBitStructuredData) != 0:
|
||||||
print("eBroadcastBitStructuredData")
|
|
||||||
return True
|
return True
|
||||||
# NB: Thread events not currently processes
|
# NB: Thread events not currently processes
|
||||||
if lldb.SBThread.EventIsThreadEvent(event):
|
if lldb.SBThread.EventIsThreadEvent(event):
|
||||||
print('Event:', desc)
|
|
||||||
if (type & lldb.SBThread.eBroadcastBitStackChanged) != 0:
|
if (type & lldb.SBThread.eBroadcastBitStackChanged) != 0:
|
||||||
print("eBroadcastBitStackChanged")
|
|
||||||
return on_frame_selected()
|
return on_frame_selected()
|
||||||
if (type & lldb.SBThread.eBroadcastBitThreadSuspended) != 0:
|
if (type & lldb.SBThread.eBroadcastBitThreadSuspended) != 0:
|
||||||
print("eBroadcastBitThreadSuspended")
|
|
||||||
if event_process.is_stopped:
|
if event_process.is_stopped:
|
||||||
return on_stop(event)
|
return on_stop(event)
|
||||||
if (type & lldb.SBThread.eBroadcastBitThreadResumed) != 0:
|
if (type & lldb.SBThread.eBroadcastBitThreadResumed) != 0:
|
||||||
print("eBroadcastBitThreadResumed")
|
|
||||||
return on_cont(event)
|
return on_cont(event)
|
||||||
if (type & lldb.SBThread.eBroadcastBitSelectedFrameChanged) != 0:
|
if (type & lldb.SBThread.eBroadcastBitSelectedFrameChanged) != 0:
|
||||||
print("eBroadcastBitSelectedFrameChanged")
|
|
||||||
return on_frame_selected()
|
return on_frame_selected()
|
||||||
if (type & lldb.SBThread.eBroadcastBitThreadSelected) != 0:
|
if (type & lldb.SBThread.eBroadcastBitThreadSelected) != 0:
|
||||||
print("eBroadcastBitThreadSelected")
|
|
||||||
return on_thread_selected()
|
return on_thread_selected()
|
||||||
if lldb.SBBreakpoint.EventIsBreakpointEvent(event):
|
if lldb.SBBreakpoint.EventIsBreakpointEvent(event):
|
||||||
print('Event:', desc)
|
btype = lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event)
|
||||||
btype = lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event);
|
bpt = lldb.SBBreakpoint.GetBreakpointFromEvent(event)
|
||||||
bpt = lldb.SBBreakpoint.GetBreakpointFromEvent(event);
|
|
||||||
if btype is lldb.eBreakpointEventTypeAdded:
|
if btype is lldb.eBreakpointEventTypeAdded:
|
||||||
print("eBreakpointEventTypeAdded")
|
|
||||||
return on_breakpoint_created(bpt)
|
return on_breakpoint_created(bpt)
|
||||||
if btype is lldb.eBreakpointEventTypeAutoContinueChanged:
|
if btype is lldb.eBreakpointEventTypeAutoContinueChanged:
|
||||||
print("elldb.BreakpointEventTypeAutoContinueChanged")
|
|
||||||
return on_breakpoint_modified(bpt)
|
return on_breakpoint_modified(bpt)
|
||||||
if btype is lldb.eBreakpointEventTypeCommandChanged:
|
if btype is lldb.eBreakpointEventTypeCommandChanged:
|
||||||
print("eBreakpointEventTypeCommandChanged")
|
|
||||||
return on_breakpoint_modified(bpt)
|
return on_breakpoint_modified(bpt)
|
||||||
if btype is lldb.eBreakpointEventTypeConditionChanged:
|
if btype is lldb.eBreakpointEventTypeConditionChanged:
|
||||||
print("eBreakpointEventTypeConditionChanged")
|
|
||||||
return on_breakpoint_modified(bpt)
|
return on_breakpoint_modified(bpt)
|
||||||
if btype is lldb.eBreakpointEventTypeDisabled:
|
if btype is lldb.eBreakpointEventTypeDisabled:
|
||||||
print("eBreakpointEventTypeDisabled")
|
|
||||||
return on_breakpoint_modified(bpt)
|
return on_breakpoint_modified(bpt)
|
||||||
if btype is lldb.eBreakpointEventTypeEnabled:
|
if btype is lldb.eBreakpointEventTypeEnabled:
|
||||||
print("eBreakpointEventTypeEnabled")
|
|
||||||
return on_breakpoint_modified(bpt)
|
return on_breakpoint_modified(bpt)
|
||||||
if btype is lldb.eBreakpointEventTypeIgnoreChanged:
|
if btype is lldb.eBreakpointEventTypeIgnoreChanged:
|
||||||
print("eBreakpointEventTypeIgnoreChanged")
|
|
||||||
return True
|
return True
|
||||||
if btype is lldb.eBreakpointEventTypeInvalidType:
|
if btype is lldb.eBreakpointEventTypeInvalidType:
|
||||||
print("eBreakpointEventTypeInvalidType")
|
|
||||||
return True
|
return True
|
||||||
if btype is lldb.eBreakpointEventTypeLocationsAdded:
|
if btype is lldb.eBreakpointEventTypeLocationsAdded:
|
||||||
print("eBreakpointEventTypeLocationsAdded")
|
|
||||||
return on_breakpoint_modified(bpt)
|
return on_breakpoint_modified(bpt)
|
||||||
if btype is lldb.eBreakpointEventTypeLocationsRemoved:
|
if btype is lldb.eBreakpointEventTypeLocationsRemoved:
|
||||||
print("eBreakpointEventTypeLocationsRemoved")
|
|
||||||
return on_breakpoint_modified(bpt)
|
return on_breakpoint_modified(bpt)
|
||||||
if btype is lldb.eBreakpointEventTypeLocationsResolved:
|
if btype is lldb.eBreakpointEventTypeLocationsResolved:
|
||||||
print("eBreakpointEventTypeLocationsResolved")
|
|
||||||
return on_breakpoint_modified(bpt)
|
return on_breakpoint_modified(bpt)
|
||||||
if btype is lldb.eBreakpointEventTypeRemoved:
|
if btype is lldb.eBreakpointEventTypeRemoved:
|
||||||
print("eBreakpointEventTypeRemoved")
|
|
||||||
return on_breakpoint_deleted(bpt)
|
return on_breakpoint_deleted(bpt)
|
||||||
if btype is lldb.eBreakpointEventTypeThreadChanged:
|
if btype is lldb.eBreakpointEventTypeThreadChanged:
|
||||||
print("eBreakpointEventTypeThreadChanged")
|
|
||||||
return on_breakpoint_modified(bpt)
|
return on_breakpoint_modified(bpt)
|
||||||
print("UNKNOWN BREAKPOINT EVENT")
|
print("UNKNOWN BREAKPOINT EVENT")
|
||||||
return True
|
return True
|
||||||
if lldb.SBWatchpoint.EventIsWatchpointEvent(event):
|
if lldb.SBWatchpoint.EventIsWatchpointEvent(event):
|
||||||
print('Event:', desc)
|
btype = lldb.SBWatchpoint.GetWatchpointEventTypeFromEvent(event)
|
||||||
btype = lldb.SBWatchpoint.GetWatchpointEventTypeFromEvent(event);
|
bpt = lldb.SBWatchpoint.GetWatchpointFromEvent(eventt)
|
||||||
bpt = lldb.SBWatchpoint.GetWatchpointFromEvent(eventt);
|
|
||||||
if btype is lldb.eWatchpointEventTypeAdded:
|
if btype is lldb.eWatchpointEventTypeAdded:
|
||||||
print("eWatchpointEventTypeAdded")
|
|
||||||
return on_watchpoint_added(bpt)
|
return on_watchpoint_added(bpt)
|
||||||
if btype is lldb.eWatchpointEventTypeCommandChanged:
|
if btype is lldb.eWatchpointEventTypeCommandChanged:
|
||||||
print("eWatchpointEventTypeCommandChanged")
|
|
||||||
return on_watchpoint_modified(bpt)
|
return on_watchpoint_modified(bpt)
|
||||||
if btype is lldb.eWatchpointEventTypeConditionChanged:
|
if btype is lldb.eWatchpointEventTypeConditionChanged:
|
||||||
print("eWatchpointEventTypeConditionChanged")
|
|
||||||
return on_watchpoint_modified(bpt)
|
return on_watchpoint_modified(bpt)
|
||||||
if btype is lldb.eWatchpointEventTypeDisabled:
|
if btype is lldb.eWatchpointEventTypeDisabled:
|
||||||
print("eWatchpointEventTypeDisabled")
|
|
||||||
return on_watchpoint_modified(bpt)
|
return on_watchpoint_modified(bpt)
|
||||||
if btype is lldb.eWatchpointEventTypeEnabled:
|
if btype is lldb.eWatchpointEventTypeEnabled:
|
||||||
print("eWatchpointEventTypeEnabled")
|
|
||||||
return on_watchpoint_modified(bpt)
|
return on_watchpoint_modified(bpt)
|
||||||
if btype is lldb.eWatchpointEventTypeIgnoreChanged:
|
if btype is lldb.eWatchpointEventTypeIgnoreChanged:
|
||||||
print("eWatchpointEventTypeIgnoreChanged")
|
|
||||||
return True
|
return True
|
||||||
if btype is lldb.eWatchpointEventTypeInvalidType:
|
if btype is lldb.eWatchpointEventTypeInvalidType:
|
||||||
print("eWatchpointEventTypeInvalidType")
|
|
||||||
return True
|
return True
|
||||||
if btype is lldb.eWatchpointEventTypeRemoved:
|
if btype is lldb.eWatchpointEventTypeRemoved:
|
||||||
print("eWatchpointEventTypeRemoved")
|
|
||||||
return on_watchpoint_deleted(bpt)
|
return on_watchpoint_deleted(bpt)
|
||||||
if btype is lldb.eWatchpointEventTypeThreadChanged:
|
if btype is lldb.eWatchpointEventTypeThreadChanged:
|
||||||
print("eWatchpointEventTypeThreadChanged")
|
|
||||||
return on_watchpoint_modified(bpt)
|
return on_watchpoint_modified(bpt)
|
||||||
if btype is lldb.eWatchpointEventTypeTypeChanged:
|
if btype is lldb.eWatchpointEventTypeTypeChanged:
|
||||||
print("eWatchpointEventTypeTypeChanged")
|
|
||||||
return on_watchpoint_modified(bpt)
|
return on_watchpoint_modified(bpt)
|
||||||
print("UNKNOWN WATCHPOINT EVENT")
|
print("UNKNOWN WATCHPOINT EVENT")
|
||||||
return True
|
return True
|
||||||
if lldb.SBCommandInterpreter.EventIsCommandInterpreterEvent(event):
|
if lldb.SBCommandInterpreter.EventIsCommandInterpreterEvent(event):
|
||||||
print('Event:', desc)
|
|
||||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitAsynchronousErrorData) != 0:
|
if (type & lldb.SBCommandInterpreter.eBroadcastBitAsynchronousErrorData) != 0:
|
||||||
print("eBroadcastBitAsynchronousErrorData")
|
|
||||||
return True
|
return True
|
||||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitAsynchronousOutputData) != 0:
|
if (type & lldb.SBCommandInterpreter.eBroadcastBitAsynchronousOutputData) != 0:
|
||||||
print("eBroadcastBitAsynchronousOutputData")
|
|
||||||
return True
|
return True
|
||||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitQuitCommandReceived) != 0:
|
if (type & lldb.SBCommandInterpreter.eBroadcastBitQuitCommandReceived) != 0:
|
||||||
print("eBroadcastBitQuitCommandReceived")
|
|
||||||
return True
|
return True
|
||||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitResetPrompt) != 0:
|
if (type & lldb.SBCommandInterpreter.eBroadcastBitResetPrompt) != 0:
|
||||||
print("eBroadcastBitResetPrompt")
|
|
||||||
return True
|
return True
|
||||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitThreadShouldExit) != 0:
|
if (type & lldb.SBCommandInterpreter.eBroadcastBitThreadShouldExit) != 0:
|
||||||
print("eBroadcastBitThreadShouldExit")
|
|
||||||
return True
|
return True
|
||||||
print("UNKNOWN EVENT")
|
print("UNKNOWN EVENT")
|
||||||
return True
|
return True
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
|
|
||||||
class EventThread(threading.Thread):
|
class EventThread(threading.Thread):
|
||||||
func = process_event
|
func = process_event
|
||||||
event = lldb.SBEvent()
|
event = lldb.SBEvent()
|
||||||
|
@ -329,7 +290,8 @@ class EventThread(threading.Thread):
|
||||||
print("add listener for process failed")
|
print("add listener for process failed")
|
||||||
return
|
return
|
||||||
|
|
||||||
rc = listener.StartListeningForEventClass(util.get_debugger(), lldb.SBThread.GetBroadcasterClassName(), ALL_EVENTS)
|
rc = listener.StartListeningForEventClass(
|
||||||
|
util.get_debugger(), lldb.SBThread.GetBroadcasterClassName(), ALL_EVENTS)
|
||||||
if rc is False:
|
if rc is False:
|
||||||
print("add listener for threads failed")
|
print("add listener for threads failed")
|
||||||
return
|
return
|
||||||
|
@ -344,13 +306,14 @@ class EventThread(threading.Thread):
|
||||||
while listener.GetNextEvent(self.event):
|
while listener.GetNextEvent(self.event):
|
||||||
self.func(listener, self.event)
|
self.func(listener, self.event)
|
||||||
event_recvd = True
|
event_recvd = True
|
||||||
except Exception as e:
|
except BaseException as e:
|
||||||
print(e)
|
print(e)
|
||||||
proc = util.get_process()
|
proc = util.get_process()
|
||||||
if proc is not None and not proc.is_alive:
|
if proc is not None and not proc.is_alive:
|
||||||
break
|
break
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Not sure if this is possible in LLDB...
|
# Not sure if this is possible in LLDB...
|
||||||
|
|
||||||
|
@ -475,7 +438,7 @@ def on_memory_changed(event):
|
||||||
with commands.STATE.client.batch():
|
with commands.STATE.client.batch():
|
||||||
with trace.open_tx("Memory *0x{:08x} changed".format(event.address)):
|
with trace.open_tx("Memory *0x{:08x} changed".format(event.address)):
|
||||||
commands.put_bytes(event.address, event.address + event.length,
|
commands.put_bytes(event.address, event.address + event.length,
|
||||||
pages=False, is_mi=False, from_tty=False)
|
pages=False, is_mi=False, result=None)
|
||||||
|
|
||||||
|
|
||||||
def on_register_changed(event):
|
def on_register_changed(event):
|
||||||
|
@ -547,11 +510,13 @@ def on_exited(event):
|
||||||
commands.put_event_thread()
|
commands.put_event_thread()
|
||||||
commands.activate()
|
commands.activate()
|
||||||
|
|
||||||
|
|
||||||
def notify_others_breaks(proc):
|
def notify_others_breaks(proc):
|
||||||
for num, state in PROC_STATE.items():
|
for num, state in PROC_STATE.items():
|
||||||
if num != proc.GetProcessID():
|
if num != proc.GetProcessID():
|
||||||
state.breaks = True
|
state.breaks = True
|
||||||
|
|
||||||
|
|
||||||
def notify_others_watches(proc):
|
def notify_others_watches(proc):
|
||||||
for num, state in PROC_STATE.items():
|
for num, state in PROC_STATE.items():
|
||||||
if num != proc.GetProcessID():
|
if num != proc.GetProcessID():
|
||||||
|
@ -697,6 +662,7 @@ def remove_hooks():
|
||||||
return
|
return
|
||||||
HOOK_STATE.installed = False
|
HOOK_STATE.installed = False
|
||||||
|
|
||||||
|
|
||||||
def enable_current_process():
|
def enable_current_process():
|
||||||
proc = util.get_process()
|
proc = util.get_process()
|
||||||
PROC_STATE[proc.GetProcessID()] = ProcessState()
|
PROC_STATE[proc.GetProcessID()] = ProcessState()
|
||||||
|
|
|
@ -18,7 +18,6 @@ import re
|
||||||
|
|
||||||
from ghidratrace import sch
|
from ghidratrace import sch
|
||||||
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
|
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
|
||||||
|
|
||||||
import lldb
|
import lldb
|
||||||
|
|
||||||
from . import commands, util
|
from . import commands, util
|
||||||
|
@ -66,9 +65,7 @@ def find_proc_by_num(procnum):
|
||||||
|
|
||||||
|
|
||||||
def find_proc_by_pattern(object, pattern, err_msg):
|
def find_proc_by_pattern(object, pattern, err_msg):
|
||||||
print(object.path)
|
|
||||||
mat = pattern.fullmatch(object.path)
|
mat = pattern.fullmatch(object.path)
|
||||||
print(mat)
|
|
||||||
if mat is None:
|
if mat is None:
|
||||||
raise TypeError(f"{object} is not {err_msg}")
|
raise TypeError(f"{object} is not {err_msg}")
|
||||||
procnum = int(mat['procnum'])
|
procnum = int(mat['procnum'])
|
||||||
|
@ -81,11 +78,12 @@ def find_proc_by_obj(object):
|
||||||
|
|
||||||
def find_proc_by_procbreak_obj(object):
|
def find_proc_by_procbreak_obj(object):
|
||||||
return find_proc_by_pattern(object, PROC_BREAKS_PATTERN,
|
return find_proc_by_pattern(object, PROC_BREAKS_PATTERN,
|
||||||
"a BreakpointLocationContainer")
|
"a BreakpointLocationContainer")
|
||||||
|
|
||||||
|
|
||||||
def find_proc_by_procwatch_obj(object):
|
def find_proc_by_procwatch_obj(object):
|
||||||
return find_proc_by_pattern(object, PROC_WATCHES_PATTERN,
|
return find_proc_by_pattern(object, PROC_WATCHES_PATTERN,
|
||||||
"a WatchpointContainer")
|
"a WatchpointContainer")
|
||||||
|
|
||||||
|
|
||||||
def find_proc_by_env_obj(object):
|
def find_proc_by_env_obj(object):
|
||||||
|
@ -108,7 +106,8 @@ def find_thread_by_num(proc, tnum):
|
||||||
for t in proc.threads:
|
for t in proc.threads:
|
||||||
if t.GetThreadID() == tnum:
|
if t.GetThreadID() == tnum:
|
||||||
return t
|
return t
|
||||||
raise KeyError(f"Processes[{proc.GetProcessID()}].Threads[{tnum}] does not exist")
|
raise KeyError(
|
||||||
|
f"Processes[{proc.GetProcessID()}].Threads[{tnum}] does not exist")
|
||||||
|
|
||||||
|
|
||||||
def find_thread_by_pattern(pattern, object, err_msg):
|
def find_thread_by_pattern(pattern, object, err_msg):
|
||||||
|
@ -166,7 +165,7 @@ def find_reg_by_name(f, name):
|
||||||
# I could keep my own cache in a dict, but why?
|
# I could keep my own cache in a dict, but why?
|
||||||
def find_bpt_by_number(breaknum):
|
def find_bpt_by_number(breaknum):
|
||||||
# TODO: If len exceeds some threshold, use binary search?
|
# TODO: If len exceeds some threshold, use binary search?
|
||||||
for i in range(0,util.get_target().GetNumBreakpoints()):
|
for i in range(0, util.get_target().GetNumBreakpoints()):
|
||||||
b = util.get_target().GetBreakpointAtIndex(i)
|
b = util.get_target().GetBreakpointAtIndex(i)
|
||||||
if b.GetID() == breaknum:
|
if b.GetID() == breaknum:
|
||||||
return b
|
return b
|
||||||
|
@ -189,7 +188,7 @@ def find_bpt_by_obj(object):
|
||||||
# I could keep my own cache in a dict, but why?
|
# I could keep my own cache in a dict, but why?
|
||||||
def find_wpt_by_number(watchnum):
|
def find_wpt_by_number(watchnum):
|
||||||
# TODO: If len exceeds some threshold, use binary search?
|
# TODO: If len exceeds some threshold, use binary search?
|
||||||
for i in range(0,util.get_target().GetNumWatchpoints()):
|
for i in range(0, util.get_target().GetNumWatchpoints()):
|
||||||
w = util.get_target().GetWatchpointAtIndex(i)
|
w = util.get_target().GetWatchpointAtIndex(i)
|
||||||
if w.GetID() == watchnum:
|
if w.GetID() == watchnum:
|
||||||
return w
|
return w
|
||||||
|
@ -203,6 +202,7 @@ def find_wpt_by_pattern(pattern, object, err_msg):
|
||||||
watchnum = int(mat['watchnum'])
|
watchnum = int(mat['watchnum'])
|
||||||
return find_wpt_by_number(watchnum)
|
return find_wpt_by_number(watchnum)
|
||||||
|
|
||||||
|
|
||||||
def find_wpt_by_obj(object):
|
def find_wpt_by_obj(object):
|
||||||
return find_wpt_by_pattern(PROC_WATCHLOC_PATTERN, object, "a WatchpointSpec")
|
return find_wpt_by_pattern(PROC_WATCHLOC_PATTERN, object, "a WatchpointSpec")
|
||||||
|
|
||||||
|
@ -244,7 +244,7 @@ def execute(cmd: str, to_string: bool=False):
|
||||||
def refresh_available(node: sch.Schema('AvailableContainer')):
|
def refresh_available(node: sch.Schema('AvailableContainer')):
|
||||||
"""List processes on lldb's host system."""
|
"""List processes on lldb's host system."""
|
||||||
with commands.open_tracked_tx('Refresh Available'):
|
with commands.open_tracked_tx('Refresh Available'):
|
||||||
util.get_debugger().HandleCommand('ghidra_trace_put_available')
|
util.get_debugger().HandleCommand('ghidra trace put-available')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='refresh')
|
@REGISTRY.method(action='refresh')
|
||||||
|
@ -254,14 +254,14 @@ def refresh_breakpoints(node: sch.Schema('BreakpointContainer')):
|
||||||
process).
|
process).
|
||||||
"""
|
"""
|
||||||
with commands.open_tracked_tx('Refresh Breakpoints'):
|
with commands.open_tracked_tx('Refresh Breakpoints'):
|
||||||
util.get_debugger().HandleCommand('ghidra_trace_put_breakpoints')
|
util.get_debugger().HandleCommand('ghidra trace put-breakpoints')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='refresh')
|
@REGISTRY.method(action='refresh')
|
||||||
def refresh_processes(node: sch.Schema('ProcessContainer')):
|
def refresh_processes(node: sch.Schema('ProcessContainer')):
|
||||||
"""Refresh the list of processes."""
|
"""Refresh the list of processes."""
|
||||||
with commands.open_tracked_tx('Refresh Processes'):
|
with commands.open_tracked_tx('Refresh Processes'):
|
||||||
util.get_debugger().HandleCommand('ghidra_trace_put_threads')
|
util.get_debugger().HandleCommand('ghidra trace put-threads')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='refresh')
|
@REGISTRY.method(action='refresh')
|
||||||
|
@ -273,7 +273,7 @@ def refresh_proc_breakpoints(node: sch.Schema('BreakpointLocationContainer')):
|
||||||
refreshed.
|
refreshed.
|
||||||
"""
|
"""
|
||||||
with commands.open_tracked_tx('Refresh Breakpoint Locations'):
|
with commands.open_tracked_tx('Refresh Breakpoint Locations'):
|
||||||
util.get_debugger().HandleCommand('ghidra_trace_put_breakpoints');
|
util.get_debugger().HandleCommand('ghidra trace put-breakpoints')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='refresh')
|
@REGISTRY.method(action='refresh')
|
||||||
|
@ -285,20 +285,21 @@ def refresh_proc_watchpoints(node: sch.Schema('WatchpointContainer')):
|
||||||
refreshed.
|
refreshed.
|
||||||
"""
|
"""
|
||||||
with commands.open_tracked_tx('Refresh Watchpoint Locations'):
|
with commands.open_tracked_tx('Refresh Watchpoint Locations'):
|
||||||
util.get_debugger().HandleCommand('ghidra_trace_put_watchpoints');
|
util.get_debugger().HandleCommand('ghidra trace put-watchpoints')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='refresh')
|
@REGISTRY.method(action='refresh')
|
||||||
def refresh_environment(node: sch.Schema('Environment')):
|
def refresh_environment(node: sch.Schema('Environment')):
|
||||||
"""Refresh the environment descriptors (arch, os, endian)."""
|
"""Refresh the environment descriptors (arch, os, endian)."""
|
||||||
with commands.open_tracked_tx('Refresh Environment'):
|
with commands.open_tracked_tx('Refresh Environment'):
|
||||||
util.get_debugger().HandleCommand('ghidra_trace_put_environment')
|
util.get_debugger().HandleCommand('ghidra trace put-environment')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='refresh')
|
@REGISTRY.method(action='refresh')
|
||||||
def refresh_threads(node: sch.Schema('ThreadContainer')):
|
def refresh_threads(node: sch.Schema('ThreadContainer')):
|
||||||
"""Refresh the list of threads in the process."""
|
"""Refresh the list of threads in the process."""
|
||||||
with commands.open_tracked_tx('Refresh Threads'):
|
with commands.open_tracked_tx('Refresh Threads'):
|
||||||
util.get_debugger().HandleCommand('ghidra_trace_put_threads')
|
util.get_debugger().HandleCommand('ghidra trace put-threads')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='refresh')
|
@REGISTRY.method(action='refresh')
|
||||||
|
@ -307,7 +308,7 @@ def refresh_stack(node: sch.Schema('Stack')):
|
||||||
t = find_thread_by_stack_obj(node)
|
t = find_thread_by_stack_obj(node)
|
||||||
t.process.SetSelectedThread(t)
|
t.process.SetSelectedThread(t)
|
||||||
with commands.open_tracked_tx('Refresh Stack'):
|
with commands.open_tracked_tx('Refresh Stack'):
|
||||||
util.get_debugger().HandleCommand('ghidra_trace_put_frames');
|
util.get_debugger().HandleCommand('ghidra trace put-frames')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='refresh')
|
@REGISTRY.method(action='refresh')
|
||||||
|
@ -317,14 +318,14 @@ def refresh_registers(node: sch.Schema('RegisterValueContainer')):
|
||||||
f.thread.SetSelectedFrame(f.GetFrameID())
|
f.thread.SetSelectedFrame(f.GetFrameID())
|
||||||
# TODO: Groups?
|
# TODO: Groups?
|
||||||
with commands.open_tracked_tx('Refresh Registers'):
|
with commands.open_tracked_tx('Refresh Registers'):
|
||||||
util.get_debugger().HandleCommand('ghidra_trace_putreg');
|
util.get_debugger().HandleCommand('ghidra trace putreg')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='refresh')
|
@REGISTRY.method(action='refresh')
|
||||||
def refresh_mappings(node: sch.Schema('Memory')):
|
def refresh_mappings(node: sch.Schema('Memory')):
|
||||||
"""Refresh the list of memory regions for the process."""
|
"""Refresh the list of memory regions for the process."""
|
||||||
with commands.open_tracked_tx('Refresh Memory Regions'):
|
with commands.open_tracked_tx('Refresh Memory Regions'):
|
||||||
util.get_debugger().HandleCommand('ghidra_trace_put_regions');
|
util.get_debugger().HandleCommand('ghidra trace put-regions')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='refresh')
|
@REGISTRY.method(action='refresh')
|
||||||
|
@ -335,7 +336,7 @@ def refresh_modules(node: sch.Schema('ModuleContainer')):
|
||||||
This will refresh the sections for all modules, not just the selected one.
|
This will refresh the sections for all modules, not just the selected one.
|
||||||
"""
|
"""
|
||||||
with commands.open_tracked_tx('Refresh Modules'):
|
with commands.open_tracked_tx('Refresh Modules'):
|
||||||
util.get_debugger().HandleCommand('ghidra_trace_put_modules');
|
util.get_debugger().HandleCommand('ghidra trace put-modules')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='activate')
|
@REGISTRY.method(action='activate')
|
||||||
|
@ -343,6 +344,7 @@ def activate_process(process: sch.Schema('Process')):
|
||||||
"""Switch to the process."""
|
"""Switch to the process."""
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='activate')
|
@REGISTRY.method(action='activate')
|
||||||
def activate_thread(thread: sch.Schema('Thread')):
|
def activate_thread(thread: sch.Schema('Thread')):
|
||||||
"""Switch to the thread."""
|
"""Switch to the thread."""
|
||||||
|
@ -376,11 +378,13 @@ def attach_obj(process: sch.Schema('Process'), target: sch.Schema('Attachable'))
|
||||||
pid = find_availpid_by_obj(target)
|
pid = find_availpid_by_obj(target)
|
||||||
util.get_debugger().HandleCommand(f'process attach -p {pid}')
|
util.get_debugger().HandleCommand(f'process attach -p {pid}')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='attach')
|
@REGISTRY.method(action='attach')
|
||||||
def attach_pid(process: sch.Schema('Process'), pid: int):
|
def attach_pid(process: sch.Schema('Process'), pid: int):
|
||||||
"""Attach the process to the given target."""
|
"""Attach the process to the given target."""
|
||||||
util.get_debugger().HandleCommand(f'process attach -p {pid}')
|
util.get_debugger().HandleCommand(f'process attach -p {pid}')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='attach')
|
@REGISTRY.method(action='attach')
|
||||||
def attach_name(process: sch.Schema('Process'), name: str):
|
def attach_name(process: sch.Schema('Process'), name: str):
|
||||||
"""Attach the process to the given target."""
|
"""Attach the process to the given target."""
|
||||||
|
@ -395,23 +399,24 @@ def detach(process: sch.Schema('Process')):
|
||||||
|
|
||||||
@REGISTRY.method(action='launch')
|
@REGISTRY.method(action='launch')
|
||||||
def launch_loader(process: sch.Schema('Process'),
|
def launch_loader(process: sch.Schema('Process'),
|
||||||
file: ParamDesc(str, display='File'),
|
file: ParamDesc(str, display='File'),
|
||||||
args: ParamDesc(str, display='Arguments')=''):
|
args: ParamDesc(str, display='Arguments')=''):
|
||||||
"""
|
"""
|
||||||
Start a native process with the given command line, stopping at 'main'.
|
Start a native process with the given command line, stopping at 'main'.
|
||||||
|
|
||||||
If 'main' is not defined in the file, this behaves like 'run'.
|
If 'main' is not defined in the file, this behaves like 'run'.
|
||||||
"""
|
"""
|
||||||
util.get_debugger().HandleCommand(f'file {file}')
|
util.get_debugger().HandleCommand(f'file {file}')
|
||||||
if args is not '':
|
if args != '':
|
||||||
util.get_debugger().HandleCommand(f'settings set target.run-args {args}')
|
util.get_debugger().HandleCommand(
|
||||||
|
f'settings set target.run-args {args}')
|
||||||
util.get_debugger().HandleCommand(f'process launch --stop-at-entry')
|
util.get_debugger().HandleCommand(f'process launch --stop-at-entry')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='launch')
|
@REGISTRY.method(action='launch')
|
||||||
def launch(process: sch.Schema('Process'),
|
def launch(process: sch.Schema('Process'),
|
||||||
file: ParamDesc(str, display='File'),
|
file: ParamDesc(str, display='File'),
|
||||||
args: ParamDesc(str, display='Arguments')=''):
|
args: ParamDesc(str, display='Arguments')=''):
|
||||||
"""
|
"""
|
||||||
Run a native process with the given command line.
|
Run a native process with the given command line.
|
||||||
|
|
||||||
|
@ -419,8 +424,9 @@ def launch(process: sch.Schema('Process'),
|
||||||
signaled.
|
signaled.
|
||||||
"""
|
"""
|
||||||
util.get_debugger().HandleCommand(f'file {file}')
|
util.get_debugger().HandleCommand(f'file {file}')
|
||||||
if args is not '':
|
if args != '':
|
||||||
util.get_debugger().HandleCommand(f'settings set target.run-args {args}')
|
util.get_debugger().HandleCommand(
|
||||||
|
f'settings set target.run-args {args}')
|
||||||
util.get_debugger().HandleCommand(f'run')
|
util.get_debugger().HandleCommand(f'run')
|
||||||
|
|
||||||
|
|
||||||
|
@ -440,9 +446,9 @@ def _continue(process: sch.Schema('Process')):
|
||||||
def interrupt():
|
def interrupt():
|
||||||
"""Interrupt the execution of the debugged program."""
|
"""Interrupt the execution of the debugged program."""
|
||||||
util.get_debugger().HandleCommand('process interrupt')
|
util.get_debugger().HandleCommand('process interrupt')
|
||||||
#util.get_process().SendAsyncInterrupt()
|
# util.get_process().SendAsyncInterrupt()
|
||||||
#util.get_debugger().HandleCommand('^c')
|
# util.get_debugger().HandleCommand('^c')
|
||||||
#util.get_process().Signal(2)
|
# util.get_process().Signal(2)
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='step_into')
|
@REGISTRY.method(action='step_into')
|
||||||
|
@ -527,13 +533,15 @@ def break_read_range(process: sch.Schema('Process'), range: AddressRange):
|
||||||
offset_start = process.trace.memory_mapper.map_back(
|
offset_start = process.trace.memory_mapper.map_back(
|
||||||
proc, Address(range.space, range.min))
|
proc, Address(range.space, range.min))
|
||||||
sz = range.length()
|
sz = range.length()
|
||||||
util.get_debugger().HandleCommand(f'watchpoint set expression -s {sz} -w read -- {offset_start}')
|
util.get_debugger().HandleCommand(
|
||||||
|
f'watchpoint set expression -s {sz} -w read -- {offset_start}')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='break_read')
|
@REGISTRY.method(action='break_read')
|
||||||
def break_read_expression(expression: str):
|
def break_read_expression(expression: str):
|
||||||
"""Set a read watchpoint."""
|
"""Set a read watchpoint."""
|
||||||
util.get_debugger().HandleCommand(f'watchpoint set expression -w read -- {expression}')
|
util.get_debugger().HandleCommand(
|
||||||
|
f'watchpoint set expression -w read -- {expression}')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='break_write')
|
@REGISTRY.method(action='break_write')
|
||||||
|
@ -543,13 +551,15 @@ def break_write_range(process: sch.Schema('Process'), range: AddressRange):
|
||||||
offset_start = process.trace.memory_mapper.map_back(
|
offset_start = process.trace.memory_mapper.map_back(
|
||||||
proc, Address(range.space, range.min))
|
proc, Address(range.space, range.min))
|
||||||
sz = range.length()
|
sz = range.length()
|
||||||
util.get_debugger().HandleCommand(f'watchpoint set expression -s {sz} -- {offset_start}')
|
util.get_debugger().HandleCommand(
|
||||||
|
f'watchpoint set expression -s {sz} -- {offset_start}')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='break_write')
|
@REGISTRY.method(action='break_write')
|
||||||
def break_write_expression(expression: str):
|
def break_write_expression(expression: str):
|
||||||
"""Set a watchpoint."""
|
"""Set a watchpoint."""
|
||||||
util.get_debugger().HandleCommand(f'watchpoint set expression -- {expression}')
|
util.get_debugger().HandleCommand(
|
||||||
|
f'watchpoint set expression -- {expression}')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='break_access')
|
@REGISTRY.method(action='break_access')
|
||||||
|
@ -559,13 +569,15 @@ def break_access_range(process: sch.Schema('Process'), range: AddressRange):
|
||||||
offset_start = process.trace.memory_mapper.map_back(
|
offset_start = process.trace.memory_mapper.map_back(
|
||||||
proc, Address(range.space, range.min))
|
proc, Address(range.space, range.min))
|
||||||
sz = range.length()
|
sz = range.length()
|
||||||
util.get_debugger().HandleCommand(f'watchpoint set expression -s {sz} -w read_write -- {offset_start}')
|
util.get_debugger().HandleCommand(
|
||||||
|
f'watchpoint set expression -s {sz} -w read_write -- {offset_start}')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='break_access')
|
@REGISTRY.method(action='break_access')
|
||||||
def break_access_expression(expression: str):
|
def break_access_expression(expression: str):
|
||||||
"""Set an access watchpoint."""
|
"""Set an access watchpoint."""
|
||||||
util.get_debugger().HandleCommand(f'watchpoint set expression -w read_write -- {expression}')
|
util.get_debugger().HandleCommand(
|
||||||
|
f'watchpoint set expression -w read_write -- {expression}')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='break_ext')
|
@REGISTRY.method(action='break_ext')
|
||||||
|
@ -580,12 +592,14 @@ def toggle_watchpoint(breakpoint: sch.Schema('WatchpointSpec'), enabled: bool):
|
||||||
wpt = find_wpt_by_obj(watchpoint)
|
wpt = find_wpt_by_obj(watchpoint)
|
||||||
wpt.enabled = enabled
|
wpt.enabled = enabled
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='toggle')
|
@REGISTRY.method(action='toggle')
|
||||||
def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
|
def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
|
||||||
"""Toggle a breakpoint."""
|
"""Toggle a breakpoint."""
|
||||||
bpt = find_bpt_by_obj(breakpoint)
|
bpt = find_bpt_by_obj(breakpoint)
|
||||||
bpt.enabled = enabled
|
bpt.enabled = enabled
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='toggle')
|
@REGISTRY.method(action='toggle')
|
||||||
def toggle_breakpoint_location(location: sch.Schema('BreakpointLocation'), enabled: bool):
|
def toggle_breakpoint_location(location: sch.Schema('BreakpointLocation'), enabled: bool):
|
||||||
"""Toggle a breakpoint location."""
|
"""Toggle a breakpoint location."""
|
||||||
|
@ -601,6 +615,7 @@ def delete_watchpoint(watchpoint: sch.Schema('WatchpointSpec')):
|
||||||
wptnum = wpt.GetID()
|
wptnum = wpt.GetID()
|
||||||
util.get_debugger().HandleCommand(f'watchpoint delete {wptnum}')
|
util.get_debugger().HandleCommand(f'watchpoint delete {wptnum}')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='delete')
|
@REGISTRY.method(action='delete')
|
||||||
def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
|
def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
|
||||||
"""Delete a breakpoint."""
|
"""Delete a breakpoint."""
|
||||||
|
@ -615,8 +630,16 @@ def read_mem(process: sch.Schema('Process'), range: AddressRange):
|
||||||
proc = find_proc_by_obj(process)
|
proc = find_proc_by_obj(process)
|
||||||
offset_start = process.trace.memory_mapper.map_back(
|
offset_start = process.trace.memory_mapper.map_back(
|
||||||
proc, Address(range.space, range.min))
|
proc, Address(range.space, range.min))
|
||||||
|
ci = util.get_debugger().GetCommandInterpreter()
|
||||||
with commands.open_tracked_tx('Read Memory'):
|
with commands.open_tracked_tx('Read Memory'):
|
||||||
util.get_debugger().HandleCommand(f'ghidra_trace_putmem 0x{offset_start:x} {range.length()}')
|
result = lldb.SBCommandReturnObject()
|
||||||
|
ci.HandleCommand(
|
||||||
|
f'ghidra trace putmem 0x{offset_start:x} {range.length()}', result)
|
||||||
|
if result.Succeeded():
|
||||||
|
return
|
||||||
|
print(f"Could not read 0x{offset_start:x}: {result}")
|
||||||
|
util.get_debugger().HandleCommand(
|
||||||
|
f'ghidra trace putmem-state 0x{offset_start:x} {range.length()} error')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method
|
@REGISTRY.method
|
||||||
|
@ -628,7 +651,7 @@ def write_mem(process: sch.Schema('Process'), address: Address, data: bytes):
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method
|
@REGISTRY.method
|
||||||
def write_reg(frame: sch.Schema('Frame'), name: str, value: bytes):
|
def write_reg(frame: sch.Schema('StackFrame'), name: str, value: bytes):
|
||||||
"""Write a register."""
|
"""Write a register."""
|
||||||
f = find_frame_by_obj(frame)
|
f = find_frame_by_obj(frame)
|
||||||
f.select()
|
f.select()
|
||||||
|
@ -637,4 +660,5 @@ def write_reg(frame: sch.Schema('Frame'), name: str, value: bytes):
|
||||||
reg = find_reg_by_name(f, mname)
|
reg = find_reg_by_name(f, mname)
|
||||||
size = int(lldb.parse_and_eval(f'sizeof(${mname})'))
|
size = int(lldb.parse_and_eval(f'sizeof(${mname})'))
|
||||||
arr = '{' + ','.join(str(b) for b in mval) + '}'
|
arr = '{' + ','.join(str(b) for b in mval) + '}'
|
||||||
util.get_debugger().HandleCommand(f'expr ((unsigned char[{size}])${mname}) = {arr};')
|
util.get_debugger().HandleCommand(
|
||||||
|
f'expr ((unsigned char[{size}])${mname}) = {arr};')
|
||||||
|
|
|
@ -1,46 +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.
|
|
||||||
##
|
|
||||||
import lldb
|
|
||||||
|
|
||||||
# TODO: I don't know how to register a custom parameter prefix. I would rather
|
|
||||||
# these were 'ghidra language' and 'ghidra compiler'
|
|
||||||
|
|
||||||
|
|
||||||
class GhidraLanguageParameter(lldb.Parameter):
|
|
||||||
"""
|
|
||||||
The language id for Ghidra traces. Set this to 'auto' to try to derive it
|
|
||||||
from 'show arch' and 'show endian'. Otherwise, set it to a Ghidra
|
|
||||||
LanguageID.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__('ghidra-language', lldb.COMMAND_DATA, lldb.PARAM_STRING)
|
|
||||||
self.value = 'auto'
|
|
||||||
GhidraLanguageParameter()
|
|
||||||
|
|
||||||
|
|
||||||
class GhidraCompilerParameter(lldb.Parameter):
|
|
||||||
"""
|
|
||||||
The compiler spec id for Ghidra traces. Set this to 'auto' to try to derive
|
|
||||||
it from 'show osabi'. Otherwise, set it to a Ghidra CompilerSpecID. Note
|
|
||||||
that valid compiler spec ids depend on the language id.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__('ghidra-compiler', lldb.COMMAND_DATA, lldb.PARAM_STRING)
|
|
||||||
self.value = 'auto'
|
|
||||||
GhidraCompilerParameter()
|
|
||||||
|
|
|
@ -27,7 +27,10 @@ LldbVersion = namedtuple('LldbVersion', ['full', 'major', 'minor'])
|
||||||
def _compute_lldb_ver():
|
def _compute_lldb_ver():
|
||||||
blurb = lldb.debugger.GetVersionString()
|
blurb = lldb.debugger.GetVersionString()
|
||||||
top = blurb.split('\n')[0]
|
top = blurb.split('\n')[0]
|
||||||
full = top.split(' ')[2]
|
if ' version ' in top:
|
||||||
|
full = top.split(' ')[2] # "lldb version x.y.z"
|
||||||
|
else:
|
||||||
|
full = top.split('-')[1] # "lldb-x.y.z"
|
||||||
major, minor = full.split('.')[:2]
|
major, minor = full.split('.')[:2]
|
||||||
return LldbVersion(full, int(major), int(minor))
|
return LldbVersion(full, int(major), int(minor))
|
||||||
|
|
||||||
|
@ -36,6 +39,7 @@ LLDB_VERSION = _compute_lldb_ver()
|
||||||
|
|
||||||
GNU_DEBUGDATA_PREFIX = ".gnu_debugdata for "
|
GNU_DEBUGDATA_PREFIX = ".gnu_debugdata for "
|
||||||
|
|
||||||
|
|
||||||
class Module(namedtuple('BaseModule', ['name', 'base', 'max', 'sections'])):
|
class Module(namedtuple('BaseModule', ['name', 'base', 'max', 'sections'])):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -96,7 +100,7 @@ class ModuleInfoReader(object):
|
||||||
fspec = module.GetFileSpec()
|
fspec = module.GetFileSpec()
|
||||||
name = debracket(fspec.GetFilename())
|
name = debracket(fspec.GetFilename())
|
||||||
sections = {}
|
sections = {}
|
||||||
for i in range(0, module.GetNumSections()):
|
for i in range(0, module.GetNumSections()):
|
||||||
s = self.section_from_sbsection(module.GetSectionAtIndex(i))
|
s = self.section_from_sbsection(module.GetSectionAtIndex(i))
|
||||||
sname = debracket(s.name)
|
sname = debracket(s.name)
|
||||||
sections[sname] = s
|
sections[sname] = s
|
||||||
|
@ -107,8 +111,8 @@ class ModuleInfoReader(object):
|
||||||
def _choose_module_info_reader():
|
def _choose_module_info_reader():
|
||||||
return ModuleInfoReader()
|
return ModuleInfoReader()
|
||||||
|
|
||||||
MODULE_INFO_READER = _choose_module_info_reader()
|
|
||||||
|
|
||||||
|
MODULE_INFO_READER = _choose_module_info_reader()
|
||||||
|
|
||||||
|
|
||||||
class Region(namedtuple('BaseRegion', ['start', 'end', 'offset', 'perms', 'objfile'])):
|
class Region(namedtuple('BaseRegion', ['start', 'end', 'offset', 'perms', 'objfile'])):
|
||||||
|
@ -137,8 +141,8 @@ class RegionInfoReader(object):
|
||||||
reglist = get_process().GetMemoryRegions()
|
reglist = get_process().GetMemoryRegions()
|
||||||
for i in range(0, reglist.GetSize()):
|
for i in range(0, reglist.GetSize()):
|
||||||
module = get_target().GetModuleAtIndex(i)
|
module = get_target().GetModuleAtIndex(i)
|
||||||
info = lldb.SBMemoryRegionInfo();
|
info = lldb.SBMemoryRegionInfo()
|
||||||
success = reglist.GetMemoryRegionAtIndex(i, info);
|
success = reglist.GetMemoryRegionAtIndex(i, info)
|
||||||
if success:
|
if success:
|
||||||
r = self.region_from_sbmemreg(info)
|
r = self.region_from_sbmemreg(info)
|
||||||
regions.append(r)
|
regions.append(r)
|
||||||
|
@ -177,28 +181,39 @@ def _choose_breakpoint_location_info_reader():
|
||||||
|
|
||||||
BREAKPOINT_LOCATION_INFO_READER = _choose_breakpoint_location_info_reader()
|
BREAKPOINT_LOCATION_INFO_READER = _choose_breakpoint_location_info_reader()
|
||||||
|
|
||||||
|
|
||||||
def get_debugger():
|
def get_debugger():
|
||||||
return lldb.SBDebugger.FindDebuggerWithID(1)
|
return lldb.SBDebugger.FindDebuggerWithID(1)
|
||||||
|
|
||||||
|
|
||||||
def get_target():
|
def get_target():
|
||||||
return get_debugger().GetTargetAtIndex(0)
|
return get_debugger().GetTargetAtIndex(0)
|
||||||
|
|
||||||
|
|
||||||
def get_process():
|
def get_process():
|
||||||
return get_target().GetProcess()
|
return get_target().GetProcess()
|
||||||
|
|
||||||
|
|
||||||
def selected_thread():
|
def selected_thread():
|
||||||
return get_process().GetSelectedThread()
|
return get_process().GetSelectedThread()
|
||||||
|
|
||||||
|
|
||||||
def selected_frame():
|
def selected_frame():
|
||||||
return selected_thread().GetSelectedFrame()
|
return selected_thread().GetSelectedFrame()
|
||||||
|
|
||||||
|
|
||||||
def parse_and_eval(expr, signed=False):
|
def parse_and_eval(expr, signed=False):
|
||||||
if signed is True:
|
if signed is True:
|
||||||
return get_target().EvaluateExpression(expr).GetValueAsSigned()
|
return get_eval(expr).GetValueAsSigned()
|
||||||
return get_target().EvaluateExpression(expr).GetValueAsUnsigned()
|
return get_eval(expr).GetValueAsUnsigned()
|
||||||
|
|
||||||
|
|
||||||
def get_eval(expr):
|
def get_eval(expr):
|
||||||
return get_target().EvaluateExpression(expr)
|
eval = get_target().EvaluateExpression(expr)
|
||||||
|
if eval.GetError().Fail():
|
||||||
|
raise ValueError(eval.GetError().GetCString())
|
||||||
|
return eval
|
||||||
|
|
||||||
|
|
||||||
def get_description(object, level=None):
|
def get_description(object, level=None):
|
||||||
stream = lldb.SBStream()
|
stream = lldb.SBStream()
|
||||||
|
@ -208,8 +223,10 @@ def get_description(object, level=None):
|
||||||
object.GetDescription(stream, level)
|
object.GetDescription(stream, level)
|
||||||
return escape_ansi(stream.GetData())
|
return escape_ansi(stream.GetData())
|
||||||
|
|
||||||
|
|
||||||
conv_map = {}
|
conv_map = {}
|
||||||
|
|
||||||
|
|
||||||
def get_convenience_variable(id):
|
def get_convenience_variable(id):
|
||||||
#val = get_target().GetEnvironment().Get(id)
|
#val = get_target().GetEnvironment().Get(id)
|
||||||
if id not in conv_map:
|
if id not in conv_map:
|
||||||
|
@ -219,18 +236,20 @@ def get_convenience_variable(id):
|
||||||
return "auto"
|
return "auto"
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
def set_convenience_variable(id, value):
|
def set_convenience_variable(id, value):
|
||||||
#env = get_target().GetEnvironment()
|
#env = get_target().GetEnvironment()
|
||||||
#return env.Set(id, value, True)
|
# return env.Set(id, value, True)
|
||||||
conv_map[id] = value
|
conv_map[id] = value
|
||||||
|
|
||||||
|
|
||||||
def escape_ansi(line):
|
def escape_ansi(line):
|
||||||
ansi_escape =re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
|
ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
|
||||||
return ansi_escape.sub('', line)
|
return ansi_escape.sub('', line)
|
||||||
|
|
||||||
|
|
||||||
def debracket(init):
|
def debracket(init):
|
||||||
val = init
|
val = init
|
||||||
val = val.replace("[","(")
|
val = val.replace("[", "(")
|
||||||
val = val.replace("]",")")
|
val = val.replace("]", ")")
|
||||||
return val
|
return val
|
||||||
|
|
|
@ -37,6 +37,13 @@ public interface TraceRmiAcceptor {
|
||||||
*/
|
*/
|
||||||
TraceRmiConnection accept() throws IOException, CancelledException;
|
TraceRmiConnection accept() throws IOException, CancelledException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the acceptor is actually still accepting.
|
||||||
|
*
|
||||||
|
* @return true if not accepting anymore
|
||||||
|
*/
|
||||||
|
boolean isClosed();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the address (and port) where the acceptor is listening
|
* Get the address (and port) where the acceptor is listening
|
||||||
*
|
*
|
||||||
|
|
|
@ -57,16 +57,30 @@ public interface TraceRmiLaunchOffer {
|
||||||
* @param exception optional error, if failed
|
* @param exception optional error, if failed
|
||||||
*/
|
*/
|
||||||
public record LaunchResult(Program program, Map<String, TerminalSession> sessions,
|
public record LaunchResult(Program program, Map<String, TerminalSession> sessions,
|
||||||
TraceRmiConnection connection, Trace trace, Throwable exception)
|
TraceRmiAcceptor acceptor, TraceRmiConnection connection, Trace trace,
|
||||||
implements AutoCloseable {
|
Throwable exception) implements AutoCloseable {
|
||||||
|
public LaunchResult(Program program, Map<String, TerminalSession> sessions,
|
||||||
|
TraceRmiAcceptor acceptor, TraceRmiConnection connection, Trace trace,
|
||||||
|
Throwable exception) {
|
||||||
|
this.program = program;
|
||||||
|
this.sessions = sessions;
|
||||||
|
this.acceptor = acceptor == null || acceptor.isClosed() ? null : acceptor;
|
||||||
|
this.connection = connection;
|
||||||
|
this.trace = trace;
|
||||||
|
this.exception = exception;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws Exception {
|
public void close() throws Exception {
|
||||||
for (TerminalSession s : sessions.values()) {
|
|
||||||
s.close();
|
|
||||||
}
|
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
connection.close();
|
connection.close();
|
||||||
}
|
}
|
||||||
|
if (acceptor != null) {
|
||||||
|
acceptor.cancel();
|
||||||
|
}
|
||||||
|
for (TerminalSession s : sessions.values()) {
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class RunBashInTerminalScript extends TerminalGhidraScript {
|
||||||
Map<String, String> env = new HashMap<>(System.getenv());
|
Map<String, String> env = new HashMap<>(System.getenv());
|
||||||
env.put("TERM", "xterm-256color");
|
env.put("TERM", "xterm-256color");
|
||||||
PtySession session = pty.getChild().session(new String[] { "/usr/bin/bash" }, env);
|
PtySession session = pty.getChild().session(new String[] { "/usr/bin/bash" }, env);
|
||||||
displayInTerminal(pty.getParent(), () -> {
|
displayInTerminal(pty, () -> {
|
||||||
try {
|
try {
|
||||||
session.waitExited();
|
session.waitExited();
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,14 +37,16 @@ public class TerminalGhidraScript extends GhidraScript {
|
||||||
return state.getTool().getService(TerminalService.class);
|
return state.getTool().getService(TerminalService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void displayInTerminal(PtyParent parent, Runnable waiter) throws PluginException {
|
protected void displayInTerminal(Pty pty, Runnable waiter) throws PluginException {
|
||||||
TerminalService terminalService = ensureTerminalService();
|
TerminalService terminalService = ensureTerminalService();
|
||||||
|
PtyParent parent = pty.getParent();
|
||||||
|
PtyChild child = pty.getChild();
|
||||||
try (Terminal term = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
try (Terminal term = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
||||||
parent.getInputStream(), parent.getOutputStream())) {
|
parent.getInputStream(), parent.getOutputStream())) {
|
||||||
term.addTerminalListener(new TerminalListener() {
|
term.addTerminalListener(new TerminalListener() {
|
||||||
@Override
|
@Override
|
||||||
public void resized(short cols, short rows) {
|
public void resized(short cols, short rows) {
|
||||||
parent.setWindowSize(cols, rows);
|
child.setWindowSize(cols, rows);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
waiter.run();
|
waiter.run();
|
||||||
|
@ -55,7 +57,7 @@ public class TerminalGhidraScript extends GhidraScript {
|
||||||
Map<String, String> env = new HashMap<>(System.getenv());
|
Map<String, String> env = new HashMap<>(System.getenv());
|
||||||
env.put("TERM", "xterm-256color");
|
env.put("TERM", "xterm-256color");
|
||||||
pty.getChild().nullSession();
|
pty.getChild().nullSession();
|
||||||
displayInTerminal(pty.getParent(), () -> {
|
displayInTerminal(pty, () -> {
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(100000);
|
Thread.sleep(100000);
|
||||||
|
|
|
@ -441,6 +441,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
Pty pty = factory.openpty();
|
Pty pty = factory.openpty();
|
||||||
|
|
||||||
PtyParent parent = pty.getParent();
|
PtyParent parent = pty.getParent();
|
||||||
|
PtyChild child = pty.getChild();
|
||||||
Terminal terminal = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
Terminal terminal = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
||||||
parent.getInputStream(), parent.getOutputStream());
|
parent.getInputStream(), parent.getOutputStream());
|
||||||
terminal.setSubTitle(ShellUtils.generateLine(commandLine));
|
terminal.setSubTitle(ShellUtils.generateLine(commandLine));
|
||||||
|
@ -448,7 +449,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
@Override
|
@Override
|
||||||
public void resized(short cols, short rows) {
|
public void resized(short cols, short rows) {
|
||||||
try {
|
try {
|
||||||
parent.setWindowSize(cols, rows);
|
child.setWindowSize(cols, rows);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
Msg.error(this, "Could not resize pty: " + e);
|
Msg.error(this, "Could not resize pty: " + e);
|
||||||
|
@ -490,12 +491,13 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
Pty pty = factory.openpty();
|
Pty pty = factory.openpty();
|
||||||
|
|
||||||
PtyParent parent = pty.getParent();
|
PtyParent parent = pty.getParent();
|
||||||
|
PtyChild child = pty.getChild();
|
||||||
Terminal terminal = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
Terminal terminal = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
||||||
parent.getInputStream(), parent.getOutputStream());
|
parent.getInputStream(), parent.getOutputStream());
|
||||||
TerminalListener resizeListener = new TerminalListener() {
|
TerminalListener resizeListener = new TerminalListener() {
|
||||||
@Override
|
@Override
|
||||||
public void resized(short cols, short rows) {
|
public void resized(short cols, short rows) {
|
||||||
parent.setWindowSize(cols, rows);
|
child.setWindowSize(cols, rows);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
terminal.addTerminalListener(resizeListener);
|
terminal.addTerminalListener(resizeListener);
|
||||||
|
@ -549,7 +551,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
if (lastExc == null) {
|
if (lastExc == null) {
|
||||||
lastExc = new CancelledException();
|
lastExc = new CancelledException();
|
||||||
}
|
}
|
||||||
return new LaunchResult(program, sessions, connection, trace, lastExc);
|
return new LaunchResult(program, sessions, acceptor, connection, trace, lastExc);
|
||||||
}
|
}
|
||||||
acceptor = null;
|
acceptor = null;
|
||||||
sessions.clear();
|
sessions.clear();
|
||||||
|
@ -598,10 +600,16 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
|
DebuggerConsoleService consoleService =
|
||||||
|
tool.getService(DebuggerConsoleService.class);
|
||||||
|
if (consoleService != null) {
|
||||||
|
consoleService.log(DebuggerResources.ICON_LOG_ERROR,
|
||||||
|
"Launch %s Failed".formatted(getTitle()), e);
|
||||||
|
}
|
||||||
lastExc = e;
|
lastExc = e;
|
||||||
prompt = mode != PromptMode.NEVER;
|
prompt = mode != PromptMode.NEVER;
|
||||||
LaunchResult result =
|
LaunchResult result =
|
||||||
new LaunchResult(program, sessions, connection, trace, lastExc);
|
new LaunchResult(program, sessions, acceptor, connection, trace, lastExc);
|
||||||
if (prompt) {
|
if (prompt) {
|
||||||
switch (promptError(result)) {
|
switch (promptError(result)) {
|
||||||
case KEEP:
|
case KEEP:
|
||||||
|
@ -621,13 +629,13 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
catch (Exception e1) {
|
catch (Exception e1) {
|
||||||
Msg.error(this, "Could not close", e1);
|
Msg.error(this, "Could not close", e1);
|
||||||
}
|
}
|
||||||
return new LaunchResult(program, Map.of(), null, null, lastExc);
|
return new LaunchResult(program, Map.of(), null, null, null, lastExc);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return new LaunchResult(program, sessions, connection, trace, null);
|
return new LaunchResult(program, sessions, null, connection, trace, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -702,21 +710,25 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (Entry<String, TerminalSession> ent : result.sessions().entrySet()) {
|
for (Entry<String, TerminalSession> ent : result.sessions().entrySet()) {
|
||||||
TerminalSession session = ent.getValue();
|
TerminalSession session = ent.getValue();
|
||||||
sb.append("<li>Terminal: " + HTMLUtilities.escapeHTML(ent.getKey()) + " → <tt>" +
|
sb.append("<li>Terminal: %s → <tt>%s</tt>".formatted(
|
||||||
HTMLUtilities.escapeHTML(session.description()) + "</tt>");
|
HTMLUtilities.escapeHTML(ent.getKey()),
|
||||||
|
HTMLUtilities.escapeHTML(session.description())));
|
||||||
if (session.isTerminated()) {
|
if (session.isTerminated()) {
|
||||||
sb.append(" (Terminated)");
|
sb.append(" (Terminated)");
|
||||||
}
|
}
|
||||||
sb.append("</li>\n");
|
sb.append("</li>\n");
|
||||||
}
|
}
|
||||||
|
if (result.acceptor() != null) {
|
||||||
|
sb.append("<li>Acceptor: <tt>%s</tt></li>\n".formatted(
|
||||||
|
HTMLUtilities.escapeHTML(result.acceptor().getAddress().toString())));
|
||||||
|
}
|
||||||
if (result.connection() != null) {
|
if (result.connection() != null) {
|
||||||
sb.append("<li>Connection: <tt>" +
|
sb.append("<li>Connection: <tt>%s</tt></li>\n".formatted(
|
||||||
HTMLUtilities.escapeHTML(result.connection().getRemoteAddress().toString()) +
|
HTMLUtilities.escapeHTML(result.connection().getRemoteAddress().toString())));
|
||||||
"</tt></li>\n");
|
|
||||||
}
|
}
|
||||||
if (result.trace() != null) {
|
if (result.trace() != null) {
|
||||||
sb.append(
|
sb.append("<li>Trace: %s</li>\n".formatted(
|
||||||
"<li>Trace: " + HTMLUtilities.escapeHTML(result.trace().getName()) + "</li>\n");
|
HTMLUtilities.escapeHTML(result.trace().getName())));
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,11 @@ public class DefaultTraceRmiAcceptor extends AbstractTraceRmiListener implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() {
|
||||||
|
return socket.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
plugin.removeAcceptor(this);
|
plugin.removeAcceptor(this);
|
||||||
|
|
|
@ -310,7 +310,11 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
|
||||||
|
|
||||||
PropertyEditor editor = getEditor(param);
|
PropertyEditor editor = getEditor(param);
|
||||||
Object val = computeMemorizedValue(param);
|
Object val = computeMemorizedValue(param);
|
||||||
editor.setValue(val);
|
if (val == null) {
|
||||||
|
editor.setValue("");
|
||||||
|
} else {
|
||||||
|
editor.setValue(val);
|
||||||
|
}
|
||||||
editor.addPropertyChangeListener(this);
|
editor.addPropertyChangeListener(this);
|
||||||
pairPanel.add(MiscellaneousUtils.getEditorComponent(editor));
|
pairPanel.add(MiscellaneousUtils.getEditorComponent(editor));
|
||||||
paramEditors.put(param, editor);
|
paramEditors.put(param, editor);
|
||||||
|
|
|
@ -28,8 +28,8 @@ dependencies {
|
||||||
api project(':SoftwareModeling')
|
api project(':SoftwareModeling')
|
||||||
api project(':ProposedUtils')
|
api project(':ProposedUtils')
|
||||||
|
|
||||||
api "net.java.dev.jna:jna:5.4.0"
|
api "net.java.dev.jna:jna:5.14.0"
|
||||||
api "net.java.dev.jna:jna-platform:5.4.0"
|
api "net.java.dev.jna:jna-platform:5.14.0"
|
||||||
|
|
||||||
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,4 +101,12 @@ public interface PtyChild extends PtyEndpoint {
|
||||||
default String nullSession(TermMode... mode) throws IOException {
|
default String nullSession(TermMode... mode) throws IOException {
|
||||||
return nullSession(List.of(mode));
|
return nullSession(List.of(mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resize the terminal window to the given width and height, in characters
|
||||||
|
*
|
||||||
|
* @param cols the width in characters
|
||||||
|
* @param rows the height in characters
|
||||||
|
*/
|
||||||
|
void setWindowSize(short cols, short rows);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,4 @@ package ghidra.pty;
|
||||||
* The parent (UNIX "master") end of a pseudo-terminal
|
* The parent (UNIX "master") end of a pseudo-terminal
|
||||||
*/
|
*/
|
||||||
public interface PtyParent extends PtyEndpoint {
|
public interface PtyParent extends PtyEndpoint {
|
||||||
/**
|
|
||||||
* Resize the terminal window to the given width and height, in characters
|
|
||||||
*
|
|
||||||
* @param cols the width in characters
|
|
||||||
* @param rows the height in characters
|
|
||||||
*/
|
|
||||||
void setWindowSize(short cols, short rows);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.pty;
|
package ghidra.pty;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A session led by the child pty
|
* A session led by the child pty
|
||||||
*
|
*
|
||||||
|
@ -31,6 +34,8 @@ public interface PtySession {
|
||||||
*/
|
*/
|
||||||
int waitExited() throws InterruptedException;
|
int waitExited() throws InterruptedException;
|
||||||
|
|
||||||
|
int waitExited(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Take the greatest efforts to terminate the session (leader and descendants)
|
* Take the greatest efforts to terminate the session (leader and descendants)
|
||||||
*
|
*
|
||||||
|
|
|
@ -15,20 +15,24 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.pty.linux;
|
package ghidra.pty.linux;
|
||||||
|
|
||||||
import ghidra.pty.PtyParent;
|
import ghidra.pty.unix.PosixC.Ioctls;
|
||||||
import ghidra.pty.linux.PosixC.Winsize;
|
import ghidra.pty.unix.UnixPtySessionLeader;
|
||||||
|
|
||||||
public class LinuxPtyParent extends LinuxPtyEndpoint implements PtyParent {
|
public enum LinuxIoctls implements Ioctls {
|
||||||
LinuxPtyParent(int fd) {
|
INSTANCE;
|
||||||
super(fd);
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends UnixPtySessionLeader> leaderClass() {
|
||||||
|
return LinuxPtySessionLeader.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setWindowSize(short cols, short rows) {
|
public long TIOCSCTTY() {
|
||||||
Winsize.ByReference ws = new Winsize.ByReference();
|
return 0x540eL;
|
||||||
ws.ws_col = cols;
|
}
|
||||||
ws.ws_row = rows;
|
|
||||||
ws.write();
|
@Override
|
||||||
PosixC.INSTANCE.ioctl(fd, Winsize.TIOCSWINSZ, ws.getPointer());
|
public long TIOCSWINSZ() {
|
||||||
|
return 0x5414L;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -19,15 +19,16 @@ import java.io.IOException;
|
||||||
|
|
||||||
import ghidra.pty.Pty;
|
import ghidra.pty.Pty;
|
||||||
import ghidra.pty.PtyFactory;
|
import ghidra.pty.PtyFactory;
|
||||||
|
import ghidra.pty.unix.UnixPty;
|
||||||
|
|
||||||
public enum LinuxPtyFactory implements PtyFactory {
|
public enum LinuxPtyFactory implements PtyFactory {
|
||||||
INSTANCE;
|
INSTANCE;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Pty openpty(short cols, short rows) throws IOException {
|
public Pty openpty(short cols, short rows) throws IOException {
|
||||||
LinuxPty pty = LinuxPty.openpty();
|
UnixPty pty = UnixPty.openpty(LinuxIoctls.INSTANCE);
|
||||||
if (cols != 0 && rows != 0) {
|
if (cols != 0 && rows != 0) {
|
||||||
pty.getParent().setWindowSize(cols, rows);
|
pty.getChild().setWindowSize(cols, rows);
|
||||||
}
|
}
|
||||||
return pty;
|
return pty;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,10 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.pty.linux;
|
package ghidra.pty.linux;
|
||||||
|
|
||||||
import java.util.List;
|
import ghidra.pty.unix.PosixC.Ioctls;
|
||||||
|
import ghidra.pty.unix.UnixPtySessionLeader;
|
||||||
|
|
||||||
public class LinuxPtySessionLeader {
|
public class LinuxPtySessionLeader extends UnixPtySessionLeader {
|
||||||
private static final PosixC LIB_POSIX = PosixC.INSTANCE;
|
|
||||||
private static final int O_RDWR = 2; // TODO: Find this in libs
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
LinuxPtySessionLeader leader = new LinuxPtySessionLeader();
|
LinuxPtySessionLeader leader = new LinuxPtySessionLeader();
|
||||||
|
@ -27,61 +26,8 @@ public class LinuxPtySessionLeader {
|
||||||
leader.run();
|
leader.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String ptyPath;
|
@Override
|
||||||
protected List<String> subArgs;
|
protected Ioctls ioctls() {
|
||||||
|
return LinuxIoctls.INSTANCE;
|
||||||
protected void parseArgs(String[] args) {
|
|
||||||
ptyPath = args[0];
|
|
||||||
subArgs = List.of(args).subList(1, args.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void run() throws Exception {
|
|
||||||
/** This tells Linux to make this process the leader of a new session. */
|
|
||||||
LIB_POSIX.setsid();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open the TTY. On Linux, the first TTY opened since becoming a session leader becomes the
|
|
||||||
* session's controlling TTY. Other platforms, e.g., BSD may require an explicit IOCTL.
|
|
||||||
*/
|
|
||||||
int bk = -1;
|
|
||||||
try {
|
|
||||||
int fd = LIB_POSIX.open(ptyPath, O_RDWR, 0);
|
|
||||||
|
|
||||||
/** Copy stderr to a backup descriptor, in case something goes wrong. */
|
|
||||||
int bkt = fd + 1;
|
|
||||||
LIB_POSIX.dup2(2, bkt);
|
|
||||||
bk = bkt;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy the TTY fd over all standard streams. This effectively redirects the leader's
|
|
||||||
* standard streams to the TTY.
|
|
||||||
*/
|
|
||||||
LIB_POSIX.dup2(fd, 0);
|
|
||||||
LIB_POSIX.dup2(fd, 1);
|
|
||||||
LIB_POSIX.dup2(fd, 2);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* At this point, we are the session leader and the named TTY is the controlling PTY.
|
|
||||||
* Now, exec the specified image with arguments as the session leader. Recall, this
|
|
||||||
* replaces the image of this process.
|
|
||||||
*/
|
|
||||||
LIB_POSIX.execv(subArgs.get(0), subArgs.toArray(new String[0]));
|
|
||||||
}
|
|
||||||
catch (Throwable t) {
|
|
||||||
// Print to both redirected and to inherited stderr
|
|
||||||
System.err.println("Could not execute " + subArgs.get(0) + ": " + t.getMessage());
|
|
||||||
if (bk != -1) {
|
|
||||||
try {
|
|
||||||
int bkt = bk;
|
|
||||||
LIB_POSIX.dup2(bkt, 2);
|
|
||||||
}
|
|
||||||
catch (Throwable t2) {
|
|
||||||
// Catastrophic
|
|
||||||
System.exit(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
System.err.println("Could not execute " + subArgs.get(0) + ": " + t.getMessage());
|
|
||||||
System.exit(127);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.pty.local;
|
package ghidra.pty.local;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import ghidra.pty.PtySession;
|
import ghidra.pty.PtySession;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
|
@ -36,6 +39,15 @@ public class LocalProcessPtySession implements PtySession {
|
||||||
return process.waitFor();
|
return process.waitFor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int waitExited(long timeout, TimeUnit unit)
|
||||||
|
throws InterruptedException, TimeoutException {
|
||||||
|
if (!process.waitFor(timeout, unit)) {
|
||||||
|
throw new TimeoutException();
|
||||||
|
}
|
||||||
|
return process.exitValue();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroyForcibly() {
|
public void destroyForcibly() {
|
||||||
process.destroyForcibly();
|
process.destroyForcibly();
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.pty.local;
|
package ghidra.pty.local;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import com.sun.jna.LastErrorException;
|
import com.sun.jna.LastErrorException;
|
||||||
import com.sun.jna.platform.win32.Kernel32;
|
import com.sun.jna.platform.win32.Kernel32;
|
||||||
import com.sun.jna.platform.win32.WinBase;
|
import com.sun.jna.platform.win32.WinBase;
|
||||||
|
@ -42,10 +45,9 @@ public class LocalWindowsNativeProcessPtySession implements PtySession {
|
||||||
Msg.info(this, "local Windows Pty session. PID = " + pid);
|
Msg.info(this, "local Windows Pty session. PID = " + pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected int doWaitExited(int millis) throws TimeoutException {
|
||||||
public int waitExited() throws InterruptedException {
|
|
||||||
while (true) {
|
while (true) {
|
||||||
switch (Kernel32.INSTANCE.WaitForSingleObject(processHandle.getNative(), -1)) {
|
switch (Kernel32.INSTANCE.WaitForSingleObject(processHandle.getNative(), millis)) {
|
||||||
case Kernel32.WAIT_OBJECT_0:
|
case Kernel32.WAIT_OBJECT_0:
|
||||||
case Kernel32.WAIT_ABANDONED:
|
case Kernel32.WAIT_ABANDONED:
|
||||||
IntByReference lpExitCode = new IntByReference();
|
IntByReference lpExitCode = new IntByReference();
|
||||||
|
@ -54,13 +56,32 @@ public class LocalWindowsNativeProcessPtySession implements PtySession {
|
||||||
return lpExitCode.getValue();
|
return lpExitCode.getValue();
|
||||||
}
|
}
|
||||||
case Kernel32.WAIT_TIMEOUT:
|
case Kernel32.WAIT_TIMEOUT:
|
||||||
throw new AssertionError();
|
throw new TimeoutException();
|
||||||
case Kernel32.WAIT_FAILED:
|
case Kernel32.WAIT_FAILED:
|
||||||
throw new LastErrorException(Kernel32.INSTANCE.GetLastError());
|
throw new LastErrorException(Kernel32.INSTANCE.GetLastError());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int waitExited() {
|
||||||
|
try {
|
||||||
|
return doWaitExited(-1);
|
||||||
|
}
|
||||||
|
catch (TimeoutException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int waitExited(long timeout, TimeUnit unit) throws TimeoutException {
|
||||||
|
long millis = TimeUnit.MILLISECONDS.convert(timeout, unit);
|
||||||
|
if (millis > Integer.MAX_VALUE) {
|
||||||
|
throw new IllegalArgumentException("Too long a timeout");
|
||||||
|
}
|
||||||
|
return doWaitExited((int) millis);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroyForcibly() {
|
public void destroyForcibly() {
|
||||||
if (!Kernel32.INSTANCE.TerminateProcess(processHandle.getNative(), 1)) {
|
if (!Kernel32.INSTANCE.TerminateProcess(processHandle.getNative(), 1)) {
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/* ###
|
||||||
|
* 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.pty.macos;
|
||||||
|
|
||||||
|
import ghidra.pty.unix.PosixC.Ioctls;
|
||||||
|
import ghidra.pty.unix.UnixPtySessionLeader;
|
||||||
|
|
||||||
|
public enum MacosIoctls implements Ioctls {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends UnixPtySessionLeader> leaderClass() {
|
||||||
|
return MacosPtySessionLeader.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long TIOCSCTTY() {
|
||||||
|
return 0x20007461L;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long TIOCSWINSZ() {
|
||||||
|
return 0x80087467L;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,22 +19,22 @@ import java.io.IOException;
|
||||||
|
|
||||||
import ghidra.pty.Pty;
|
import ghidra.pty.Pty;
|
||||||
import ghidra.pty.PtyFactory;
|
import ghidra.pty.PtyFactory;
|
||||||
import ghidra.pty.linux.LinuxPty;
|
import ghidra.pty.unix.UnixPty;
|
||||||
|
|
||||||
public enum MacosPtyFactory implements PtyFactory {
|
public enum MacosPtyFactory implements PtyFactory {
|
||||||
INSTANCE;
|
INSTANCE;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Pty openpty(short cols, short rows) throws IOException {
|
public Pty openpty(short cols, short rows) throws IOException {
|
||||||
LinuxPty pty = LinuxPty.openpty();
|
UnixPty pty = UnixPty.openpty(MacosIoctls.INSTANCE);
|
||||||
if (cols != 0 && rows != 0) {
|
if (cols != 0 && rows != 0) {
|
||||||
pty.getParent().setWindowSize(cols, rows);
|
pty.getChild().setWindowSize(cols, rows);
|
||||||
}
|
}
|
||||||
return pty;
|
return pty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return "local (MacOS)";
|
return "local (macOS)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/* ###
|
||||||
|
* 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.pty.macos;
|
||||||
|
|
||||||
|
import ghidra.pty.unix.PosixC.Ioctls;
|
||||||
|
import ghidra.pty.unix.UnixPtySessionLeader;
|
||||||
|
|
||||||
|
public class MacosPtySessionLeader extends UnixPtySessionLeader {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
MacosPtySessionLeader leader = new MacosPtySessionLeader();
|
||||||
|
leader.parseArgs(args);
|
||||||
|
leader.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Ioctls ioctls() {
|
||||||
|
return MacosIoctls.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -234,7 +234,7 @@ public class GhidraSshPtyFactory implements PtyFactory {
|
||||||
try {
|
try {
|
||||||
SshPty pty = new SshPty((ChannelExec) session.openChannel("exec"));
|
SshPty pty = new SshPty((ChannelExec) session.openChannel("exec"));
|
||||||
if (cols != 0 && rows != 0) {
|
if (cols != 0 && rows != 0) {
|
||||||
pty.getParent().setWindowSize(cols, rows);
|
pty.getChild().setWindowSize(cols, rows);
|
||||||
}
|
}
|
||||||
return pty;
|
return pty;
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,4 +118,8 @@ public class SshPtyChild extends SshPtyEndpoint implements PtyChild {
|
||||||
public OutputStream getOutputStream() {
|
public OutputStream getOutputStream() {
|
||||||
throw new UnsupportedOperationException("The child is not local");
|
throw new UnsupportedOperationException("The child is not local");
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
public void setWindowSize(short cols, short rows) {
|
||||||
|
channel.setPtySize(Short.toUnsignedInt(cols), Short.toUnsignedInt(rows), 0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,4 @@ public class SshPtyParent extends SshPtyEndpoint implements PtyParent {
|
||||||
public SshPtyParent(ChannelExec channel, OutputStream outputStream, InputStream inputStream) {
|
public SshPtyParent(ChannelExec channel, OutputStream outputStream, InputStream inputStream) {
|
||||||
super(channel, outputStream, inputStream);
|
super(channel, outputStream, inputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setWindowSize(short cols, short rows) {
|
|
||||||
channel.setPtySize(Short.toUnsignedInt(cols), Short.toUnsignedInt(rows), 0, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.pty.ssh;
|
package ghidra.pty.ssh;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import com.jcraft.jsch.*;
|
import com.jcraft.jsch.*;
|
||||||
|
|
||||||
import ghidra.pty.PtySession;
|
import ghidra.pty.PtySession;
|
||||||
|
@ -27,16 +30,37 @@ public class SshPtySession implements PtySession {
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected int doWaitExited(Long millis) throws InterruptedException, TimeoutException {
|
||||||
public int waitExited() throws InterruptedException {
|
long startMs = System.currentTimeMillis();
|
||||||
// Doesn't look like there's a clever way to wait. So do the spin sleep :(
|
// Doesn't look like there's a clever way to wait. So do the spin sleep :(
|
||||||
while (!channel.isEOF()) {
|
while (!channel.isEOF()) {
|
||||||
Thread.sleep(1000);
|
Thread.sleep(100);
|
||||||
|
long elapsed = System.currentTimeMillis() - startMs;
|
||||||
|
if (millis != null && elapsed > millis) {
|
||||||
|
throw new TimeoutException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// NB. May not be available
|
// NB. May not be available
|
||||||
return channel.getExitStatus();
|
return channel.getExitStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int waitExited() throws InterruptedException {
|
||||||
|
try {
|
||||||
|
return doWaitExited(null);
|
||||||
|
}
|
||||||
|
catch (TimeoutException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int waitExited(long timeout, TimeUnit unit)
|
||||||
|
throws InterruptedException, TimeoutException {
|
||||||
|
long millis = TimeUnit.MILLISECONDS.convert(timeout, unit);
|
||||||
|
return doWaitExited(millis);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroyForcibly() {
|
public void destroyForcibly() {
|
||||||
channel.disconnect();
|
channel.disconnect();
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.pty.linux;
|
package ghidra.pty.unix;
|
||||||
|
|
||||||
import com.sun.jna.LastErrorException;
|
import com.sun.jna.LastErrorException;
|
||||||
import com.sun.jna.Native;
|
import com.sun.jna.Native;
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.pty.linux;
|
package ghidra.pty.unix;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.pty.linux;
|
package ghidra.pty.unix;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.pty.linux;
|
package ghidra.pty.unix;
|
||||||
|
|
||||||
import com.sun.jna.*;
|
import com.sun.jna.*;
|
||||||
import com.sun.jna.Structure.FieldOrder;
|
import com.sun.jna.Structure.FieldOrder;
|
||||||
|
@ -26,10 +26,16 @@ import com.sun.jna.Structure.FieldOrder;
|
||||||
*/
|
*/
|
||||||
public interface PosixC extends Library {
|
public interface PosixC extends Library {
|
||||||
|
|
||||||
|
interface Ioctls {
|
||||||
|
Class<? extends UnixPtySessionLeader> leaderClass();
|
||||||
|
|
||||||
|
long TIOCSCTTY();
|
||||||
|
|
||||||
|
long TIOCSWINSZ();
|
||||||
|
}
|
||||||
|
|
||||||
@FieldOrder({ "ws_row", "ws_col", "ws_xpixel", "ws_ypixel" })
|
@FieldOrder({ "ws_row", "ws_col", "ws_xpixel", "ws_ypixel" })
|
||||||
class Winsize extends Structure {
|
class Winsize extends Structure {
|
||||||
public static final int TIOCSWINSZ = 0x5414; // This may actually be Linux-specific
|
|
||||||
|
|
||||||
public short ws_row;
|
public short ws_row;
|
||||||
public short ws_col;
|
public short ws_col;
|
||||||
public short ws_xpixel; // Unused
|
public short ws_xpixel; // Unused
|
||||||
|
@ -39,11 +45,21 @@ public interface PosixC extends Library {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FieldOrder({ "c_iflag", "c_oflag", "c_cflag", "c_lflag", "c_line", "c_cc", "c_ispeed",
|
@FieldOrder({ "steal" })
|
||||||
"c_ospeed" })
|
class ControllingTty extends Structure {
|
||||||
class Termios extends Structure {
|
public int steal;
|
||||||
public static final int TCSANOW = 0;
|
|
||||||
|
|
||||||
|
public static class ByReference extends ControllingTty implements Structure.ByReference {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FieldOrder(
|
||||||
|
{ "c_iflag", "c_oflag", "c_cflag", "c_lflag", "c_line", "c_cc", "c_ispeed",
|
||||||
|
"c_ospeed" }
|
||||||
|
)
|
||||||
|
class Termios extends Structure {
|
||||||
|
// TCSANOW and ECHO are the same on Linux and macOS
|
||||||
|
public static final int TCSANOW = 0;
|
||||||
public static final int ECHO = 0000010; // Octal
|
public static final int ECHO = 0000010; // Octal
|
||||||
|
|
||||||
public int c_iflag;
|
public int c_iflag;
|
||||||
|
@ -110,7 +126,7 @@ public interface PosixC extends Library {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int ioctl(int fd, int cmd, Pointer... args) {
|
public int ioctl(int fd, long cmd, Pointer... args) {
|
||||||
return Err.checkLt0(BARE.ioctl(fd, cmd, args));
|
return Err.checkLt0(BARE.ioctl(fd, cmd, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +157,7 @@ public interface PosixC extends Library {
|
||||||
|
|
||||||
int execv(String path, String[] argv);
|
int execv(String path, String[] argv);
|
||||||
|
|
||||||
int ioctl(int fd, int cmd, Pointer... args);
|
int ioctl(int fd, long cmd, Pointer... args);
|
||||||
|
|
||||||
int tcgetattr(int fd, Termios.ByReference termios_p);
|
int tcgetattr(int fd, Termios.ByReference termios_p);
|
||||||
|
|
|
@ -13,53 +13,53 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.pty.linux;
|
package ghidra.pty.unix;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import com.sun.jna.*;
|
import com.sun.jna.Memory;
|
||||||
import com.sun.jna.ptr.IntByReference;
|
import com.sun.jna.ptr.IntByReference;
|
||||||
|
|
||||||
import ghidra.pty.Pty;
|
import ghidra.pty.Pty;
|
||||||
|
import ghidra.pty.unix.PosixC.Ioctls;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
public class LinuxPty implements Pty {
|
public class UnixPty implements Pty {
|
||||||
|
|
||||||
static final PosixC LIB_POSIX = PosixC.INSTANCE;
|
static final PosixC LIB_POSIX = PosixC.INSTANCE;
|
||||||
|
|
||||||
private final int aparent;
|
private final int aparent;
|
||||||
private final int achild;
|
private final int achild;
|
||||||
//private final String name;
|
|
||||||
private boolean closed = false;
|
private boolean closed = false;
|
||||||
|
|
||||||
private final LinuxPtyParent parent;
|
private final UnixPtyParent parent;
|
||||||
private final LinuxPtyChild child;
|
private final UnixPtyChild child;
|
||||||
|
|
||||||
public static LinuxPty openpty() throws IOException {
|
public static UnixPty openpty(Ioctls ioctls) throws IOException {
|
||||||
// TODO: Support termp and winp?
|
// TODO: Support termp and winp?
|
||||||
IntByReference p = new IntByReference();
|
IntByReference p = new IntByReference();
|
||||||
IntByReference c = new IntByReference();
|
IntByReference c = new IntByReference();
|
||||||
Memory n = new Memory(1024);
|
Memory n = new Memory(1024);
|
||||||
Util.INSTANCE.openpty(p, c, n, null, null);
|
Util.INSTANCE.openpty(p, c, n, null, null);
|
||||||
return new LinuxPty(p.getValue(), c.getValue(), n.getString(0));
|
return new UnixPty(ioctls, p.getValue(), c.getValue(), n.getString(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
LinuxPty(int aparent, int achild, String name) {
|
UnixPty(Ioctls ioctls, int aparent, int achild, String name) {
|
||||||
Msg.debug(this, "New Pty: " + name + " at (" + aparent + "," + achild + ")");
|
Msg.debug(this, "New Pty: " + name + " at (" + aparent + "," + achild + ")");
|
||||||
this.aparent = aparent;
|
this.aparent = aparent;
|
||||||
this.achild = achild;
|
this.achild = achild;
|
||||||
|
|
||||||
this.parent = new LinuxPtyParent(aparent);
|
this.parent = new UnixPtyParent(ioctls, aparent);
|
||||||
this.child = new LinuxPtyChild(achild, name);
|
this.child = new UnixPtyChild(ioctls, achild, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LinuxPtyParent getParent() {
|
public UnixPtyParent getParent() {
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LinuxPtyChild getChild() {
|
public UnixPtyChild getChild() {
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.pty.linux;
|
package ghidra.pty.unix;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -21,17 +21,17 @@ import java.util.*;
|
||||||
|
|
||||||
import ghidra.pty.PtyChild;
|
import ghidra.pty.PtyChild;
|
||||||
import ghidra.pty.PtySession;
|
import ghidra.pty.PtySession;
|
||||||
import ghidra.pty.linux.PosixC.Termios;
|
|
||||||
import ghidra.pty.local.LocalProcessPtySession;
|
import ghidra.pty.local.LocalProcessPtySession;
|
||||||
|
import ghidra.pty.unix.PosixC.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
public class LinuxPtyChild extends LinuxPtyEndpoint implements PtyChild {
|
public class UnixPtyChild extends UnixPtyEndpoint implements PtyChild {
|
||||||
static final PosixC LIB_POSIX = PosixC.INSTANCE;
|
static final PosixC LIB_POSIX = PosixC.INSTANCE;
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
LinuxPtyChild(int fd, String name) {
|
UnixPtyChild(Ioctls ioctls, int fd, String name) {
|
||||||
super(fd);
|
super(ioctls, fd);
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ public class LinuxPtyChild extends LinuxPtyEndpoint implements PtyChild {
|
||||||
argsList.add(javaCommand);
|
argsList.add(javaCommand);
|
||||||
argsList.add("-cp");
|
argsList.add("-cp");
|
||||||
argsList.add(System.getProperty("java.class.path"));
|
argsList.add(System.getProperty("java.class.path"));
|
||||||
argsList.add(LinuxPtySessionLeader.class.getCanonicalName());
|
argsList.add(ioctls.leaderClass().getCanonicalName());
|
||||||
|
|
||||||
argsList.add(name);
|
argsList.add(name);
|
||||||
argsList.addAll(Arrays.asList(args));
|
argsList.addAll(Arrays.asList(args));
|
||||||
|
@ -106,4 +106,18 @@ public class LinuxPtyChild extends LinuxPtyEndpoint implements PtyChild {
|
||||||
tmios.c_lflag &= ~Termios.ECHO;
|
tmios.c_lflag &= ~Termios.ECHO;
|
||||||
LIB_POSIX.tcsetattr(fd, Termios.TCSANOW, tmios);
|
LIB_POSIX.tcsetattr(fd, Termios.TCSANOW, tmios);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWindowSize(short cols, short rows) {
|
||||||
|
Winsize.ByReference ws = new Winsize.ByReference();
|
||||||
|
ws.ws_col = cols;
|
||||||
|
ws.ws_row = rows;
|
||||||
|
ws.write();
|
||||||
|
try {
|
||||||
|
PosixC.INSTANCE.ioctl(fd, ioctls.TIOCSWINSZ(), ws.getPointer());
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
Msg.error(this, "Could not set terminal window size: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -13,19 +13,22 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.pty.linux;
|
package ghidra.pty.unix;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
import ghidra.pty.PtyEndpoint;
|
import ghidra.pty.PtyEndpoint;
|
||||||
|
import ghidra.pty.unix.PosixC.Ioctls;
|
||||||
|
|
||||||
public class LinuxPtyEndpoint implements PtyEndpoint {
|
public class UnixPtyEndpoint implements PtyEndpoint {
|
||||||
|
protected final Ioctls ioctls;
|
||||||
protected final int fd;
|
protected final int fd;
|
||||||
private final FdOutputStream outputStream;
|
private final FdOutputStream outputStream;
|
||||||
private final FdInputStream inputStream;
|
private final FdInputStream inputStream;
|
||||||
|
|
||||||
LinuxPtyEndpoint(int fd) {
|
UnixPtyEndpoint(Ioctls ioctls, int fd) {
|
||||||
|
this.ioctls = ioctls;
|
||||||
this.fd = fd;
|
this.fd = fd;
|
||||||
this.outputStream = new FdOutputStream(fd);
|
this.outputStream = new FdOutputStream(fd);
|
||||||
this.inputStream = new FdInputStream(fd);
|
this.inputStream = new FdInputStream(fd);
|
|
@ -0,0 +1,25 @@
|
||||||
|
/* ###
|
||||||
|
* 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.pty.unix;
|
||||||
|
|
||||||
|
import ghidra.pty.PtyParent;
|
||||||
|
import ghidra.pty.unix.PosixC.Ioctls;
|
||||||
|
|
||||||
|
public class UnixPtyParent extends UnixPtyEndpoint implements PtyParent {
|
||||||
|
UnixPtyParent(Ioctls ioctls, int fd) {
|
||||||
|
super(ioctls, fd);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
/* ###
|
||||||
|
* 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.pty.unix;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import ghidra.pty.unix.PosixC.ControllingTty;
|
||||||
|
import ghidra.pty.unix.PosixC.Ioctls;
|
||||||
|
|
||||||
|
public abstract class UnixPtySessionLeader {
|
||||||
|
private static final PosixC LIB_POSIX = PosixC.INSTANCE;
|
||||||
|
private static final int O_RDWR = 2; // TODO: Find this in libs
|
||||||
|
|
||||||
|
protected String ptyPath;
|
||||||
|
protected List<String> subArgs;
|
||||||
|
|
||||||
|
protected abstract Ioctls ioctls();
|
||||||
|
|
||||||
|
protected void parseArgs(String[] args) {
|
||||||
|
ptyPath = args[0];
|
||||||
|
subArgs = List.of(args).subList(1, args.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void run() throws Exception {
|
||||||
|
/**
|
||||||
|
* Open the TTY. On Linux, the first TTY opened since becoming a session leader becomes the
|
||||||
|
* session's controlling TTY. Other platforms, e.g., BSD may require an explicit IOCTL.
|
||||||
|
*/
|
||||||
|
int bk = -1;
|
||||||
|
try {
|
||||||
|
int fd = LIB_POSIX.open(ptyPath, O_RDWR, 0);
|
||||||
|
|
||||||
|
/** Copy stderr to a backup descriptor, in case something goes wrong. */
|
||||||
|
int bkt = fd + 1;
|
||||||
|
LIB_POSIX.dup2(2, bkt);
|
||||||
|
bk = bkt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy the TTY fd over all standard streams. This effectively redirects the leader's
|
||||||
|
* standard streams to the TTY.
|
||||||
|
*/
|
||||||
|
LIB_POSIX.close(0);
|
||||||
|
LIB_POSIX.close(1);
|
||||||
|
LIB_POSIX.close(2);
|
||||||
|
LIB_POSIX.dup2(fd, 0);
|
||||||
|
LIB_POSIX.dup2(fd, 1);
|
||||||
|
LIB_POSIX.dup2(fd, 2);
|
||||||
|
LIB_POSIX.close(fd);
|
||||||
|
|
||||||
|
/** This tells Linux to make this process the leader of a new session. */
|
||||||
|
LIB_POSIX.setsid();
|
||||||
|
ControllingTty.ByReference ctty = new ControllingTty.ByReference();
|
||||||
|
ctty.steal = 0;
|
||||||
|
LIB_POSIX.ioctl(0, ioctls().TIOCSCTTY(), ctty.getPointer());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* At this point, we are the session leader and the named TTY is the controlling PTY.
|
||||||
|
* Now, exec the specified image with arguments as the session leader. Recall, this
|
||||||
|
* replaces the image of this process.
|
||||||
|
*/
|
||||||
|
LIB_POSIX.execv(subArgs.get(0), subArgs.toArray(new String[0]));
|
||||||
|
}
|
||||||
|
catch (Throwable t) {
|
||||||
|
// Print to both redirected and to inherited stderr
|
||||||
|
System.err.println("Could not execute " + subArgs.get(0) + ": " + t.getMessage());
|
||||||
|
if (bk != -1) {
|
||||||
|
try {
|
||||||
|
int bkt = bk;
|
||||||
|
LIB_POSIX.dup2(bkt, 2);
|
||||||
|
}
|
||||||
|
catch (Throwable t2) {
|
||||||
|
// Catastrophic
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.err.println("Could not execute " + subArgs.get(0) + ": " + t.getMessage());
|
||||||
|
System.exit(127);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.pty.linux;
|
package ghidra.pty.unix;
|
||||||
|
|
||||||
import com.sun.jna.*;
|
import com.sun.jna.*;
|
||||||
import com.sun.jna.ptr.IntByReference;
|
import com.sun.jna.ptr.IntByReference;
|
|
@ -111,4 +111,9 @@ public class ConPtyChild extends ConPtyEndpoint implements PtyChild {
|
||||||
public String nullSession(Collection<TermMode> mode) throws IOException {
|
public String nullSession(Collection<TermMode> mode) throws IOException {
|
||||||
throw new UnsupportedOperationException("ConPTY does not have a name");
|
throw new UnsupportedOperationException("ConPTY does not have a name");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWindowSize(short cols, short rows) {
|
||||||
|
pseudoConsoleHandle.resize(rows, cols);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,4 @@ public class ConPtyParent extends ConPtyEndpoint implements PtyParent {
|
||||||
PseudoConsoleHandle pseudoConsoleHandle) {
|
PseudoConsoleHandle pseudoConsoleHandle) {
|
||||||
super(writeHandle, readHandle, pseudoConsoleHandle);
|
super(writeHandle, readHandle, pseudoConsoleHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setWindowSize(short cols, short rows) {
|
|
||||||
pseudoConsoleHandle.resize(rows, cols);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,201 +15,24 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.pty.linux;
|
package ghidra.pty.linux;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.junit.Assume.assumeTrue;
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import ghidra.dbg.testutil.DummyProc;
|
|
||||||
import ghidra.framework.OperatingSystem;
|
import ghidra.framework.OperatingSystem;
|
||||||
import ghidra.pty.AbstractPtyTest;
|
import ghidra.pty.unix.AbstractUnixPtyTest;
|
||||||
import ghidra.pty.PtyChild.Echo;
|
import ghidra.pty.unix.UnixPty;
|
||||||
import ghidra.pty.PtySession;
|
|
||||||
|
|
||||||
public class LinuxPtyTest extends AbstractPtyTest {
|
public class LinuxPtyTest extends AbstractUnixPtyTest {
|
||||||
@Before
|
@Before
|
||||||
public void checkLinux() {
|
public void checkLinux() {
|
||||||
assumeTrue(OperatingSystem.LINUX == OperatingSystem.CURRENT_OPERATING_SYSTEM);
|
assumeTrue(OperatingSystem.LINUX == OperatingSystem.CURRENT_OPERATING_SYSTEM);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testOpenClosePty() throws IOException {
|
protected UnixPty openpty() throws IOException {
|
||||||
LinuxPty pty = LinuxPty.openpty();
|
return UnixPty.openpty(LinuxIoctls.INSTANCE);
|
||||||
pty.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParentToChild() throws IOException {
|
|
||||||
try (LinuxPty pty = LinuxPty.openpty()) {
|
|
||||||
PrintWriter writer = new PrintWriter(pty.getParent().getOutputStream());
|
|
||||||
BufferedReader reader =
|
|
||||||
new BufferedReader(new InputStreamReader(pty.getChild().getInputStream()));
|
|
||||||
|
|
||||||
writer.println("Hello, World!");
|
|
||||||
writer.flush();
|
|
||||||
assertEquals("Hello, World!", reader.readLine());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testChildToParent() throws IOException {
|
|
||||||
try (LinuxPty pty = LinuxPty.openpty()) {
|
|
||||||
PrintWriter writer = new PrintWriter(pty.getChild().getOutputStream());
|
|
||||||
BufferedReader reader =
|
|
||||||
new BufferedReader(new InputStreamReader(pty.getParent().getInputStream()));
|
|
||||||
|
|
||||||
writer.println("Hello, World!");
|
|
||||||
writer.flush();
|
|
||||||
assertEquals("Hello, World!", reader.readLine());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSessionBash() throws IOException, InterruptedException {
|
|
||||||
try (LinuxPty pty = LinuxPty.openpty()) {
|
|
||||||
PtySession bash =
|
|
||||||
pty.getChild().session(new String[] { DummyProc.which("bash") }, null);
|
|
||||||
pty.getParent().getOutputStream().write("exit\n".getBytes());
|
|
||||||
assertEquals(0, bash.waitExited());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testForkIntoNonExistent() throws IOException, InterruptedException {
|
|
||||||
try (LinuxPty pty = LinuxPty.openpty()) {
|
|
||||||
PtySession dies =
|
|
||||||
pty.getChild().session(new String[] { "thisHadBetterNotExist" }, null);
|
|
||||||
/**
|
|
||||||
* Choice of 127 is based on bash setting "exit code" to 127 for "command not found"
|
|
||||||
*/
|
|
||||||
assertEquals(127, dies.waitExited());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSessionBashEchoTest() throws IOException, InterruptedException {
|
|
||||||
Map<String, String> env = new HashMap<>();
|
|
||||||
env.put("PS1", "BASH:");
|
|
||||||
env.put("PROMPT_COMMAND", "");
|
|
||||||
env.put("TERM", "");
|
|
||||||
try (LinuxPty pty = LinuxPty.openpty()) {
|
|
||||||
LinuxPtyParent parent = pty.getParent();
|
|
||||||
PrintWriter writer = new PrintWriter(parent.getOutputStream());
|
|
||||||
BufferedReader reader = loggingReader(parent.getInputStream());
|
|
||||||
PtySession bash =
|
|
||||||
pty.getChild().session(new String[] { DummyProc.which("bash"), "--norc" }, env);
|
|
||||||
runExitCheck(3, bash);
|
|
||||||
|
|
||||||
writer.println("echo test");
|
|
||||||
writer.flush();
|
|
||||||
String line;
|
|
||||||
do {
|
|
||||||
line = reader.readLine();
|
|
||||||
}
|
|
||||||
while (!"test".equals(line));
|
|
||||||
|
|
||||||
writer.println("exit 3");
|
|
||||||
writer.flush();
|
|
||||||
|
|
||||||
line = reader.readLine();
|
|
||||||
assertTrue("Not 'exit 3' or 'BASH:exit 3': '" + line + "'",
|
|
||||||
Set.of("BASH:exit 3", "exit 3").contains(line));
|
|
||||||
|
|
||||||
assertEquals(3, bash.waitExited());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSessionBashInterruptCat() throws IOException, InterruptedException {
|
|
||||||
Map<String, String> env = new HashMap<>();
|
|
||||||
env.put("PS1", "BASH:");
|
|
||||||
env.put("PROMPT_COMMAND", "");
|
|
||||||
env.put("TERM", "");
|
|
||||||
try (LinuxPty pty = LinuxPty.openpty()) {
|
|
||||||
LinuxPtyParent parent = pty.getParent();
|
|
||||||
PrintWriter writer = new PrintWriter(parent.getOutputStream());
|
|
||||||
BufferedReader reader = loggingReader(parent.getInputStream());
|
|
||||||
PtySession bash =
|
|
||||||
pty.getChild().session(new String[] { DummyProc.which("bash"), "--norc" }, env);
|
|
||||||
runExitCheck(3, bash);
|
|
||||||
|
|
||||||
writer.println("echo test");
|
|
||||||
writer.flush();
|
|
||||||
String line;
|
|
||||||
do {
|
|
||||||
line = reader.readLine();
|
|
||||||
}
|
|
||||||
while (!"test".equals(line));
|
|
||||||
|
|
||||||
writer.println("cat");
|
|
||||||
writer.flush();
|
|
||||||
line = reader.readLine();
|
|
||||||
assertTrue("Not 'cat' or 'BASH:cat': '" + line + "'",
|
|
||||||
Set.of("BASH:cat", "cat").contains(line));
|
|
||||||
|
|
||||||
writer.println("Hello, cat!");
|
|
||||||
writer.flush();
|
|
||||||
assertEquals("Hello, cat!", reader.readLine()); // echo back
|
|
||||||
assertEquals("Hello, cat!", reader.readLine()); // cat back
|
|
||||||
|
|
||||||
writer.write(3); // should interrupt
|
|
||||||
writer.flush();
|
|
||||||
do {
|
|
||||||
line = reader.readLine();
|
|
||||||
}
|
|
||||||
while (!"^C".equals(line));
|
|
||||||
writer.println("echo test");
|
|
||||||
writer.flush();
|
|
||||||
|
|
||||||
do {
|
|
||||||
line = reader.readLine();
|
|
||||||
}
|
|
||||||
while (!"test".equals(line));
|
|
||||||
|
|
||||||
writer.println("exit 3");
|
|
||||||
writer.flush();
|
|
||||||
assertTrue(Set.of("BASH:exit 3", "exit 3").contains(reader.readLine()));
|
|
||||||
|
|
||||||
assertEquals(3, bash.waitExited());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLocalEchoOn() throws IOException {
|
|
||||||
try (LinuxPty pty = LinuxPty.openpty()) {
|
|
||||||
pty.getChild().nullSession();
|
|
||||||
|
|
||||||
PrintWriter writer = new PrintWriter(pty.getParent().getOutputStream());
|
|
||||||
BufferedReader reader =
|
|
||||||
new BufferedReader(new InputStreamReader(pty.getParent().getInputStream()));
|
|
||||||
|
|
||||||
writer.println("Hello, World!");
|
|
||||||
writer.flush();
|
|
||||||
assertEquals("Hello, World!", reader.readLine());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLocalEchoOff() throws IOException {
|
|
||||||
try (LinuxPty pty = LinuxPty.openpty()) {
|
|
||||||
pty.getChild().nullSession(Echo.OFF);
|
|
||||||
|
|
||||||
PrintWriter writerP = new PrintWriter(pty.getParent().getOutputStream());
|
|
||||||
PrintWriter writerC = new PrintWriter(pty.getChild().getOutputStream());
|
|
||||||
BufferedReader reader =
|
|
||||||
new BufferedReader(new InputStreamReader(pty.getParent().getInputStream()));
|
|
||||||
|
|
||||||
writerP.println("Hello, World!");
|
|
||||||
writerP.flush();
|
|
||||||
writerC.println("Good bye!");
|
|
||||||
writerC.flush();
|
|
||||||
|
|
||||||
assertEquals("Good bye!", reader.readLine());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/* ###
|
||||||
|
* 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.pty.macos;
|
||||||
|
|
||||||
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
|
||||||
|
import ghidra.framework.OperatingSystem;
|
||||||
|
import ghidra.pty.unix.AbstractUnixPtyTest;
|
||||||
|
import ghidra.pty.unix.UnixPty;
|
||||||
|
|
||||||
|
public class MacosPtyTest extends AbstractUnixPtyTest {
|
||||||
|
@Before
|
||||||
|
public void checkLinux() {
|
||||||
|
assumeTrue(OperatingSystem.MAC_OS_X == OperatingSystem.CURRENT_OPERATING_SYSTEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UnixPty openpty() throws IOException {
|
||||||
|
return UnixPty.openpty(MacosIoctls.INSTANCE);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,215 @@
|
||||||
|
/* ###
|
||||||
|
* 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.pty.unix;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import ghidra.dbg.testutil.DummyProc;
|
||||||
|
import ghidra.pty.AbstractPtyTest;
|
||||||
|
import ghidra.pty.PtyChild.Echo;
|
||||||
|
import ghidra.pty.PtySession;
|
||||||
|
|
||||||
|
public abstract class AbstractUnixPtyTest extends AbstractPtyTest {
|
||||||
|
|
||||||
|
protected abstract UnixPty openpty() throws IOException;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpenClosePty() throws IOException {
|
||||||
|
UnixPty pty = openpty();
|
||||||
|
pty.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParentToChild() throws IOException {
|
||||||
|
try (UnixPty pty = openpty()) {
|
||||||
|
PrintWriter writer = new PrintWriter(pty.getParent().getOutputStream());
|
||||||
|
BufferedReader reader =
|
||||||
|
new BufferedReader(new InputStreamReader(pty.getChild().getInputStream()));
|
||||||
|
|
||||||
|
writer.println("Hello, World!");
|
||||||
|
writer.flush();
|
||||||
|
assertEquals("Hello, World!", reader.readLine());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChildToParent() throws IOException {
|
||||||
|
try (UnixPty pty = openpty()) {
|
||||||
|
PrintWriter writer = new PrintWriter(pty.getChild().getOutputStream());
|
||||||
|
BufferedReader reader =
|
||||||
|
new BufferedReader(new InputStreamReader(pty.getParent().getInputStream()));
|
||||||
|
|
||||||
|
writer.println("Hello, World!");
|
||||||
|
writer.flush();
|
||||||
|
assertEquals("Hello, World!", reader.readLine());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSessionBash() throws IOException, InterruptedException, TimeoutException {
|
||||||
|
try (UnixPty pty = openpty()) {
|
||||||
|
PtySession bash =
|
||||||
|
pty.getChild().session(new String[] { DummyProc.which("bash") }, null);
|
||||||
|
pty.getParent().getOutputStream().write("exit\n".getBytes());
|
||||||
|
assertEquals(0, bash.waitExited(2, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testForkIntoNonExistent()
|
||||||
|
throws IOException, InterruptedException, TimeoutException {
|
||||||
|
try (UnixPty pty = openpty()) {
|
||||||
|
PtySession dies =
|
||||||
|
pty.getChild().session(new String[] { "thisHadBetterNotExist" }, null);
|
||||||
|
/**
|
||||||
|
* Choice of 127 is based on bash setting "exit code" to 127 for "command not found"
|
||||||
|
*/
|
||||||
|
assertEquals(127, dies.waitExited(2, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSessionBashEchoTest()
|
||||||
|
throws IOException, InterruptedException, TimeoutException {
|
||||||
|
Map<String, String> env = new HashMap<>();
|
||||||
|
env.put("PS1", "BASH:");
|
||||||
|
env.put("PROMPT_COMMAND", "");
|
||||||
|
env.put("TERM", "");
|
||||||
|
try (UnixPty pty = openpty()) {
|
||||||
|
UnixPtyParent parent = pty.getParent();
|
||||||
|
PrintWriter writer = new PrintWriter(parent.getOutputStream());
|
||||||
|
BufferedReader reader = loggingReader(parent.getInputStream());
|
||||||
|
PtySession bash =
|
||||||
|
pty.getChild().session(new String[] { DummyProc.which("bash"), "--norc" }, env);
|
||||||
|
runExitCheck(3, bash);
|
||||||
|
|
||||||
|
writer.println("echo test");
|
||||||
|
writer.flush();
|
||||||
|
String line;
|
||||||
|
do {
|
||||||
|
line = reader.readLine();
|
||||||
|
}
|
||||||
|
while (!"test".equals(line));
|
||||||
|
|
||||||
|
writer.println("exit 3");
|
||||||
|
writer.flush();
|
||||||
|
|
||||||
|
line = reader.readLine();
|
||||||
|
assertTrue("Not 'exit 3' or 'BASH:exit 3': '" + line + "'",
|
||||||
|
Set.of("BASH:exit 3", "exit 3").contains(line));
|
||||||
|
|
||||||
|
assertEquals(3, bash.waitExited(2, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSessionBashInterruptCat()
|
||||||
|
throws IOException, InterruptedException, TimeoutException {
|
||||||
|
Map<String, String> env = new HashMap<>();
|
||||||
|
env.put("PS1", "BASH:");
|
||||||
|
env.put("PROMPT_COMMAND", "");
|
||||||
|
env.put("TERM", "");
|
||||||
|
try (UnixPty pty = openpty()) {
|
||||||
|
UnixPtyParent parent = pty.getParent();
|
||||||
|
PrintWriter writer = new PrintWriter(parent.getOutputStream());
|
||||||
|
BufferedReader reader = loggingReader(parent.getInputStream());
|
||||||
|
PtySession bash =
|
||||||
|
pty.getChild().session(new String[] { DummyProc.which("bash"), "--norc" }, env);
|
||||||
|
runExitCheck(3, bash);
|
||||||
|
|
||||||
|
writer.println("echo test");
|
||||||
|
writer.flush();
|
||||||
|
String line;
|
||||||
|
do {
|
||||||
|
line = reader.readLine();
|
||||||
|
}
|
||||||
|
while (!"test".equals(line));
|
||||||
|
|
||||||
|
writer.println("cat");
|
||||||
|
writer.flush();
|
||||||
|
line = reader.readLine();
|
||||||
|
assertTrue("Not 'cat' or 'BASH:cat': '" + line + "'",
|
||||||
|
Set.of("BASH:cat", "cat").contains(line));
|
||||||
|
|
||||||
|
writer.println("Hello, cat!");
|
||||||
|
writer.flush();
|
||||||
|
assertEquals("Hello, cat!", reader.readLine()); // echo back
|
||||||
|
assertEquals("Hello, cat!", reader.readLine()); // cat back
|
||||||
|
|
||||||
|
writer.write(3); // should interrupt
|
||||||
|
writer.flush();
|
||||||
|
do {
|
||||||
|
line = reader.readLine();
|
||||||
|
}
|
||||||
|
while (!"^C".equals(line));
|
||||||
|
writer.println("echo test");
|
||||||
|
writer.flush();
|
||||||
|
|
||||||
|
do {
|
||||||
|
line = reader.readLine();
|
||||||
|
}
|
||||||
|
while (!"test".equals(line));
|
||||||
|
|
||||||
|
writer.println("exit 3");
|
||||||
|
writer.flush();
|
||||||
|
assertTrue(Set.of("BASH:exit 3", "exit 3").contains(reader.readLine()));
|
||||||
|
|
||||||
|
assertEquals(3, bash.waitExited(2, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLocalEchoOn() throws IOException {
|
||||||
|
try (UnixPty pty = openpty()) {
|
||||||
|
pty.getChild().nullSession();
|
||||||
|
|
||||||
|
PrintWriter writer = new PrintWriter(pty.getParent().getOutputStream());
|
||||||
|
BufferedReader reader =
|
||||||
|
new BufferedReader(new InputStreamReader(pty.getParent().getInputStream()));
|
||||||
|
|
||||||
|
writer.println("Hello, World!");
|
||||||
|
writer.flush();
|
||||||
|
assertEquals("Hello, World!", reader.readLine());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLocalEchoOff() throws IOException {
|
||||||
|
try (UnixPty pty = openpty()) {
|
||||||
|
pty.getChild().nullSession(Echo.OFF);
|
||||||
|
|
||||||
|
PrintWriter writerP = new PrintWriter(pty.getParent().getOutputStream());
|
||||||
|
PrintWriter writerC = new PrintWriter(pty.getChild().getOutputStream());
|
||||||
|
BufferedReader reader =
|
||||||
|
new BufferedReader(new InputStreamReader(pty.getParent().getInputStream()));
|
||||||
|
|
||||||
|
writerP.println("Hello, World!");
|
||||||
|
writerP.flush();
|
||||||
|
writerC.println("Good bye!");
|
||||||
|
writerC.flush();
|
||||||
|
|
||||||
|
assertEquals("Good bye!", reader.readLine());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -70,13 +70,14 @@ public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerTest {
|
||||||
PtySession session = pty.getChild().session(new String[] { "/usr/bin/bash" }, env);
|
PtySession session = pty.getChild().session(new String[] { "/usr/bin/bash" }, env);
|
||||||
|
|
||||||
PtyParent parent = pty.getParent();
|
PtyParent parent = pty.getParent();
|
||||||
|
PtyChild child = pty.getChild();
|
||||||
try (Terminal term = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
try (Terminal term = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
||||||
parent.getInputStream(), parent.getOutputStream())) {
|
parent.getInputStream(), parent.getOutputStream())) {
|
||||||
term.addTerminalListener(new TerminalListener() {
|
term.addTerminalListener(new TerminalListener() {
|
||||||
@Override
|
@Override
|
||||||
public void resized(short cols, short rows) {
|
public void resized(short cols, short rows) {
|
||||||
System.err.println("resized: " + cols + "x" + rows);
|
System.err.println("resized: " + cols + "x" + rows);
|
||||||
parent.setWindowSize(cols, rows);
|
child.setWindowSize(cols, rows);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
session.waitExited();
|
session.waitExited();
|
||||||
|
@ -101,13 +102,14 @@ public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerTest {
|
||||||
pty.getChild().session(new String[] { "C:\\Windows\\system32\\cmd.exe" }, env);
|
pty.getChild().session(new String[] { "C:\\Windows\\system32\\cmd.exe" }, env);
|
||||||
|
|
||||||
PtyParent parent = pty.getParent();
|
PtyParent parent = pty.getParent();
|
||||||
|
PtyChild child = pty.getChild();
|
||||||
try (Terminal term = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
try (Terminal term = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
||||||
parent.getInputStream(), parent.getOutputStream())) {
|
parent.getInputStream(), parent.getOutputStream())) {
|
||||||
term.addTerminalListener(new TerminalListener() {
|
term.addTerminalListener(new TerminalListener() {
|
||||||
@Override
|
@Override
|
||||||
public void resized(short cols, short rows) {
|
public void resized(short cols, short rows) {
|
||||||
System.err.println("resized: " + cols + "x" + rows);
|
System.err.println("resized: " + cols + "x" + rows);
|
||||||
parent.setWindowSize(cols, rows);
|
child.setWindowSize(cols, rows);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
session.waitExited();
|
session.waitExited();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue