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.impl.GdbManagerImpl;
|
||||
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
|
||||
|
@ -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
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* @param listener the listener to add
|
||||
|
|
|
@ -551,7 +551,7 @@ def putmem_state(address, length, state, pages=True):
|
|||
inf = gdb.selected_inferior()
|
||||
base, addr = STATE.trace.memory_mapper.map(inf, start)
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
@ -17,11 +17,10 @@ from concurrent.futures import Future, Executor
|
|||
from contextlib import contextmanager
|
||||
import re
|
||||
|
||||
import gdb
|
||||
from ghidratrace import sch
|
||||
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
|
||||
|
||||
import gdb
|
||||
|
||||
from . import commands, hooks, util
|
||||
|
||||
|
||||
|
@ -690,8 +689,8 @@ def read_mem(inferior: sch.Schema('Inferior'), range: AddressRange):
|
|||
gdb.execute(
|
||||
f'ghidra trace putmem 0x{offset_start:x} {range.length()}')
|
||||
except:
|
||||
commands.putmem_state(
|
||||
offset_start, offset_start+range.length() - 1, 'error')
|
||||
gdb.execute(
|
||||
f'ghidra trace putmem-state 0x{offset_start:x} {range.length()} error')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
|
|
|
@ -23,7 +23,8 @@ import org.junit.Ignore;
|
|||
|
||||
import agent.gdb.manager.GdbManager;
|
||||
import ghidra.pty.PtySession;
|
||||
import ghidra.pty.linux.LinuxPty;
|
||||
import ghidra.pty.linux.LinuxIoctls;
|
||||
import ghidra.pty.unix.UnixPty;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@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;
|
||||
|
||||
@Override
|
||||
protected CompletableFuture<Void> startManager(GdbManager manager) {
|
||||
try {
|
||||
ptyUserGdb = LinuxPty.openpty();
|
||||
ptyUserGdb = UnixPty.openpty(LinuxIoctls.INSTANCE);
|
||||
manager.start(null);
|
||||
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.
|
||||
##
|
||||
from ghidratrace.client import Address, RegVal
|
||||
|
||||
import lldb
|
||||
|
||||
from . import util
|
||||
|
||||
|
||||
# NOTE: This map is derived from the ldefs using a script
|
||||
language_map = {
|
||||
'aarch64': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A'],
|
||||
'armv7': ['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'],
|
||||
'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'],
|
||||
'arm64e': ['ARM:BE:64:v8', 'ARM:LE:64:v8'],
|
||||
'arm64e': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:v8A'],
|
||||
'i386': ['x86:LE:32:default'],
|
||||
'thumbv7': ['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',
|
||||
}
|
||||
|
||||
x86_compiler_map = {
|
||||
default_compiler_map = {
|
||||
'freebsd': 'gcc',
|
||||
'linux': 'gcc',
|
||||
'netbsd': 'gcc',
|
||||
|
@ -55,10 +55,12 @@ x86_compiler_map = {
|
|||
}
|
||||
|
||||
compiler_map = {
|
||||
'DATA:BE:64:default': data64_compiler_map,
|
||||
'DATA:LE:64:default': data64_compiler_map,
|
||||
'x86:LE:32:default': x86_compiler_map,
|
||||
'x86:LE:64:default': x86_compiler_map,
|
||||
'DATA:BE:64:': data64_compiler_map,
|
||||
'DATA:LE:64:': data64_compiler_map,
|
||||
'x86:LE:32:': default_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
|
||||
|
||||
# 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'
|
||||
comp_map = compiler_map[lang]
|
||||
comp_map = compiler_map[matched_lang[0]]
|
||||
osabi = get_osabi()
|
||||
if osabi in comp_map:
|
||||
return comp_map[osabi]
|
||||
matched_osabi = sorted(
|
||||
(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:
|
||||
return comp_map[None]
|
||||
return 'default'
|
||||
|
@ -161,7 +171,8 @@ class DefaultMemoryMapper(object):
|
|||
def map_back(self, proc: lldb.SBProcess, address: Address) -> int:
|
||||
if address.space == self.defaultSpace:
|
||||
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')
|
||||
|
@ -203,7 +214,7 @@ class DefaultRegisterMapper(object):
|
|||
|
||||
def map_value(self, proc, name, value):
|
||||
try:
|
||||
### TODO: this seems half-baked
|
||||
# TODO: this seems half-baked
|
||||
av = value.to_bytes(8, "big")
|
||||
except e:
|
||||
raise ValueError("Cannot convert {}'s value: '{}', type: '{}'"
|
||||
|
@ -258,4 +269,3 @@ def compute_register_mapper(lang):
|
|||
if ':LE:' in lang:
|
||||
return DEFAULT_LE_REGISTER_MAPPER
|
||||
return register_mappers[lang]
|
||||
|
||||
|
|
|
@ -14,20 +14,23 @@
|
|||
# limitations under the License.
|
||||
##
|
||||
from contextlib import contextmanager
|
||||
import functools
|
||||
import inspect
|
||||
import os.path
|
||||
import shlex
|
||||
import socket
|
||||
import time
|
||||
import sys
|
||||
import time
|
||||
|
||||
import psutil
|
||||
|
||||
from ghidratrace import sch
|
||||
from ghidratrace.client import Client, Address, AddressRange, TraceObject
|
||||
import psutil
|
||||
|
||||
import lldb
|
||||
|
||||
from . import arch, hooks, methods, util
|
||||
|
||||
|
||||
PAGE_SIZE = 4096
|
||||
|
||||
DEFAULT_REGISTER_BANK = "General Purpose Registers"
|
||||
|
@ -119,59 +122,124 @@ class State(object):
|
|||
STATE = State()
|
||||
|
||||
if __name__ == '__main__':
|
||||
lldb.SBDebugger.InitializeWithErrorHandling();
|
||||
lldb.SBDebugger.InitializeWithErrorHandling()
|
||||
lldb.debugger = lldb.SBDebugger.Create()
|
||||
elif lldb.debugger:
|
||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_connect "ghidra_trace_connect"')
|
||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_listen "ghidra_trace_listen"')
|
||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_disconnect "ghidra_trace_disconnect"')
|
||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_start "ghidra_trace_start"')
|
||||
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('command script add -f ghidralldb.commands.ghidra_trace_info "ghidra_trace_info"')
|
||||
lldb.debugger.HandleCommand('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_txstart "ghidra_trace_txstart"')
|
||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_txcommit "ghidra_trace_txcommit"')
|
||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_txabort "ghidra_trace_txabort"')
|
||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_txopen "ghidra_trace_txopen"')
|
||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_save "ghidra_trace_save"')
|
||||
lldb.debugger.HandleCommand('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_set_snap "ghidra_trace_set_snap"')
|
||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_putmem "ghidra_trace_putmem"')
|
||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_putval "ghidra_trace_putval"')
|
||||
lldb.debugger.HandleCommand('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_delmem "ghidra_trace_delmem"')
|
||||
lldb.debugger.HandleCommand('command script add -f ghidralldb.commands.ghidra_trace_putreg "ghidra_trace_putreg"')
|
||||
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(
|
||||
'command container add -h "Commands for connecting to Ghidra" ghidra')
|
||||
lldb.debugger.HandleCommand(
|
||||
'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_connect ghidra trace connect')
|
||||
lldb.debugger.HandleCommand(
|
||||
'command script add -f ghidralldb.commands.ghidra_trace_listen ghidra trace listen')
|
||||
lldb.debugger.HandleCommand(
|
||||
'command script add -f ghidralldb.commands.ghidra_trace_disconnect ghidra trace disconnect')
|
||||
lldb.debugger.HandleCommand(
|
||||
'command script add -f ghidralldb.commands.ghidra_trace_start ghidra trace start')
|
||||
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(
|
||||
'command script add -f ghidralldb.commands.ghidra_trace_info ghidra trace info')
|
||||
lldb.debugger.HandleCommand(
|
||||
'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_txstart ghidra trace tx-start')
|
||||
lldb.debugger.HandleCommand(
|
||||
'command script add -f ghidralldb.commands.ghidra_trace_txcommit ghidra trace tx-commit')
|
||||
lldb.debugger.HandleCommand(
|
||||
'command script add -f ghidralldb.commands.ghidra_trace_txabort ghidra trace tx-abort')
|
||||
lldb.debugger.HandleCommand(
|
||||
'command script add -f ghidralldb.commands.ghidra_trace_txopen ghidra trace tx-open')
|
||||
lldb.debugger.HandleCommand(
|
||||
'command script add -f ghidralldb.commands.ghidra_trace_save ghidra trace save')
|
||||
lldb.debugger.HandleCommand(
|
||||
'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_set_snap ghidra trace set-snap')
|
||||
lldb.debugger.HandleCommand(
|
||||
'command script add -f ghidralldb.commands.ghidra_trace_putmem ghidra trace putmem')
|
||||
lldb.debugger.HandleCommand(
|
||||
'command script add -f ghidralldb.commands.ghidra_trace_putval ghidra trace putval')
|
||||
lldb.debugger.HandleCommand(
|
||||
'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_delmem ghidra trace delmem')
|
||||
lldb.debugger.HandleCommand(
|
||||
'command script add -f ghidralldb.commands.ghidra_trace_putreg ghidra trace putreg')
|
||||
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.SetAsync(True)
|
||||
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):
|
||||
"""
|
||||
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'
|
||||
"""
|
||||
|
||||
args = shlex.split(command)
|
||||
|
||||
STATE.require_no_client()
|
||||
address = command if len(command) > 0 else None
|
||||
if address is None:
|
||||
raise RuntimeError("'ghidra_trace_connect': missing required argument 'address'")
|
||||
if len(args) != 1:
|
||||
raise RuntimeError(
|
||||
"ghidra trace connect: missing required argument 'address'")
|
||||
address = args[0]
|
||||
|
||||
parts = address.split(':')
|
||||
if len(parts) != 2:
|
||||
|
@ -191,11 +262,13 @@ def ghidra_trace_connect(debugger, command, result, internal_dict):
|
|||
try:
|
||||
c = socket.socket()
|
||||
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:
|
||||
raise RuntimeError("port must be numeric")
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_listen(debugger, command, result, internal_dict):
|
||||
"""
|
||||
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)))
|
||||
host, port = s.getsockname()
|
||||
s.listen(1)
|
||||
print("Listening at {}:{}...\n".format(host, port))
|
||||
print(f"Listening at {host}:{port}...")
|
||||
c, (chost, cport) = s.accept()
|
||||
s.close()
|
||||
print("Connection from {}:{}\n".format(chost, cport))
|
||||
STATE.client = Client(c, "lldb-" + util.LLDB_VERSION.full, methods.REGISTRY)
|
||||
print(f"Connection from {chost}:{cport}")
|
||||
STATE.client = Client(
|
||||
c, "lldb-" + util.LLDB_VERSION.full, methods.REGISTRY)
|
||||
except ValueError:
|
||||
raise RuntimeError("port must be numeric")
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_disconnect(debugger, command, result, internal_dict):
|
||||
"""Disconnect LLDB from Ghidra for tracing"""
|
||||
|
||||
|
@ -266,6 +341,7 @@ def start_trace(name):
|
|||
util.set_convenience_variable('_ghidra_tracing', "true")
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_start(debugger, command, result, internal_dict):
|
||||
"""Start a Trace in Ghidra"""
|
||||
|
||||
|
@ -277,6 +353,7 @@ def ghidra_trace_start(debugger, command, result, internal_dict):
|
|||
start_trace(name)
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_stop(debugger, command, result, internal_dict):
|
||||
"""Stop the Trace in Ghidra"""
|
||||
|
||||
|
@ -284,6 +361,7 @@ def ghidra_trace_stop(debugger, command, result, internal_dict):
|
|||
STATE.reset_trace()
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_restart(debugger, command, result, internal_dict):
|
||||
"""Restart or start the Trace in Ghidra"""
|
||||
|
||||
|
@ -297,22 +375,24 @@ def ghidra_trace_restart(debugger, command, result, internal_dict):
|
|||
start_trace(name)
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_info(debugger, command, result, internal_dict):
|
||||
"""Get info about the Ghidra connection"""
|
||||
|
||||
result = {}
|
||||
if STATE.client is None:
|
||||
print("Not connected to Ghidra\n")
|
||||
print("Not connected to Ghidra")
|
||||
return
|
||||
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:
|
||||
print("No trace\n")
|
||||
print("No trace")
|
||||
return
|
||||
print("Trace active\n")
|
||||
print("Trace active")
|
||||
return result
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_info_lcsp(debugger, command, result, internal_dict):
|
||||
"""
|
||||
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()
|
||||
print("Selected Ghidra language: {}\n".format(language))
|
||||
print("Selected Ghidra compiler: {}\n".format(compiler))
|
||||
print(f"Selected Ghidra language: {language}")
|
||||
print(f"Selected Ghidra compiler: {compiler}")
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_txstart(debugger, command, result, internal_dict):
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_txcommit(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Commit the current transaction
|
||||
|
@ -344,6 +426,7 @@ def ghidra_trace_txcommit(debugger, command, result, internal_dict):
|
|||
STATE.reset_tx()
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_txabort(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Abort the current transaction
|
||||
|
@ -352,7 +435,7 @@ def ghidra_trace_txabort(debugger, command, result, internal_dict):
|
|||
"""
|
||||
|
||||
tx = STATE.require_tx()
|
||||
print("Aborting trace transaction!\n")
|
||||
print("Aborting trace transaction!")
|
||||
tx.abort()
|
||||
STATE.reset_tx()
|
||||
|
||||
|
@ -365,37 +448,23 @@ def open_tracked_tx(description):
|
|||
STATE.reset_tx()
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_txopen(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Run a command with an open transaction
|
||||
|
||||
If possible, use this in the following idiom to ensure your transactions
|
||||
are closed:
|
||||
|
||||
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.
|
||||
This is generally useful only when executing a single 'put' command, or
|
||||
when executing a custom command that performs several puts.
|
||||
"""
|
||||
|
||||
items = command.split(" ");
|
||||
items = command.split(" ")
|
||||
description = items[0]
|
||||
command = items[1]
|
||||
with open_tracked_tx(description):
|
||||
lldb.debugger.HandleCommand(command);
|
||||
lldb.debugger.HandleCommand(command)
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_save(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Save the current trace
|
||||
|
@ -404,6 +473,7 @@ def ghidra_trace_save(debugger, command, result, internal_dict):
|
|||
STATE.require_trace().save()
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_new_snap(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Create a new snapshot
|
||||
|
@ -413,15 +483,15 @@ def ghidra_trace_new_snap(debugger, command, result, internal_dict):
|
|||
|
||||
description = str(command)
|
||||
STATE.require_tx()
|
||||
return {'snap': STATE.require_trace().snapshot(description)}
|
||||
|
||||
# TODO: A convenience var for the current snapshot
|
||||
# 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.)
|
||||
# ghidra_trace_trace start/stop/restart
|
||||
# ghidra trace trace start/stop/restart
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_set_snap(debugger, command, result, internal_dict):
|
||||
"""
|
||||
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))
|
||||
|
||||
|
||||
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()
|
||||
if pages:
|
||||
start = start // PAGE_SIZE * PAGE_SIZE
|
||||
end = (end + PAGE_SIZE - 1) // PAGE_SIZE * PAGE_SIZE
|
||||
start, end = quantize_pages(start, end)
|
||||
proc = util.get_process()
|
||||
error = lldb.SBError()
|
||||
if end - start <= 0:
|
||||
return {'count': 0}
|
||||
return
|
||||
buf = proc.ReadMemory(start, end - start, error)
|
||||
|
||||
count = 0
|
||||
|
@ -454,32 +527,34 @@ def put_bytes(start, end, pages, from_tty):
|
|||
if base != addr.space:
|
||||
trace.create_overlay_space(base, addr.space)
|
||||
count = trace.put_bytes(addr, buf)
|
||||
if from_tty:
|
||||
print("Wrote {} bytes\n".format(count))
|
||||
return {'count': count}
|
||||
if result is not None:
|
||||
result.PutCString(f"Wrote {count} bytes")
|
||||
else:
|
||||
raise RuntimeError(f"Cannot read memory at {start:x}")
|
||||
|
||||
|
||||
def eval_address(address):
|
||||
try:
|
||||
return util.parse_and_eval(address)
|
||||
except e:
|
||||
raise RuntimeError("Cannot convert '{}' to address".format(address))
|
||||
except BaseException as e:
|
||||
raise RuntimeError(f"Cannot convert '{address}' to address: {e}")
|
||||
|
||||
|
||||
def eval_range(address, length):
|
||||
start = eval_address(address)
|
||||
try:
|
||||
end = start + util.parse_and_eval(length)
|
||||
except e:
|
||||
raise RuntimeError("Cannot convert '{}' to length".format(length))
|
||||
except BaseException as e:
|
||||
raise RuntimeError(f"Cannot convert '{length}' to length: {e}")
|
||||
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)
|
||||
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):
|
||||
"""
|
||||
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
|
||||
|
||||
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):
|
||||
"""
|
||||
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()
|
||||
try:
|
||||
start = util.parse_and_eval(value)
|
||||
except e:
|
||||
raise RuntimeError("Value '{}' has no address".format(value))
|
||||
except BaseExcepion as e:
|
||||
raise RuntimeError(f"Value '{value}' has no address: {e}")
|
||||
end = start + int(start.GetType().GetByteSize())
|
||||
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):
|
||||
"""
|
||||
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.require_tx()
|
||||
STATE.trace.validate_state(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)
|
||||
putmem_state(address, length, state)
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_delmem(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Delete the given range of memory from the Ghidra trace.
|
||||
|
@ -568,12 +652,14 @@ def putreg(frame, bank):
|
|||
values = []
|
||||
for i in range(0, bank.GetNumChildren()):
|
||||
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()))
|
||||
# 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):
|
||||
"""
|
||||
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()
|
||||
frame = util.selected_frame()
|
||||
regs = frame.GetRegisters()
|
||||
if group is not 'all':
|
||||
if group != 'all':
|
||||
bank = regs.GetFirstValueByName(group)
|
||||
return putreg(frame, bank)
|
||||
|
||||
|
@ -595,6 +681,7 @@ def ghidra_trace_putreg(debugger, command, result, internal_dict):
|
|||
putreg(frame, bank)
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_delreg(debugger, command, result, internal_dict):
|
||||
"""
|
||||
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()
|
||||
proc = util.get_process()
|
||||
frame = util.selected_frame()
|
||||
space = 'Processes[{}].Threads[{}].Stack[{}].Registers'.format(
|
||||
proc.GetProcessID(), util.selected_thread().GetThreadID(), frame.GetFrameID()
|
||||
)
|
||||
space = REGS_PATTERN.format(procnum=proc.GetProcessID(), tnum=util.selected_thread().GetThreadID(),
|
||||
level=frame.GetFrameID())
|
||||
mapper = STATE.trace.register_mapper
|
||||
names = []
|
||||
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)
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_create_obj(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Create an object in the Ghidra trace.
|
||||
|
@ -631,10 +718,10 @@ def ghidra_trace_create_obj(debugger, command, result, internal_dict):
|
|||
STATE.require_tx()
|
||||
obj = STATE.trace.create_object(path)
|
||||
obj.insert()
|
||||
print("Created object: id={}, path='{}'\n".format(obj.id, obj.path))
|
||||
return {'id': obj.id, 'path': obj.path}
|
||||
print(f"Created object: id={obj.id}, path='{obj.path}'")
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_insert_obj(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Insert an object into the Ghidra trace.
|
||||
|
@ -646,10 +733,10 @@ def ghidra_trace_insert_obj(debugger, command, result, internal_dict):
|
|||
# humans.
|
||||
STATE.require_tx()
|
||||
span = STATE.trace.proxy_object_path(path).insert()
|
||||
print("Inserted object: lifespan={}\n".format(span))
|
||||
return {'lifespan': span}
|
||||
print(f"Inserted object: lifespan={span}")
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_remove_obj(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Remove an object from the Ghidra trace.
|
||||
|
@ -673,7 +760,8 @@ def to_bytes(value, type):
|
|||
|
||||
def to_string(value, type, encoding, full):
|
||||
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)
|
||||
|
||||
|
||||
|
@ -767,10 +855,10 @@ def eval_value(value, schema=None):
|
|||
proc = util.get_process()
|
||||
base, addr = STATE.trace.memory_mapper.map(proc, offset)
|
||||
return (base, addr), sch.ADDRESS
|
||||
raise ValueError(
|
||||
"Cannot convert ({}): '{}', value='{}'".format(schema, value, val))
|
||||
raise ValueError(f"Cannot convert ({schema}): '{value}', value='{val}'")
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_set_value(debugger, command, result, internal_dict):
|
||||
"""
|
||||
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]
|
||||
key = items[1]
|
||||
value = items[2]
|
||||
if len(items) > 3 and items[3] is not "":
|
||||
if len(items) > 3 and items[3] != "":
|
||||
schema = items[3]
|
||||
# This is a horrible hack
|
||||
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)
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_retain_values(debugger, command, result, internal_dict):
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_get_obj(debugger, command, result, internal_dict):
|
||||
"""
|
||||
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()
|
||||
object = trace.get_object(path)
|
||||
print("{}\t{}\n".format(object.id, object.path))
|
||||
return object
|
||||
|
||||
|
||||
|
@ -881,7 +970,7 @@ class TableColumn(object):
|
|||
|
||||
def print_cell(self, i):
|
||||
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):
|
||||
|
@ -901,14 +990,14 @@ class Tabular(object):
|
|||
for rn in range(self.num_rows):
|
||||
for c in self.columns:
|
||||
c.print_cell(rn)
|
||||
print('\n')
|
||||
print('')
|
||||
|
||||
|
||||
def val_repr(value):
|
||||
if isinstance(value, TraceObject):
|
||||
return value.path
|
||||
elif isinstance(value, Address):
|
||||
return '{}:{:08x}'.format(value.space, value.offset)
|
||||
return f'{value.space}:{value.offset:08x}'
|
||||
return repr(value)
|
||||
|
||||
|
||||
|
@ -920,6 +1009,7 @@ def print_values(values):
|
|||
table.print_table()
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_get_values(debugger, command, result, internal_dict):
|
||||
"""
|
||||
List all values matching a given path pattern.
|
||||
|
@ -933,6 +1023,7 @@ def ghidra_trace_get_values(debugger, command, result, internal_dict):
|
|||
return values
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_get_values_rng(debugger, command, result, internal_dict):
|
||||
"""
|
||||
List all values intersecting a given address range.
|
||||
|
@ -962,13 +1053,15 @@ def activate(path=None):
|
|||
else:
|
||||
frame = util.selected_frame()
|
||||
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:
|
||||
path = FRAME_PATTERN.format(
|
||||
procnum=proc.GetProcessID(), tnum=t.GetThreadID(), level=frame.GetFrameID())
|
||||
trace.proxy_object_path(path).activate()
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_activate(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Activate an object in Ghidra's GUI.
|
||||
|
@ -982,6 +1075,7 @@ def ghidra_trace_activate(debugger, command, result, internal_dict):
|
|||
activate(path)
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_disassemble(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Disassemble starting at the given seed.
|
||||
|
@ -1000,8 +1094,7 @@ def ghidra_trace_disassemble(debugger, command, result, internal_dict):
|
|||
trace.create_overlay_space(base, addr.space)
|
||||
|
||||
length = STATE.trace.disassemble(addr)
|
||||
print("Disassembled {} bytes\n".format(length))
|
||||
return {'length': length}
|
||||
print(f"Disassembled {length} bytes")
|
||||
|
||||
|
||||
def compute_proc_state(proc=None):
|
||||
|
@ -1021,6 +1114,7 @@ def put_processes():
|
|||
procobj.insert()
|
||||
STATE.trace.proxy_object_path(PROCESSES_PATH).retain_values(keys)
|
||||
|
||||
|
||||
def put_state(event_process):
|
||||
STATE.require_no_tx()
|
||||
STATE.tx = STATE.require_trace().start_tx("state", undoable=False)
|
||||
|
@ -1033,6 +1127,7 @@ def put_state(event_process):
|
|||
STATE.reset_tx()
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_put_processes(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Put the list of processes into the trace's Processes list.
|
||||
|
@ -1050,11 +1145,12 @@ def put_available():
|
|||
procobj = STATE.trace.create_object(ppath)
|
||||
keys.append(AVAILABLE_KEY_PATTERN.format(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()
|
||||
STATE.trace.proxy_object_path(AVAILABLES_PATH).retain_values(keys)
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_put_available(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Put the list of available processes into the trace's Available list.
|
||||
|
@ -1108,9 +1204,11 @@ def put_single_breakpoint(b, ibobj, proc, ikeys):
|
|||
brkobj.retain_values(keys)
|
||||
brkobj.insert()
|
||||
|
||||
|
||||
def put_single_watchpoint(b, ibobj, proc, ikeys):
|
||||
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)
|
||||
desc = util.get_description(b, level=0)
|
||||
brkobj.set_value('_expression', desc)
|
||||
|
@ -1148,6 +1246,7 @@ def put_breakpoints():
|
|||
STATE.trace.proxy_object_path(BREAKPOINTS_PATH).retain_values(keys)
|
||||
ibobj.retain_values(ikeys)
|
||||
|
||||
|
||||
def put_watchpoints():
|
||||
target = util.get_target()
|
||||
proc = util.get_process()
|
||||
|
@ -1163,6 +1262,7 @@ def put_watchpoints():
|
|||
STATE.trace.proxy_object_path(WATCHPOINTS_PATH).retain_values(keys)
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_put_breakpoints(debugger, command, result, internal_dict):
|
||||
"""
|
||||
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:
|
||||
put_breakpoints()
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_put_watchpoints(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Put the current process's watchpoints into the trace.
|
||||
|
@ -1193,6 +1295,7 @@ def put_environment():
|
|||
envobj.insert()
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_put_environment(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Put some environment indicators into the Ghidra trace
|
||||
|
@ -1214,7 +1317,8 @@ def put_regions():
|
|||
mapper = STATE.trace.memory_mapper
|
||||
keys = []
|
||||
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))
|
||||
regobj = STATE.trace.create_object(rpath)
|
||||
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)
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_put_regions(debugger, command, result, internal_dict):
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_put_modules(debugger, command, result, internal_dict):
|
||||
"""
|
||||
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):
|
||||
# TODO: This does not seem to work - currently supplanted by proc.is_running
|
||||
if t.IsSuspended():
|
||||
return 'SUSPENDED'
|
||||
if t.IsStopped():
|
||||
|
@ -1320,17 +1427,18 @@ def put_threads():
|
|||
proc = util.get_process()
|
||||
keys = []
|
||||
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)
|
||||
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())
|
||||
tid = t.GetThreadID()
|
||||
tobj.set_value('_tid', tid)
|
||||
tidstr = ('0x{:x}' if radix ==
|
||||
16 else '0{:o}' if radix == 8 else '{}').format(tid)
|
||||
tobj.set_value('_short_display', '[{}.{}:{}]'.format(
|
||||
proc.GetProcessID(), t.GetThreadID(), tidstr))
|
||||
tidstr = f'0x{tid:x}' if radix == 16 else f'0{tid:o}' if radix == 8 else f'{tid}'
|
||||
tobj.set_value('_short_display',
|
||||
f'[{proc.GetProcessID()}.{t.GetThreadID()}:{tidstr}]')
|
||||
tobj.set_value('_display', compute_thread_display(t))
|
||||
tobj.insert()
|
||||
STATE.trace.proxy_object_path(
|
||||
|
@ -1342,13 +1450,15 @@ def put_event_thread():
|
|||
# Assumption: Event thread is selected by lldb upon stopping
|
||||
t = util.selected_thread()
|
||||
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)
|
||||
else:
|
||||
tobj = None
|
||||
STATE.trace.proxy_object_path('').set_value('_event_thread', tobj)
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_put_threads(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Put the current process's threads into the Ghidra trace
|
||||
|
@ -1383,6 +1493,7 @@ def put_frames():
|
|||
procnum=proc.GetProcessID(), tnum=t.GetThreadID())).retain_values(keys)
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_put_frames(debugger, command, result, internal_dict):
|
||||
"""
|
||||
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()
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_put_all(debugger, command, result, internal_dict):
|
||||
"""
|
||||
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()
|
||||
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, "$sp 1", result, internal_dict)
|
||||
put_processes()
|
||||
|
@ -1414,6 +1527,7 @@ def ghidra_trace_put_all(debugger, command, result, internal_dict):
|
|||
put_available()
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_install_hooks(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Install hooks to trace in Ghidra
|
||||
|
@ -1422,6 +1536,7 @@ def ghidra_trace_install_hooks(debugger, command, result, internal_dict):
|
|||
hooks.install_hooks()
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_remove_hooks(debugger, command, result, internal_dict):
|
||||
"""
|
||||
Remove hooks to trace in Ghidra
|
||||
|
@ -1434,6 +1549,7 @@ def ghidra_trace_remove_hooks(debugger, command, result, internal_dict):
|
|||
hooks.remove_hooks()
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_sync_enable(debugger, command, result, internal_dict):
|
||||
"""
|
||||
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()
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_trace_sync_disable(debugger, command, result, internal_dict):
|
||||
"""
|
||||
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()
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_util_wait_stopped(debugger, command, result, internal_dict):
|
||||
"""
|
||||
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')
|
||||
|
||||
|
||||
@convert_errors
|
||||
def ghidra_util_mark(debugger, command, result, internal_dict):
|
||||
print(command)
|
||||
|
|
|
@ -13,15 +13,17 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
import time
|
||||
import threading
|
||||
import time
|
||||
|
||||
import lldb
|
||||
|
||||
from . import commands, util
|
||||
|
||||
|
||||
ALL_EVENTS = 0xFFFF
|
||||
|
||||
|
||||
class HookState(object):
|
||||
__slots__ = ('installed', 'mem_catchpoint')
|
||||
|
||||
|
@ -31,7 +33,8 @@ class HookState(object):
|
|||
|
||||
|
||||
class ProcessState(object):
|
||||
__slots__ = ('first', 'regions', 'modules', 'threads', 'breaks', 'watches', 'visited')
|
||||
__slots__ = ('first', 'regions', 'modules', 'threads',
|
||||
'breaks', 'watches', 'visited')
|
||||
|
||||
def __init__(self):
|
||||
self.first = True
|
||||
|
@ -64,9 +67,10 @@ class ProcessState(object):
|
|||
hashable_frame = (thread.GetThreadID(), frame.GetFrameID())
|
||||
if first or hashable_frame not in self.visited:
|
||||
banks = frame.GetRegisters()
|
||||
commands.putreg(frame, banks.GetFirstValueByName(commands.DEFAULT_REGISTER_BANK))
|
||||
commands.putmem("$pc", "1", from_tty=False)
|
||||
commands.putmem("$sp", "1", from_tty=False)
|
||||
commands.putreg(frame, banks.GetFirstValueByName(
|
||||
commands.DEFAULT_REGISTER_BANK))
|
||||
commands.putmem("$pc", "1", result=None)
|
||||
commands.putmem("$sp", "1", result=None)
|
||||
self.visited.add(hashable_frame)
|
||||
if first or self.regions or self.threads or self.modules:
|
||||
# Sections, memory syscalls, or stack allocations
|
||||
|
@ -117,10 +121,11 @@ HOOK_STATE = HookState()
|
|||
BRK_STATE = BrkState()
|
||||
PROC_STATE = {}
|
||||
|
||||
|
||||
def process_event(self, listener, event):
|
||||
try:
|
||||
desc = util.get_description(event)
|
||||
#event_process = lldb.SBProcess_GetProcessFromEvent(event)
|
||||
# print('Event:', desc)
|
||||
event_process = util.get_process()
|
||||
if event_process not in PROC_STATE:
|
||||
PROC_STATE[event_process.GetProcessID()] = ProcessState()
|
||||
|
@ -128,35 +133,29 @@ def process_event(self, listener, event):
|
|||
if rc is False:
|
||||
print("add listener for process failed")
|
||||
|
||||
# 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()
|
||||
if lldb.SBTarget.EventIsTargetEvent(event):
|
||||
print('Event:', desc)
|
||||
if (type & lldb.SBTarget.eBroadcastBitBreakpointChanged) != 0:
|
||||
print("eBroadcastBitBreakpointChanged")
|
||||
return on_breakpoint_modified(event)
|
||||
if (type & lldb.SBTarget.eBroadcastBitWatchpointChanged) != 0:
|
||||
print("eBroadcastBitWatchpointChanged")
|
||||
return on_watchpoint_modified(event)
|
||||
if (type & lldb.SBTarget.eBroadcastBitModulesLoaded) != 0:
|
||||
print("eBroadcastBitModulesLoaded")
|
||||
return on_new_objfile(event)
|
||||
if (type & lldb.SBTarget.eBroadcastBitModulesUnloaded) != 0:
|
||||
print("eBroadcastBitModulesUnloaded")
|
||||
return on_free_objfile(event)
|
||||
if (type & lldb.SBTarget.eBroadcastBitSymbolsLoaded) != 0:
|
||||
print("eBroadcastBitSymbolsLoaded")
|
||||
return True
|
||||
if lldb.SBProcess.EventIsProcessEvent(event):
|
||||
if (type & lldb.SBProcess.eBroadcastBitStateChanged) != 0:
|
||||
print("eBroadcastBitStateChanged")
|
||||
if not event_process.is_alive:
|
||||
return on_exited(event)
|
||||
if event_process.is_stopped:
|
||||
return on_stop(event)
|
||||
return True
|
||||
if (type & lldb.SBProcess.eBroadcastBitInterrupt) != 0:
|
||||
print("eBroadcastBitInterrupt")
|
||||
if event_process.is_stopped:
|
||||
return on_stop(event)
|
||||
if (type & lldb.SBProcess.eBroadcastBitSTDOUT) != 0:
|
||||
|
@ -164,133 +163,95 @@ def process_event(self, listener, event):
|
|||
if (type & lldb.SBProcess.eBroadcastBitSTDERR) != 0:
|
||||
return True
|
||||
if (type & lldb.SBProcess.eBroadcastBitProfileData) != 0:
|
||||
print("eBroadcastBitProfileData")
|
||||
return True
|
||||
if (type & lldb.SBProcess.eBroadcastBitStructuredData) != 0:
|
||||
print("eBroadcastBitStructuredData")
|
||||
return True
|
||||
# NB: Thread events not currently processes
|
||||
if lldb.SBThread.EventIsThreadEvent(event):
|
||||
print('Event:', desc)
|
||||
if (type & lldb.SBThread.eBroadcastBitStackChanged) != 0:
|
||||
print("eBroadcastBitStackChanged")
|
||||
return on_frame_selected()
|
||||
if (type & lldb.SBThread.eBroadcastBitThreadSuspended) != 0:
|
||||
print("eBroadcastBitThreadSuspended")
|
||||
if event_process.is_stopped:
|
||||
return on_stop(event)
|
||||
if (type & lldb.SBThread.eBroadcastBitThreadResumed) != 0:
|
||||
print("eBroadcastBitThreadResumed")
|
||||
return on_cont(event)
|
||||
if (type & lldb.SBThread.eBroadcastBitSelectedFrameChanged) != 0:
|
||||
print("eBroadcastBitSelectedFrameChanged")
|
||||
return on_frame_selected()
|
||||
if (type & lldb.SBThread.eBroadcastBitThreadSelected) != 0:
|
||||
print("eBroadcastBitThreadSelected")
|
||||
return on_thread_selected()
|
||||
if lldb.SBBreakpoint.EventIsBreakpointEvent(event):
|
||||
print('Event:', desc)
|
||||
btype = lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event);
|
||||
bpt = lldb.SBBreakpoint.GetBreakpointFromEvent(event);
|
||||
btype = lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event)
|
||||
bpt = lldb.SBBreakpoint.GetBreakpointFromEvent(event)
|
||||
if btype is lldb.eBreakpointEventTypeAdded:
|
||||
print("eBreakpointEventTypeAdded")
|
||||
return on_breakpoint_created(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeAutoContinueChanged:
|
||||
print("elldb.BreakpointEventTypeAutoContinueChanged")
|
||||
return on_breakpoint_modified(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeCommandChanged:
|
||||
print("eBreakpointEventTypeCommandChanged")
|
||||
return on_breakpoint_modified(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeConditionChanged:
|
||||
print("eBreakpointEventTypeConditionChanged")
|
||||
return on_breakpoint_modified(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeDisabled:
|
||||
print("eBreakpointEventTypeDisabled")
|
||||
return on_breakpoint_modified(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeEnabled:
|
||||
print("eBreakpointEventTypeEnabled")
|
||||
return on_breakpoint_modified(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeIgnoreChanged:
|
||||
print("eBreakpointEventTypeIgnoreChanged")
|
||||
return True
|
||||
if btype is lldb.eBreakpointEventTypeInvalidType:
|
||||
print("eBreakpointEventTypeInvalidType")
|
||||
return True
|
||||
if btype is lldb.eBreakpointEventTypeLocationsAdded:
|
||||
print("eBreakpointEventTypeLocationsAdded")
|
||||
return on_breakpoint_modified(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeLocationsRemoved:
|
||||
print("eBreakpointEventTypeLocationsRemoved")
|
||||
return on_breakpoint_modified(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeLocationsResolved:
|
||||
print("eBreakpointEventTypeLocationsResolved")
|
||||
return on_breakpoint_modified(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeRemoved:
|
||||
print("eBreakpointEventTypeRemoved")
|
||||
return on_breakpoint_deleted(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeThreadChanged:
|
||||
print("eBreakpointEventTypeThreadChanged")
|
||||
return on_breakpoint_modified(bpt)
|
||||
print("UNKNOWN BREAKPOINT EVENT")
|
||||
return True
|
||||
if lldb.SBWatchpoint.EventIsWatchpointEvent(event):
|
||||
print('Event:', desc)
|
||||
btype = lldb.SBWatchpoint.GetWatchpointEventTypeFromEvent(event);
|
||||
bpt = lldb.SBWatchpoint.GetWatchpointFromEvent(eventt);
|
||||
btype = lldb.SBWatchpoint.GetWatchpointEventTypeFromEvent(event)
|
||||
bpt = lldb.SBWatchpoint.GetWatchpointFromEvent(eventt)
|
||||
if btype is lldb.eWatchpointEventTypeAdded:
|
||||
print("eWatchpointEventTypeAdded")
|
||||
return on_watchpoint_added(bpt)
|
||||
if btype is lldb.eWatchpointEventTypeCommandChanged:
|
||||
print("eWatchpointEventTypeCommandChanged")
|
||||
return on_watchpoint_modified(bpt)
|
||||
if btype is lldb.eWatchpointEventTypeConditionChanged:
|
||||
print("eWatchpointEventTypeConditionChanged")
|
||||
return on_watchpoint_modified(bpt)
|
||||
if btype is lldb.eWatchpointEventTypeDisabled:
|
||||
print("eWatchpointEventTypeDisabled")
|
||||
return on_watchpoint_modified(bpt)
|
||||
if btype is lldb.eWatchpointEventTypeEnabled:
|
||||
print("eWatchpointEventTypeEnabled")
|
||||
return on_watchpoint_modified(bpt)
|
||||
if btype is lldb.eWatchpointEventTypeIgnoreChanged:
|
||||
print("eWatchpointEventTypeIgnoreChanged")
|
||||
return True
|
||||
if btype is lldb.eWatchpointEventTypeInvalidType:
|
||||
print("eWatchpointEventTypeInvalidType")
|
||||
return True
|
||||
if btype is lldb.eWatchpointEventTypeRemoved:
|
||||
print("eWatchpointEventTypeRemoved")
|
||||
return on_watchpoint_deleted(bpt)
|
||||
if btype is lldb.eWatchpointEventTypeThreadChanged:
|
||||
print("eWatchpointEventTypeThreadChanged")
|
||||
return on_watchpoint_modified(bpt)
|
||||
if btype is lldb.eWatchpointEventTypeTypeChanged:
|
||||
print("eWatchpointEventTypeTypeChanged")
|
||||
return on_watchpoint_modified(bpt)
|
||||
print("UNKNOWN WATCHPOINT EVENT")
|
||||
return True
|
||||
if lldb.SBCommandInterpreter.EventIsCommandInterpreterEvent(event):
|
||||
print('Event:', desc)
|
||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitAsynchronousErrorData) != 0:
|
||||
print("eBroadcastBitAsynchronousErrorData")
|
||||
return True
|
||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitAsynchronousOutputData) != 0:
|
||||
print("eBroadcastBitAsynchronousOutputData")
|
||||
return True
|
||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitQuitCommandReceived) != 0:
|
||||
print("eBroadcastBitQuitCommandReceived")
|
||||
return True
|
||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitResetPrompt) != 0:
|
||||
print("eBroadcastBitResetPrompt")
|
||||
return True
|
||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitThreadShouldExit) != 0:
|
||||
print("eBroadcastBitThreadShouldExit")
|
||||
return True
|
||||
print("UNKNOWN EVENT")
|
||||
return True
|
||||
except RuntimeError as e:
|
||||
print(e)
|
||||
|
||||
|
||||
class EventThread(threading.Thread):
|
||||
func = process_event
|
||||
event = lldb.SBEvent()
|
||||
|
@ -329,7 +290,8 @@ class EventThread(threading.Thread):
|
|||
print("add listener for process failed")
|
||||
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:
|
||||
print("add listener for threads failed")
|
||||
return
|
||||
|
@ -344,13 +306,14 @@ class EventThread(threading.Thread):
|
|||
while listener.GetNextEvent(self.event):
|
||||
self.func(listener, self.event)
|
||||
event_recvd = True
|
||||
except Exception as e:
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
proc = util.get_process()
|
||||
if proc is not None and not proc.is_alive:
|
||||
break
|
||||
return
|
||||
|
||||
|
||||
"""
|
||||
# Not sure if this is possible in LLDB...
|
||||
|
||||
|
@ -475,7 +438,7 @@ def on_memory_changed(event):
|
|||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Memory *0x{:08x} changed".format(event.address)):
|
||||
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):
|
||||
|
@ -547,11 +510,13 @@ def on_exited(event):
|
|||
commands.put_event_thread()
|
||||
commands.activate()
|
||||
|
||||
|
||||
def notify_others_breaks(proc):
|
||||
for num, state in PROC_STATE.items():
|
||||
if num != proc.GetProcessID():
|
||||
state.breaks = True
|
||||
|
||||
|
||||
def notify_others_watches(proc):
|
||||
for num, state in PROC_STATE.items():
|
||||
if num != proc.GetProcessID():
|
||||
|
@ -697,6 +662,7 @@ def remove_hooks():
|
|||
return
|
||||
HOOK_STATE.installed = False
|
||||
|
||||
|
||||
def enable_current_process():
|
||||
proc = util.get_process()
|
||||
PROC_STATE[proc.GetProcessID()] = ProcessState()
|
||||
|
|
|
@ -18,7 +18,6 @@ import re
|
|||
|
||||
from ghidratrace import sch
|
||||
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
|
||||
|
||||
import lldb
|
||||
|
||||
from . import commands, util
|
||||
|
@ -66,9 +65,7 @@ def find_proc_by_num(procnum):
|
|||
|
||||
|
||||
def find_proc_by_pattern(object, pattern, err_msg):
|
||||
print(object.path)
|
||||
mat = pattern.fullmatch(object.path)
|
||||
print(mat)
|
||||
if mat is None:
|
||||
raise TypeError(f"{object} is not {err_msg}")
|
||||
procnum = int(mat['procnum'])
|
||||
|
@ -83,6 +80,7 @@ def find_proc_by_procbreak_obj(object):
|
|||
return find_proc_by_pattern(object, PROC_BREAKS_PATTERN,
|
||||
"a BreakpointLocationContainer")
|
||||
|
||||
|
||||
def find_proc_by_procwatch_obj(object):
|
||||
return find_proc_by_pattern(object, PROC_WATCHES_PATTERN,
|
||||
"a WatchpointContainer")
|
||||
|
@ -108,7 +106,8 @@ def find_thread_by_num(proc, tnum):
|
|||
for t in proc.threads:
|
||||
if t.GetThreadID() == tnum:
|
||||
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):
|
||||
|
@ -203,6 +202,7 @@ def find_wpt_by_pattern(pattern, object, err_msg):
|
|||
watchnum = int(mat['watchnum'])
|
||||
return find_wpt_by_number(watchnum)
|
||||
|
||||
|
||||
def find_wpt_by_obj(object):
|
||||
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')):
|
||||
"""List processes on lldb's host system."""
|
||||
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')
|
||||
|
@ -254,14 +254,14 @@ def refresh_breakpoints(node: sch.Schema('BreakpointContainer')):
|
|||
process).
|
||||
"""
|
||||
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')
|
||||
def refresh_processes(node: sch.Schema('ProcessContainer')):
|
||||
"""Refresh the list of 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')
|
||||
|
@ -273,7 +273,7 @@ def refresh_proc_breakpoints(node: sch.Schema('BreakpointLocationContainer')):
|
|||
refreshed.
|
||||
"""
|
||||
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')
|
||||
|
@ -285,20 +285,21 @@ def refresh_proc_watchpoints(node: sch.Schema('WatchpointContainer')):
|
|||
refreshed.
|
||||
"""
|
||||
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')
|
||||
def refresh_environment(node: sch.Schema('Environment')):
|
||||
"""Refresh the environment descriptors (arch, os, endian)."""
|
||||
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')
|
||||
def refresh_threads(node: sch.Schema('ThreadContainer')):
|
||||
"""Refresh the list of threads in the process."""
|
||||
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')
|
||||
|
@ -307,7 +308,7 @@ def refresh_stack(node: sch.Schema('Stack')):
|
|||
t = find_thread_by_stack_obj(node)
|
||||
t.process.SetSelectedThread(t)
|
||||
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')
|
||||
|
@ -317,14 +318,14 @@ def refresh_registers(node: sch.Schema('RegisterValueContainer')):
|
|||
f.thread.SetSelectedFrame(f.GetFrameID())
|
||||
# TODO: Groups?
|
||||
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')
|
||||
def refresh_mappings(node: sch.Schema('Memory')):
|
||||
"""Refresh the list of memory regions for the process."""
|
||||
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')
|
||||
|
@ -335,7 +336,7 @@ def refresh_modules(node: sch.Schema('ModuleContainer')):
|
|||
This will refresh the sections for all modules, not just the selected one.
|
||||
"""
|
||||
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')
|
||||
|
@ -343,6 +344,7 @@ def activate_process(process: sch.Schema('Process')):
|
|||
"""Switch to the process."""
|
||||
return
|
||||
|
||||
|
||||
@REGISTRY.method(action='activate')
|
||||
def activate_thread(thread: sch.Schema('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)
|
||||
util.get_debugger().HandleCommand(f'process attach -p {pid}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
def attach_pid(process: sch.Schema('Process'), pid: int):
|
||||
"""Attach the process to the given target."""
|
||||
util.get_debugger().HandleCommand(f'process attach -p {pid}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
def attach_name(process: sch.Schema('Process'), name: str):
|
||||
"""Attach the process to the given target."""
|
||||
|
@ -403,8 +407,9 @@ def launch_loader(process: sch.Schema('Process'),
|
|||
If 'main' is not defined in the file, this behaves like 'run'.
|
||||
"""
|
||||
util.get_debugger().HandleCommand(f'file {file}')
|
||||
if args is not '':
|
||||
util.get_debugger().HandleCommand(f'settings set target.run-args {args}')
|
||||
if args != '':
|
||||
util.get_debugger().HandleCommand(
|
||||
f'settings set target.run-args {args}')
|
||||
util.get_debugger().HandleCommand(f'process launch --stop-at-entry')
|
||||
|
||||
|
||||
|
@ -419,8 +424,9 @@ def launch(process: sch.Schema('Process'),
|
|||
signaled.
|
||||
"""
|
||||
util.get_debugger().HandleCommand(f'file {file}')
|
||||
if args is not '':
|
||||
util.get_debugger().HandleCommand(f'settings set target.run-args {args}')
|
||||
if args != '':
|
||||
util.get_debugger().HandleCommand(
|
||||
f'settings set target.run-args {args}')
|
||||
util.get_debugger().HandleCommand(f'run')
|
||||
|
||||
|
||||
|
@ -527,13 +533,15 @@ def break_read_range(process: sch.Schema('Process'), range: AddressRange):
|
|||
offset_start = process.trace.memory_mapper.map_back(
|
||||
proc, Address(range.space, range.min))
|
||||
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')
|
||||
def break_read_expression(expression: str):
|
||||
"""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')
|
||||
|
@ -543,13 +551,15 @@ def break_write_range(process: sch.Schema('Process'), range: AddressRange):
|
|||
offset_start = process.trace.memory_mapper.map_back(
|
||||
proc, Address(range.space, range.min))
|
||||
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')
|
||||
def break_write_expression(expression: str):
|
||||
"""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')
|
||||
|
@ -559,13 +569,15 @@ def break_access_range(process: sch.Schema('Process'), range: AddressRange):
|
|||
offset_start = process.trace.memory_mapper.map_back(
|
||||
proc, Address(range.space, range.min))
|
||||
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')
|
||||
def break_access_expression(expression: str):
|
||||
"""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')
|
||||
|
@ -580,12 +592,14 @@ def toggle_watchpoint(breakpoint: sch.Schema('WatchpointSpec'), enabled: bool):
|
|||
wpt = find_wpt_by_obj(watchpoint)
|
||||
wpt.enabled = enabled
|
||||
|
||||
|
||||
@REGISTRY.method(action='toggle')
|
||||
def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
|
||||
"""Toggle a breakpoint."""
|
||||
bpt = find_bpt_by_obj(breakpoint)
|
||||
bpt.enabled = enabled
|
||||
|
||||
|
||||
@REGISTRY.method(action='toggle')
|
||||
def toggle_breakpoint_location(location: sch.Schema('BreakpointLocation'), enabled: bool):
|
||||
"""Toggle a breakpoint location."""
|
||||
|
@ -601,6 +615,7 @@ def delete_watchpoint(watchpoint: sch.Schema('WatchpointSpec')):
|
|||
wptnum = wpt.GetID()
|
||||
util.get_debugger().HandleCommand(f'watchpoint delete {wptnum}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='delete')
|
||||
def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
|
||||
"""Delete a breakpoint."""
|
||||
|
@ -615,8 +630,16 @@ def read_mem(process: sch.Schema('Process'), range: AddressRange):
|
|||
proc = find_proc_by_obj(process)
|
||||
offset_start = process.trace.memory_mapper.map_back(
|
||||
proc, Address(range.space, range.min))
|
||||
ci = util.get_debugger().GetCommandInterpreter()
|
||||
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
|
||||
|
@ -628,7 +651,7 @@ def write_mem(process: sch.Schema('Process'), address: Address, data: bytes):
|
|||
|
||||
|
||||
@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."""
|
||||
f = find_frame_by_obj(frame)
|
||||
f.select()
|
||||
|
@ -637,4 +660,5 @@ def write_reg(frame: sch.Schema('Frame'), name: str, value: bytes):
|
|||
reg = find_reg_by_name(f, mname)
|
||||
size = int(lldb.parse_and_eval(f'sizeof(${mname})'))
|
||||
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():
|
||||
blurb = lldb.debugger.GetVersionString()
|
||||
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]
|
||||
return LldbVersion(full, int(major), int(minor))
|
||||
|
||||
|
@ -36,6 +39,7 @@ LLDB_VERSION = _compute_lldb_ver()
|
|||
|
||||
GNU_DEBUGDATA_PREFIX = ".gnu_debugdata for "
|
||||
|
||||
|
||||
class Module(namedtuple('BaseModule', ['name', 'base', 'max', 'sections'])):
|
||||
pass
|
||||
|
||||
|
@ -107,8 +111,8 @@ class ModuleInfoReader(object):
|
|||
def _choose_module_info_reader():
|
||||
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'])):
|
||||
|
@ -137,8 +141,8 @@ class RegionInfoReader(object):
|
|||
reglist = get_process().GetMemoryRegions()
|
||||
for i in range(0, reglist.GetSize()):
|
||||
module = get_target().GetModuleAtIndex(i)
|
||||
info = lldb.SBMemoryRegionInfo();
|
||||
success = reglist.GetMemoryRegionAtIndex(i, info);
|
||||
info = lldb.SBMemoryRegionInfo()
|
||||
success = reglist.GetMemoryRegionAtIndex(i, info)
|
||||
if success:
|
||||
r = self.region_from_sbmemreg(info)
|
||||
regions.append(r)
|
||||
|
@ -177,28 +181,39 @@ def _choose_breakpoint_location_info_reader():
|
|||
|
||||
BREAKPOINT_LOCATION_INFO_READER = _choose_breakpoint_location_info_reader()
|
||||
|
||||
|
||||
def get_debugger():
|
||||
return lldb.SBDebugger.FindDebuggerWithID(1)
|
||||
|
||||
|
||||
def get_target():
|
||||
return get_debugger().GetTargetAtIndex(0)
|
||||
|
||||
|
||||
def get_process():
|
||||
return get_target().GetProcess()
|
||||
|
||||
|
||||
def selected_thread():
|
||||
return get_process().GetSelectedThread()
|
||||
|
||||
|
||||
def selected_frame():
|
||||
return selected_thread().GetSelectedFrame()
|
||||
|
||||
|
||||
def parse_and_eval(expr, signed=False):
|
||||
if signed is True:
|
||||
return get_target().EvaluateExpression(expr).GetValueAsSigned()
|
||||
return get_target().EvaluateExpression(expr).GetValueAsUnsigned()
|
||||
return get_eval(expr).GetValueAsSigned()
|
||||
return get_eval(expr).GetValueAsUnsigned()
|
||||
|
||||
|
||||
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):
|
||||
stream = lldb.SBStream()
|
||||
|
@ -208,8 +223,10 @@ def get_description(object, level=None):
|
|||
object.GetDescription(stream, level)
|
||||
return escape_ansi(stream.GetData())
|
||||
|
||||
|
||||
conv_map = {}
|
||||
|
||||
|
||||
def get_convenience_variable(id):
|
||||
#val = get_target().GetEnvironment().Get(id)
|
||||
if id not in conv_map:
|
||||
|
@ -219,6 +236,7 @@ def get_convenience_variable(id):
|
|||
return "auto"
|
||||
return val
|
||||
|
||||
|
||||
def set_convenience_variable(id, value):
|
||||
#env = get_target().GetEnvironment()
|
||||
# return env.Set(id, value, True)
|
||||
|
@ -229,6 +247,7 @@ def escape_ansi(line):
|
|||
ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
|
||||
return ansi_escape.sub('', line)
|
||||
|
||||
|
||||
def debracket(init):
|
||||
val = init
|
||||
val = val.replace("[", "(")
|
||||
|
|
|
@ -37,6 +37,13 @@ public interface TraceRmiAcceptor {
|
|||
*/
|
||||
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
|
||||
*
|
||||
|
|
|
@ -57,16 +57,30 @@ public interface TraceRmiLaunchOffer {
|
|||
* @param exception optional error, if failed
|
||||
*/
|
||||
public record LaunchResult(Program program, Map<String, TerminalSession> sessions,
|
||||
TraceRmiConnection connection, Trace trace, Throwable exception)
|
||||
implements AutoCloseable {
|
||||
TraceRmiAcceptor acceptor, TraceRmiConnection connection, Trace trace,
|
||||
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
|
||||
public void close() throws Exception {
|
||||
for (TerminalSession s : sessions.values()) {
|
||||
s.close();
|
||||
}
|
||||
if (connection != null) {
|
||||
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());
|
||||
env.put("TERM", "xterm-256color");
|
||||
PtySession session = pty.getChild().session(new String[] { "/usr/bin/bash" }, env);
|
||||
displayInTerminal(pty.getParent(), () -> {
|
||||
displayInTerminal(pty, () -> {
|
||||
try {
|
||||
session.waitExited();
|
||||
}
|
||||
|
|
|
@ -37,14 +37,16 @@ public class TerminalGhidraScript extends GhidraScript {
|
|||
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();
|
||||
PtyParent parent = pty.getParent();
|
||||
PtyChild child = pty.getChild();
|
||||
try (Terminal term = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
||||
parent.getInputStream(), parent.getOutputStream())) {
|
||||
term.addTerminalListener(new TerminalListener() {
|
||||
@Override
|
||||
public void resized(short cols, short rows) {
|
||||
parent.setWindowSize(cols, rows);
|
||||
child.setWindowSize(cols, rows);
|
||||
}
|
||||
});
|
||||
waiter.run();
|
||||
|
@ -55,7 +57,7 @@ public class TerminalGhidraScript extends GhidraScript {
|
|||
Map<String, String> env = new HashMap<>(System.getenv());
|
||||
env.put("TERM", "xterm-256color");
|
||||
pty.getChild().nullSession();
|
||||
displayInTerminal(pty.getParent(), () -> {
|
||||
displayInTerminal(pty, () -> {
|
||||
while (true) {
|
||||
try {
|
||||
Thread.sleep(100000);
|
||||
|
|
|
@ -441,6 +441,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
|||
Pty pty = factory.openpty();
|
||||
|
||||
PtyParent parent = pty.getParent();
|
||||
PtyChild child = pty.getChild();
|
||||
Terminal terminal = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
||||
parent.getInputStream(), parent.getOutputStream());
|
||||
terminal.setSubTitle(ShellUtils.generateLine(commandLine));
|
||||
|
@ -448,7 +449,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
|||
@Override
|
||||
public void resized(short cols, short rows) {
|
||||
try {
|
||||
parent.setWindowSize(cols, rows);
|
||||
child.setWindowSize(cols, rows);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Could not resize pty: " + e);
|
||||
|
@ -490,12 +491,13 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
|||
Pty pty = factory.openpty();
|
||||
|
||||
PtyParent parent = pty.getParent();
|
||||
PtyChild child = pty.getChild();
|
||||
Terminal terminal = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
||||
parent.getInputStream(), parent.getOutputStream());
|
||||
TerminalListener resizeListener = new TerminalListener() {
|
||||
@Override
|
||||
public void resized(short cols, short rows) {
|
||||
parent.setWindowSize(cols, rows);
|
||||
child.setWindowSize(cols, rows);
|
||||
}
|
||||
};
|
||||
terminal.addTerminalListener(resizeListener);
|
||||
|
@ -549,7 +551,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
|||
if (lastExc == null) {
|
||||
lastExc = new CancelledException();
|
||||
}
|
||||
return new LaunchResult(program, sessions, connection, trace, lastExc);
|
||||
return new LaunchResult(program, sessions, acceptor, connection, trace, lastExc);
|
||||
}
|
||||
acceptor = null;
|
||||
sessions.clear();
|
||||
|
@ -598,10 +600,16 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
|||
}
|
||||
}
|
||||
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;
|
||||
prompt = mode != PromptMode.NEVER;
|
||||
LaunchResult result =
|
||||
new LaunchResult(program, sessions, connection, trace, lastExc);
|
||||
new LaunchResult(program, sessions, acceptor, connection, trace, lastExc);
|
||||
if (prompt) {
|
||||
switch (promptError(result)) {
|
||||
case KEEP:
|
||||
|
@ -621,13 +629,13 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
|||
catch (Exception 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;
|
||||
}
|
||||
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();
|
||||
for (Entry<String, TerminalSession> ent : result.sessions().entrySet()) {
|
||||
TerminalSession session = ent.getValue();
|
||||
sb.append("<li>Terminal: " + HTMLUtilities.escapeHTML(ent.getKey()) + " → <tt>" +
|
||||
HTMLUtilities.escapeHTML(session.description()) + "</tt>");
|
||||
sb.append("<li>Terminal: %s → <tt>%s</tt>".formatted(
|
||||
HTMLUtilities.escapeHTML(ent.getKey()),
|
||||
HTMLUtilities.escapeHTML(session.description())));
|
||||
if (session.isTerminated()) {
|
||||
sb.append(" (Terminated)");
|
||||
}
|
||||
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) {
|
||||
sb.append("<li>Connection: <tt>" +
|
||||
HTMLUtilities.escapeHTML(result.connection().getRemoteAddress().toString()) +
|
||||
"</tt></li>\n");
|
||||
sb.append("<li>Connection: <tt>%s</tt></li>\n".formatted(
|
||||
HTMLUtilities.escapeHTML(result.connection().getRemoteAddress().toString())));
|
||||
}
|
||||
if (result.trace() != null) {
|
||||
sb.append(
|
||||
"<li>Trace: " + HTMLUtilities.escapeHTML(result.trace().getName()) + "</li>\n");
|
||||
sb.append("<li>Trace: %s</li>\n".formatted(
|
||||
HTMLUtilities.escapeHTML(result.trace().getName())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
|
|
@ -62,6 +62,11 @@ public class DefaultTraceRmiAcceptor extends AbstractTraceRmiListener implements
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return socket.isClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
plugin.removeAcceptor(this);
|
||||
|
|
|
@ -310,7 +310,11 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
|
|||
|
||||
PropertyEditor editor = getEditor(param);
|
||||
Object val = computeMemorizedValue(param);
|
||||
if (val == null) {
|
||||
editor.setValue("");
|
||||
} else {
|
||||
editor.setValue(val);
|
||||
}
|
||||
editor.addPropertyChangeListener(this);
|
||||
pairPanel.add(MiscellaneousUtils.getEditorComponent(editor));
|
||||
paramEditors.put(param, editor);
|
||||
|
|
|
@ -28,8 +28,8 @@ dependencies {
|
|||
api project(':SoftwareModeling')
|
||||
api project(':ProposedUtils')
|
||||
|
||||
api "net.java.dev.jna:jna:5.4.0"
|
||||
api "net.java.dev.jna:jna-platform:5.4.0"
|
||||
api "net.java.dev.jna:jna:5.14.0"
|
||||
api "net.java.dev.jna:jna-platform:5.14.0"
|
||||
|
||||
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
||||
}
|
||||
|
|
|
@ -101,4 +101,12 @@ public interface PtyChild extends PtyEndpoint {
|
|||
default String nullSession(TermMode... mode) throws IOException {
|
||||
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
|
||||
*/
|
||||
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;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* A session led by the child pty
|
||||
*
|
||||
|
@ -31,6 +34,8 @@ public interface PtySession {
|
|||
*/
|
||||
int waitExited() throws InterruptedException;
|
||||
|
||||
int waitExited(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException;
|
||||
|
||||
/**
|
||||
* Take the greatest efforts to terminate the session (leader and descendants)
|
||||
*
|
||||
|
|
|
@ -15,20 +15,24 @@
|
|||
*/
|
||||
package ghidra.pty.linux;
|
||||
|
||||
import ghidra.pty.PtyParent;
|
||||
import ghidra.pty.linux.PosixC.Winsize;
|
||||
import ghidra.pty.unix.PosixC.Ioctls;
|
||||
import ghidra.pty.unix.UnixPtySessionLeader;
|
||||
|
||||
public class LinuxPtyParent extends LinuxPtyEndpoint implements PtyParent {
|
||||
LinuxPtyParent(int fd) {
|
||||
super(fd);
|
||||
public enum LinuxIoctls implements Ioctls {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Class<? extends UnixPtySessionLeader> leaderClass() {
|
||||
return LinuxPtySessionLeader.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWindowSize(short cols, short rows) {
|
||||
Winsize.ByReference ws = new Winsize.ByReference();
|
||||
ws.ws_col = cols;
|
||||
ws.ws_row = rows;
|
||||
ws.write();
|
||||
PosixC.INSTANCE.ioctl(fd, Winsize.TIOCSWINSZ, ws.getPointer());
|
||||
public long TIOCSCTTY() {
|
||||
return 0x540eL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long TIOCSWINSZ() {
|
||||
return 0x5414L;
|
||||
}
|
||||
}
|
|
@ -19,15 +19,16 @@ import java.io.IOException;
|
|||
|
||||
import ghidra.pty.Pty;
|
||||
import ghidra.pty.PtyFactory;
|
||||
import ghidra.pty.unix.UnixPty;
|
||||
|
||||
public enum LinuxPtyFactory implements PtyFactory {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Pty openpty(short cols, short rows) throws IOException {
|
||||
LinuxPty pty = LinuxPty.openpty();
|
||||
UnixPty pty = UnixPty.openpty(LinuxIoctls.INSTANCE);
|
||||
if (cols != 0 && rows != 0) {
|
||||
pty.getParent().setWindowSize(cols, rows);
|
||||
pty.getChild().setWindowSize(cols, rows);
|
||||
}
|
||||
return pty;
|
||||
}
|
||||
|
|
|
@ -15,11 +15,10 @@
|
|||
*/
|
||||
package ghidra.pty.linux;
|
||||
|
||||
import java.util.List;
|
||||
import ghidra.pty.unix.PosixC.Ioctls;
|
||||
import ghidra.pty.unix.UnixPtySessionLeader;
|
||||
|
||||
public class LinuxPtySessionLeader {
|
||||
private static final PosixC LIB_POSIX = PosixC.INSTANCE;
|
||||
private static final int O_RDWR = 2; // TODO: Find this in libs
|
||||
public class LinuxPtySessionLeader extends UnixPtySessionLeader {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
LinuxPtySessionLeader leader = new LinuxPtySessionLeader();
|
||||
|
@ -27,61 +26,8 @@ public class LinuxPtySessionLeader {
|
|||
leader.run();
|
||||
}
|
||||
|
||||
protected String ptyPath;
|
||||
protected List<String> subArgs;
|
||||
|
||||
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);
|
||||
}
|
||||
@Override
|
||||
protected Ioctls ioctls() {
|
||||
return LinuxIoctls.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
*/
|
||||
package ghidra.pty.local;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import ghidra.pty.PtySession;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
|
@ -36,6 +39,15 @@ public class LocalProcessPtySession implements PtySession {
|
|||
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
|
||||
public void destroyForcibly() {
|
||||
process.destroyForcibly();
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
*/
|
||||
package ghidra.pty.local;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import com.sun.jna.LastErrorException;
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int waitExited() throws InterruptedException {
|
||||
protected int doWaitExited(int millis) throws TimeoutException {
|
||||
while (true) {
|
||||
switch (Kernel32.INSTANCE.WaitForSingleObject(processHandle.getNative(), -1)) {
|
||||
switch (Kernel32.INSTANCE.WaitForSingleObject(processHandle.getNative(), millis)) {
|
||||
case Kernel32.WAIT_OBJECT_0:
|
||||
case Kernel32.WAIT_ABANDONED:
|
||||
IntByReference lpExitCode = new IntByReference();
|
||||
|
@ -54,13 +56,32 @@ public class LocalWindowsNativeProcessPtySession implements PtySession {
|
|||
return lpExitCode.getValue();
|
||||
}
|
||||
case Kernel32.WAIT_TIMEOUT:
|
||||
throw new AssertionError();
|
||||
throw new TimeoutException();
|
||||
case Kernel32.WAIT_FAILED:
|
||||
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
|
||||
public void destroyForcibly() {
|
||||
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.PtyFactory;
|
||||
import ghidra.pty.linux.LinuxPty;
|
||||
import ghidra.pty.unix.UnixPty;
|
||||
|
||||
public enum MacosPtyFactory implements PtyFactory {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Pty openpty(short cols, short rows) throws IOException {
|
||||
LinuxPty pty = LinuxPty.openpty();
|
||||
UnixPty pty = UnixPty.openpty(MacosIoctls.INSTANCE);
|
||||
if (cols != 0 && rows != 0) {
|
||||
pty.getParent().setWindowSize(cols, rows);
|
||||
pty.getChild().setWindowSize(cols, rows);
|
||||
}
|
||||
return pty;
|
||||
}
|
||||
|
||||
@Override
|
||||
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 {
|
||||
SshPty pty = new SshPty((ChannelExec) session.openChannel("exec"));
|
||||
if (cols != 0 && rows != 0) {
|
||||
pty.getParent().setWindowSize(cols, rows);
|
||||
pty.getChild().setWindowSize(cols, rows);
|
||||
}
|
||||
return pty;
|
||||
}
|
||||
|
|
|
@ -118,4 +118,8 @@ public class SshPtyChild extends SshPtyEndpoint implements PtyChild {
|
|||
public OutputStream getOutputStream() {
|
||||
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) {
|
||||
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;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import com.jcraft.jsch.*;
|
||||
|
||||
import ghidra.pty.PtySession;
|
||||
|
@ -27,16 +30,37 @@ public class SshPtySession implements PtySession {
|
|||
this.channel = channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int waitExited() throws InterruptedException {
|
||||
protected int doWaitExited(Long millis) throws InterruptedException, TimeoutException {
|
||||
long startMs = System.currentTimeMillis();
|
||||
// Doesn't look like there's a clever way to wait. So do the spin sleep :(
|
||||
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
|
||||
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
|
||||
public void destroyForcibly() {
|
||||
channel.disconnect();
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pty.linux;
|
||||
package ghidra.pty.unix;
|
||||
|
||||
import com.sun.jna.LastErrorException;
|
||||
import com.sun.jna.Native;
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pty.linux;
|
||||
package ghidra.pty.unix;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pty.linux;
|
||||
package ghidra.pty.unix;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pty.linux;
|
||||
package ghidra.pty.unix;
|
||||
|
||||
import com.sun.jna.*;
|
||||
import com.sun.jna.Structure.FieldOrder;
|
||||
|
@ -26,10 +26,16 @@ import com.sun.jna.Structure.FieldOrder;
|
|||
*/
|
||||
public interface PosixC extends Library {
|
||||
|
||||
interface Ioctls {
|
||||
Class<? extends UnixPtySessionLeader> leaderClass();
|
||||
|
||||
long TIOCSCTTY();
|
||||
|
||||
long TIOCSWINSZ();
|
||||
}
|
||||
|
||||
@FieldOrder({ "ws_row", "ws_col", "ws_xpixel", "ws_ypixel" })
|
||||
class Winsize extends Structure {
|
||||
public static final int TIOCSWINSZ = 0x5414; // This may actually be Linux-specific
|
||||
|
||||
public short ws_row;
|
||||
public short ws_col;
|
||||
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",
|
||||
"c_ospeed" })
|
||||
class Termios extends Structure {
|
||||
public static final int TCSANOW = 0;
|
||||
@FieldOrder({ "steal" })
|
||||
class ControllingTty extends Structure {
|
||||
public int steal;
|
||||
|
||||
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 int c_iflag;
|
||||
|
@ -110,7 +126,7 @@ public interface PosixC extends Library {
|
|||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
|
@ -141,7 +157,7 @@ public interface PosixC extends Library {
|
|||
|
||||
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);
|
||||
|
|
@ -13,53 +13,53 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pty.linux;
|
||||
package ghidra.pty.unix;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.sun.jna.*;
|
||||
import com.sun.jna.Memory;
|
||||
import com.sun.jna.ptr.IntByReference;
|
||||
|
||||
import ghidra.pty.Pty;
|
||||
import ghidra.pty.unix.PosixC.Ioctls;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class LinuxPty implements Pty {
|
||||
public class UnixPty implements Pty {
|
||||
|
||||
static final PosixC LIB_POSIX = PosixC.INSTANCE;
|
||||
|
||||
private final int aparent;
|
||||
private final int achild;
|
||||
//private final String name;
|
||||
private boolean closed = false;
|
||||
|
||||
private final LinuxPtyParent parent;
|
||||
private final LinuxPtyChild child;
|
||||
private final UnixPtyParent parent;
|
||||
private final UnixPtyChild child;
|
||||
|
||||
public static LinuxPty openpty() throws IOException {
|
||||
public static UnixPty openpty(Ioctls ioctls) throws IOException {
|
||||
// TODO: Support termp and winp?
|
||||
IntByReference p = new IntByReference();
|
||||
IntByReference c = new IntByReference();
|
||||
Memory n = new Memory(1024);
|
||||
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 + ")");
|
||||
this.aparent = aparent;
|
||||
this.achild = achild;
|
||||
|
||||
this.parent = new LinuxPtyParent(aparent);
|
||||
this.child = new LinuxPtyChild(achild, name);
|
||||
this.parent = new UnixPtyParent(ioctls, aparent);
|
||||
this.child = new UnixPtyChild(ioctls, achild, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinuxPtyParent getParent() {
|
||||
public UnixPtyParent getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinuxPtyChild getChild() {
|
||||
public UnixPtyChild getChild() {
|
||||
return child;
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pty.linux;
|
||||
package ghidra.pty.unix;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
@ -21,17 +21,17 @@ import java.util.*;
|
|||
|
||||
import ghidra.pty.PtyChild;
|
||||
import ghidra.pty.PtySession;
|
||||
import ghidra.pty.linux.PosixC.Termios;
|
||||
import ghidra.pty.local.LocalProcessPtySession;
|
||||
import ghidra.pty.unix.PosixC.*;
|
||||
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;
|
||||
|
||||
private final String name;
|
||||
|
||||
LinuxPtyChild(int fd, String name) {
|
||||
super(fd);
|
||||
UnixPtyChild(Ioctls ioctls, int fd, String name) {
|
||||
super(ioctls, fd);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ public class LinuxPtyChild extends LinuxPtyEndpoint implements PtyChild {
|
|||
argsList.add(javaCommand);
|
||||
argsList.add("-cp");
|
||||
argsList.add(System.getProperty("java.class.path"));
|
||||
argsList.add(LinuxPtySessionLeader.class.getCanonicalName());
|
||||
argsList.add(ioctls.leaderClass().getCanonicalName());
|
||||
|
||||
argsList.add(name);
|
||||
argsList.addAll(Arrays.asList(args));
|
||||
|
@ -106,4 +106,18 @@ public class LinuxPtyChild extends LinuxPtyEndpoint implements PtyChild {
|
|||
tmios.c_lflag &= ~Termios.ECHO;
|
||||
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
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pty.linux;
|
||||
package ghidra.pty.unix;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
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;
|
||||
private final FdOutputStream outputStream;
|
||||
private final FdInputStream inputStream;
|
||||
|
||||
LinuxPtyEndpoint(int fd) {
|
||||
UnixPtyEndpoint(Ioctls ioctls, int fd) {
|
||||
this.ioctls = ioctls;
|
||||
this.fd = fd;
|
||||
this.outputStream = new FdOutputStream(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
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pty.linux;
|
||||
package ghidra.pty.unix;
|
||||
|
||||
import com.sun.jna.*;
|
||||
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 {
|
||||
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) {
|
||||
super(writeHandle, readHandle, pseudoConsoleHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWindowSize(short cols, short rows) {
|
||||
pseudoConsoleHandle.resize(rows, cols);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,201 +15,24 @@
|
|||
*/
|
||||
package ghidra.pty.linux;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.dbg.testutil.DummyProc;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
import ghidra.pty.AbstractPtyTest;
|
||||
import ghidra.pty.PtyChild.Echo;
|
||||
import ghidra.pty.PtySession;
|
||||
import ghidra.pty.unix.AbstractUnixPtyTest;
|
||||
import ghidra.pty.unix.UnixPty;
|
||||
|
||||
public class LinuxPtyTest extends AbstractPtyTest {
|
||||
public class LinuxPtyTest extends AbstractUnixPtyTest {
|
||||
@Before
|
||||
public void checkLinux() {
|
||||
assumeTrue(OperatingSystem.LINUX == OperatingSystem.CURRENT_OPERATING_SYSTEM);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenClosePty() throws IOException {
|
||||
LinuxPty pty = LinuxPty.openpty();
|
||||
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());
|
||||
}
|
||||
@Override
|
||||
protected UnixPty openpty() throws IOException {
|
||||
return UnixPty.openpty(LinuxIoctls.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
PtyParent parent = pty.getParent();
|
||||
PtyChild child = pty.getChild();
|
||||
try (Terminal term = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
||||
parent.getInputStream(), parent.getOutputStream())) {
|
||||
term.addTerminalListener(new TerminalListener() {
|
||||
@Override
|
||||
public void resized(short cols, short rows) {
|
||||
System.err.println("resized: " + cols + "x" + rows);
|
||||
parent.setWindowSize(cols, rows);
|
||||
child.setWindowSize(cols, rows);
|
||||
}
|
||||
});
|
||||
session.waitExited();
|
||||
|
@ -101,13 +102,14 @@ public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerTest {
|
|||
pty.getChild().session(new String[] { "C:\\Windows\\system32\\cmd.exe" }, env);
|
||||
|
||||
PtyParent parent = pty.getParent();
|
||||
PtyChild child = pty.getChild();
|
||||
try (Terminal term = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
||||
parent.getInputStream(), parent.getOutputStream())) {
|
||||
term.addTerminalListener(new TerminalListener() {
|
||||
@Override
|
||||
public void resized(short cols, short rows) {
|
||||
System.err.println("resized: " + cols + "x" + rows);
|
||||
parent.setWindowSize(cols, rows);
|
||||
child.setWindowSize(cols, rows);
|
||||
}
|
||||
});
|
||||
session.waitExited();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue