mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-22 04:05:39 +00: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
@ -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,11 +214,11 @@ 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: '{}'"
|
||||
.format(name, value, value.type))
|
||||
.format(name, value, value.type))
|
||||
return RegVal(self.map_name(proc, name), av)
|
||||
|
||||
def map_name_back(self, proc, name):
|
||||
@ -258,4 +269,3 @@ def compute_register_mapper(lang):
|
||||
if ':LE:' in lang:
|
||||
return DEFAULT_LE_REGISTER_MAPPER
|
||||
return register_mappers[lang]
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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")
|
||||
|
||||
commands.put_state(event_process)
|
||||
# NB: Calling put_state on running leaves an open transaction
|
||||
if event_process.is_running is False:
|
||||
commands.put_state(event_process)
|
||||
type = event.GetType()
|
||||
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,138 +163,100 @@ 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()
|
||||
|
||||
def run(self):
|
||||
|
||||
def run(self):
|
||||
# Let's only try at most 4 times to retrieve any kind of event.
|
||||
# After that, the thread exits.
|
||||
listener = lldb.SBListener('eventlistener')
|
||||
@ -314,7 +275,7 @@ class EventThread(threading.Thread):
|
||||
if rc is False:
|
||||
print("add listener for process failed")
|
||||
return
|
||||
|
||||
|
||||
# Not sure what effect this logic has
|
||||
rc = cli.GetBroadcaster().AddInitialEventsToListener(listener, ALL_EVENTS)
|
||||
if rc is False:
|
||||
@ -329,12 +290,13 @@ 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
|
||||
# THIS WILL NOT WORK: listener = util.get_debugger().GetListener()
|
||||
|
||||
# THIS WILL NOT WORK: listener = util.get_debugger().GetListener()
|
||||
|
||||
while True:
|
||||
event_recvd = False
|
||||
while event_recvd is False:
|
||||
@ -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'])
|
||||
@ -81,11 +78,12 @@ def find_proc_by_obj(object):
|
||||
|
||||
def find_proc_by_procbreak_obj(object):
|
||||
return find_proc_by_pattern(object, PROC_BREAKS_PATTERN,
|
||||
"a BreakpointLocationContainer")
|
||||
"a BreakpointLocationContainer")
|
||||
|
||||
|
||||
def find_proc_by_procwatch_obj(object):
|
||||
return find_proc_by_pattern(object, PROC_WATCHES_PATTERN,
|
||||
"a WatchpointContainer")
|
||||
"a WatchpointContainer")
|
||||
|
||||
|
||||
def find_proc_by_env_obj(object):
|
||||
@ -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):
|
||||
@ -166,7 +165,7 @@ def find_reg_by_name(f, name):
|
||||
# I could keep my own cache in a dict, but why?
|
||||
def find_bpt_by_number(breaknum):
|
||||
# TODO: If len exceeds some threshold, use binary search?
|
||||
for i in range(0,util.get_target().GetNumBreakpoints()):
|
||||
for i in range(0, util.get_target().GetNumBreakpoints()):
|
||||
b = util.get_target().GetBreakpointAtIndex(i)
|
||||
if b.GetID() == breaknum:
|
||||
return b
|
||||
@ -189,7 +188,7 @@ def find_bpt_by_obj(object):
|
||||
# I could keep my own cache in a dict, but why?
|
||||
def find_wpt_by_number(watchnum):
|
||||
# TODO: If len exceeds some threshold, use binary search?
|
||||
for i in range(0,util.get_target().GetNumWatchpoints()):
|
||||
for i in range(0, util.get_target().GetNumWatchpoints()):
|
||||
w = util.get_target().GetWatchpointAtIndex(i)
|
||||
if w.GetID() == watchnum:
|
||||
return w
|
||||
@ -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."""
|
||||
@ -395,23 +399,24 @@ def detach(process: sch.Schema('Process')):
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
def launch_loader(process: sch.Schema('Process'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
"""
|
||||
Start a native process with the given command line, stopping at 'main'.
|
||||
|
||||
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')
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
def launch(process: sch.Schema('Process'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
"""
|
||||
Run a native process with the given command line.
|
||||
|
||||
@ -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')
|
||||
|
||||
|
||||
@ -440,9 +446,9 @@ def _continue(process: sch.Schema('Process')):
|
||||
def interrupt():
|
||||
"""Interrupt the execution of the debugged program."""
|
||||
util.get_debugger().HandleCommand('process interrupt')
|
||||
#util.get_process().SendAsyncInterrupt()
|
||||
#util.get_debugger().HandleCommand('^c')
|
||||
#util.get_process().Signal(2)
|
||||
# util.get_process().SendAsyncInterrupt()
|
||||
# util.get_debugger().HandleCommand('^c')
|
||||
# util.get_process().Signal(2)
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_into')
|
||||
@ -527,13 +533,15 @@ def break_read_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
offset_start = process.trace.memory_mapper.map_back(
|
||||
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
|
||||
|
||||
@ -70,7 +74,7 @@ class ModuleInfoReader(object):
|
||||
name = s.GetName()
|
||||
attrs = s.GetPermissions()
|
||||
return Section(name, start, end, offset, attrs)
|
||||
|
||||
|
||||
def finish_module(self, name, sections):
|
||||
alloc = {k: s for k, s in sections.items()}
|
||||
if len(alloc) == 0:
|
||||
@ -96,7 +100,7 @@ class ModuleInfoReader(object):
|
||||
fspec = module.GetFileSpec()
|
||||
name = debracket(fspec.GetFilename())
|
||||
sections = {}
|
||||
for i in range(0, module.GetNumSections()):
|
||||
for i in range(0, module.GetNumSections()):
|
||||
s = self.section_from_sbsection(module.GetSectionAtIndex(i))
|
||||
sname = debracket(s.name)
|
||||
sections[sname] = s
|
||||
@ -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,18 +236,20 @@ 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)
|
||||
# return env.Set(id, value, True)
|
||||
conv_map[id] = value
|
||||
|
||||
|
||||
|
||||
def escape_ansi(line):
|
||||
ansi_escape =re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
|
||||
ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
|
||||
return ansi_escape.sub('', line)
|
||||
|
||||
|
||||
|
||||
def debracket(init):
|
||||
val = init
|
||||
val = val.replace("[","(")
|
||||
val = val.replace("]",")")
|
||||
val = val.replace("[", "(")
|
||||
val = val.replace("]", ")")
|
||||
return val
|
||||
|
@ -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);
|
||||
editor.setValue(val);
|
||||
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…
Reference in New Issue
Block a user