GP-4144: Many fixes, esp., for dbgeng Trace RMI.

This commit is contained in:
Dan 2024-01-29 14:56:28 -05:00
parent 1281fb979b
commit a6549947ab
30 changed files with 1526 additions and 725 deletions

View file

@ -28,7 +28,4 @@ if exist "%GHIDRA_HOME%\ghidra\.git\" (
set PYTHONPATH=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\pypkg\src;%PYTHONPATH% set PYTHONPATH=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\pypkg\src;%PYTHONPATH%
) )
echo PYTHONPATH is %PYTHONPATH%
echo OPT_TARGET_IMG is [%OPT_TARGET_IMG%]
"%OPT_PYTHON_EXE%" -i ..\support\local-dbgeng.py "%OPT_PYTHON_EXE%" -i ..\support\local-dbgeng.py

View file

@ -14,6 +14,7 @@
# limitations under the License. # limitations under the License.
## ##
import os import os
from ghidradbg.util import *
from ghidradbg.commands import * from ghidradbg.commands import *
ghidra_trace_connect(os.getenv('GHIDRA_TRACE_RMI_ADDR')) ghidra_trace_connect(os.getenv('GHIDRA_TRACE_RMI_ADDR'))
@ -25,6 +26,6 @@ ghidra_trace_start(os.getenv('OPT_TARGET_IMG'))
ghidra_trace_sync_enable() ghidra_trace_sync_enable()
# TODO: HACK # TODO: HACK
dbg().wait() dbg.wait()
repl() repl()

View file

@ -13,7 +13,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
## ##
import ctypes
ctypes.windll.kernel32.SetErrorMode(0x0001|0x0002|0x8000)
from . import util, commands, methods, hooks from . import util, commands, methods, hooks
import ctypes
ctypes.windll.kernel32.SetErrorMode(0x0001 | 0x0002 | 0x8000)

View file

@ -55,8 +55,9 @@ compiler_map = {
def get_arch(): def get_arch():
try: try:
type = util.get_debugger()._control.GetActualProcessorType() type = util.dbg.get_actual_processor_type()
except Exception: except Exception:
print("Error getting actual processor type.")
return "Unknown" return "Unknown"
if type is None: if type is None:
return "x86_64" return "x86_64"
@ -85,10 +86,11 @@ def get_osabi():
if not parm in ['auto', 'default']: if not parm in ['auto', 'default']:
return parm return parm
try: try:
os = util.get_debugger().cmd("vertarget") os = util.dbg.cmd("vertarget")
if "Windows" not in os: if "Windows" not in os:
return "default" return "default"
except Exception: except Exception:
print("Error getting target OS/ABI")
pass pass
return "windows" return "windows"
@ -154,7 +156,8 @@ class DefaultMemoryMapper(object):
def map_back(self, proc: int, address: Address) -> int: def map_back(self, proc: int, address: Address) -> int:
if address.space == self.defaultSpace: if address.space == self.defaultSpace:
return address.offset return address.offset
raise ValueError(f"Address {address} is not in process {proc.GetProcessID()}") raise ValueError(
f"Address {address} is not in process {proc.GetProcessID()}")
DEFAULT_MEMORY_MAPPER = DefaultMemoryMapper('ram') DEFAULT_MEMORY_MAPPER = DefaultMemoryMapper('ram')
@ -179,14 +182,13 @@ class DefaultRegisterMapper(object):
def map_name(self, proc, name): def map_name(self, proc, name):
return name return name
def map_value(self, proc, name, value): def map_value(self, proc, name, value):
try: try:
### TODO: this seems half-baked # TODO: this seems half-baked
av = value.to_bytes(8, "big") av = value.to_bytes(8, "big")
except Exception: except Exception:
raise ValueError("Cannot convert {}'s value: '{}', type: '{}'" raise ValueError("Cannot convert {}'s value: '{}', type: '{}'"
.format(name, value, type(value))) .format(name, value, type(value)))
return RegVal(self.map_name(proc, name), av) return RegVal(self.map_name(proc, name), av)
def map_name_back(self, proc, name): def map_name_back(self, proc, name):
@ -237,4 +239,3 @@ def compute_register_mapper(lang):
if ':LE:' in lang: if ':LE:' in lang:
return DEFAULT_LE_REGISTER_MAPPER return DEFAULT_LE_REGISTER_MAPPER
return register_mappers[lang] return register_mappers[lang]

View file

@ -13,23 +13,24 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
## ##
import code
from contextlib import contextmanager from contextlib import contextmanager
import inspect import inspect
import os.path import os.path
import socket
import time
import sys
import re import re
import socket
from ghidratrace import sch import sys
from ghidratrace.client import Client, Address, AddressRange, TraceObject import time
from pybag import pydbg, userdbg, kerneldbg from pybag import pydbg, userdbg, kerneldbg
from pybag.dbgeng import core as DbgEng from pybag.dbgeng import core as DbgEng
from pybag.dbgeng import exception from pybag.dbgeng import exception
from ghidratrace import sch
from ghidratrace.client import Client, Address, AddressRange, TraceObject
from . import util, arch, methods, hooks from . import util, arch, methods, hooks
import code
PAGE_SIZE = 4096 PAGE_SIZE = 4096
@ -170,10 +171,10 @@ def ghidra_trace_listen(address='0.0.0.0:0'):
s.bind((host, int(port))) s.bind((host, int(port)))
host, port = s.getsockname() host, port = s.getsockname()
s.listen(1) s.listen(1)
print("Listening at {}:{}...\n".format(host, port)) print("Listening at {}:{}...".format(host, port))
c, (chost, cport) = s.accept() c, (chost, cport) = s.accept()
s.close() s.close()
print("Connection from {}:{}\n".format(chost, cport)) print("Connection from {}:{}".format(chost, cport))
STATE.client = Client(c, "dbgeng.dll", methods.REGISTRY) STATE.client = Client(c, "dbgeng.dll", methods.REGISTRY)
except ValueError: except ValueError:
raise RuntimeError("port must be numeric") raise RuntimeError("port must be numeric")
@ -209,7 +210,7 @@ def start_trace(name):
schema_xml = schema_file.read() schema_xml = schema_file.read()
with STATE.trace.open_tx("Create Root Object"): with STATE.trace.open_tx("Create Root Object"):
root = STATE.trace.create_root_object(schema_xml, 'Session') root = STATE.trace.create_root_object(schema_xml, 'Session')
root.set_value('_display', 'pydbg(dbgeng) ' + util.DBG_VERSION.full) root.set_value('_display', util.DBG_VERSION.full + ' via pybag')
util.set_convenience_variable('_ghidra_tracing', "true") util.set_convenience_variable('_ghidra_tracing', "true")
@ -240,47 +241,49 @@ def ghidra_trace_restart(name=None):
start_trace(name) start_trace(name)
def ghidra_trace_create(command=None, initial_break=True, timeout=None, start_trace=True): @util.dbg.eng_thread
def ghidra_trace_create(command=None, initial_break=True, timeout=DbgEng.WAIT_INFINITE, start_trace=True):
""" """
Create a session. Create a session.
""" """
util.base = userdbg.UserDbg() dbg = util.dbg._base
if command != None: if command != None:
if timeout != None: dbg._client.CreateProcess(command, DbgEng.DEBUG_PROCESS)
util.base._client.CreateProcess(command, DbgEng.DEBUG_PROCESS) if initial_break:
if initial_break: dbg._control.AddEngineOptions(DbgEng.DEBUG_ENGINITIAL_BREAK)
util.base._control.AddEngineOptions( dbg.wait(timeout)
DbgEng.DEBUG_ENGINITIAL_BREAK)
util.base.wait(timeout)
else:
util.base.create(command, initial_break)
if start_trace: if start_trace:
ghidra_trace_start(command) ghidra_trace_start(command)
@util.dbg.eng_thread
def ghidra_trace_kill(): def ghidra_trace_kill():
""" """
Kill a session. Kill a session.
""" """
dbg()._client.TerminateCurrentProcess() dbg = util.dbg._base
dbg._client.TerminateCurrentProcess()
try:
dbg.wait()
except exception.E_UNEXPECTED_Error:
# Expect the unexpected, I guess.
pass
def ghidra_trace_info(): def ghidra_trace_info():
"""Get info about the Ghidra connection""" """Get info about the Ghidra connection"""
result = {}
if STATE.client is None: if STATE.client is None:
print("Not connected to Ghidra\n") print("Not connected to Ghidra")
return return
host, port = STATE.client.s.getpeername() host, port = STATE.client.s.getpeername()
print(f"Connected to {STATE.client.description} at {host}:{port}\n") print(f"Connected to {STATE.client.description} at {host}:{port}")
if STATE.trace is None: if STATE.trace is None:
print("No trace\n") print("No trace")
return return
print("Trace active\n") print("Trace active")
return result
def ghidra_trace_info_lcsp(): def ghidra_trace_info_lcsp():
@ -289,8 +292,8 @@ def ghidra_trace_info_lcsp():
""" """
language, compiler = arch.compute_ghidra_lcsp() language, compiler = arch.compute_ghidra_lcsp()
print("Selected Ghidra language: {}\n".format(language)) print("Selected Ghidra language: {}".format(language))
print("Selected Ghidra compiler: {}\n".format(compiler)) print("Selected Ghidra compiler: {}".format(compiler))
def ghidra_trace_txstart(description="tx"): def ghidra_trace_txstart(description="tx"):
@ -319,7 +322,7 @@ def ghidra_trace_txabort():
""" """
tx = STATE.require_tx() tx = STATE.require_tx()
print("Aborting trace transaction!\n") print("Aborting trace transaction!")
tx.abort() tx.abort()
STATE.reset_tx() STATE.reset_tx()
@ -362,15 +365,22 @@ def ghidra_trace_set_snap(snap=None):
STATE.require_trace().set_snap(int(snap)) STATE.require_trace().set_snap(int(snap))
def quantize_pages(start, end):
return (start // PAGE_SIZE * PAGE_SIZE, (end+PAGE_SIZE-1) // PAGE_SIZE*PAGE_SIZE)
@util.dbg.eng_thread
def put_bytes(start, end, pages, display_result): def put_bytes(start, end, pages, display_result):
trace = STATE.require_trace() trace = STATE.require_trace()
if pages: if pages:
start = start // PAGE_SIZE * PAGE_SIZE start, end = quantize_pages(start, end)
end = (end + PAGE_SIZE - 1) // PAGE_SIZE * PAGE_SIZE
nproc = util.selected_process() nproc = util.selected_process()
if end - start <= 0: if end - start <= 0:
return {'count': 0} return {'count': 0}
buf = dbg().read(start, end - start) try:
buf = util.dbg._base.read(start, end - start)
except OSError:
return {'count': 0}
count = 0 count = 0
if buf != None: if buf != None:
@ -379,7 +389,7 @@ def put_bytes(start, end, pages, display_result):
trace.create_overlay_space(base, addr.space) trace.create_overlay_space(base, addr.space)
count = trace.put_bytes(addr, buf) count = trace.put_bytes(addr, buf)
if display_result: if display_result:
print("Wrote {} bytes\n".format(count)) print("Wrote {} bytes".format(count))
return {'count': count} return {'count': count}
@ -404,16 +414,11 @@ def putmem(address, length, pages=True, display_result=True):
return put_bytes(start, end, pages, display_result) return put_bytes(start, end, pages, display_result)
def ghidra_trace_putmem(items): def ghidra_trace_putmem(address, length, pages=True):
""" """
Record the given block of memory into the Ghidra trace. Record the given block of memory into the Ghidra trace.
""" """
items = items.split(" ")
address = items[0]
length = items[1]
pages = items[2] if len(items) > 2 else True
STATE.require_tx() STATE.require_tx()
return putmem(address, length, pages, True) return putmem(address, length, pages, True)
@ -436,27 +441,28 @@ def ghidra_trace_putval(items):
return put_bytes(start, end, pages, True) return put_bytes(start, end, pages, True)
def ghidra_trace_putmem_state(items): def putmem_state(address, length, state, pages=True):
"""
Set the state of the given range of memory in the Ghidra trace.
"""
items = items.split(" ")
address = items[0]
length = items[1]
state = items[2]
STATE.require_tx()
STATE.trace.validate_state(state) STATE.trace.validate_state(state)
start, end = eval_range(address, length) start, end = eval_range(address, length)
if pages:
start, end = quantize_pages(start, end)
nproc = util.selected_process() nproc = util.selected_process()
base, addr = STATE.trace.memory_mapper.map(nproc, start) base, addr = STATE.trace.memory_mapper.map(nproc, start)
if base != addr.space: if base != addr.space and state != 'unknown':
trace.create_overlay_space(base, addr.space) trace.create_overlay_space(base, addr.space)
STATE.trace.set_memory_state(addr.extend(end - start), state) STATE.trace.set_memory_state(addr.extend(end - start), state)
def ghidra_trace_delmem(items): def ghidra_trace_putmem_state(address, length, state, pages=True):
"""
Set the state of the given range of memory in the Ghidra trace.
"""
STATE.require_tx()
return putmem_state(address, length, state, pages)
def ghidra_trace_delmem(address, length):
""" """
Delete the given range of memory from the Ghidra trace. Delete the given range of memory from the Ghidra trace.
@ -465,10 +471,6 @@ def ghidra_trace_delmem(items):
not quantize. You must do that yourself, if necessary. not quantize. You must do that yourself, if necessary.
""" """
items = items.split(" ")
address = items[0]
length = items[1]
STATE.require_tx() STATE.require_tx()
start, end = eval_range(address, length) start, end = eval_range(address, length)
nproc = util.selected_process() nproc = util.selected_process()
@ -477,6 +479,7 @@ def ghidra_trace_delmem(items):
STATE.trace.delete_bytes(addr.extend(end - start)) STATE.trace.delete_bytes(addr.extend(end - start))
@util.dbg.eng_thread
def putreg(): def putreg():
nproc = util.selected_process() nproc = util.selected_process()
if nproc < 0: if nproc < 0:
@ -488,7 +491,7 @@ def putreg():
robj.insert() robj.insert()
mapper = STATE.trace.register_mapper mapper = STATE.trace.register_mapper
values = [] values = []
regs = dbg().reg regs = util.dbg._base.reg
for i in range(0, len(regs)): for i in range(0, len(regs)):
name = regs._reg.GetDescription(i)[0] name = regs._reg.GetDescription(i)[0]
value = regs._get_register_by_index(i) value = regs._get_register_by_index(i)
@ -511,6 +514,7 @@ def ghidra_trace_putreg():
putreg() putreg()
@util.dbg.eng_thread
def ghidra_trace_delreg(group='all'): def ghidra_trace_delreg(group='all'):
""" """
Delete the given register group for the curent frame from the Ghidra trace. Delete the given register group for the curent frame from the Ghidra trace.
@ -524,11 +528,11 @@ def ghidra_trace_delreg(group='all'):
space = REGS_PATTERN.format(procnum=nproc, tnum=nthrd) space = REGS_PATTERN.format(procnum=nproc, tnum=nthrd)
mapper = STATE.trace.register_mapper mapper = STATE.trace.register_mapper
names = [] names = []
regs = dbg().reg regs = util.dbg._base.reg
for i in range(0, len(regs)): for i in range(0, len(regs)):
name = regs._reg.GetDescription(i)[0] name = regs._reg.GetDescription(i)[0]
names.append(mapper.map_name(nproc, name)) names.append(mapper.map_name(nproc, name))
return STATE.trace.delete_registers(space, names) STATE.trace.delete_registers(space, names)
def ghidra_trace_create_obj(path=None): def ghidra_trace_create_obj(path=None):
@ -543,8 +547,7 @@ def ghidra_trace_create_obj(path=None):
STATE.require_tx() STATE.require_tx()
obj = STATE.trace.create_object(path) obj = STATE.trace.create_object(path)
obj.insert() obj.insert()
print("Created object: id={}, path='{}'\n".format(obj.id, obj.path)) print("Created object: id={}, path='{}'".format(obj.id, obj.path))
return {'id': obj.id, 'path': obj.path}
def ghidra_trace_insert_obj(path): def ghidra_trace_insert_obj(path):
@ -556,8 +559,7 @@ def ghidra_trace_insert_obj(path):
# humans. # humans.
STATE.require_tx() STATE.require_tx()
span = STATE.trace.proxy_object_path(path).insert() span = STATE.trace.proxy_object_path(path).insert()
print("Inserted object: lifespan={}\n".format(span)) print("Inserted object: lifespan={}".format(span))
return {'lifespan': span}
def ghidra_trace_remove_obj(path): def ghidra_trace_remove_obj(path):
@ -600,10 +602,10 @@ def to_string_list(value, encoding):
def eval_value(value, schema=None): def eval_value(value, schema=None):
if schema == sch.CHAR or schema == sch.BYTE or schema == sch.SHORT or schema == sch.INT or schema == sch.LONG or schema == None: if schema == sch.CHAR or schema == sch.BYTE or schema == sch.SHORT or schema == sch.INT or schema == sch.LONG or schema == None:
value = util.get_eval(value) value = util.parse_and_eval(value)
return value, schema return value, schema
if schema == sch.ADDRESS: if schema == sch.ADDRESS:
value = util.get_eval(value) value = util.parse_and_eval(value)
nproc = util.selected_process() nproc = util.selected_process()
base, addr = STATE.trace.memory_mapper.map(nproc, value) base, addr = STATE.trace.memory_mapper.map(nproc, value)
return (base, addr), sch.ADDRESS return (base, addr), sch.ADDRESS
@ -696,8 +698,7 @@ def ghidra_trace_get_obj(path):
trace = STATE.require_trace() trace = STATE.require_trace()
object = trace.get_object(path) object = trace.get_object(path)
print("{}\t{}\n".format(object.id, object.path)) print("{}\t{}".format(object.id, object.path))
return object
class TableColumn(object): class TableColumn(object):
@ -734,7 +735,7 @@ class Tabular(object):
for rn in range(self.num_rows): for rn in range(self.num_rows):
for c in self.columns: for c in self.columns:
c.print_cell(rn) c.print_cell(rn)
print('\n') print('')
def val_repr(value): def val_repr(value):
@ -761,18 +762,13 @@ def ghidra_trace_get_values(pattern):
trace = STATE.require_trace() trace = STATE.require_trace()
values = trace.get_values(pattern) values = trace.get_values(pattern)
print_values(values) print_values(values)
return values
def ghidra_trace_get_values_rng(items): def ghidra_trace_get_values_rng(address, length):
""" """
List all values intersecting a given address range. List all values intersecting a given address range.
""" """
items = items.split(" ")
address = items[0]
length = items[1]
trace = STATE.require_trace() trace = STATE.require_trace()
start, end = eval_range(address, length) start, end = eval_range(address, length)
nproc = util.selected_process() nproc = util.selected_process()
@ -780,7 +776,6 @@ def ghidra_trace_get_values_rng(items):
# Do not create the space. We're querying. No tx. # Do not create the space. We're querying. No tx.
values = trace.get_values_intersecting(addr.extend(end - start)) values = trace.get_values_intersecting(addr.extend(end - start))
print_values(values) print_values(values)
return values
def activate(path=None): def activate(path=None):
@ -825,34 +820,33 @@ def ghidra_trace_disassemble(address):
trace.create_overlay_space(base, addr.space) trace.create_overlay_space(base, addr.space)
length = STATE.trace.disassemble(addr) length = STATE.trace.disassemble(addr)
print("Disassembled {} bytes\n".format(length)) print("Disassembled {} bytes".format(length))
return {'length': length}
@util.dbg.eng_thread
def compute_proc_state(nproc=None): def compute_proc_state(nproc=None):
status = dbg()._control.GetExecutionStatus() status = util.dbg._base._control.GetExecutionStatus()
if status == DbgEng.DEBUG_STATUS_BREAK: if status == DbgEng.DEBUG_STATUS_BREAK:
return 'STOPPED' return 'STOPPED'
return 'RUNNING' return 'RUNNING'
def put_processes(running=False): def put_processes(running=False):
radix = util.get_convenience_variable('output-radix') # | always displays PID in hex
if radix == 'auto': # TODO: I'm not sure about the engine id
radix = 16
keys = [] keys = []
for i, p in enumerate(util.process_list(running)): # Set running=True to avoid process changes, even while stopped
for i, p in enumerate(util.process_list(running=True)):
ipath = PROCESS_PATTERN.format(procnum=i) ipath = PROCESS_PATTERN.format(procnum=i)
keys.append(PROCESS_KEY_PATTERN.format(procnum=i)) keys.append(PROCESS_KEY_PATTERN.format(procnum=i))
procobj = STATE.trace.create_object(ipath) procobj = STATE.trace.create_object(ipath)
istate = compute_proc_state(i) istate = compute_proc_state(i)
procobj.set_value('_state', istate) procobj.set_value('_state', istate)
if running == False: pid = p[0]
procobj.set_value('_pid', p[0]) procobj.set_value('_pid', pid)
pidstr = ('0x{:x}' if radix == procobj.set_value('_display', '{:x} {:x}'.format(i, pid))
16 else '0{:o}' if radix == 8 else '{}').format(p[0]) if len(p) > 1:
procobj.set_value('_display', pidstr)
procobj.set_value('Name', str(p[1])) procobj.set_value('Name', str(p[1]))
procobj.set_value('PEB', hex(p[2])) procobj.set_value('PEB', hex(p[2]))
procobj.insert() procobj.insert()
@ -860,8 +854,6 @@ def put_processes(running=False):
def put_state(event_process): def put_state(event_process):
STATE.require_no_tx()
STATE.tx = STATE.require_trace().start_tx("state", undoable=False)
ipath = PROCESS_PATTERN.format(procnum=event_process) ipath = PROCESS_PATTERN.format(procnum=event_process)
procobj = STATE.trace.create_object(ipath) procobj = STATE.trace.create_object(ipath)
state = compute_proc_state(event_process) state = compute_proc_state(event_process)
@ -873,8 +865,6 @@ def put_state(event_process):
threadobj = STATE.trace.create_object(ipath) threadobj = STATE.trace.create_object(ipath)
threadobj.set_value('_state', state) threadobj.set_value('_state', state)
threadobj.insert() threadobj.insert()
STATE.require_tx().commit()
STATE.reset_tx()
def ghidra_trace_put_processes(): def ghidra_trace_put_processes():
@ -887,10 +877,11 @@ def ghidra_trace_put_processes():
put_processes() put_processes()
@util.dbg.eng_thread
def put_available(): def put_available():
radix = util.get_convenience_variable('output-radix') radix = util.get_convenience_variable('output-radix')
keys = [] keys = []
result = dbg().cmd(".tlist") result = util.dbg._base.cmd(".tlist")
lines = result.split("\n") lines = result.split("\n")
for i in lines: for i in lines:
i = i.strip() i = i.strip()
@ -923,6 +914,7 @@ def ghidra_trace_put_available():
put_available() put_available()
@util.dbg.eng_thread
def put_single_breakpoint(bp, ibobj, nproc, ikeys): def put_single_breakpoint(bp, ibobj, nproc, ikeys):
mapper = STATE.trace.memory_mapper mapper = STATE.trace.memory_mapper
bpath = PROC_BREAK_PATTERN.format(procnum=nproc, breaknum=bp.GetId()) bpath = PROC_BREAK_PATTERN.format(procnum=nproc, breaknum=bp.GetId())
@ -937,7 +929,7 @@ def put_single_breakpoint(bp, ibobj, nproc, ikeys):
else: else:
address = bp.GetOffset() address = bp.GetOffset()
offset = "%016x" % address offset = "%016x" % address
expr = dbg().get_name_by_offset(address) expr = util.dbg._base.get_name_by_offset(address)
try: try:
tid = bp.GetMatchThreadId() tid = bp.GetMatchThreadId()
tid = "%04x" % tid tid = "%04x" % tid
@ -965,7 +957,7 @@ def put_single_breakpoint(bp, ibobj, nproc, ikeys):
STATE.trace.create_overlay_space(base, addr.space) STATE.trace.create_overlay_space(base, addr.space)
brkobj.set_value('_range', addr.extend(width)) brkobj.set_value('_range', addr.extend(width))
except Exception as e: except Exception as e:
print("Error: Could not get range for breakpoint: {}\n".format(e)) print("Error: Could not get range for breakpoint: {}".format(e))
else: # I guess it's a catchpoint else: # I guess it's a catchpoint
pass pass
@ -985,6 +977,7 @@ def put_single_breakpoint(bp, ibobj, nproc, ikeys):
ikeys.append(k) ikeys.append(k)
@util.dbg.eng_thread
def put_breakpoints(): def put_breakpoints():
target = util.get_target() target = util.get_target()
nproc = util.selected_process() nproc = util.selected_process()
@ -992,12 +985,12 @@ def put_breakpoints():
ibobj = STATE.trace.create_object(ibpath) ibobj = STATE.trace.create_object(ibpath)
keys = [] keys = []
ikeys = [] ikeys = []
ids = [bpid for bpid in dbg().breakpoints] ids = [bpid for bpid in util.dbg._base.breakpoints]
for bpid in ids: for bpid in ids:
try: try:
bp = dbg()._control.GetBreakpointById(bpid) bp = util.dbg._base._control.GetBreakpointById(bpid)
except exception.E_NOINTERFACE_Error: except exception.E_NOINTERFACE_Error:
dbg().breakpoints._remove_stale(bpid) util.dbg._base.breakpoints._remove_stale(bpid)
continue continue
keys.append(PROC_BREAK_KEY_PATTERN.format(breaknum=bpid)) keys.append(PROC_BREAK_KEY_PATTERN.format(breaknum=bpid))
put_single_breakpoint(bp, ibobj, nproc, ikeys) put_single_breakpoint(bp, ibobj, nproc, ikeys)
@ -1036,10 +1029,11 @@ def ghidra_trace_put_environment():
put_environment() put_environment()
@util.dbg.eng_thread
def put_regions(): def put_regions():
nproc = util.selected_process() nproc = util.selected_process()
try: try:
regions = dbg().memory_list() regions = util.dbg._base.memory_list()
except Exception: except Exception:
regions = [] regions = []
if len(regions) == 0 and util.selected_thread() != None: if len(regions) == 0 and util.selected_thread() != None:
@ -1082,10 +1076,11 @@ def ghidra_trace_put_regions():
put_regions() put_regions()
@util.dbg.eng_thread
def put_modules(): def put_modules():
target = util.get_target() target = util.get_target()
nproc = util.selected_process() nproc = util.selected_process()
modules = dbg().module_list() modules = util.dbg._base.module_list()
mapper = STATE.trace.memory_mapper mapper = STATE.trace.memory_mapper
mod_keys = [] mod_keys = []
for m in modules: for m in modules:
@ -1137,38 +1132,33 @@ def convert_state(t):
return 'RUNNING' return 'RUNNING'
def convert_tid(t): def compute_thread_display(i, pid, tid, t):
if t[1] == 0: if len(t) > 1:
return t[2] return '{:x} {:x}:{:x} {}'.format(i, pid, tid, t[2])
return t[1] return '{:x} {:x}:{:x}'.format(i, pid, tid)
def compute_thread_display(tidstr, t):
return '[{} {}]'.format(tidstr, t[2])
def put_threads(running=False): def put_threads(running=False):
radix = util.get_convenience_variable('output-radix') # ~ always displays PID:TID in hex
if radix == 'auto': # TODO: I'm not sure about the engine id
radix = 16
nproc = util.selected_process() nproc = util.selected_process()
if nproc == None: if nproc is None:
return return
pid = util.dbg.pid
keys = [] keys = []
for i, t in enumerate(util.thread_list(running)): # Set running=True to avoid thread changes, even while stopped
for i, t in enumerate(util.thread_list(running=True)):
tpath = THREAD_PATTERN.format(procnum=nproc, tnum=i) tpath = THREAD_PATTERN.format(procnum=nproc, tnum=i)
tobj = STATE.trace.create_object(tpath) tobj = STATE.trace.create_object(tpath)
keys.append(THREAD_KEY_PATTERN.format(tnum=i)) keys.append(THREAD_KEY_PATTERN.format(tnum=i))
#tobj.set_value('_state', convert_state(t))
if running == False: tid = t[0]
tobj.set_value('_name', t[2]) tobj.set_value('_tid', tid)
tid = t[0] tobj.set_value('_short_display',
tobj.set_value('_tid', tid) '{:x} {:x}:{:x}'.format(i, pid, tid))
tidstr = ('0x{:x}' if radix == tobj.set_value('_display', compute_thread_display(i, pid, tid, t))
16 else '0{:o}' if radix == 8 else '{}').format(tid) if len(t) > 1:
tobj.set_value('_short_display', '[{}.{}:{}]'.format(
nproc, i, tidstr))
tobj.set_value('_display', compute_thread_display(tidstr, t))
tobj.set_value('TEB', hex(t[1])) tobj.set_value('TEB', hex(t[1]))
tobj.set_value('Name', t[2]) tobj.set_value('Name', t[2])
tobj.insert() tobj.insert()
@ -1199,6 +1189,7 @@ def ghidra_trace_put_threads():
put_threads() put_threads()
@util.dbg.eng_thread
def put_frames(): def put_frames():
nproc = util.selected_process() nproc = util.selected_process()
mapper = STATE.trace.memory_mapper mapper = STATE.trace.memory_mapper
@ -1207,7 +1198,7 @@ def put_frames():
return return
keys = [] keys = []
# f : _DEBUG_STACK_FRAME # f : _DEBUG_STACK_FRAME
for f in dbg().backtrace_list(): for f in util.dbg._base.backtrace_list():
fpath = FRAME_PATTERN.format( fpath = FRAME_PATTERN.format(
procnum=nproc, tnum=nthrd, level=f.FrameNumber) procnum=nproc, tnum=nthrd, level=f.FrameNumber)
fobj = STATE.trace.create_object(fpath) fobj = STATE.trace.create_object(fpath)
@ -1254,8 +1245,8 @@ def ghidra_trace_put_all():
put_breakpoints() put_breakpoints()
put_available() put_available()
ghidra_trace_putreg() ghidra_trace_putreg()
ghidra_trace_putmem("$pc 1") ghidra_trace_putmem(util.get_pc(), 1)
ghidra_trace_putmem("$sp 1") ghidra_trace_putmem(util.get_sp(), 1)
def ghidra_trace_install_hooks(): def ghidra_trace_install_hooks():
@ -1324,34 +1315,42 @@ def ghidra_util_wait_stopped(timeout=1):
raise RuntimeError('Timed out waiting for thread to stop') raise RuntimeError('Timed out waiting for thread to stop')
def dbg(): def get_prompt_text():
return util.get_debugger() try:
return util.dbg.get_prompt_text()
except util.DebuggeeRunningException:
return 'Running>'
SHOULD_WAIT = ['GO', 'STEP_BRANCH', 'STEP_INTO', 'STEP_OVER'] @util.dbg.eng_thread
def exec_cmd(cmd):
dbg = util.dbg
dbg.cmd(cmd, quiet=False)
stat = dbg.exec_status()
if stat != 'BREAK':
dbg.wait()
def repl(): def repl():
print("This is the dbgeng.dll (WinDbg) REPL. To drop to Python3, press Ctrl-C.") print("")
print("This is the Windows Debugger REPL. To drop to Python, type .exit")
while True: while True:
# TODO: Implement prompt retrieval in PR to pybag? print(get_prompt_text(), end=' ')
print('dbg> ', end='')
try: try:
cmd = input().strip() cmd = input().strip()
if not cmd: if cmd == '':
continue continue
dbg().cmd(cmd, quiet=True) elif cmd == '.exit':
stat = dbg().exec_status() break
if stat != 'BREAK': exec_cmd(cmd)
dbg().wait()
else:
pass
# dbg().dispatch_events()
except KeyboardInterrupt as e: except KeyboardInterrupt as e:
util.dbg.interrupt()
except util.DebuggeeRunningException as e:
print("") print("")
print("You have left the dbgeng REPL and are now at the Python3 interpreter.") print("Debuggee is Running. Use Ctrl-C to interrupt.")
print("use repl() to re-enter.") except BaseException as e:
return pass # Error is printed by another mechanism
except: print("")
# Assume cmd() has already output the error print("You have left the Windows Debugger REPL and are now at the Python "
pass "interpreter.")
print("To re-enter, type repl()")

View file

@ -13,20 +13,27 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
## ##
from _ctypes_test import func
import functools
import sys import sys
import time
import threading import threading
import time
import traceback
from comtypes.hresult import S_OK
from pybag import pydbg from pybag import pydbg
from pybag.dbgeng.callbacks import EventHandler
from pybag.dbgeng import core as DbgEng from pybag.dbgeng import core as DbgEng
from pybag.dbgeng import exception from pybag.dbgeng import exception
from pybag.dbgeng.callbacks import EventHandler
from pybag.dbgeng.idebugbreakpoint import DebugBreakpoint from pybag.dbgeng.idebugbreakpoint import DebugBreakpoint
from . import commands, util from . import commands, util
ALL_EVENTS = 0xFFFF ALL_EVENTS = 0xFFFF
class HookState(object): class HookState(object):
__slots__ = ('installed', 'mem_catchpoint') __slots__ = ('installed', 'mem_catchpoint')
@ -36,7 +43,8 @@ class HookState(object):
class ProcessState(object): class ProcessState(object):
__slots__ = ('first', 'regions', 'modules', 'threads', 'breaks', 'watches', 'visited', 'waiting') __slots__ = ('first', 'regions', 'modules', 'threads',
'breaks', 'watches', 'visited', 'waiting')
def __init__(self): def __init__(self):
self.first = True self.first = True
@ -48,10 +56,10 @@ class ProcessState(object):
self.watches = False self.watches = False
# For frames and threads that have already been synced since last stop # For frames and threads that have already been synced since last stop
self.visited = set() self.visited = set()
self.waiting = True self.waiting = False
def record(self, description=None): def record(self, description=None):
#print("RECORDING") # print("RECORDING")
first = self.first first = self.first
self.first = False self.first = False
if description is not None: if description is not None:
@ -66,8 +74,10 @@ class ProcessState(object):
if thread is not None: if thread is not None:
if first or thread not in self.visited: if first or thread not in self.visited:
commands.putreg() commands.putreg()
commands.putmem("$pc", "1", display_result=False) commands.putmem('0x{:x}'.format(util.get_pc()),
commands.putmem("$sp", "1", display_result=False) "1", display_result=False)
commands.putmem('0x{:x}'.format(util.get_sp()),
"1", display_result=False)
commands.put_frames() commands.put_frames()
self.visited.add(thread) self.visited.add(thread)
frame = util.selected_frame() frame = util.selected_frame()
@ -122,55 +132,90 @@ BRK_STATE = BrkState()
PROC_STATE = {} PROC_STATE = {}
def log_errors(func):
'''
Wrap a function in a try-except that prints and reraises the
exception.
This is needed because pybag and/or the COM wrappers do not print
exceptions that occur during event callbacks.
'''
@functools.wraps(func)
def _func(*args, **kwargs):
try:
return func(*args, **kwargs)
except:
traceback.print_exc()
raise
return _func
@log_errors
def on_state_changed(*args): def on_state_changed(*args):
#print("ON_STATE_CHANGED") # print("ON_STATE_CHANGED")
# print(args)
if args[0] == DbgEng.DEBUG_CES_CURRENT_THREAD: if args[0] == DbgEng.DEBUG_CES_CURRENT_THREAD:
return on_thread_selected(args) return on_thread_selected(args)
elif args[0] == DbgEng.DEBUG_CES_BREAKPOINTS: elif args[0] == DbgEng.DEBUG_CES_BREAKPOINTS:
return on_breakpoint_modified(args) return on_breakpoint_modified(args)
elif args[0] == DbgEng.DEBUG_CES_RADIX: elif args[0] == DbgEng.DEBUG_CES_RADIX:
util.set_convenience_variable('output-radix', args[1]) util.set_convenience_variable('output-radix', args[1])
return DbgEng.DEBUG_STATUS_GO return S_OK
elif args[0] == DbgEng.DEBUG_CES_EXECUTION_STATUS: elif args[0] == DbgEng.DEBUG_CES_EXECUTION_STATUS:
util.dbg._ces_exec_status(args[1])
proc = util.selected_process() proc = util.selected_process()
if args[1] & DbgEng.DEBUG_STATUS_INSIDE_WAIT: if args[1] & DbgEng.DEBUG_STATUS_INSIDE_WAIT:
PROC_STATE[proc].waiting = True if proc in PROC_STATE:
return DbgEng.DEBUG_STATUS_GO # Process may have exited (so deleted) first
PROC_STATE[proc].waiting = False PROC_STATE[proc].waiting = True
commands.put_state(proc) return S_OK
if proc in PROC_STATE:
# Process may have exited (so deleted) first.
PROC_STATE[proc].waiting = False
trace = commands.STATE.trace
with commands.STATE.client.batch():
with trace.open_tx("State changed proc {}".format(proc)):
commands.put_state(proc)
if args[1] == DbgEng.DEBUG_STATUS_BREAK: if args[1] == DbgEng.DEBUG_STATUS_BREAK:
return on_stop(args) return on_stop(args)
else: else:
return on_cont(args) return on_cont(args)
return DbgEng.DEBUG_STATUS_GO return S_OK
@log_errors
def on_debuggee_changed(*args): def on_debuggee_changed(*args):
#print("ON_DEBUGGEE_CHANGED") # print("ON_DEBUGGEE_CHANGED: args={}".format(args))
# sys.stdout.flush()
trace = commands.STATE.trace trace = commands.STATE.trace
if trace is None: if trace is None:
return return S_OK
if args[1] == DbgEng.DEBUG_CDS_REGISTERS: if args[0] == DbgEng.DEBUG_CDS_REGISTERS:
on_register_changed(args[0][1]) on_register_changed(args[1])
#if args[1] == DbgEng.DEBUG_CDS_DATA: if args[0] == DbgEng.DEBUG_CDS_DATA:
# on_memory_changed(args[0][1]) on_memory_changed(args[1])
return DbgEng.DEBUG_STATUS_GO return S_OK
@log_errors
def on_session_status_changed(*args): def on_session_status_changed(*args):
#print("ON_STATUS_CHANGED") print("ON_STATUS_CHANGED: args={}".format(args))
trace = commands.STATE.trace trace = commands.STATE.trace
if trace is None: if trace is None:
return return
if args[0] == DbgEng.DEBUG_SESSION_ACTIVE or args[0] == DbgEng.DEBUG_SSESION_REBOOT: if args[0] == DbgEng.DEBUG_SESSION_ACTIVE or args[0] == DbgEng.DEBUG_SESSION_REBOOT:
with commands.STATE.client.batch(): with commands.STATE.client.batch():
with trace.open_tx("New Process {}".format(util.selected_process())): with trace.open_tx("New Session {}".format(util.selected_process())):
commands.put_processes() commands.put_processes()
return DbgEng.DEBUG_STATUS_GO return DbgEng.DEBUG_STATUS_GO
@log_errors
def on_symbol_state_changed(*args): def on_symbol_state_changed(*args):
#print("ON_SYMBOL_STATE_CHANGED") # print("ON_SYMBOL_STATE_CHANGED")
proc = util.selected_process()
if proc not in PROC_STATE:
return
trace = commands.STATE.trace trace = commands.STATE.trace
if trace is None: if trace is None:
return return
@ -179,20 +224,22 @@ def on_symbol_state_changed(*args):
return DbgEng.DEBUG_STATUS_GO return DbgEng.DEBUG_STATUS_GO
@log_errors
def on_system_error(*args): def on_system_error(*args):
print("ON_SYSTEM_ERROR") print("ON_SYSTEM_ERROR: args={}".format(args))
print(hex(args[0])) # print(hex(args[0]))
trace = commands.STATE.trace trace = commands.STATE.trace
if trace is None: if trace is None:
return return
with commands.STATE.client.batch(): with commands.STATE.client.batch():
with trace.open_tx("New Process {}".format(util.selected_process())): with trace.open_tx("System Error {}".format(util.selected_process())):
commands.put_processes() commands.put_processes()
return DbgEng.DEBUG_STATUS_BREAK return DbgEng.DEBUG_STATUS_BREAK
@log_errors
def on_new_process(*args): def on_new_process(*args):
#print("ON_NEW_PROCESS") # print("ON_NEW_PROCESS")
trace = commands.STATE.trace trace = commands.STATE.trace
if trace is None: if trace is None:
return return
@ -203,7 +250,7 @@ def on_new_process(*args):
def on_process_selected(): def on_process_selected():
#print("PROCESS_SELECTED") # print("PROCESS_SELECTED")
proc = util.selected_process() proc = util.selected_process()
if proc not in PROC_STATE: if proc not in PROC_STATE:
return return
@ -216,8 +263,9 @@ def on_process_selected():
commands.activate() commands.activate()
@log_errors
def on_process_deleted(*args): def on_process_deleted(*args):
#print("ON_PROCESS_DELETED") # print("ON_PROCESS_DELETED")
proc = args[0] proc = args[0]
on_exited(proc) on_exited(proc)
if proc in PROC_STATE: if proc in PROC_STATE:
@ -231,8 +279,9 @@ def on_process_deleted(*args):
return DbgEng.DEBUG_STATUS_BREAK return DbgEng.DEBUG_STATUS_BREAK
@log_errors
def on_threads_changed(*args): def on_threads_changed(*args):
#print("ON_THREADS_CHANGED") # print("ON_THREADS_CHANGED")
proc = util.selected_process() proc = util.selected_process()
if proc not in PROC_STATE: if proc not in PROC_STATE:
return DbgEng.DEBUG_STATUS_GO return DbgEng.DEBUG_STATUS_GO
@ -241,7 +290,8 @@ def on_threads_changed(*args):
def on_thread_selected(*args): def on_thread_selected(*args):
#print("THREAD_SELECTED") # print("THREAD_SELECTED: args={}".format(args))
# sys.stdout.flush()
nthrd = args[0][1] nthrd = args[0][1]
nproc = util.selected_process() nproc = util.selected_process()
if nproc not in PROC_STATE: if nproc not in PROC_STATE:
@ -261,7 +311,7 @@ def on_thread_selected(*args):
def on_register_changed(regnum): def on_register_changed(regnum):
#print("REGISTER_CHANGED") # print("REGISTER_CHANGED")
proc = util.selected_process() proc = util.selected_process()
if proc not in PROC_STATE: if proc not in PROC_STATE:
return return
@ -274,7 +324,25 @@ def on_register_changed(regnum):
commands.activate() commands.activate()
def on_memory_changed(space):
if space != DbgEng.DEBUG_DATA_SPACE_VIRTUAL:
return
proc = util.selected_process()
if proc not in PROC_STATE:
return
trace = commands.STATE.trace
if trace is None:
return
# Not great, but invalidate the whole space
# UI will only re-fetch what it needs
# But, some observations will not be recovered
with commands.STATE.client.batch():
with trace.open_tx("Memory changed"):
commands.putmem_state(0, 2**64, 'unknown')
def on_cont(*args): def on_cont(*args):
# print("ON CONT")
proc = util.selected_process() proc = util.selected_process()
if proc not in PROC_STATE: if proc not in PROC_STATE:
return return
@ -289,13 +357,14 @@ def on_cont(*args):
def on_stop(*args): def on_stop(*args):
# print("ON STOP")
proc = util.selected_process() proc = util.selected_process()
if proc not in PROC_STATE: if proc not in PROC_STATE:
print("not in state") # print("not in state")
return return
trace = commands.STATE.trace trace = commands.STATE.trace
if trace is None: if trace is None:
print("no trace") # print("no trace")
return return
state = PROC_STATE[proc] state = PROC_STATE[proc]
state.visited.clear() state.visited.clear()
@ -308,7 +377,7 @@ def on_stop(*args):
def on_exited(proc): def on_exited(proc):
if proc not in PROC_STATE: if proc not in PROC_STATE:
print("not in state") # print("not in state")
return return
trace = commands.STATE.trace trace = commands.STATE.trace
if trace is None: if trace is None:
@ -323,8 +392,9 @@ def on_exited(proc):
commands.activate() commands.activate()
@log_errors
def on_modules_changed(*args): def on_modules_changed(*args):
#print("ON_MODULES_CHANGED") # print("ON_MODULES_CHANGED")
proc = util.selected_process() proc = util.selected_process()
if proc not in PROC_STATE: if proc not in PROC_STATE:
return DbgEng.DEBUG_STATUS_GO return DbgEng.DEBUG_STATUS_GO
@ -333,6 +403,7 @@ def on_modules_changed(*args):
def on_breakpoint_created(bp): def on_breakpoint_created(bp):
# print("ON_BREAKPOINT_CREATED")
proc = util.selected_process() proc = util.selected_process()
if proc not in PROC_STATE: if proc not in PROC_STATE:
return return
@ -350,7 +421,7 @@ def on_breakpoint_created(bp):
def on_breakpoint_modified(*args): def on_breakpoint_modified(*args):
#print("BREAKPOINT_MODIFIED") # print("BREAKPOINT_MODIFIED")
proc = util.selected_process() proc = util.selected_process()
if proc not in PROC_STATE: if proc not in PROC_STATE:
return return
@ -362,9 +433,9 @@ def on_breakpoint_modified(*args):
ibobj = trace.create_object(ibpath) ibobj = trace.create_object(ibpath)
bpid = args[0][1] bpid = args[0][1]
try: try:
bp = dbg()._control.GetBreakpointById(bpid) bp = util.dbg._base._control.GetBreakpointById(bpid)
except exception.E_NOINTERFACE_Error: except exception.E_NOINTERFACE_Error:
dbg().breakpoints._remove_stale(bpid) util.dbg._base.breakpoints._remove_stale(bpid)
return on_breakpoint_deleted(bpid) return on_breakpoint_deleted(bpid)
return on_breakpoint_created(bp) return on_breakpoint_created(bp)
@ -383,32 +454,26 @@ def on_breakpoint_deleted(bpid):
trace.proxy_object_path(bpath).remove(tree=True) trace.proxy_object_path(bpath).remove(tree=True)
@log_errors
def on_breakpoint_hit(*args): def on_breakpoint_hit(*args):
trace = commands.STATE.trace # print("ON_BREAKPOINT_HIT: args={}".format(args))
if trace is None: return DbgEng.DEBUG_STATUS_BREAK
return
with commands.STATE.client.batch():
with trace.open_tx("New Process {}".format(util.selected_process())):
commands.put_processes()
return DbgEng.DEBUG_STATUS_GO
@log_errors
def on_exception(*args): def on_exception(*args):
trace = commands.STATE.trace # print("ON_EXCEPTION: args={}".format(args))
if trace is None: return DbgEng.DEBUG_STATUS_BREAK
return
with commands.STATE.client.batch():
with trace.open_tx("New Process {}".format(util.selected_process())):
commands.put_processes()
return DbgEng.DEBUG_STATUS_GO
@util.dbg.eng_thread
def install_hooks(): def install_hooks():
# print("Installing hooks")
if HOOK_STATE.installed: if HOOK_STATE.installed:
return return
HOOK_STATE.installed = True HOOK_STATE.installed = True
events = dbg().events events = util.dbg._base.events
events.engine_state(handler=on_state_changed) events.engine_state(handler=on_state_changed)
events.debuggee_state(handler=on_debuggee_changed) events.debuggee_state(handler=on_debuggee_changed)
@ -423,19 +488,23 @@ def install_hooks():
events.module_load(handler=on_modules_changed) events.module_load(handler=on_modules_changed)
events.unload_module(handler=on_modules_changed) events.unload_module(handler=on_modules_changed)
#events.breakpoint(handler=on_breakpoint_hit) events.breakpoint(handler=on_breakpoint_hit)
#events.exception(handler=on_exception) events.exception(handler=on_exception)
@util.dbg.eng_thread
def remove_hooks(): def remove_hooks():
# print("Removing hooks")
if not HOOK_STATE.installed: if not HOOK_STATE.installed:
return return
HOOK_STATE.installed = False HOOK_STATE.installed = False
dbg()._reset_callbacks() util.dbg._base._reset_callbacks()
def enable_current_process(): def enable_current_process():
# print("Enable current process")
proc = util.selected_process() proc = util.selected_process()
# print("proc: {}".format(proc))
PROC_STATE[proc] = ProcessState() PROC_STATE[proc] = ProcessState()
@ -444,6 +513,3 @@ def disable_current_process():
if proc in PROC_STATE: if proc in PROC_STATE:
# Silently ignore already disabled # Silently ignore already disabled
del PROC_STATE[proc] del PROC_STATE[proc]
def dbg():
return util.get_debugger()

View file

@ -14,21 +14,22 @@
# limitations under the License. # limitations under the License.
## ##
from concurrent.futures import Future, ThreadPoolExecutor from concurrent.futures import Future, ThreadPoolExecutor
from contextlib import redirect_stdout
from io import StringIO
import re import re
import sys import sys
from pybag import pydbg
from pybag.dbgeng import core as DbgEng, exception
from ghidratrace import sch from ghidratrace import sch
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
from pybag import pydbg
from pybag.dbgeng import core as DbgEng
from . import util, commands from . import util, commands
from contextlib import redirect_stdout
from io import StringIO
REGISTRY = MethodRegistry(ThreadPoolExecutor(max_workers=1)) REGISTRY = MethodRegistry(ThreadPoolExecutor(
max_workers=1, thread_name_prefix='MethodRegistry'))
def extre(base, ext): def extre(base, ext):
@ -85,11 +86,12 @@ def find_proc_by_obj(object):
def find_proc_by_procbreak_obj(object): def find_proc_by_procbreak_obj(object):
return find_proc_by_pattern(object, PROC_BREAKS_PATTERN, return find_proc_by_pattern(object, PROC_BREAKS_PATTERN,
"a BreakpointLocationContainer") "a BreakpointLocationContainer")
def find_proc_by_procwatch_obj(object): def find_proc_by_procwatch_obj(object):
return find_proc_by_pattern(object, PROC_WATCHES_PATTERN, return find_proc_by_pattern(object, PROC_WATCHES_PATTERN,
"a WatchpointContainer") "a WatchpointContainer")
def find_proc_by_env_obj(object): def find_proc_by_env_obj(object):
@ -178,25 +180,28 @@ def find_bpt_by_obj(object):
shared_globals = dict() shared_globals = dict()
@REGISTRY.method @REGISTRY.method
# @util.dbg.eng_thread
def execute(cmd: str, to_string: bool=False): def execute(cmd: str, to_string: bool=False):
"""Execute a CLI command.""" """Execute a Python3 command or script."""
#print("***{}***".format(cmd)) # print("***{}***".format(cmd))
#sys.stderr.flush() # sys.stderr.flush()
#sys.stdout.flush() # sys.stdout.flush()
if to_string: if to_string:
data = StringIO() data = StringIO()
with redirect_stdout(data): with redirect_stdout(data):
exec("{}".format(cmd), shared_globals) exec(cmd, shared_globals)
return data.getvalue() return data.getvalue()
else: else:
exec("{}".format(cmd), shared_globals) exec(cmd, shared_globals)
@REGISTRY.method @REGISTRY.method
# @util.dbg.eng_thread
def evaluate(expr: str): def evaluate(expr: str):
"""Execute a CLI command.""" """Evaluate a Python3 expression."""
return str(eval("{}".format(expr), shared_globals)) return str(eval(expr, shared_globals))
@REGISTRY.method(action='refresh') @REGISTRY.method(action='refresh')
@ -241,6 +246,7 @@ def refresh_environment(node: sch.Schema('Environment')):
with commands.open_tracked_tx('Refresh Environment'): with commands.open_tracked_tx('Refresh Environment'):
commands.ghidra_trace_put_environment() commands.ghidra_trace_put_environment()
@REGISTRY.method(action='refresh') @REGISTRY.method(action='refresh')
def refresh_threads(node: sch.Schema('ThreadContainer')): def refresh_threads(node: sch.Schema('ThreadContainer')):
"""Refresh the list of threads in the process.""" """Refresh the list of threads in the process."""
@ -287,6 +293,7 @@ def activate_process(process: sch.Schema('Process')):
"""Switch to the process.""" """Switch to the process."""
find_proc_by_obj(process) find_proc_by_obj(process)
@REGISTRY.method(action='activate') @REGISTRY.method(action='activate')
def activate_thread(thread: sch.Schema('Thread')): def activate_thread(thread: sch.Schema('Thread')):
"""Switch to the thread.""" """Switch to the thread."""
@ -300,46 +307,54 @@ def activate_frame(frame: sch.Schema('StackFrame')):
@REGISTRY.method(action='delete') @REGISTRY.method(action='delete')
@util.dbg.eng_thread
def remove_process(process: sch.Schema('Process')): def remove_process(process: sch.Schema('Process')):
"""Remove the process.""" """Remove the process."""
find_proc_by_obj(process) find_proc_by_obj(process)
dbg().detach() dbg().detach_proc()
@REGISTRY.method(action='connect') @REGISTRY.method(action='connect')
@util.dbg.eng_thread
def target(process: sch.Schema('Process'), spec: str): def target(process: sch.Schema('Process'), spec: str):
"""Connect to a target machine or process.""" """Connect to a target machine or process."""
find_proc_by_obj(process) find_proc_by_obj(process)
dbg().attach(spec) dbg().attach_kernel(spec)
@REGISTRY.method(action='attach') @REGISTRY.method(action='attach')
@util.dbg.eng_thread
def attach_obj(target: sch.Schema('Attachable')): def attach_obj(target: sch.Schema('Attachable')):
"""Attach the process to the given target.""" """Attach the process to the given target."""
pid = find_availpid_by_obj(target) pid = find_availpid_by_obj(target)
dbg().attach(pid) dbg().attach_proc(pid)
@REGISTRY.method(action='attach') @REGISTRY.method(action='attach')
@util.dbg.eng_thread
def attach_pid(pid: int): def attach_pid(pid: int):
"""Attach the process to the given target.""" """Attach the process to the given target."""
dbg().attach(pid) dbg().attach_proc(pid)
@REGISTRY.method(action='attach') @REGISTRY.method(action='attach')
@util.dbg.eng_thread
def attach_name(process: sch.Schema('Process'), name: str): def attach_name(process: sch.Schema('Process'), name: str):
"""Attach the process to the given target.""" """Attach the process to the given target."""
dbg().atach(name) dbg().attach_proc(name)
@REGISTRY.method @REGISTRY.method
@util.dbg.eng_thread
def detach(process: sch.Schema('Process')): def detach(process: sch.Schema('Process')):
"""Detach the process's target.""" """Detach the process's target."""
dbg().detach() dbg().detach_proc()
@REGISTRY.method(action='launch') @REGISTRY.method(action='launch')
def launch_loader( def launch_loader(
file: ParamDesc(str, display='File'), file: ParamDesc(str, display='File'),
args: ParamDesc(str, display='Arguments')=''): args: ParamDesc(str, display='Arguments')=''):
""" """
Start a native process with the given command line, stopping at the ntdll initial breakpoint. Start a native process with the given command line, stopping at the ntdll initial breakpoint.
""" """
@ -351,65 +366,72 @@ def launch_loader(
@REGISTRY.method(action='launch') @REGISTRY.method(action='launch')
def launch( def launch(
timeout: ParamDesc(int, display='Timeout'),
file: ParamDesc(str, display='File'), file: ParamDesc(str, display='File'),
args: ParamDesc(str, display='Arguments')=''): args: ParamDesc(str, display='Arguments')='',
initial_break: ParamDesc(bool, display='Initial Break')=True,
timeout: ParamDesc(int, display='Timeout')=-1):
""" """
Run a native process with the given command line. Run a native process with the given command line.
""" """
command = file command = file
if args != None: if args != None:
command += " "+args command += " "+args
commands.ghidra_trace_create(command, initial_break=False, timeout=timeout, start_trace=False) commands.ghidra_trace_create(
command, initial_break=initial_break, timeout=timeout, start_trace=False)
@REGISTRY.method @REGISTRY.method
@util.dbg.eng_thread
def kill(process: sch.Schema('Process')): def kill(process: sch.Schema('Process')):
"""Kill execution of the process.""" """Kill execution of the process."""
dbg().terminate() commands.ghidra_trace_kill()
@REGISTRY.method(name='continue', action='resume') @REGISTRY.method(action='resume')
def _continue(process: sch.Schema('Process')): def go(process: sch.Schema('Process')):
"""Continue execution of the process.""" """Continue execution of the process."""
dbg().go() util.dbg.run_async(lambda: dbg().go())
@REGISTRY.method @REGISTRY.method
def interrupt(process: sch.Schema('Process')): def interrupt(process: sch.Schema('Process')):
"""Interrupt the execution of the debugged program.""" """Interrupt the execution of the debugged program."""
dbg()._control.SetInterrupt(DbgEng.DEBUG_INTERRUPT_ACTIVE) # SetInterrupt is reentrant, so bypass the thread checks
util.dbg._protected_base._control.SetInterrupt(
DbgEng.DEBUG_INTERRUPT_ACTIVE)
@REGISTRY.method(action='step_into') @REGISTRY.method(action='step_into')
def step_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1): def step_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
"""Step one instruction exactly.""" """Step one instruction exactly."""
find_thread_by_obj(thread) find_thread_by_obj(thread)
dbg().stepi(n) util.dbg.run_async(lambda: dbg().stepi(n))
@REGISTRY.method(action='step_over') @REGISTRY.method(action='step_over')
def step_over(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1): def step_over(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
"""Step one instruction, but proceed through subroutine calls.""" """Step one instruction, but proceed through subroutine calls."""
find_thread_by_obj(thread) find_thread_by_obj(thread)
dbg().stepo(n) util.dbg.run_async(lambda: dbg().stepo(n))
@REGISTRY.method(action='step_out') @REGISTRY.method(action='step_out')
def step_out(thread: sch.Schema('Thread')): def step_out(thread: sch.Schema('Thread')):
"""Execute until the current stack frame returns.""" """Execute until the current stack frame returns."""
find_thread_by_obj(thread) find_thread_by_obj(thread)
dbg().stepout() util.dbg.run_async(lambda: dbg().stepout())
@REGISTRY.method(action='step_to') @REGISTRY.method(action='step_to')
def step_to(thread: sch.Schema('Thread'), address: Address, max=None): def step_to(thread: sch.Schema('Thread'), address: Address, max=None):
"""Continue execution up to the given address.""" """Continue execution up to the given address."""
find_thread_by_obj(thread) find_thread_by_obj(thread)
return dbg().stepto(address.offset, max) # TODO: The address may need mapping.
util.dbg.run_async(lambda: dbg().stepto(address.offset, max))
@REGISTRY.method(action='break_sw_execute') @REGISTRY.method(action='break_sw_execute')
@util.dbg.eng_thread
def break_address(process: sch.Schema('Process'), address: Address): def break_address(process: sch.Schema('Process'), address: Address):
"""Set a breakpoint.""" """Set a breakpoint."""
find_proc_by_obj(process) find_proc_by_obj(process)
@ -417,6 +439,7 @@ def break_address(process: sch.Schema('Process'), address: Address):
@REGISTRY.method(action='break_sw_execute') @REGISTRY.method(action='break_sw_execute')
@util.dbg.eng_thread
def break_expression(expression: str): def break_expression(expression: str):
"""Set a breakpoint.""" """Set a breakpoint."""
# TODO: Escape? # TODO: Escape?
@ -424,6 +447,7 @@ def break_expression(expression: str):
@REGISTRY.method(action='break_hw_execute') @REGISTRY.method(action='break_hw_execute')
@util.dbg.eng_thread
def break_hw_address(process: sch.Schema('Process'), address: Address): def break_hw_address(process: sch.Schema('Process'), address: Address):
"""Set a hardware-assisted breakpoint.""" """Set a hardware-assisted breakpoint."""
find_proc_by_obj(process) find_proc_by_obj(process)
@ -431,12 +455,14 @@ def break_hw_address(process: sch.Schema('Process'), address: Address):
@REGISTRY.method(action='break_hw_execute') @REGISTRY.method(action='break_hw_execute')
@util.dbg.eng_thread
def break_hw_expression(expression: str): def break_hw_expression(expression: str):
"""Set a hardware-assisted breakpoint.""" """Set a hardware-assisted breakpoint."""
dbg().ba(expr=expression) dbg().ba(expr=expression)
@REGISTRY.method(action='break_read') @REGISTRY.method(action='break_read')
@util.dbg.eng_thread
def break_read_range(process: sch.Schema('Process'), range: AddressRange): def break_read_range(process: sch.Schema('Process'), range: AddressRange):
"""Set a read watchpoint.""" """Set a read watchpoint."""
find_proc_by_obj(process) find_proc_by_obj(process)
@ -444,12 +470,14 @@ def break_read_range(process: sch.Schema('Process'), range: AddressRange):
@REGISTRY.method(action='break_read') @REGISTRY.method(action='break_read')
@util.dbg.eng_thread
def break_read_expression(expression: str): def break_read_expression(expression: str):
"""Set a read watchpoint.""" """Set a read watchpoint."""
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ) dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ)
@REGISTRY.method(action='break_write') @REGISTRY.method(action='break_write')
@util.dbg.eng_thread
def break_write_range(process: sch.Schema('Process'), range: AddressRange): def break_write_range(process: sch.Schema('Process'), range: AddressRange):
"""Set a watchpoint.""" """Set a watchpoint."""
find_proc_by_obj(process) find_proc_by_obj(process)
@ -457,25 +485,30 @@ def break_write_range(process: sch.Schema('Process'), range: AddressRange):
@REGISTRY.method(action='break_write') @REGISTRY.method(action='break_write')
@util.dbg.eng_thread
def break_write_expression(expression: str): def break_write_expression(expression: str):
"""Set a watchpoint.""" """Set a watchpoint."""
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_WRITE) dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_WRITE)
@REGISTRY.method(action='break_access') @REGISTRY.method(action='break_access')
@util.dbg.eng_thread
def break_access_range(process: sch.Schema('Process'), range: AddressRange): def break_access_range(process: sch.Schema('Process'), range: AddressRange):
"""Set an access watchpoint.""" """Set an access watchpoint."""
find_proc_by_obj(process) find_proc_by_obj(process)
dbg().ba(expr=range.min, size=range.length(), access=DbgEng.DEBUG_BREAK_READ|DbgEng.DEBUG_BREAK_WRITE) dbg().ba(expr=range.min, size=range.length(),
access=DbgEng.DEBUG_BREAK_READ | DbgEng.DEBUG_BREAK_WRITE)
@REGISTRY.method(action='break_access') @REGISTRY.method(action='break_access')
@util.dbg.eng_thread
def break_access_expression(expression: str): def break_access_expression(expression: str):
"""Set an access watchpoint.""" """Set an access watchpoint."""
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ|DbgEng.DEBUG_BREAK_WRITE) dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ | DbgEng.DEBUG_BREAK_WRITE)
@REGISTRY.method(action='toggle') @REGISTRY.method(action='toggle')
@util.dbg.eng_thread
def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool): def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
"""Toggle a breakpoint.""" """Toggle a breakpoint."""
bpt = find_bpt_by_obj(breakpoint) bpt = find_bpt_by_obj(breakpoint)
@ -486,6 +519,7 @@ def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
@REGISTRY.method(action='delete') @REGISTRY.method(action='delete')
@util.dbg.eng_thread
def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')): def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
"""Delete a breakpoint.""" """Delete a breakpoint."""
bpt = find_bpt_by_obj(breakpoint) bpt = find_bpt_by_obj(breakpoint)
@ -495,14 +529,20 @@ def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
@REGISTRY.method @REGISTRY.method
def read_mem(process: sch.Schema('Process'), range: AddressRange): def read_mem(process: sch.Schema('Process'), range: AddressRange):
"""Read memory.""" """Read memory."""
# print("READ_MEM: process={}, range={}".format(process, range))
nproc = find_proc_by_obj(process) nproc = find_proc_by_obj(process)
offset_start = process.trace.memory_mapper.map_back( offset_start = process.trace.memory_mapper.map_back(
nproc, Address(range.space, range.min)) nproc, Address(range.space, range.min))
with commands.open_tracked_tx('Read Memory'): with commands.open_tracked_tx('Read Memory'):
dbg().read(range.min, range.length()) result = commands.put_bytes(
offset_start, offset_start + range.length() - 1, pages=True, display_result=False)
if result['count'] == 0:
commands.putmem_state(
offset_start, offset_start+range.length() - 1, 'error')
@REGISTRY.method @REGISTRY.method
@util.dbg.eng_thread
def write_mem(process: sch.Schema('Process'), address: Address, data: bytes): def write_mem(process: sch.Schema('Process'), address: Address, data: bytes):
"""Write memory.""" """Write memory."""
nproc = find_proc_by_obj(process) nproc = find_proc_by_obj(process)
@ -511,6 +551,7 @@ def write_mem(process: sch.Schema('Process'), address: Address, data: bytes):
@REGISTRY.method @REGISTRY.method
@util.dbg.eng_thread
def write_reg(frame: sch.Schema('StackFrame'), name: str, value: bytes): def write_reg(frame: sch.Schema('StackFrame'), name: str, value: bytes):
"""Write a register.""" """Write a register."""
util.select_frame() util.select_frame()
@ -519,4 +560,4 @@ def write_reg(frame: sch.Schema('StackFrame'), name: str, value: bytes):
def dbg(): def dbg():
return util.get_debugger() return util.dbg._base

View file

@ -96,7 +96,8 @@
<attribute name="_spec" schema="BreakpointSpec" /> <attribute name="_spec" schema="BreakpointSpec" />
<attribute name="_range" schema="RANGE" hidden="yes" /> <attribute name="_range" schema="RANGE" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" /> <attribute name="Enabled" schema="BOOL" required="yes" />
<attribute-alias from="_enabled" to="Enabled" />
<attribute name="Commands" schema="STRING" /> <attribute name="Commands" schema="STRING" />
<attribute name="Condition" schema="STRING" /> <attribute name="Condition" schema="STRING" />
<attribute name="Hit Count" schema="INT" /> <attribute name="Hit Count" schema="INT" />

View file

@ -14,49 +14,397 @@
# limitations under the License. # limitations under the License.
## ##
from collections import namedtuple from collections import namedtuple
from concurrent.futures import Future
import concurrent.futures
from ctypes import *
import functools
import io
import os import os
import queue
import re import re
import sys import sys
import threading
import traceback
from ctypes import * from comtypes import CoClass, GUID
from pybag import pydbg import comtypes
from comtypes.hresult import S_OK
from pybag import pydbg, userdbg, kerneldbg, crashdbg
from pybag.dbgeng import core as DbgEng from pybag.dbgeng import core as DbgEng
from pybag.dbgeng import exception from pybag.dbgeng import exception
from pybag.dbgeng import util as DbgUtil from pybag.dbgeng import util as DbgUtil
from pybag.dbgeng.callbacks import DbgEngCallbacks
base = pydbg.DebuggerBase()
DbgVersion = namedtuple('DbgVersion', ['full', 'major', 'minor'])
def _compute_pydbg_ver(): DbgVersion = namedtuple('DbgVersion', ['full', 'name', 'dotted', 'arch'])
blurb = "" #base._control.GetActualProcessorType()
full = ""
major = 0
minor = 0
return DbgVersion(full, int(major), int(minor))
DBG_VERSION = _compute_pydbg_ver() class StdInputCallbacks(CoClass):
# This is the UUID listed for IDebugInputCallbacks in DbgEng.h
# See https://github.com/tpn/winsdk-10/blob/master/Include/10.0.10240.0/um/DbgEng.h
# Accessed 9 Jan 2024
_reg_clsid_ = GUID("{9f50e42c-f136-499e-9a97-73036c94ed2d}")
_reg_threading_ = "Both"
_reg_progid_ = "dbgeng.DbgEngInputCallbacks.1"
_reg_novers_progid_ = "dbgeng.DbgEngInputCallbacks"
_reg_desc_ = "InputCallbacks"
_reg_clsctx_ = comtypes.CLSCTX_INPROC_SERVER
_com_interfaces_ = [DbgEng.IDebugInputCallbacks,
comtypes.typeinfo.IProvideClassInfo2,
comtypes.errorinfo.ISupportErrorInfo,
comtypes.connectionpoints.IConnectionPointContainer]
def __init__(self, ghidra_dbg):
self.ghidra_dbg = ghidra_dbg
self.expecting_input = False
def IDebugInputCallbacks_StartInput(self, buffer_size):
try:
self.expecting_input = True
self.buffer_size = buffer_size
print('Input>', end=' ')
line = input()
self.ghidra_dbg.return_input(line)
return S_OK
except:
traceback.print_exc()
raise
def IDebugInputCallbacks_EndInput(self):
self.expecting_input = False
def get_debugger(): class _Worker(threading.Thread):
return base def __init__(self, new_base, work_queue, dispatch):
super().__init__(name='DbgWorker', daemon=True)
self.new_base = new_base
self.work_queue = work_queue
self.dispatch = dispatch
def run(self):
self.new_base()
while True:
try:
work_item = self.work_queue.get_nowait()
except queue.Empty:
work_item = None
if work_item is None:
# HACK to avoid lockup on race condition
try:
self.dispatch(100)
except exception.DbgEngTimeout:
# This is routine :/
pass
else:
work_item.run()
# Derived from Python core library
# https://github.com/python/cpython/blob/main/Lib/concurrent/futures/thread.py
# accessed 9 Jan 2024
class _WorkItem(object):
def __init__(self, future, fn, args, kwargs):
self.future = future
self.fn = fn
self.args = args
self.kwargs = kwargs
def run(self):
try:
result = self.fn(*self.args, **self.kwargs)
except BaseException as exc:
self.future.set_exception(exc)
# Python core lib does this, I presume for good reason
self = None
else:
self.future.set_result(result)
class DebuggeeRunningException(BaseException):
pass
class DbgExecutor(object):
def __init__(self, ghidra_dbg):
self._ghidra_dbg = ghidra_dbg
self._work_queue = queue.SimpleQueue()
self._thread = _Worker(ghidra_dbg._new_base,
self._work_queue, ghidra_dbg._dispatch_events)
self._thread.start()
self._executing = False
def submit(self, fn, / , *args, **kwargs):
f = self._submit_no_exit(fn, *args, **kwargs)
self._ghidra_dbg.exit_dispatch()
return f
def _submit_no_exit(self, fn, / , *args, **kwargs):
f = Future()
if self._executing:
f.set_exception(DebuggeeRunningException("Debuggee is Running"))
return f
w = _WorkItem(f, fn, args, kwargs)
self._work_queue.put(w)
return f
def _clear_queue(self):
while True:
try:
work_item = self._work_queue.get_nowait()
except queue.Empty:
return
work_item.future.set_exception(
DebuggeeRunningException("Debuggee is Running"))
def _state_execute(self):
self._executing = True
self._clear_queue()
def _state_break(self):
self._executing = False
class WrongThreadException(BaseException):
pass
class AllDbg(pydbg.DebuggerBase):
# Steal user-mode methods
proc_list = userdbg.UserDbg.proc_list
ps = userdbg.UserDbg.ps
pids_by_name = userdbg.UserDbg.pids_by_name
create_proc = userdbg.UserDbg.create
attach_proc = userdbg.UserDbg.attach
reattach_proc = userdbg.UserDbg.reattach
detach_proc = userdbg.UserDbg.detach
abandon_proc = userdbg.UserDbg.abandon
terminate_proc = userdbg.UserDbg.terminate
handoff_proc = userdbg.UserDbg.handoff
connect_proc = userdbg.UserDbg.connect
disconnect_proc = userdbg.UserDbg.disconnect
# Steal kernel-mode methods
attach_kernel = kerneldbg.KernelDbg.attach
detach_kernel = kerneldbg.KernelDbg.detach
# Steal crash methods
load_dump = crashdbg.CrashDbg.load_dump
class GhidraDbg(object):
def __init__(self):
self._queue = DbgExecutor(self)
self._thread = self._queue._thread
# Wait for the executor to be operational before getting base
self._queue._submit_no_exit(lambda: None).result()
self._install_stdin()
base = self._protected_base
for name in ['set_output_mask', 'get_output_mask',
'exec_status', 'go', 'goto', 'go_handled', 'go_nothandled',
'stepi', 'stepo', 'stepbr', 'stepto', 'stepout',
'trace', 'traceto',
'wait',
'bitness',
'read', 'readstr', 'readptr', 'poi',
'write', 'writeptr',
'memory_list', 'address',
'instruction_at', 'disasm',
'pc', 'r', 'registers',
'get_name_by_offset', 'symbol', 'find_symbol',
'whereami',
'dd', 'dp', 'ds',
'bl', 'bc', 'bd', 'be', 'bp', 'ba',
'handle_list', 'handles',
'get_thread', 'set_thread', 'apply_threads', 'thread_list', 'threads',
'teb_addr', 'teb', 'peb_addr', 'peb',
'backtrace_list', 'backtrace',
'module_list', 'lm', 'exports', 'imports',
# User-mode
'proc_list', 'ps', 'pids_by_name',
'create_proc', 'attach_proc', 'reattach_proc',
'detach_proc', 'abandon_proc', 'terminate_proc', 'handoff_proc',
'connect_proc', 'disconnect_proc',
# Kernel-model
'attach_kernel', 'detach_kernel',
# Crash dump
'load_dump'
]:
setattr(self, name, self.eng_thread(getattr(base, name)))
def _new_base(self):
self._protected_base = AllDbg()
@property
def _base(self):
if threading.current_thread() is not self._thread:
raise WrongThreadException("Was {}. Want {}".format(
threading.current_thread(), self._thread))
return self._protected_base
def run(self, fn, *args, **kwargs):
# TODO: Remove this check?
if hasattr(self, '_thread') and threading.current_thread() is self._thread:
raise WrongThreadException()
future = self._queue.submit(fn, *args, **kwargs)
# https://stackoverflow.com/questions/72621731/is-there-any-graceful-way-to-interrupt-a-python-concurrent-future-result-call gives an alternative
while True:
try:
return future.result(0.5)
except concurrent.futures.TimeoutError:
pass
def run_async(self, fn, *args, **kwargs):
return self._queue.submit(fn, *args, **kwargs)
@staticmethod
def check_thread(func):
'''
For methods inside of GhidraDbg, ensure it runs on the dbgeng
thread
'''
@functools.wraps(func)
def _func(self, *args, **kwargs):
if threading.current_thread() is self._thread:
return func(self, *args, **kwargs)
else:
return self.run(func, self, *args, **kwargs)
return _func
def eng_thread(self, func):
'''
For methods and functions outside of GhidraDbg, ensure it
runs on this GhidraDbg's dbgeng thread
'''
@functools.wraps(func)
def _func(*args, **kwargs):
if threading.current_thread() is self._thread:
return func(*args, **kwargs)
else:
return self.run(func, *args, **kwargs)
return _func
def _ces_exec_status(self, argument):
if argument & 0xfffffff == DbgEng.DEBUG_STATUS_BREAK:
self._queue._state_break()
else:
self._queue._state_execute()
@check_thread
def _install_stdin(self):
self.input = StdInputCallbacks(self)
self._base._client.SetInputCallbacks(self.input)
# Manually decorated to preserve undecorated
def _dispatch_events(self, timeout=DbgEng.WAIT_INFINITE):
# NB: pybag's impl doesn't heed standalone
self._protected_base._client.DispatchCallbacks(timeout)
dispatch_events = check_thread(_dispatch_events)
# no check_thread. Must allow reentry
def exit_dispatch(self):
self._protected_base._client.ExitDispatch()
@check_thread
def cmd(self, cmdline, quiet=True):
# NB: pybag's impl always captures.
# Here, we let it print without capture if quiet is False
if quiet:
try:
buffer = io.StringIO()
self._base.callbacks.stdout = buffer
self._base._control.Execute(cmdline)
buffer.seek(0)
return buffer.read()
finally:
self._base.callbacks.reset_stdout()
else:
return self._base._control.Execute(cmdline)
@check_thread
def return_input(self, input):
# TODO: Contribute fix upstream (check_hr -> check_err)
# return self._base._control.ReturnInput(input.encode())
hr = self._base._control._ctrl.ReturnInput(input.encode())
exception.check_err(hr)
def interrupt(self):
# Contribute upstream?
# NOTE: This can be called from any thread
self._protected_base._control.SetInterrupt(
DbgEng.DEBUG_INTERRUPT_ACTIVE)
@check_thread
def get_current_system_id(self):
# TODO: upstream?
sys_id = c_ulong()
hr = self._base._systems._sys.GetCurrentSystemId(byref(sys_id))
exception.check_err(hr)
return sys_id.value
@check_thread
def get_prompt_text(self):
# TODO: upstream?
size = c_ulong()
hr = self._base._control._ctrl.GetPromptText(None, 0, byref(size))
prompt_buf = create_string_buffer(size.value)
hr = self._base._control._ctrl.GetPromptText(prompt_buf, size, None)
return prompt_buf.value.decode()
@check_thread
def get_actual_processor_type(self):
return self._base._control.GetActualProcessorType()
@property
@check_thread
def pid(self):
try:
return self._base._systems.GetCurrentProcessSystemId()
except exception.E_UNEXPECTED_Error:
# There is no process
return None
dbg = GhidraDbg()
@dbg.eng_thread
def compute_pydbg_ver():
pat = re.compile(
'(?P<name>.*Debugger.*) Version (?P<dotted>[\\d\\.]*) (?P<arch>\\w*)')
blurb = dbg.cmd('version')
matches = [pat.match(l) for l in blurb.splitlines() if pat.match(l)]
if len(matches) == 0:
return DbgVersion('Unknown', 'Unknown', '0', 'Unknown')
m = matches[0]
return DbgVersion(full=m.group(), **m.groupdict())
name, dotted_and_arch = full.split(' Version ')
DBG_VERSION = compute_pydbg_ver()
def get_target(): def get_target():
return 0 #get_debugger()._systems.GetCurrentSystemId() return dbg.get_current_system_id()
@dbg.eng_thread
def disassemble1(addr):
return DbgUtil.disassemble_instruction(dbg._base.bitness(), addr, dbg.read(addr, 15))
def get_inst(addr): def get_inst(addr):
dbg = get_debugger() return str(disassemble1(addr))
ins = DbgUtil.disassemble_instruction(dbg.bitness(), addr, dbg.read(addr, 15))
return str(ins)
def get_inst_sz(addr): def get_inst_sz(addr):
dbg = get_debugger() return int(disassemble1(addr).size)
ins = DbgUtil.disassemble_instruction(dbg.bitness(), addr, dbg.read(addr, 15))
return str(ins.size)
@dbg.eng_thread
def get_breakpoints(): def get_breakpoints():
ids = [bpid for bpid in get_debugger().breakpoints] ids = [bpid for bpid in dbg._base.breakpoints]
offset_set = [] offset_set = []
expr_set = [] expr_set = []
prot_set = [] prot_set = []
@ -64,7 +412,7 @@ def get_breakpoints():
stat_set = [] stat_set = []
for bpid in ids: for bpid in ids:
try: try:
bp = get_debugger()._control.GetBreakpointById(bpid) bp = dbg._base._control.GetBreakpointById(bpid)
except exception.E_NOINTERFACE_Error: except exception.E_NOINTERFACE_Error:
continue continue
@ -73,7 +421,7 @@ def get_breakpoints():
expr = bp.GetOffsetExpression() expr = bp.GetOffsetExpression()
else: else:
offset = "%016x" % bp.GetOffset() offset = "%016x" % bp.GetOffset()
expr = get_debugger().get_name_by_offset(bp.GetOffset()) expr = dbg._base.get_name_by_offset(bp.GetOffset())
if bp.GetType()[0] == DbgEng.DEBUG_BREAKPOINT_DATA: if bp.GetType()[0] == DbgEng.DEBUG_BREAKPOINT_DATA:
width, prot = bp.GetDataParameters() width, prot = bp.GetDataParameters()
@ -81,7 +429,7 @@ def get_breakpoints():
prot = {4: 'type=x', 3: 'type=rw', 2: 'type=w', 1: 'type=r'}[prot] prot = {4: 'type=x', 3: 'type=rw', 2: 'type=w', 1: 'type=r'}[prot]
else: else:
width = '' width = ''
prot = '' prot = ''
if bp.GetFlags() & DbgEng.DEBUG_BREAKPOINT_ENABLED: if bp.GetFlags() & DbgEng.DEBUG_BREAKPOINT_ENABLED:
status = 'enabled' status = 'enabled'
@ -95,48 +443,65 @@ def get_breakpoints():
stat_set.append(status) stat_set.append(status)
return zip(offset_set, expr_set, prot_set, width_set, stat_set) return zip(offset_set, expr_set, prot_set, width_set, stat_set)
@dbg.eng_thread
def selected_process(): def selected_process():
try: try:
return get_debugger()._systems.GetCurrentProcessId() return dbg._base._systems.GetCurrentProcessId()
#return current_process except exception.E_UNEXPECTED_Error:
except Exception:
return None return None
@dbg.eng_thread
def selected_thread(): def selected_thread():
try: try:
return get_debugger()._systems.GetCurrentThreadId() return dbg._base._systems.GetCurrentThreadId()
except Exception: except exception.E_UNEXPECTED_Error:
return None return None
@dbg.eng_thread
def selected_frame(): def selected_frame():
return 0 #selected_thread().GetSelectedFrame() try:
line = dbg.cmd('.frame').strip()
if not line:
return None
num_str = line.split(sep=None, maxsplit=1)[0]
return int(num_str, 16)
except OSError:
return None
except ValueError:
return None
@dbg.eng_thread
def select_process(id: int): def select_process(id: int):
return get_debugger()._systems.SetCurrentProcessId(id) return dbg._base._systems.SetCurrentProcessId(id)
@dbg.eng_thread
def select_thread(id: int): def select_thread(id: int):
return get_debugger()._systems.SetCurrentThreadId(id) return dbg._base._systems.SetCurrentThreadId(id)
@dbg.eng_thread
def select_frame(id: int): def select_frame(id: int):
#TODO: this needs to be fixed return dbg.cmd('.frame 0x{:x}'.format(id))
return id
def parse_and_eval(expr):
regs = get_debugger().reg
if expr == "$pc":
return regs.get_pc()
if expr == "$sp":
return regs.get_sp()
return get_eval(expr)
def get_eval(expr, type=None): @dbg.eng_thread
ctrl = get_debugger()._control._ctrl def parse_and_eval(expr, type=None):
if isinstance(expr, int):
return expr
# TODO: This could be contributed upstream
ctrl = dbg._base._control._ctrl
ctrl.SetExpressionSyntax(1) ctrl.SetExpressionSyntax(1)
value = DbgEng._DEBUG_VALUE() value = DbgEng._DEBUG_VALUE()
index = c_ulong() index = c_ulong()
if type == None: if type == None:
type = DbgEng.DEBUG_VALUE_INT64 type = DbgEng.DEBUG_VALUE_INT64
hr = ctrl.Evaluate(Expression="{}".format(expr).encode(),DesiredType=type,Value=byref(value),RemainderIndex=byref(index)) hr = ctrl.Evaluate(Expression=expr.encode(), DesiredType=type,
Value=byref(value), RemainderIndex=byref(index))
exception.check_err(hr) exception.check_err(hr)
if type == DbgEng.DEBUG_VALUE_INT8: if type == DbgEng.DEBUG_VALUE_INT8:
return value.u.I8 return value.u.I8
@ -157,96 +522,128 @@ def get_eval(expr, type=None):
if type == DbgEng.DEBUG_VALUE_FLOAT128: if type == DbgEng.DEBUG_VALUE_FLOAT128:
return value.u.F128Bytes return value.u.F128Bytes
@dbg.eng_thread
def get_pc():
return dbg._base.reg.get_pc()
@dbg.eng_thread
def get_sp():
return dbg._base.reg.get_sp()
@dbg.eng_thread
def GetProcessIdsByIndex(count=0): def GetProcessIdsByIndex(count=0):
# TODO: This could be contributed upstream?
if count == 0: if count == 0:
try : try:
count = get_debugger()._systems.GetNumberProcesses() count = dbg._base._systems.GetNumberProcesses()
except Exception: except Exception:
count = 0 count = 0
ids = (c_ulong * count)() ids = (c_ulong * count)()
sysids = (c_ulong * count)() sysids = (c_ulong * count)()
if count != 0: if count != 0:
hr = get_debugger()._systems._sys.GetProcessIdsByIndex(0, count, ids, sysids) hr = dbg._base._systems._sys.GetProcessIdsByIndex(
0, count, ids, sysids)
exception.check_err(hr) exception.check_err(hr)
return (tuple(ids), tuple(sysids)) return (tuple(ids), tuple(sysids))
@dbg.eng_thread
def GetCurrentProcessExecutableName(): def GetCurrentProcessExecutableName():
dbg = get_debugger() # TODO: upstream?
_dbg = dbg._base
size = c_ulong() size = c_ulong()
exesize = c_ulong() exesize = c_ulong()
hr = dbg._systems._sys.GetCurrentProcessExecutableName(None, size, byref(exesize)) hr = _dbg._systems._sys.GetCurrentProcessExecutableName(
None, size, byref(exesize))
exception.check_err(hr) exception.check_err(hr)
buffer = create_string_buffer(exesize.value) buffer = create_string_buffer(exesize.value)
size = exesize size = exesize
hr = dbg._systems._sys.GetCurrentProcessExecutableName(buffer, size, None) hr = _dbg._systems._sys.GetCurrentProcessExecutableName(buffer, size, None)
exception.check_err(hr) exception.check_err(hr)
buffer = buffer[:size.value] buffer = buffer[:size.value]
buffer = buffer.rstrip(b'\x00') buffer = buffer.rstrip(b'\x00')
return buffer return buffer
@dbg.eng_thread
def GetCurrentProcessPeb(): def GetCurrentProcessPeb():
dbg = get_debugger() # TODO: upstream?
_dbg = dbg._base
offset = c_ulonglong() offset = c_ulonglong()
hr = dbg._systems._sys.GetCurrentProcessPeb(byref(offset)) hr = _dbg._systems._sys.GetCurrentProcessPeb(byref(offset))
exception.check_err(hr) exception.check_err(hr)
return offset.value return offset.value
@dbg.eng_thread
def GetExitCode(): def GetExitCode():
# TODO: upstream?
exit_code = c_ulong() exit_code = c_ulong()
hr = get_debugger()._client._cli.GetExitCode(byref(exit_code)) hr = dbg._base._client._cli.GetExitCode(byref(exit_code))
return exit_code.value return exit_code.value
@dbg.eng_thread
def process_list(running=False): def process_list(running=False):
"""process_list() -> list of all processes""" """Get the list of all processes"""
dbg = get_debugger() _dbg = dbg._base
ids, sysids = GetProcessIdsByIndex() ids, sysids = GetProcessIdsByIndex()
pebs = [] pebs = []
names = [] names = []
try : curid = selected_process()
curid = dbg._systems.GetCurrentProcessId() try:
if running == False: if running:
return zip(sysids)
else:
for id in ids: for id in ids:
dbg._systems.SetCurrentProcessId(id) _dbg._systems.SetCurrentProcessId(id)
names.append(GetCurrentProcessExecutableName()) names.append(GetCurrentProcessExecutableName())
pebs.append(GetCurrentProcessPeb()) pebs.append(GetCurrentProcessPeb())
if running == False:
dbg._systems.SetCurrentProcessId(curid)
return zip(sysids, names, pebs) return zip(sysids, names, pebs)
except Exception: except Exception:
pass return zip(sysids)
return zip(sysids) finally:
if not running and curid is not None:
_dbg._systems.SetCurrentProcessId(curid)
@dbg.eng_thread
def thread_list(running=False): def thread_list(running=False):
"""thread_list() -> list of all threads""" """Get the list of all threads"""
dbg = get_debugger() _dbg = dbg._base
try : try:
ids, sysids = dbg._systems.GetThreadIdsByIndex() ids, sysids = _dbg._systems.GetThreadIdsByIndex()
except Exception: except Exception:
return zip([]) return zip([])
tebs = [] tebs = []
syms = [] syms = []
curid = dbg._systems.GetCurrentThreadId() curid = selected_thread()
if running == False: try:
for id in ids: if running:
dbg._systems.SetCurrentThreadId(id) return zip(sysids)
tebs.append(dbg._systems.GetCurrentThreadTeb()) else:
addr = dbg.reg.get_pc() for id in ids:
syms.append(dbg.get_name_by_offset(addr)) _dbg._systems.SetCurrentThreadId(id)
if running == False: tebs.append(_dbg._systems.GetCurrentThreadTeb())
dbg._systems.SetCurrentThreadId(curid) addr = _dbg.reg.get_pc()
return zip(sysids, tebs, syms) syms.append(_dbg.get_name_by_offset(addr))
return zip(sysids) return zip(sysids, tebs, syms)
except Exception:
return zip(sysids)
finally:
if not running and curid is not None:
_dbg._systems.SetCurrentThreadId(curid)
conv_map = {} conv_map = {}
def get_convenience_variable(id): def get_convenience_variable(id):
#val = get_target().GetEnvironment().Get(id)
if id not in conv_map: if id not in conv_map:
return "auto" return "auto"
val = conv_map[id] val = conv_map[id]
@ -254,7 +651,6 @@ def get_convenience_variable(id):
return "auto" return "auto"
return val return val
def set_convenience_variable(id, value): def set_convenience_variable(id, value):
#env = get_target().GetEnvironment()
#return env.Set(id, value, True)
conv_map[id] = value conv_map[id] = value

View file

@ -391,7 +391,8 @@ public class DebuggerCoordinates {
return NOWHERE; return NOWHERE;
} }
long snap = newTime.getSnap(); long snap = newTime.getSnap();
TraceThread newThread = thread != null && thread.getLifespan().contains(snap) ? thread Lifespan threadLifespan = thread == null ? null : thread.getLifespan();
TraceThread newThread = threadLifespan != null && threadLifespan.contains(snap) ? thread
: resolveThread(trace, target, newTime); : resolveThread(trace, target, newTime);
// This will cause the frame to reset to 0 on every snap change. That's fair.... // This will cause the frame to reset to 0 on every snap change. That's fair....
Integer newFrame = resolveFrame(newThread, newTime); Integer newFrame = resolveFrame(newThread, newTime);

View file

@ -23,6 +23,7 @@ import ghidra.rmi.trace.TraceRmi.*;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.Msg;
class OpenTrace implements ValueDecoder { class OpenTrace implements ValueDecoder {
final DoId doId; final DoId doId;
@ -84,19 +85,47 @@ class OpenTrace implements ValueDecoder {
@Override @Override
public Address toAddress(Addr addr, boolean required) { public Address toAddress(Addr addr, boolean required) {
/**
* Do not clamp here, like we do for ranges. The purpose of the given address is more
* specific here. Plus, we're not just omitting some addresses (like we would for ranges),
* we'd be moving the address. Thus, we'd be applying some attribute to a location that was
* never intended.
*/
AddressSpace space = getSpace(addr.getSpace(), required); AddressSpace space = getSpace(addr.getSpace(), required);
return space.getAddress(addr.getOffset()); return space.getAddress(addr.getOffset());
} }
@Override @Override
public AddressRange toRange(AddrRange range, boolean required) public AddressRange toRange(AddrRange range, boolean required) {
throws AddressOverflowException {
AddressSpace space = getSpace(range.getSpace(), required); AddressSpace space = getSpace(range.getSpace(), required);
if (space == null) { if (space == null) {
return null; return null;
} }
Address min = space.getAddress(range.getOffset()); /**
Address max = space.getAddress(range.getOffset() + range.getExtend()); * Clamp to only the valid addresses, but do at least warn.
*/
long minOffset = range.getOffset();
if (Long.compareUnsigned(minOffset, space.getMinAddress().getOffset()) < 0) {
Msg.warn(this, "Range [%s:%x+%x] partially exceeds space min. Clamping."
.formatted(range.getSpace(), range.getOffset(), range.getExtend()));
minOffset = space.getMinAddress().getOffset();
}
else if (Long.compareUnsigned(minOffset, space.getMaxAddress().getOffset()) > 0) {
throw new AddressOutOfBoundsException("Range [%s:%x+%x] entirely exceeds space max"
.formatted(range.getSpace(), range.getOffset(), range.getExtend()));
}
long maxOffset = range.getOffset() + range.getExtend(); // Use the requested offset, not adjusted
if (Long.compareUnsigned(maxOffset, space.getMaxAddress().getOffset()) > 0) {
Msg.warn(this, "Range [%s:%x+%x] partially exceeds space max. Clamping."
.formatted(range.getSpace(), range.getOffset(), range.getExtend()));
maxOffset = space.getMaxAddress().getOffset();
}
else if (Long.compareUnsigned(maxOffset, space.getMinAddress().getOffset()) < 0) {
throw new AddressOutOfBoundsException("Range [%s:%x+%x] entirely exceeds space min"
.formatted(range.getSpace(), range.getOffset(), range.getExtend()));
}
Address min = space.getAddress(minOffset);
Address max = space.getAddress(maxOffset);
return new AddressRangeImpl(min, max); return new AddressRangeImpl(min, max);
} }

View file

@ -503,22 +503,29 @@ public class TraceRmiHandler implements TraceRmiConnection {
} }
default String toString(RootMessage req) { default String toString(RootMessage req) {
return switch (req.getMsgCase()) { try {
case REQUEST_ACTIVATE -> "activate(%d, %d, %s)".formatted( return switch (req.getMsgCase()) {
req.getRequestActivate().getOid().getId(), case REQUEST_ACTIVATE -> "activate(%d, %d, %s)".formatted(
req.getRequestActivate().getObject().getId(), req.getRequestActivate().getOid().getId(),
req.getRequestActivate().getObject().getPath().getPath()); req.getRequestActivate().getObject().getId(),
case REQUEST_END_TX -> "endTx(%d)".formatted( req.getRequestActivate().getObject().getPath().getPath());
req.getRequestEndTx().getTxid().getId()); case REQUEST_END_TX -> "endTx(%d)".formatted(
case REQUEST_START_TX -> "startTx(%d,%s)".formatted( req.getRequestEndTx().getTxid().getId());
req.getRequestStartTx().getTxid().getId(), case REQUEST_START_TX -> "startTx(%d,%s)".formatted(
req.getRequestStartTx().getDescription()); req.getRequestStartTx().getTxid().getId(),
case REQUEST_SET_VALUE -> "setValue(%d,%s,%s)".formatted( req.getRequestStartTx().getDescription());
req.getRequestSetValue().getValue().getParent().getId(), case REQUEST_SET_VALUE -> "setValue(%d,%s,%s,=%s)".formatted(
req.getRequestSetValue().getValue().getParent().getPath().getPath(), req.getRequestSetValue().getValue().getParent().getId(),
req.getRequestSetValue().getValue().getKey()); req.getRequestSetValue().getValue().getParent().getPath().getPath(),
default -> null; req.getRequestSetValue().getValue().getKey(),
}; ValueDecoder.DISPLAY
.toValue(req.getRequestSetValue().getValue().getValue()));
default -> null;
};
}
catch (Throwable e) {
return "ERROR toStringing request: " + e;
}
} }
} }

View file

@ -70,7 +70,7 @@ public class TraceRmiTarget extends AbstractTarget {
private final Trace trace; private final Trace trace;
private final Matches matches = new Matches(); private final Matches matches = new Matches();
private final RequestCaches requestCaches = new RequestCaches(); private final RequestCaches requestCaches = new DorkedRequestCaches();
private final Set<TraceBreakpointKind> supportedBreakpointKinds; private final Set<TraceBreakpointKind> supportedBreakpointKinds;
public TraceRmiTarget(PluginTool tool, TraceRmiConnection connection, Trace trace) { public TraceRmiTarget(PluginTool tool, TraceRmiConnection connection, Trace trace) {
@ -760,21 +760,41 @@ public class TraceRmiTarget extends AbstractTarget {
} }
} }
protected static class RequestCaches { interface RequestCaches {
void invalidate();
void invalidateMemory();
CompletableFuture<Void> readBlock(Address min, RemoteMethod method,
Map<String, Object> args);
CompletableFuture<Void> readRegs(TraceObject obj, RemoteMethod method,
Map<String, Object> args);
}
protected static class DefaultRequestCaches implements RequestCaches {
final Map<TraceObject, CompletableFuture<Void>> readRegs = new HashMap<>(); final Map<TraceObject, CompletableFuture<Void>> readRegs = new HashMap<>();
final Map<Address, CompletableFuture<Void>> readBlock = new HashMap<>(); final Map<Address, CompletableFuture<Void>> readBlock = new HashMap<>();
@Override
public synchronized void invalidateMemory() {
readBlock.clear();
}
@Override
public synchronized void invalidate() { public synchronized void invalidate() {
readRegs.clear(); readRegs.clear();
readBlock.clear(); readBlock.clear();
} }
@Override
public synchronized CompletableFuture<Void> readRegs(TraceObject obj, RemoteMethod method, public synchronized CompletableFuture<Void> readRegs(TraceObject obj, RemoteMethod method,
Map<String, Object> args) { Map<String, Object> args) {
return readRegs.computeIfAbsent(obj, return readRegs.computeIfAbsent(obj,
o -> method.invokeAsync(args).toCompletableFuture().thenApply(__ -> null)); o -> method.invokeAsync(args).toCompletableFuture().thenApply(__ -> null));
} }
@Override
public synchronized CompletableFuture<Void> readBlock(Address min, RemoteMethod method, public synchronized CompletableFuture<Void> readBlock(Address min, RemoteMethod method,
Map<String, Object> args) { Map<String, Object> args) {
return readBlock.computeIfAbsent(min, return readBlock.computeIfAbsent(min,
@ -782,6 +802,28 @@ public class TraceRmiTarget extends AbstractTarget {
} }
} }
protected static class DorkedRequestCaches implements RequestCaches {
@Override
public void invalidate() {
}
@Override
public void invalidateMemory() {
}
@Override
public CompletableFuture<Void> readBlock(Address min, RemoteMethod method,
Map<String, Object> args) {
return method.invokeAsync(args).toCompletableFuture().thenApply(__ -> null);
}
@Override
public CompletableFuture<Void> readRegs(TraceObject obj, RemoteMethod method,
Map<String, Object> args) {
return method.invokeAsync(args).toCompletableFuture().thenApply(__ -> null);
}
}
@Override @Override
public CompletableFuture<Void> activateAsync(DebuggerCoordinates prev, public CompletableFuture<Void> activateAsync(DebuggerCoordinates prev,
DebuggerCoordinates coords) { DebuggerCoordinates coords) {
@ -818,6 +860,7 @@ public class TraceRmiTarget extends AbstractTarget {
@Override @Override
public CompletableFuture<Void> invalidateMemoryCachesAsync() { public CompletableFuture<Void> invalidateMemoryCachesAsync() {
requestCaches.invalidateMemory();
return AsyncUtils.nil(); return AsyncUtils.nil();
} }

View file

@ -15,6 +15,9 @@
*/ */
package ghidra.app.plugin.core.debug.service.tracermi; package ghidra.app.plugin.core.debug.service.tracermi;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
@ -22,6 +25,44 @@ import ghidra.rmi.trace.TraceRmi.*;
public interface ValueDecoder { public interface ValueDecoder {
ValueDecoder DEFAULT = new ValueDecoder() {}; ValueDecoder DEFAULT = new ValueDecoder() {};
ValueDecoder DISPLAY = new ValueDecoder() {
final Map<String, AddressSpace> spaces = new HashMap<>();
private AddressSpace getSpace(String space) {
return spaces.computeIfAbsent(space, name -> {
return new GenericAddressSpace(name, 64, AddressSpace.TYPE_RAM, 0);
});
}
@Override
public Address toAddress(Addr addr, boolean required) {
AddressSpace space = getSpace(addr.getSpace());
return space.getAddress(addr.getOffset());
}
@Override
public AddressRange toRange(AddrRange range, boolean required) {
AddressSpace space = getSpace(range.getSpace());
Address min = space.getAddress(range.getOffset());
Address max = space.getAddress(range.getOffset() + range.getExtend());
return new AddressRangeImpl(min, max);
}
@Override
public Object getObject(ObjDesc desc, boolean required) {
return "<Object id=%d path=%s>".formatted(desc.getId(), desc.getPath().getPath());
}
@Override
public Object getObject(ObjSpec spec, boolean required) {
return switch (spec.getKeyCase()) {
case KEY_NOT_SET -> "<ERROR: No key>";
case ID -> "<Object id=%d>".formatted(spec.getId());
case PATH -> "<Object path=%s>".formatted(spec.getPath());
default -> "<ERROR: default>";
};
}
};
default Address toAddress(Addr addr, boolean required) { default Address toAddress(Addr addr, boolean required) {
if (required) { if (required) {
@ -30,8 +71,7 @@ public interface ValueDecoder {
return null; return null;
} }
default AddressRange toRange(AddrRange range, boolean required) default AddressRange toRange(AddrRange range, boolean required) {
throws AddressOverflowException {
if (required) { if (required) {
throw new IllegalStateException("AddressRange requires a trace for context"); throw new IllegalStateException("AddressRange requires a trace for context");
} }
@ -52,7 +92,7 @@ public interface ValueDecoder {
return null; return null;
} }
default Object toValue(Value value) throws AddressOverflowException { default Object toValue(Value value) {
return switch (value.getValueCase()) { return switch (value.getValueCase()) {
case NULL_VALUE -> null; case NULL_VALUE -> null;
case BOOL_VALUE -> value.getBoolValue(); case BOOL_VALUE -> value.getBoolValue();

View file

@ -61,7 +61,7 @@ class Receiver(Thread):
result = self.client._handle_invoke_method(request) result = self.client._handle_invoke_method(request)
Client._write_value( Client._write_value(
reply.xreply_invoke_method.return_value, result) reply.xreply_invoke_method.return_value, result)
except Exception as e: except BaseException as e:
reply.xreply_invoke_method.error = ''.join( reply.xreply_invoke_method.error = ''.join(
traceback.format_exc()) traceback.format_exc())
self.client._send(reply) self.client._send(reply)
@ -79,7 +79,7 @@ class Receiver(Thread):
result = request.handler( result = request.handler(
getattr(reply, request.field_name)) getattr(reply, request.field_name))
request.set_result(result) request.set_result(result)
except Exception as e: except BaseException as e:
request.set_exception(e) request.set_exception(e)
def _recv(self, field_name, handler): def _recv(self, field_name, handler):

View file

@ -18,67 +18,37 @@ package ghidra.app.plugin.core.debug.disassemble;
import docking.ActionContext; import docking.ActionContext;
import docking.action.*; import docking.action.*;
import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingActionContext;
import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin.Reqs; import ghidra.app.plugin.core.debug.disassemble.CurrentPlatformTraceDisassembleCommand.Reqs;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingActionContext; import ghidra.framework.plugintool.PluginTool;
import ghidra.debug.api.platform.DebuggerPlatformMapper;
import ghidra.debug.api.platform.DisassemblyResult;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.cmd.TypedBackgroundCommand;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.util.ProgramSelection; import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.task.TaskMonitor;
public class CurrentPlatformTraceDisassembleAction extends DockingAction { public class CurrentPlatformTraceDisassembleAction extends DockingAction {
private static final String NAME = "Disassemble"; private static final String NAME = "Disassemble";
private static final String MENU_GROUP = "Disassembly"; private static final String MENU_GROUP = "Disassembly";
private static final KeyBindingData KEY_BINDING = new KeyBindingData("D"); private static final KeyBindingData KEY_BINDING = new KeyBindingData("D");
private final DebuggerDisassemblerPlugin plugin; private final PluginTool tool;
public CurrentPlatformTraceDisassembleAction(DebuggerDisassemblerPlugin plugin) { public CurrentPlatformTraceDisassembleAction(DebuggerDisassemblerPlugin plugin) {
super(NAME, plugin.getName()); super(NAME, plugin.getName());
this.plugin = plugin; this.tool = plugin.getTool();
setPopupMenuData(new MenuData(new String[] { NAME }, MENU_GROUP)); setPopupMenuData(new MenuData(new String[] { NAME }, MENU_GROUP));
setKeyBindingData(KEY_BINDING); setKeyBindingData(KEY_BINDING);
setHelpLocation(new HelpLocation(plugin.getName(), "disassemble")); setHelpLocation(new HelpLocation(plugin.getName(), "disassemble"));
} }
protected Reqs getReqs(ActionContext context) {
if (plugin.platformService == null) {
return null;
}
if (!(context instanceof DebuggerListingActionContext lac)) {
return null;
}
TraceProgramView view = lac.getProgram();
Trace trace = view.getTrace();
DebuggerCoordinates current = plugin.traceManager == null ? DebuggerCoordinates.NOWHERE
: plugin.traceManager.getCurrentFor(trace);
TraceThread thread = current.getThread();
TraceObject object = current.getObject();
DebuggerPlatformMapper mapper =
plugin.platformService.getMapper(trace, object, view.getSnap());
if (mapper == null) {
return null;
}
return new Reqs(mapper, thread, object, view);
}
@Override @Override
public boolean isAddToPopup(ActionContext context) { public boolean isAddToPopup(ActionContext context) {
Reqs reqs = getReqs(context); Reqs reqs = Reqs.fromContext(tool, context);
return reqs != null; return reqs != null;
} }
@Override @Override
public boolean isEnabledForContext(ActionContext context) { public boolean isEnabledForContext(ActionContext context) {
Reqs reqs = getReqs(context); Reqs reqs = Reqs.fromContext(tool, context);
if (reqs == null) { if (reqs == null) {
return false; return false;
} }
@ -87,7 +57,7 @@ public class CurrentPlatformTraceDisassembleAction extends DockingAction {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
Reqs reqs = getReqs(context); Reqs reqs = Reqs.fromContext(tool, context);
if (reqs == null) { if (reqs == null) {
return; return;
} }
@ -100,21 +70,12 @@ public class CurrentPlatformTraceDisassembleAction extends DockingAction {
set = selection; set = selection;
} }
else { else {
set = reqs.view.getAddressFactory() set = reqs.view()
.getAddressFactory()
.getAddressSet(space.getMinAddress(), space.getMaxAddress()); .getAddressSet(space.getMinAddress(), space.getMaxAddress());
} }
TypedBackgroundCommand<TraceProgramView> cmd = CurrentPlatformTraceDisassembleCommand cmd =
new TypedBackgroundCommand<>(NAME, true, true, false) { new CurrentPlatformTraceDisassembleCommand(tool, set, reqs, address);
@Override cmd.run(tool, reqs.view());
public boolean applyToTyped(TraceProgramView view, TaskMonitor monitor) {
DisassemblyResult result = reqs.mapper.disassemble(
reqs.thread, reqs.object, address, set, view.getSnap(), monitor);
if (!result.isSuccess()) {
plugin.getTool().setStatusInfo(result.getErrorMessage(), true);
}
return true;
}
};
cmd.run(plugin.getTool(), reqs.view);
} }
} }

View file

@ -0,0 +1,95 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.disassemble;
import docking.ActionContext;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingActionContext;
import ghidra.app.services.DebuggerPlatformService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.debug.api.platform.DebuggerPlatformMapper;
import ghidra.debug.api.platform.DisassemblyResult;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.cmd.TypedBackgroundCommand;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.task.TaskMonitor;
public final class CurrentPlatformTraceDisassembleCommand
extends TypedBackgroundCommand<TraceProgramView> {
public static final String NAME = "Disassemble";
public record Reqs(DebuggerPlatformMapper mapper, TraceThread thread, TraceObject object,
TraceProgramView view) {
public static Reqs fromView(PluginTool tool, TraceProgramView view) {
DebuggerTraceManagerService traceManager =
tool.getService(DebuggerTraceManagerService.class);
DebuggerPlatformService platformService =
tool.getService(DebuggerPlatformService.class);
if (platformService == null) {
return null;
}
Trace trace = view.getTrace();
DebuggerCoordinates current = traceManager == null
? DebuggerCoordinates.NOWHERE
: traceManager.getCurrentFor(trace);
TraceThread thread = current.getThread();
TraceObject object = current.getObject();
DebuggerPlatformMapper mapper =
platformService.getMapper(trace, object, view.getSnap());
if (mapper == null) {
return null;
}
return new Reqs(mapper, thread, object, view);
}
public static Reqs fromContext(PluginTool tool, ActionContext context) {
if (!(context instanceof DebuggerListingActionContext lac)) {
return null;
}
return fromView(tool, lac.getProgram());
}
}
private final PluginTool tool;
private final AddressSetView set;
private final Reqs reqs;
private final Address address;
public CurrentPlatformTraceDisassembleCommand(PluginTool tool, AddressSetView set,
Reqs reqs, Address address) {
super(NAME, true, true, false);
this.tool = tool;
this.set = set;
this.reqs = reqs;
this.address = address;
}
@Override
public boolean applyToTyped(TraceProgramView view, TaskMonitor monitor) {
DisassemblyResult result = reqs.mapper.disassemble(
reqs.thread, reqs.object, address, set, view.getSnap(), monitor);
if (!result.isSuccess()) {
tool.setStatusInfo(result.getErrorMessage(), true);
}
return true;
}
}

View file

@ -27,10 +27,7 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingActionContext; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingActionContext;
import ghidra.app.services.DebuggerPlatformService; import ghidra.app.services.DebuggerPlatformService;
import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.debug.api.platform.DebuggerPlatformMapper;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.AutoService.Wiring;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
@ -40,8 +37,6 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TraceGuestPlatform; import ghidra.trace.model.guest.TraceGuestPlatform;
import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.thread.TraceThread;
@PluginInfo( @PluginInfo(
shortDescription = "Disassemble trace bytes in the debugger", shortDescription = "Disassemble trace bytes in the debugger",
@ -61,21 +56,6 @@ import ghidra.trace.model.thread.TraceThread;
}) })
public class DebuggerDisassemblerPlugin extends Plugin implements PopupActionProvider { public class DebuggerDisassemblerPlugin extends Plugin implements PopupActionProvider {
protected static class Reqs {
final DebuggerPlatformMapper mapper;
final TraceThread thread;
final TraceObject object;
final TraceProgramView view;
public Reqs(DebuggerPlatformMapper mapper, TraceThread thread, TraceObject object,
TraceProgramView view) {
this.mapper = mapper;
this.thread = thread;
this.object = object;
this.view = view;
}
}
public static RegisterValue deriveAlternativeDefaultContext(Language language, public static RegisterValue deriveAlternativeDefaultContext(Language language,
LanguageID alternative, Address address) { LanguageID alternative, Address address) {
LanguageService langServ = DefaultLanguageService.getLanguageService(); LanguageService langServ = DefaultLanguageService.getLanguageService();
@ -100,19 +80,11 @@ public class DebuggerDisassemblerPlugin extends Plugin implements PopupActionPro
return result; return result;
} }
@AutoServiceConsumed
DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
DebuggerPlatformService platformService;
@SuppressWarnings("unused")
private final Wiring autoServiceWiring;
CurrentPlatformTraceDisassembleAction actionDisassemble; CurrentPlatformTraceDisassembleAction actionDisassemble;
CurrentPlatformTracePatchInstructionAction actionPatchInstruction; CurrentPlatformTracePatchInstructionAction actionPatchInstruction;
public DebuggerDisassemblerPlugin(PluginTool tool) { public DebuggerDisassemblerPlugin(PluginTool tool) {
super(tool); super(tool);
this.autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
} }
@Override @Override

View file

@ -33,6 +33,7 @@ import ghidra.debug.api.target.Target;
import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.cmd.BackgroundCommand; import ghidra.framework.cmd.BackgroundCommand;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.model.DomainObjectChangeRecord;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoConfigStateField; import ghidra.framework.plugintool.annotation.AutoConfigStateField;
@ -100,10 +101,16 @@ public abstract class DebuggerReadsMemoryTrait {
protected class ForReadsTraceListener extends TraceDomainObjectListener { protected class ForReadsTraceListener extends TraceDomainObjectListener {
public ForReadsTraceListener() { public ForReadsTraceListener() {
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, this::objectRestored);
listenFor(TraceSnapshotChangeType.ADDED, this::snapshotAdded); listenFor(TraceSnapshotChangeType.ADDED, this::snapshotAdded);
listenFor(TraceMemoryStateChangeType.CHANGED, this::memStateChanged); listenFor(TraceMemoryStateChangeType.CHANGED, this::memStateChanged);
} }
private void objectRestored(DomainObjectChangeRecord rec) {
actionRefreshSelected.updateEnabled(null);
doAutoRead();
}
private void snapshotAdded(TraceSnapshot snapshot) { private void snapshotAdded(TraceSnapshot snapshot) {
actionRefreshSelected.updateEnabled(null); actionRefreshSelected.updateEnabled(null);
} }

View file

@ -56,7 +56,7 @@ public class VisibleAutoReadMemorySpec implements AutoReadMemorySpec {
Target target = coordinates.getTarget(); Target target = coordinates.getTarget();
TraceMemoryManager mm = coordinates.getTrace().getMemoryManager(); TraceMemoryManager mm = coordinates.getTrace().getMemoryManager();
AddressSetView alreadyKnown = mm.getAddressesWithState(coordinates.getSnap(), visible, AddressSetView alreadyKnown = mm.getAddressesWithState(coordinates.getSnap(), visible,
s -> s == TraceMemoryState.KNOWN); s -> s == TraceMemoryState.KNOWN || s == TraceMemoryState.ERROR);
AddressSet toRead = visible.subtract(alreadyKnown); AddressSet toRead = visible.subtract(alreadyKnown);
if (toRead.isEmpty()) { if (toRead.isEmpty()) {

View file

@ -57,7 +57,7 @@ public class VisibleROOnceAutoReadMemorySpec implements AutoReadMemorySpec {
Target target = coordinates.getTarget(); Target target = coordinates.getTarget();
TraceMemoryManager mm = coordinates.getTrace().getMemoryManager(); TraceMemoryManager mm = coordinates.getTrace().getMemoryManager();
AddressSetView alreadyKnown = mm.getAddressesWithState(coordinates.getSnap(), visible, AddressSetView alreadyKnown = mm.getAddressesWithState(coordinates.getSnap(), visible,
s -> s == TraceMemoryState.KNOWN); s -> s == TraceMemoryState.KNOWN || s == TraceMemoryState.ERROR);
AddressSet toRead = visible.subtract(alreadyKnown); AddressSet toRead = visible.subtract(alreadyKnown);
if (toRead.isEmpty()) { if (toRead.isEmpty()) {

View file

@ -48,7 +48,8 @@ import ghidra.app.nav.ListingPanelContainer;
import ghidra.app.plugin.core.clipboard.CodeBrowserClipboardProvider; import ghidra.app.plugin.core.clipboard.CodeBrowserClipboardProvider;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel; import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
import ghidra.app.plugin.core.debug.disassemble.TraceDisassembleCommand; import ghidra.app.plugin.core.debug.disassemble.CurrentPlatformTraceDisassembleCommand;
import ghidra.app.plugin.core.debug.disassemble.CurrentPlatformTraceDisassembleCommand.Reqs;
import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel; import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
@ -180,10 +181,10 @@ public class DebuggerListingProvider extends CodeViewerProvider {
if (codeViewer.isEmpty()) { if (codeViewer.isEmpty()) {
return; return;
} }
Swing.runIfSwingOrRunLater( ListingPanel listingPanel = codeViewer.get().getListingPanel();
() -> codeViewer.get() Swing.runIfSwingOrRunLater(() -> {
.getListingPanel() listingPanel.scrollTo(new ProgramLocation(program, selection.getMinAddress()));
.scrollTo(new ProgramLocation(program, selection.getMinAddress()))); });
} }
@Override @Override
@ -1203,8 +1204,10 @@ public class DebuggerListingProvider extends CodeViewerProvider {
} }
AddressSpace space = start.getAddressSpace(); AddressSpace space = start.getAddressSpace();
AddressSet set = new AddressSet(space.getMinAddress(), space.getMaxAddress()); AddressSet set = new AddressSet(space.getMinAddress(), space.getMaxAddress());
TraceDisassembleCommand dis =
new TraceDisassembleCommand(current.getPlatform(), start, set); Reqs reqs = Reqs.fromView(tool, view);
CurrentPlatformTraceDisassembleCommand dis =
new CurrentPlatformTraceDisassembleCommand(tool, set, reqs, start);
dis.run(tool, view); dis.run(tool, view);
} }

View file

@ -210,7 +210,7 @@ public class ObjectTreeModel implements DisplaysModified {
/** /**
* Our nodes are re-usable. They're cached so that as an item comes and goes, its * Our nodes are re-usable. They're cached so that as an item comes and goes, its
* corresponding node can also come and go without being re-instantiated each time. * corresponding node can also come and go without being re-instantiated each time.
* Furthermore, it's like to have all the same children as before, too. For now, we'll * Furthermore, it's likely to have all the same children as before, too. For now, we'll
* just ignore dispose. If there's too many unexpected behaviors resulting from this, * just ignore dispose. If there's too many unexpected behaviors resulting from this,
* then perhaps we should just have dispose also remove itself from the node cache. * then perhaps we should just have dispose also remove itself from the node cache.
*/ */
@ -219,12 +219,25 @@ public class ObjectTreeModel implements DisplaysModified {
@Override @Override
public int compareTo(GTreeNode node) { public int compareTo(GTreeNode node) {
return TargetObjectKeyComparator.CHILD.compare(this.getName(), node.getName()); if (!(node instanceof AbstractNode that)) {
return -1;
}
int c;
c = TargetObjectKeyComparator.CHILD.compare(this.getValue().getEntryKey(),
that.getValue().getEntryKey());
if (c != 0) {
return c;
}
c = Lifespan.DOMAIN.compare(this.getValue().getMinSnap(), that.getValue().getMinSnap());
if (c != 0) {
return c;
}
return 0;
} }
@Override @Override
public String getName() { public String getName() {
return getValue().getEntryKey() + "@" + getValue().getMinSnap(); return getValue().getEntryKey() + "@" + System.identityHashCode(getValue());
} }
@Override @Override
@ -340,14 +353,14 @@ public class ObjectTreeModel implements DisplaysModified {
@Override @Override
public String getDisplayText() { public String getDisplayText() {
if (trace == null) { if (trace == null) {
return "<html><em>No trace is active</em>"; return "<html><em>No&nbsp;trace&nbsp;is&nbsp;active</em>";
} }
TraceObject root = trace.getObjectManager().getRootObject(); TraceObject root = trace.getObjectManager().getRootObject();
if (root == null) { if (root == null) {
return "<html><em>Trace has no model</em>"; return "<html><em>Trace&nbsp;has&nbsp;no&nbsp;model</em>";
} }
return "<html>" + return "<html>" + HTMLUtilities
HTMLUtilities.escapeHTML(display.getObjectDisplay(root.getCanonicalParent(0))); .escapeHTML(display.getObjectDisplay(root.getCanonicalParent(0)), true);
} }
@Override @Override
@ -424,7 +437,8 @@ public class ObjectTreeModel implements DisplaysModified {
@Override @Override
public String getDisplayText() { public String getDisplayText() {
String html = HTMLUtilities.escapeHTML( String html = HTMLUtilities.escapeHTML(
value.getEntryKey() + ": " + display.getPrimitiveValueDisplay(value.getValue())); value.getEntryKey() + ": " + display.getPrimitiveValueDisplay(value.getValue()),
true);
return "<html>" + html; return "<html>" + html;
} }
@ -471,8 +485,8 @@ public class ObjectTreeModel implements DisplaysModified {
@Override @Override
public String getDisplayText() { public String getDisplayText() {
return "<html>" + HTMLUtilities.escapeHTML(value.getEntryKey()) + ": <em>" + return "<html>" + HTMLUtilities.escapeHTML(value.getEntryKey(), true) + ":&nbsp;<em>" +
HTMLUtilities.escapeHTML(display.getObjectLinkDisplay(value)) + "</em>"; HTMLUtilities.escapeHTML(display.getObjectLinkDisplay(value), true) + "</em>";
} }
@Override @Override
@ -513,7 +527,7 @@ public class ObjectTreeModel implements DisplaysModified {
@Override @Override
public String getDisplayText() { public String getDisplayText() {
return "<html>" + HTMLUtilities.escapeHTML(display.getObjectDisplay(value)); return "<html>" + HTMLUtilities.escapeHTML(display.getObjectDisplay(value), true);
} }
@Override @Override

View file

@ -262,6 +262,20 @@ public class ObjectsTreePanel extends JPanel {
} }
//tree.filterChanged(); //tree.filterChanged();
// Repaint for bold current path is already going to happen // Repaint for bold current path is already going to happen
// Repaint is not enough, as node sizes may change
for (TraceObjectKeyPath path = current.getPath(); path != null; path = path.parent()) {
AbstractNode node = treeModel.getNode(path);
if (node != null) {
node.fireNodeChanged();
}
}
for (TraceObjectKeyPath path = previous.getPath(); path != null; path = path.parent()) {
AbstractNode node = treeModel.getNode(path);
if (node != null) {
node.fireNodeChanged();
}
}
} }
} }

View file

@ -218,8 +218,41 @@ public class TerminalTextFieldElement implements FieldElement {
@Override @Override
public void paint(JComponent c, Graphics g, int x, int y) { public void paint(JComponent c, Graphics g, int x, int y) {
line.forEachRun( if (!(g instanceof Graphics2D g2)) {
(attrs, start, end) -> paintChars(c, g, x, y, attrs, start, end)); line.forEachRun(
(attrs, start, end) -> paintChars(c, g, x, y, attrs, start, end));
return;
}
Object aaHint = c.getClientProperty(RenderingHints.KEY_TEXT_ANTIALIASING);
Object lcdHint = c.getClientProperty(RenderingHints.KEY_TEXT_LCD_CONTRAST);
Object aaOld =
aaHint == null ? null : g2.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
Object lcdOld =
lcdHint == null ? null : g2.getRenderingHint(RenderingHints.KEY_TEXT_LCD_CONTRAST);
if (aaOld == aaHint) {
aaHint = null;
}
if (lcdOld == lcdHint) {
lcdHint = null;
}
try {
if (aaHint != null) {
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, aaHint);
}
if (lcdHint != null) {
g2.setRenderingHint(RenderingHints.KEY_TEXT_LCD_CONTRAST, lcdHint);
}
line.forEachRun(
(attrs, start, end) -> paintChars(c, g, x, y, attrs, start, end));
}
finally {
if (aaHint != null) {
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, aaOld);
}
if (lcdHint != null) {
g2.setRenderingHint(RenderingHints.KEY_TEXT_LCD_CONTRAST, lcdOld);
}
}
} }
@Override @Override

View file

@ -609,10 +609,16 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
/** /**
* Scroll the view of the listing to the given location. * Scroll the view of the listing to the given location.
*
* <p>
* If the given location is not displayed, this has no effect.
* @param location the location * @param location the location
*/ */
public void scrollTo(ProgramLocation location) { public void scrollTo(ProgramLocation location) {
FieldLocation fieldLocation = getFieldLocation(location); FieldLocation fieldLocation = getFieldLocation(location);
if (fieldLocation == null) {
return;
}
fieldPanel.scrollTo(fieldLocation); fieldPanel.scrollTo(fieldLocation);
} }

View file

@ -16,7 +16,7 @@
package agent.dbgeng.rmi; package agent.dbgeng.rmi;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.junit.Assume.*; import static org.junit.Assume.assumeTrue;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
@ -27,6 +27,7 @@ import java.util.function.Function;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin; import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
@ -63,6 +64,40 @@ public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDeb
protected static final int TIMEOUT_SECONDS = 300; protected static final int TIMEOUT_SECONDS = 300;
protected static final int QUIT_TIMEOUT_MS = 1000; protected static final int QUIT_TIMEOUT_MS = 1000;
protected static boolean didSetupPython = false;
public static final String INSTRUMENT_STATE = """
import sys
from comtypes.hresult import S_OK
from ghidradbg import commands, hooks
print("Instrumenting")
@hooks.log_errors
def on_state_changed(*args):
if args[0] != DbgEng.DEBUG_CES_EXECUTION_STATUS:
return S_OK
print("State changed: {:x}".format(args[1]))
sys.stdout.flush()
proc = util.selected_process()
trace = commands.STATE.trace
with commands.STATE.client.batch():
with trace.open_tx("State changed proc {}".format(proc)):
commands.put_state(proc)
return S_OK
# Without this, the engine seems to GO after the interrupt
@hooks.log_errors
def on_exception(*args):
return DbgEng.DEBUG_STATUS_BREAK
@util.dbg.eng_thread
def install_hooks():
print("Installing")
util.dbg._base.events.engine_state(handler=on_state_changed)
util.dbg._base.events.exception(handler=on_exception)
install_hooks()
""";
protected TraceRmiService traceRmi; protected TraceRmiService traceRmi;
private Path pythonPath; private Path pythonPath;
private Path outFile; private Path outFile;
@ -75,11 +110,17 @@ public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDeb
//@BeforeClass //@BeforeClass
public static void setupPython() throws Throwable { public static void setupPython() throws Throwable {
new ProcessBuilder("gradle", "Debugger-agent-dbgeng:assemblePyPackage") if (didSetupPython) {
// Only do this once when running the full suite.
return;
}
String gradle = DummyProc.which("gradle.bat");
new ProcessBuilder(gradle, "Debugger-agent-dbgeng:assemblePyPackage")
.directory(TestApplicationUtils.getInstallationDirectory()) .directory(TestApplicationUtils.getInstallationDirectory())
.inheritIO() .inheritIO()
.start() .start()
.waitFor(); .waitFor();
didSetupPython = true;
} }
protected void setPythonPath(ProcessBuilder pb) throws IOException { protected void setPythonPath(ProcessBuilder pb) throws IOException {
@ -140,6 +181,10 @@ public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDeb
if (stderr.contains("Error") || (0 != exitCode && 1 != exitCode && 143 != exitCode)) { if (stderr.contains("Error") || (0 != exitCode && 1 != exitCode && 143 != exitCode)) {
throw new PythonError(exitCode, stdout, stderr); throw new PythonError(exitCode, stdout, stderr);
} }
System.out.println("--stdout--");
System.out.println(stdout);
System.out.println("--stderr--");
System.out.println(stderr);
return stdout; return stdout;
} }
} }
@ -227,9 +272,9 @@ public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDeb
return execute.invokeAsync(Map.of("cmd", cmd)); return execute.invokeAsync(Map.of("cmd", cmd));
} }
public String executeCapture(String expr) { public String executeCapture(String cmd) {
RemoteMethod execute = getMethod("evaluate"); RemoteMethod execute = getMethod("execute");
return (String) execute.invoke(Map.of("expr", expr)); return (String) execute.invoke(Map.of("cmd", cmd, "to_string", true));
} }
@Override @Override
@ -281,15 +326,15 @@ public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDeb
return stdout; return stdout;
} }
protected void waitStopped() { protected void waitStopped(String message) {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0))); TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
waitForPass(() -> assertEquals("STOPPED", tb.objValue(proc, 0, "_state"))); waitForPass(() -> assertEquals(message, "STOPPED", tb.objValue(proc, 0, "_state")));
waitTxDone(); waitTxDone();
} }
protected void waitRunning() { protected void waitRunning(String message) {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0))); TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
waitForPass(() -> assertEquals("RUNNING", tb.objValue(proc, 0, "_state"))); waitForPass(() -> assertEquals(message, "RUNNING", tb.objValue(proc, 0, "_state")));
waitTxDone(); waitTxDone();
} }

View file

@ -28,18 +28,23 @@ import java.util.stream.IntStream;
import org.junit.Test; import org.junit.Test;
import db.Transaction;
import generic.Unique; import generic.Unique;
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject; import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
import ghidra.dbg.util.PathPredicates; import ghidra.dbg.util.PathPredicates;
import ghidra.debug.api.tracermi.TraceRmiAcceptor; import ghidra.debug.api.tracermi.TraceRmiAcceptor;
import ghidra.debug.api.tracermi.TraceRmiConnection; import ghidra.debug.api.tracermi.TraceRmiConnection;
import ghidra.framework.Application;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.data.Float10DataType;
import ghidra.program.model.lang.RegisterValue; import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.listing.CodeUnit;
import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.*; import ghidra.trace.model.*;
import ghidra.trace.model.breakpoint.TraceBreakpointKind; import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.listing.TraceCodeSpace;
import ghidra.trace.model.listing.TraceData;
import ghidra.trace.model.memory.*; import ghidra.trace.model.memory.*;
import ghidra.trace.model.modules.TraceModule; import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.target.*; import ghidra.trace.model.target.*;
@ -176,9 +181,11 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
%s %s
print('---Import---') print('---Import---')
ghidra_trace_info() ghidra_trace_info()
print('---BeforeConnect---')
ghidra_trace_connect('%s') ghidra_trace_connect('%s')
print('---Connect---') print('---Connect---')
ghidra_trace_info() ghidra_trace_info()
print('---Create---')
ghidra_trace_create('notepad.exe') ghidra_trace_create('notepad.exe')
print('---Start---') print('---Start---')
ghidra_trace_info() ghidra_trace_info()
@ -196,20 +203,19 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
Not connected to Ghidra""", Not connected to Ghidra""",
extractOutSection(out, "---Import---")); extractOutSection(out, "---Import---"));
assertEquals(""" assertEquals("""
Connected to Ghidra at %s Connected to %s %s at %s
No trace""".formatted(
No trace""".formatted(refAddr.get()), Application.getName(), Application.getApplicationVersion(), refAddr.get()),
extractOutSection(out, "---Connect---").replaceAll("\r", "").substring(0, 48)); extractOutSection(out, "---Connect---").replaceAll("\r", ""));
String expected = """
Connected to Ghidra at %s
Trace active""".formatted(refAddr.get());
String actual = extractOutSection(out, "---Start---").replaceAll("\r", "");
assertEquals(expected, actual.substring(0, expected.length()));
assertEquals(""" assertEquals("""
Connected to Ghidra at %s Connected to %s %s at %s
Trace active""".formatted(
No trace""".formatted(refAddr.get()), Application.getName(), Application.getApplicationVersion(), refAddr.get()),
extractOutSection(out, "---Start---").replaceAll("\r", ""));
assertEquals("""
Connected to %s %s at %s
No trace""".formatted(
Application.getName(), Application.getApplicationVersion(), refAddr.get()),
extractOutSection(out, "---Stop---").replaceAll("\r", "")); extractOutSection(out, "---Stop---").replaceAll("\r", ""));
assertEquals(""" assertEquals("""
Not connected to Ghidra""", Not connected to Ghidra""",
@ -224,7 +230,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
%s %s
print('---Import---') print('---Import---')
ghidra_trace_info_lcsp() ghidra_trace_info_lcsp()
print('---') print('---Create---')
ghidra_trace_create('notepad.exe', start_trace=False) ghidra_trace_create('notepad.exe', start_trace=False)
print('---File---') print('---File---')
ghidra_trace_info_lcsp() ghidra_trace_info_lcsp()
@ -237,21 +243,16 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
quit() quit()
""".formatted(PREAMBLE)); """.formatted(PREAMBLE));
assertTrue( assertEquals("""
extractOutSection(out, "---File---").replaceAll("\r", "") Selected Ghidra language: x86:LE:64:default
.contains( Selected Ghidra compiler: windows""",
""" extractOutSection(out, "---File---").replaceAll("\r", ""));
Selected Ghidra language: x86:LE:64:default
Selected Ghidra compiler: windows"""));
assertEquals(""" assertEquals("""
Selected Ghidra language: Toy:BE:64:default Selected Ghidra language: Toy:BE:64:default
Selected Ghidra compiler: default""", Selected Ghidra compiler: default""",
extractOutSection(out, "---Language---").replaceAll("\r", "")); extractOutSection(out, "---Language---").replaceAll("\r", ""));
assertEquals(""" assertEquals("""
Selected Ghidra language: Toy:BE:64:default Selected Ghidra language: Toy:BE:64:default
Selected Ghidra compiler: posStack""", Selected Ghidra compiler: posStack""",
extractOutSection(out, "---Compiler---").replaceAll("\r", "")); extractOutSection(out, "---Compiler---").replaceAll("\r", ""));
} }
@ -319,11 +320,11 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
ghidra_trace_create('notepad.exe') ghidra_trace_create('notepad.exe')
ghidra_trace_txstart('Create snapshot') ghidra_trace_txstart('Create snapshot')
ghidra_trace_new_snap('Scripted snapshot') ghidra_trace_new_snap('Scripted snapshot')
ghidra_trace_putmem('$pc 16') pc = util.get_pc()
ghidra_trace_putmem(pc, 16)
ghidra_trace_txcommit() ghidra_trace_txcommit()
print('---Dump---') print('---Dump---')
pc = util.get_debugger().reg.get_pc() util.dbg.dd(pc, count=1)
util.get_debugger().dd(pc, count=1)
print('---') print('---')
ghidra_trace_kill() ghidra_trace_kill()
quit() quit()
@ -348,11 +349,11 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
ghidra_trace_create('notepad.exe') ghidra_trace_create('notepad.exe')
ghidra_trace_txstart('Create snapshot') ghidra_trace_txstart('Create snapshot')
ghidra_trace_new_snap('Scripted snapshot') ghidra_trace_new_snap('Scripted snapshot')
ghidra_trace_putmem_state('$pc 16 error') pc = util.get_pc()
ghidra_trace_putmem_state(pc, 16, 'error', pages=False)
ghidra_trace_txcommit() ghidra_trace_txcommit()
print('---Start---') print('---Start---')
pc = util.get_debugger().reg.get_pc() util.dbg.dd(pc, count=1)
util.get_debugger().dd(pc, count=1)
print('---') print('---')
ghidra_trace_kill() ghidra_trace_kill()
quit() quit()
@ -380,12 +381,12 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
ghidra_trace_create('notepad.exe') ghidra_trace_create('notepad.exe')
ghidra_trace_txstart('Create snapshot') ghidra_trace_txstart('Create snapshot')
ghidra_trace_new_snap('Scripted snapshot') ghidra_trace_new_snap('Scripted snapshot')
ghidra_trace_putmem('$pc 16') pc = util.get_pc()
ghidra_trace_delmem('$pc 8') ghidra_trace_putmem(pc, 16)
ghidra_trace_delmem(pc, 8)
ghidra_trace_txcommit() ghidra_trace_txcommit()
print('---Dump---') print('---Dump---')
pc = util.get_debugger().reg.get_pc() util.dbg.dd(pc, count=1)
util.get_debugger().dd(pc, count=1)
print('---') print('---')
ghidra_trace_kill() ghidra_trace_kill()
quit() quit()
@ -412,9 +413,8 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
%s %s
ghidra_trace_connect('%s') ghidra_trace_connect('%s')
ghidra_trace_create('notepad.exe') ghidra_trace_create('notepad.exe')
regs = util.get_debugger().reg util.dbg.cmd('r rax=0xdeadbeef')
regs._set_register("rax", int(0xdeadbeef)) util.dbg.cmd('r st0=1.5')
regs._set_register("st0", int(1.5))
ghidra_trace_txstart('Create snapshot') ghidra_trace_txstart('Create snapshot')
ghidra_trace_new_snap('Scripted snapshot') ghidra_trace_new_snap('Scripted snapshot')
ghidra_trace_putreg() ghidra_trace_putreg()
@ -438,20 +438,15 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
RegisterValue rax = regs.getValue(snap, tb.reg("rax")); RegisterValue rax = regs.getValue(snap, tb.reg("rax"));
assertEquals("deadbeef", rax.getUnsignedValue().toString(16)); assertEquals("deadbeef", rax.getUnsignedValue().toString(16));
// TODO: Pybag currently doesn't suppport non-int assignments TraceData st0;
/* try (Transaction tx = tb.trace.openTransaction("Float80 unit")) {
* // RegisterValue ymm0 = regs.getValue(snap, tb.reg("ymm0")); // // LLDB TraceCodeSpace code = tb.trace.getCodeManager().getCodeSpace(t1f0, true);
* treats registers in arch's endian // assertEquals( st0 = code.definedData()
* "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100", // .create(Lifespan.nowOn(0), tb.reg("st0"), Float10DataType.dataType);
* ymm0.getUnsignedValue().toString(16)); }
*
* // TraceData st0; // try (Transaction tx = // TODO: Pybag doesn't support non-integer registers
* tb.trace.openTransaction("Float80 unit")) { // TraceCodeSpace code = // assertEquals("1.5", st0.getDefaultValueRepresentation());
* tb.trace.getCodeManager().getCodeSpace(t1f0, true); // st0 =
* code.definedData() // .create(Lifespan.nowOn(0), tb.reg("st0"),
* Float10DataType.dataType); // } // assertEquals("1.5",
* st0.getDefaultValueRepresentation());
*/
} }
} }
@ -464,8 +459,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
%s %s
ghidra_trace_connect('%s') ghidra_trace_connect('%s')
ghidra_trace_create('notepad.exe') ghidra_trace_create('notepad.exe')
regs = util.get_debugger().reg util.dbg.cmd('r rax=0xdeadbeef')
regs._set_register("st0", int(1.5))
ghidra_trace_txstart('Create snapshot') ghidra_trace_txstart('Create snapshot')
ghidra_trace_new_snap('Scripted snapshot') ghidra_trace_new_snap('Scripted snapshot')
ghidra_trace_putreg() ghidra_trace_putreg()
@ -490,19 +484,6 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
RegisterValue rax = regs.getValue(snap, tb.reg("rax")); RegisterValue rax = regs.getValue(snap, tb.reg("rax"));
assertEquals("0", rax.getUnsignedValue().toString(16)); assertEquals("0", rax.getUnsignedValue().toString(16));
// TODO: As above, not currently supported by pybag
/*
* // RegisterValue ymm0 = regs.getValue(snap, tb.reg("ymm0")); //
* assertEquals("0", ymm0.getUnsignedValue().toString(16));
*
* // TraceData st0; // try (Transaction tx =
* tb.trace.openTransaction("Float80 unit")) { // TraceCodeSpace code =
* tb.trace.getCodeManager().getCodeSpace(t1f0, true); // st0 =
* code.definedData() // .create(Lifespan.nowOn(0), tb.reg("st0"),
* Float10DataType.dataType); // } // assertEquals("0.0",
* st0.getDefaultValueRepresentation());
*/
} }
} }
@ -551,9 +532,8 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
assertNotNull(object); assertNotNull(object);
Lifespan life = Unique.assertOne(object.getLife().spans()); Lifespan life = Unique.assertOne(object.getLife().spans());
assertEquals(Lifespan.nowOn(0), life); assertEquals(Lifespan.nowOn(0), life);
String expected = "Inserted object: lifespan=[0,+inf)"; assertEquals("Inserted object: lifespan=[0,+inf)",
String actual = extractOutSection(out, "---Lifespan---"); extractOutSection(out, "---Lifespan---"));
assertEquals(expected, actual.substring(0, expected.length()));
} }
} }
@ -788,9 +768,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
TraceObject object = tb.trace.getObjectManager() TraceObject object = tb.trace.getObjectManager()
.getObjectByCanonicalPath(TraceObjectKeyPath.parse("Test.Objects[1]")); .getObjectByCanonicalPath(TraceObjectKeyPath.parse("Test.Objects[1]"));
assertNotNull(object); assertNotNull(object);
String expected = "1\tTest.Objects[1]"; assertEquals("1\tTest.Objects[1]", extractOutSection(out, "---GetObject---"));
String actual = extractOutSection(out, "---GetObject---");
assertEquals(expected, actual.substring(0, expected.length()));
} }
} }
@ -834,8 +812,9 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
""".formatted(PREAMBLE, addr)); """.formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
String expected = """ assertEquals("""
Parent Key Span Value Type Parent Key Span Value Type
Test.Objects[1] vaddr [0,+inf) ram:deadbeef ADDRESS
Test.Objects[1] vbool [0,+inf) True BOOL Test.Objects[1] vbool [0,+inf) True BOOL
Test.Objects[1] vboolarr [0,+inf) [True, False] BOOL_ARR Test.Objects[1] vboolarr [0,+inf) [True, False] BOOL_ARR
Test.Objects[1] vbyte [0,+inf) 1 BYTE Test.Objects[1] vbyte [0,+inf) 1 BYTE
@ -849,16 +828,8 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
Test.Objects[1] vobj [0,+inf) Test.Objects[1] OBJECT Test.Objects[1] vobj [0,+inf) Test.Objects[1] OBJECT
Test.Objects[1] vshort [0,+inf) 2 SHORT Test.Objects[1] vshort [0,+inf) 2 SHORT
Test.Objects[1] vshortarr [0,+inf) [1, 2, 3] SHORT_ARR Test.Objects[1] vshortarr [0,+inf) [1, 2, 3] SHORT_ARR
Test.Objects[1] vstring [0,+inf) 'Hello' STRING Test.Objects[1] vstring [0,+inf) 'Hello' STRING""",
Test.Objects[1] vaddr [0,+inf) ram:deadbeef ADDRESS""" extractOutSection(out, "---GetValues---").replaceAll("\r", ""));
.replaceAll(" ", "")
.replaceAll("\n", "");
String actual = extractOutSection(out, "---GetValues---").replaceAll(" ", "")
.replaceAll("\r", "")
.replaceAll("\n", "");
assertEquals(
expected,
actual.substring(0, expected.length()));
} }
} }
@ -874,19 +845,17 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
ghidra_trace_set_value('Test.Objects[1]', 'vaddr', '(void*)0xdeadbeef', 'ADDRESS') ghidra_trace_set_value('Test.Objects[1]', 'vaddr', '(void*)0xdeadbeef', 'ADDRESS')
ghidra_trace_txcommit() ghidra_trace_txcommit()
print('---GetValues---') print('---GetValues---')
ghidra_trace_get_values_rng('(void*)0xdeadbeef 10') ghidra_trace_get_values_rng(0xdeadbeef, 10)
print('---') print('---')
ghidra_trace_kill() ghidra_trace_kill()
quit() quit()
""".formatted(PREAMBLE, addr)); """.formatted(PREAMBLE, addr));
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
String expected = """ assertEquals("""
Parent Key Span Value Type Parent Key Span Value Type
Test.Objects[1] vaddr [0,+inf) ram:deadbeef ADDRESS""",
Test.Objects[1] vaddr [0,+inf) ram:deadbeef ADDRESS"""; extractOutSection(out, "---GetValues---").replaceAll("\r", ""));
String actual = extractOutSection(out, "---GetValues---").replaceAll("\r", "");
assertEquals(expected, actual.substring(0, expected.length()));
} }
} }
@ -919,9 +888,10 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
ghidra_trace_connect('%s') ghidra_trace_connect('%s')
ghidra_trace_create('notepad.exe') ghidra_trace_create('notepad.exe')
ghidra_trace_txstart('Tx') ghidra_trace_txstart('Tx')
ghidra_trace_putmem('$pc 16') pc = util.get_pc()
ghidra_trace_putmem(pc, 16)
print('---Disassemble---') print('---Disassemble---')
ghidra_trace_disassemble('$pc') ghidra_trace_disassemble(pc)
print('---') print('---')
ghidra_trace_txcommit() ghidra_trace_txcommit()
ghidra_trace_kill() ghidra_trace_kill()
@ -991,10 +961,9 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
%s %s
ghidra_trace_connect('%s') ghidra_trace_connect('%s')
ghidra_trace_create('notepad.exe') ghidra_trace_create('notepad.exe')
dbg = util.get_debugger() pc = util.get_pc()
pc = dbg.reg.get_pc() util.dbg.bp(expr=pc)
dbg.bp(expr=pc) util.dbg.ba(expr=pc+4)
dbg.ba(expr=pc+4)
ghidra_trace_txstart('Tx') ghidra_trace_txstart('Tx')
ghidra_trace_put_breakpoints() ghidra_trace_put_breakpoints()
ghidra_trace_txcommit() ghidra_trace_txcommit()
@ -1007,6 +976,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
.getValuePaths(Lifespan.at(0), .getValuePaths(Lifespan.at(0),
PathPredicates.parse("Processes[].Breakpoints[]")) PathPredicates.parse("Processes[].Breakpoints[]"))
.map(p -> p.getLastEntry()) .map(p -> p.getLastEntry())
.sorted(Comparator.comparing(TraceObjectValue::getEntryKey))
.toList(); .toList();
assertEquals(2, procBreakLocVals.size()); assertEquals(2, procBreakLocVals.size());
AddressRange rangeMain = AddressRange rangeMain =
@ -1029,11 +999,10 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
ghidra_trace_connect('%s') ghidra_trace_connect('%s')
ghidra_trace_create('notepad.exe') ghidra_trace_create('notepad.exe')
ghidra_trace_txstart('Tx') ghidra_trace_txstart('Tx')
dbg = util.get_debugger() pc = util.get_pc()
pc = dbg.reg.get_pc() util.dbg.ba(expr=pc, access=DbgEng.DEBUG_BREAK_EXECUTE)
dbg.ba(expr=pc, access=DbgEng.DEBUG_BREAK_EXECUTE) util.dbg.ba(expr=pc+4, access=DbgEng.DEBUG_BREAK_READ)
dbg.ba(expr=pc+4, access=DbgEng.DEBUG_BREAK_READ) util.dbg.ba(expr=pc+8, access=DbgEng.DEBUG_BREAK_WRITE)
dbg.ba(expr=pc+8, access=DbgEng.DEBUG_BREAK_WRITE)
ghidra_trace_put_breakpoints() ghidra_trace_put_breakpoints()
ghidra_trace_txcommit() ghidra_trace_txcommit()
ghidra_trace_kill() ghidra_trace_kill()
@ -1045,6 +1014,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
.getValuePaths(Lifespan.at(0), .getValuePaths(Lifespan.at(0),
PathPredicates.parse("Processes[].Breakpoints[]")) PathPredicates.parse("Processes[].Breakpoints[]"))
.map(p -> p.getLastEntry()) .map(p -> p.getLastEntry())
.sorted(Comparator.comparing(TraceObjectValue::getEntryKey))
.toList(); .toList();
assertEquals(3, procBreakVals.size()); assertEquals(3, procBreakVals.size());
AddressRange rangeMain0 = AddressRange rangeMain0 =

View file

@ -15,12 +15,11 @@
*/ */
package agent.dbgeng.rmi; package agent.dbgeng.rmi;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.nio.ByteBuffer; import java.util.*;
import java.util.List;
import java.util.Objects;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
@ -28,19 +27,22 @@ import org.junit.Test;
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject; import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
import ghidra.dbg.util.PathPattern; import ghidra.dbg.util.PathPattern;
import ghidra.dbg.util.PathPredicates; import ghidra.dbg.util.PathPredicates;
import ghidra.debug.api.tracermi.RemoteMethod;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.Lifespan; import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.TraceSnapshot;
public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest { public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
private static final long RUN_TIMEOUT_MS = 20000; private static final long RUN_TIMEOUT_MS = 5000;
private static final long RETRY_MS = 500; private static final long RETRY_MS = 500;
record PythonAndTrace(PythonAndConnection conn, ManagedDomainObject mdo) implements AutoCloseable { record PythonAndTrace(PythonAndConnection conn, ManagedDomainObject mdo)
implements AutoCloseable {
public void execute(String cmd) { public void execute(String cmd) {
conn.execute(cmd); conn.execute(cmd);
} }
@ -85,8 +87,9 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
return conn.conn.connection().getLastSnapshot(tb.trace); return conn.conn.connection().getLastSnapshot(tb.trace);
} }
@Test // The 10s wait makes this a pretty expensive test @Test
public void testOnNewThread() throws Exception { public void testOnNewThread() throws Exception {
final int INIT_NOTEPAD_THREAD_COUNT = 4; // This could be fragile
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) { try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
conn.execute("from ghidradbg.commands import *"); conn.execute("from ghidradbg.commands import *");
txPut(conn, "processes"); txPut(conn, "processes");
@ -98,14 +101,18 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
}, RUN_TIMEOUT_MS, RETRY_MS); }, RUN_TIMEOUT_MS, RETRY_MS);
txPut(conn, "threads"); txPut(conn, "threads");
waitForPass(() -> assertEquals(4, waitForPass(() -> assertEquals(INIT_NOTEPAD_THREAD_COUNT,
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()), tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
RUN_TIMEOUT_MS, RETRY_MS); RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("dbg().go(10)"); // Via method, go is asynchronous
RemoteMethod go = conn.conn.getMethod("go");
TraceObject proc = tb.objAny("Processes[]");
go.invoke(Map.of("process", proc));
waitForPass( waitForPass(
() -> assertTrue(tb.objValues(lastSnap(conn), "Processes[].Threads[]").size() > 4), () -> assertThat(tb.objValues(lastSnap(conn), "Processes[].Threads[]").size(),
greaterThan(INIT_NOTEPAD_THREAD_COUNT)),
RUN_TIMEOUT_MS, RETRY_MS); RUN_TIMEOUT_MS, RETRY_MS);
} }
} }
@ -116,39 +123,39 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
txPut(conn, "processes"); txPut(conn, "processes");
waitForPass(() -> { waitForPass(() -> {
TraceObject inf = tb.objAny("Processes[]"); TraceObject proc = tb.obj("Processes[0]");
assertNotNull(inf); assertNotNull(proc);
assertEquals("STOPPED", tb.objValue(inf, lastSnap(conn), "_state")); assertEquals("STOPPED", tb.objValue(proc, lastSnap(conn), "_state"));
}, RUN_TIMEOUT_MS, RETRY_MS); }, RUN_TIMEOUT_MS, RETRY_MS);
txPut(conn, "threads"); txPut(conn, "threads");
waitForPass(() -> assertEquals(4, waitForPass(() -> assertEquals(4,
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()), tb.objValues(lastSnap(conn), "Processes[0].Threads[]").size()),
RUN_TIMEOUT_MS, RETRY_MS); RUN_TIMEOUT_MS, RETRY_MS);
// Now the real test // Now the real test
conn.execute("print('Selecting 1')");
conn.execute("util.select_thread(1)"); conn.execute("util.select_thread(1)");
waitForPass(() -> { waitForPass(() -> {
String tnum = conn.executeCapture("util.selected_thread()"); String tnum = conn.executeCapture("print(util.selected_thread())").strip();
assertTrue(tnum.contains("1")); assertEquals("1", tnum);
String threadIndex = threadIndex(traceManager.getCurrentObject()); assertEquals(tb.obj("Processes[0].Threads[1]"), traceManager.getCurrentObject());
assertTrue(tnum.contains(threadIndex));
}, RUN_TIMEOUT_MS, RETRY_MS); }, RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("util.select_thread(2)"); conn.execute("util.select_thread(2)");
waitForPass(() -> { waitForPass(() -> {
String tnum = conn.executeCapture("util.selected_thread()"); String tnum = conn.executeCapture("print(util.selected_thread())").strip();
assertTrue(tnum.contains("2")); assertEquals("2", tnum);
String threadIndex = threadIndex(traceManager.getCurrentObject()); String threadIndex = threadIndex(traceManager.getCurrentObject());
assertTrue(tnum.contains(threadIndex)); assertEquals("2", threadIndex);
}, RUN_TIMEOUT_MS, RETRY_MS); }, RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("util.select_thread(0)"); conn.execute("util.select_thread(0)");
waitForPass(() -> { waitForPass(() -> {
String tnum = conn.executeCapture("util.selected_thread()"); String tnum = conn.executeCapture("print(util.selected_thread())").strip();
assertTrue(tnum.contains("0")); assertEquals("0", tnum);
String threadIndex = threadIndex(traceManager.getCurrentObject()); String threadIndex = threadIndex(traceManager.getCurrentObject());
assertTrue(tnum.contains(threadIndex)); assertEquals("0", threadIndex);
}, RUN_TIMEOUT_MS, RETRY_MS); }, RUN_TIMEOUT_MS, RETRY_MS);
} }
} }
@ -190,21 +197,44 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
// FWIW, I've already seen this getting exercised in other tests. // FWIW, I've already seen this getting exercised in other tests.
} }
//@Test - dbgeng has limited support via DEBUG_CDS_DATA, /**
// but expensive to implement anything here * dbgeng has limited support via DEBUG_CDS_DATA. It tells us what space has changed, but not
* the address(es). We have some options:
*
* 1) Ignore it. This puts the onus of refreshing on the user. The upside is that past
* observations are preserved. The downside is we can't be certain of their accuracy.
*
* 2) Invalidate the entire space. This will ensure the UI either updates automatically or
* indicates the possible staleness. The downside is that we lose past observations.
*
* 3) Remember what addresses have been fetched since last BREAK, and refresh them all. This is
* better than refreshing the entire space (prohibitive), but we could get right back there if
* the user has captured the full space and then modifies a single byte.
*
* For the moment, we favor option (2), as we'd prefer never to display inaccurate data,
* especially as non-stale. The lost observations are a small price to pay, since they're not
* particularly important for the interactive use case.
*/
@Test
public void testOnMemoryChanged() throws Exception { public void testOnMemoryChanged() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) { try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
conn.execute("ghidra_trace_txstart('Tx')");
conn.execute("ghidra_trace_putmem('$pc 10')");
conn.execute("ghidra_trace_txcommit()");
long address = getAddressAtOffset(conn, 0); long address = getAddressAtOffset(conn, 0);
conn.execute("util.get_debugger().write(" + address + ", b'\\x7f')");
conn.execute("ghidra_trace_txstart('Tx')");
conn.execute("ghidra_trace_putmem(%d, 10)".formatted(address));
conn.execute("ghidra_trace_txcommit()");
waitForPass(() -> { waitForPass(() -> {
ByteBuffer buf = ByteBuffer.allocate(10); assertEquals(TraceMemoryState.KNOWN,
tb.trace.getMemoryManager().getBytes(lastSnap(conn), tb.addr(address), buf); tb.trace.getMemoryManager().getState(lastSnap(conn), tb.addr(address)));
assertEquals(0x7f, buf.get(0)); }, RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("util.dbg.write(%d, b'\\x7f')".formatted(address));
waitForPass(() -> {
assertEquals(TraceMemoryState.UNKNOWN,
tb.trace.getMemoryManager().getState(lastSnap(conn), tb.addr(address)));
}, RUN_TIMEOUT_MS, RETRY_MS); }, RUN_TIMEOUT_MS, RETRY_MS);
} }
} }
@ -216,8 +246,7 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
conn.execute("ghidra_trace_txstart('Tx')"); conn.execute("ghidra_trace_txstart('Tx')");
conn.execute("ghidra_trace_putreg()"); conn.execute("ghidra_trace_putreg()");
conn.execute("ghidra_trace_txcommit()"); conn.execute("ghidra_trace_txcommit()");
conn.execute("util.get_debugger().reg._set_register('rax', 0x1234)"); conn.execute("util.dbg.cmd('r rax=0x1234')");
conn.execute("util.get_debugger().stepi()");
String path = "Processes[].Threads[].Registers"; String path = "Processes[].Threads[].Registers";
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0))); TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
@ -234,8 +263,15 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) { try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
txPut(conn, "processes"); txPut(conn, "processes");
conn.execute("util.get_debugger()._control.SetExecutionStatus(DbgEng.DEBUG_STATUS_GO)"); // WaitForEvents is not required for this test to pass.
waitRunning(); conn.execute("""
@util.dbg.eng_thread
def go_no_wait():
util.dbg._base._control.SetExecutionStatus(DbgEng.DEBUG_STATUS_GO)
go_no_wait()
""");
waitRunning("Missed running after go");
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]")); TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
waitForPass(() -> { waitForPass(() -> {
@ -260,9 +296,10 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
public void testOnExited() throws Exception { public void testOnExited() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("netstat.exe")) { try (PythonAndTrace conn = startAndSyncPython("netstat.exe")) {
txPut(conn, "processes"); txPut(conn, "processes");
waitStopped(); waitStopped("Missed initial stop");
conn.execute("util.get_debugger().go()"); // Do the synchronous wait here, since netstat should terminate
conn.execute("util.dbg.go()");
waitForPass(() -> { waitForPass(() -> {
TraceSnapshot snapshot = TraceSnapshot snapshot =
@ -285,14 +322,12 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
txPut(conn, "breakpoints"); txPut(conn, "breakpoints");
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size()); assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
conn.execute("dbg = util.get_debugger()"); conn.execute("pc = util.get_pc()");
conn.execute("pc = dbg.reg.get_pc()"); conn.execute("util.dbg.bp(expr=pc)");
conn.execute("dbg.bp(expr=pc)");
waitForPass(() -> { waitForPass(() -> {
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]"); List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
assertEquals(1, brks.size()); assertEquals(1, brks.size());
return (TraceObject) brks.get(0);
}); });
} }
} }
@ -303,24 +338,22 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
txPut(conn, "breakpoints"); txPut(conn, "breakpoints");
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size()); assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
conn.execute("dbg = util.get_debugger()"); conn.execute("pc = util.get_pc()");
conn.execute("pc = dbg.reg.get_pc()"); conn.execute("util.dbg.bp(expr=pc)");
conn.execute("dbg.bp(expr=pc)");
TraceObject brk = waitForPass(() -> { TraceObject brk = waitForPass(() -> {
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]"); List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
assertEquals(1, brks.size()); assertEquals(1, brks.size());
return (TraceObject) brks.get(0); return (TraceObject) brks.get(0);
}); });
assertEquals(true, tb.objValue(brk, lastSnap(conn), "Enabled")); assertEquals(true, tb.objValue(brk, lastSnap(conn), "Enabled"));
conn.execute("dbg.bd(0)"); conn.execute("util.dbg.bd(0)");
conn.execute("dbg.stepi()");
assertEquals(false, tb.objValue(brk, lastSnap(conn), "Enabled")); assertEquals(false, tb.objValue(brk, lastSnap(conn), "Enabled"));
/* Not currently enabled /* Not currently enabled
assertEquals("", tb.objValue(brk, lastSnap(conn), "Command")); assertEquals("", tb.objValue(brk, lastSnap(conn), "Command"));
conn.execute("dbg.bp(expr=pc, windbgcmd='bl')"); conn.execute("util.dbg.bp(expr=pc, windbgcmd='bl')");
conn.execute("dbg.stepi()");
assertEquals("bl", tb.objValue(brk, lastSnap(conn), "Command")); assertEquals("bl", tb.objValue(brk, lastSnap(conn), "Command"));
*/ */
} }
@ -332,22 +365,23 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
txPut(conn, "breakpoints"); txPut(conn, "breakpoints");
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size()); assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
conn.execute("dbg = util.get_debugger()"); conn.execute("pc = util.get_pc()");
conn.execute("pc = dbg.reg.get_pc()"); conn.execute("util.dbg.bp(expr=pc)");
conn.execute("dbg.bp(expr=pc)");
TraceObject brk = waitForPass(() -> { TraceObject brk = waitForPass(() -> {
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]"); List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
assertEquals(1, brks.size()); assertEquals(1, brks.size());
return (TraceObject) brks.get(0); return (TraceObject) brks.get(0);
}); });
String id = brk.getCanonicalPath().index();
assertEquals("0", id);
conn.execute("dbg.cmd('bc %s')".formatted(brk.getCanonicalPath().index())); // Causes access violation in pybag/comtypes during tear-down
conn.execute("dbg.stepi()"); //conn.execute("util.dbg.bc(%s)".formatted(id));
conn.execute("util.dbg.cmd('bc %s')".formatted(id));
waitForPass( waitForPass(() -> assertEquals(0,
() -> assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size()));
tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size()));
} }
} }
@ -367,7 +401,7 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
} }
private long getAddressAtOffset(PythonAndTrace conn, int offset) { private long getAddressAtOffset(PythonAndTrace conn, int offset) {
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+" + offset + ")"; String inst = "print(util.get_inst(util.get_pc()+" + offset + "))";
String ret = conn.executeCapture(inst); String ret = conn.executeCapture(inst);
String[] split = ret.split("\\s+"); // get target String[] split = ret.split("\\s+"); // get target
return Long.decode(split[1]); return Long.decode(split[1]);

View file

@ -106,10 +106,9 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
conn.execute("dbg = util.get_debugger()"); conn.execute("pc = util.get_pc()");
conn.execute("pc = dbg.reg.get_pc()"); conn.execute("util.dbg.bp(expr=pc)");
conn.execute("dbg.bp(expr=pc)"); conn.execute("util.dbg.ba(expr=pc+4)");
conn.execute("dbg.ba(expr=pc+4)");
txPut(conn, "breakpoints"); txPut(conn, "breakpoints");
TraceObject breakpoints = TraceObject breakpoints =
Objects.requireNonNull(tb.objAny("Processes[].Breakpoints")); Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
@ -119,6 +118,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
.getValuePaths(Lifespan.at(0), .getValuePaths(Lifespan.at(0),
PathPredicates.parse("Processes[].Breakpoints[]")) PathPredicates.parse("Processes[].Breakpoints[]"))
.map(p -> p.getLastEntry()) .map(p -> p.getLastEntry())
.sorted(Comparator.comparing(TraceObjectValue::getEntryKey))
.toList(); .toList();
assertEquals(2, procBreakLocVals.size()); assertEquals(2, procBreakLocVals.size());
AddressRange rangeMain = AddressRange rangeMain =
@ -145,11 +145,10 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
conn.execute("dbg = util.get_debugger()"); conn.execute("pc = util.get_pc()");
conn.execute("pc = dbg.reg.get_pc()"); conn.execute("util.dbg.ba(expr=pc, access=DbgEng.DEBUG_BREAK_EXECUTE)");
conn.execute("dbg.ba(expr=pc, access=DbgEng.DEBUG_BREAK_EXECUTE)"); conn.execute("util.dbg.ba(expr=pc+4, access=DbgEng.DEBUG_BREAK_READ)");
conn.execute("dbg.ba(expr=pc+4, access=DbgEng.DEBUG_BREAK_READ)"); conn.execute("util.dbg.ba(expr=pc+8, access=DbgEng.DEBUG_BREAK_WRITE)");
conn.execute("dbg.ba(expr=pc+8, access=DbgEng.DEBUG_BREAK_WRITE)");
TraceObject locations = TraceObject locations =
Objects.requireNonNull(tb.objAny("Processes[].Breakpoints")); Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
refreshProcWatchpoints.invoke(Map.of("node", locations)); refreshProcWatchpoints.invoke(Map.of("node", locations));
@ -158,6 +157,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
.getValuePaths(Lifespan.at(0), .getValuePaths(Lifespan.at(0),
PathPredicates.parse("Processes[].Breakpoints[]")) PathPredicates.parse("Processes[].Breakpoints[]"))
.map(p -> p.getLastEntry()) .map(p -> p.getLastEntry())
.sorted(Comparator.comparing(TraceObjectValue::getEntryKey))
.toList(); .toList();
assertEquals(3, procBreakVals.size()); assertEquals(3, procBreakVals.size());
AddressRange rangeMain0 = AddressRange rangeMain0 =
@ -290,8 +290,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
conn.execute("regs = util.get_debugger().reg"); conn.execute("util.dbg.cmd('r rax=0xdeadbeef')");
conn.execute("regs._set_register('rax', int(0xdeadbeef))");
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0))); TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
refreshRegisters.invoke(Map.of("node", registers)); refreshRegisters.invoke(Map.of("node", registers));
@ -373,9 +372,9 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
for (TraceObject t : list) { for (TraceObject t : list) {
activateThread.invoke(Map.of("thread", t)); activateThread.invoke(Map.of("thread", t));
String out = conn.executeCapture("util.get_debugger().get_thread()"); String out = conn.executeCapture("print(util.dbg.get_thread())").strip();
List<String> indices = pattern.matchKeys(t.getCanonicalPath().getKeyList()); List<String> indices = pattern.matchKeys(t.getCanonicalPath().getKeyList());
assertEquals(out, "%s".formatted(indices.get(1))); assertEquals("%s".formatted(indices.get(1)), out);
} }
} }
} }
@ -394,7 +393,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
TraceObject proc2 = Objects.requireNonNull(tb.objAny("Processes[]")); TraceObject proc2 = Objects.requireNonNull(tb.objAny("Processes[]"));
removeProcess.invoke(Map.of("process", proc2)); removeProcess.invoke(Map.of("process", proc2));
String out = conn.executeCapture("list(util.process_list())"); String out = conn.executeCapture("print(list(util.process_list()))");
assertThat(out, containsString("[]")); assertThat(out, containsString("[]"));
} }
} }
@ -414,7 +413,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
Objects.requireNonNull(tb.obj("Available[%d]".formatted(dproc.pid))); Objects.requireNonNull(tb.obj("Available[%d]".formatted(dproc.pid)));
attachObj.invoke(Map.of("target", target)); attachObj.invoke(Map.of("target", target));
String out = conn.executeCapture("list(util.process_list())"); String out = conn.executeCapture("print(list(util.process_list()))");
assertThat(out, containsString("%d".formatted(dproc.pid))); assertThat(out, containsString("%d".formatted(dproc.pid)));
} }
} }
@ -435,7 +434,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
tb.objAny("Available[" + dproc.pid + "]", Lifespan.at(0))); tb.objAny("Available[" + dproc.pid + "]", Lifespan.at(0)));
attachPid.invoke(Map.of("pid", dproc.pid)); attachPid.invoke(Map.of("pid", dproc.pid));
String out = conn.executeCapture("list(util.process_list())"); String out = conn.executeCapture("print(list(util.process_list()))");
assertThat(out, containsString("%d".formatted(dproc.pid))); assertThat(out, containsString("%d".formatted(dproc.pid)));
} }
} }
@ -455,7 +454,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]")); TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
detach.invoke(Map.of("process", proc)); detach.invoke(Map.of("process", proc));
String out = conn.executeCapture("list(util.process_list())"); String out = conn.executeCapture("print(list(util.process_list()))");
assertThat(out, containsString("[]")); assertThat(out, containsString("[]"));
} }
} }
@ -474,13 +473,13 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
launch.invoke(Map.ofEntries( launch.invoke(Map.ofEntries(
Map.entry("file", "notepad.exe"))); Map.entry("file", "notepad.exe")));
String out = conn.executeCapture("list(util.process_list())"); String out = conn.executeCapture("print(list(util.process_list()))");
assertThat(out, containsString("notepad.exe")); assertThat(out, containsString("notepad.exe"));
} }
} }
} }
@Test //Can't do this test because create(xxx, initial_break=False) doesn't return @Test
public void testLaunch() throws Exception { public void testLaunch() throws Exception {
try (PythonAndConnection conn = startAndConnectPython()) { try (PythonAndConnection conn = startAndConnectPython()) {
start(conn, null); start(conn, null);
@ -491,12 +490,12 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
launch.invoke(Map.ofEntries( launch.invoke(Map.ofEntries(
Map.entry("timeout", 1L), Map.entry("initial_break", true),
Map.entry("file", "notepad.exe"))); Map.entry("file", "notepad.exe")));
txPut(conn, "processes"); txPut(conn, "processes");
String out = conn.executeCapture("list(util.process_list())"); String out = conn.executeCapture("print(list(util.process_list()))");
assertThat(out, containsString("notepad.exe")); assertThat(out, containsString("notepad.exe"));
} }
} }
@ -511,33 +510,61 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
RemoteMethod kill = conn.getMethod("kill"); RemoteMethod kill = conn.getMethod("kill");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped("Missed initial stop");
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]")); TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
kill.invoke(Map.of("process", proc)); kill.invoke(Map.of("process", proc));
String out = conn.executeCapture("list(util.process_list())"); String out = conn.executeCapture("print(list(util.process_list()))");
assertThat(out, containsString("[]")); assertThat(out, containsString("[]"));
} }
} }
} }
@Test
public void testGoInterrupt5() throws Exception {
try (PythonAndConnection conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
conn.execute(INSTRUMENT_STATE);
RemoteMethod go = conn.getMethod("go");
RemoteMethod interrupt = conn.getMethod("interrupt");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped("Missed initial stop");
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
for (int i = 0; i < 5; i++) {
go.invoke(Map.of("process", proc));
waitRunning("Missed running " + i);
interrupt.invoke(Map.of("process", proc));
waitStopped("Missed stopped " + i);
}
}
// The waits are the assertions
}
}
@Test @Test
public void testStepInto() throws Exception { public void testStepInto() throws Exception {
try (PythonAndConnection conn = startAndConnectPython()) { try (PythonAndConnection conn = startAndConnectPython()) {
start(conn, "notepad.exe"); start(conn, "notepad.exe");
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod step_into = conn.getMethod("step_into"); RemoteMethod stepInto = conn.getMethod("step_into");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped("Missed initial stop");
txPut(conn, "threads"); txPut(conn, "threads");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]")); TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
while (!getInst(conn).contains("call")) { while (!getInst(conn).contains("call")) {
step_into.invoke(Map.of("thread", thread)); stepInto.invoke(Map.of("thread", thread));
} }
String disCall = getInst(conn); String disCall = getInst(conn);
@ -549,7 +576,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
String[] split = disCall.split("\\s+"); // get target String[] split = disCall.split("\\s+"); // get target
long pcCallee = Long.decode(split[split.length - 1]); long pcCallee = Long.decode(split[split.length - 1]);
step_into.invoke(Map.of("thread", thread)); stepInto.invoke(Map.of("thread", thread));
long pc = getAddressAtOffset(conn, 0); long pc = getAddressAtOffset(conn, 0);
assertEquals(pcCallee, pc); assertEquals(pcCallee, pc);
} }
@ -562,23 +589,23 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
start(conn, "notepad.exe"); start(conn, "notepad.exe");
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod step_over = conn.getMethod("step_over"); RemoteMethod stepOver = conn.getMethod("step_over");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped("Missed initial stop");
txPut(conn, "threads"); txPut(conn, "threads");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]")); TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
while (!getInst(conn).contains("call")) { while (!getInst(conn).contains("call")) {
step_over.invoke(Map.of("thread", thread)); stepOver.invoke(Map.of("thread", thread));
} }
String disCall = getInst(conn); String disCall = getInst(conn);
String[] split = disCall.split("\\s+"); // get target String[] split = disCall.split("\\s+"); // get target
long pcCallee = Long.decode(split[split.length - 1]); long pcCallee = Long.decode(split[split.length - 1]);
step_over.invoke(Map.of("thread", thread)); stepOver.invoke(Map.of("thread", thread));
long pc = getAddressAtOffset(conn, 0); long pc = getAddressAtOffset(conn, 0);
assertNotEquals(pcCallee, pc); assertNotEquals(pcCallee, pc);
} }
@ -590,17 +617,17 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
try (PythonAndConnection conn = startAndConnectPython()) { try (PythonAndConnection conn = startAndConnectPython()) {
start(conn, "notepad.exe"); start(conn, "notepad.exe");
RemoteMethod step_into = conn.getMethod("step_into"); RemoteMethod stepInto = conn.getMethod("step_into");
RemoteMethod step_to = conn.getMethod("step_to"); RemoteMethod stepTo = conn.getMethod("step_to");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
txPut(conn, "threads"); txPut(conn, "threads");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]")); TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
while (!getInst(conn).contains("call")) { while (!getInst(conn).contains("call")) {
step_into.invoke(Map.of("thread", thread)); stepInto.invoke(Map.of("thread", thread));
} }
step_into.invoke(Map.of("thread", thread)); stepInto.invoke(Map.of("thread", thread));
int sz = Integer.parseInt(getInstSizeAtOffset(conn, 0)); int sz = Integer.parseInt(getInstSizeAtOffset(conn, 0));
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
@ -608,10 +635,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
} }
long pcNext = getAddressAtOffset(conn, sz); long pcNext = getAddressAtOffset(conn, sz);
stepTo.invoke(Map.of("thread", thread, "address", tb.addr(pcNext), "max", 10));
boolean success = (boolean) step_to
.invoke(Map.of("thread", thread, "address", tb.addr(pcNext), "max", 10));
assertTrue(success);
long pc = getAddressAtOffset(conn, 0); long pc = getAddressAtOffset(conn, 0);
assertEquals(pcNext, pc); assertEquals(pcNext, pc);
@ -625,24 +649,24 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
start(conn, "notepad.exe"); start(conn, "notepad.exe");
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod step_into = conn.getMethod("step_into"); RemoteMethod stepInto = conn.getMethod("step_into");
RemoteMethod step_out = conn.getMethod("step_out"); RemoteMethod stepOut = conn.getMethod("step_out");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped("Missed initial stop");
txPut(conn, "threads"); txPut(conn, "threads");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]")); TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
while (!getInst(conn).contains("call")) { while (!getInst(conn).contains("call")) {
step_into.invoke(Map.of("thread", thread)); stepInto.invoke(Map.of("thread", thread));
} }
int sz = Integer.parseInt(getInstSizeAtOffset(conn, 0)); int sz = Integer.parseInt(getInstSizeAtOffset(conn, 0));
long pcNext = getAddressAtOffset(conn, sz); long pcNext = getAddressAtOffset(conn, sz);
step_into.invoke(Map.of("thread", thread)); stepInto.invoke(Map.of("thread", thread));
step_out.invoke(Map.of("thread", thread)); stepOut.invoke(Map.of("thread", thread));
long pc = getAddressAtOffset(conn, 0); long pc = getAddressAtOffset(conn, 0);
assertEquals(pcNext, pc); assertEquals(pcNext, pc);
} }
@ -664,7 +688,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
long address = getAddressAtOffset(conn, 0); long address = getAddressAtOffset(conn, 0);
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address))); breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
String out = conn.executeCapture("list(util.get_breakpoints())"); String out = conn.executeCapture("print(list(util.get_breakpoints()))");
assertThat(out, containsString(Long.toHexString(address))); assertThat(out, containsString(Long.toHexString(address)));
} }
} }
@ -679,11 +703,11 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
RemoteMethod breakExpression = conn.getMethod("break_expression"); RemoteMethod breakExpression = conn.getMethod("break_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped("Missed initial stop");
breakExpression.invoke(Map.of("expression", "entry")); breakExpression.invoke(Map.of("expression", "entry"));
String out = conn.executeCapture("list(util.get_breakpoints())"); String out = conn.executeCapture("print(list(util.get_breakpoints()))");
assertThat(out, containsString("entry")); assertThat(out, containsString("entry"));
} }
} }
@ -704,7 +728,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
long address = getAddressAtOffset(conn, 0); long address = getAddressAtOffset(conn, 0);
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address))); breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
String out = conn.executeCapture("list(util.get_breakpoints())"); String out = conn.executeCapture("print(list(util.get_breakpoints()))");
assertThat(out, containsString(Long.toHexString(address))); assertThat(out, containsString(Long.toHexString(address)));
} }
} }
@ -719,11 +743,11 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
RemoteMethod breakExpression = conn.getMethod("break_hw_expression"); RemoteMethod breakExpression = conn.getMethod("break_hw_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped("Missed initial stop");
breakExpression.invoke(Map.of("expression", "entry")); breakExpression.invoke(Map.of("expression", "entry"));
String out = conn.executeCapture("list(util.get_breakpoints())"); String out = conn.executeCapture("print(list(util.get_breakpoints()))");
assertThat(out, containsString("entry")); assertThat(out, containsString("entry"));
} }
} }
@ -738,14 +762,14 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
RemoteMethod breakRange = conn.getMethod("break_read_range"); RemoteMethod breakRange = conn.getMethod("break_read_range");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped("Missed initial stop");
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]")); TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = getAddressAtOffset(conn, 0); long address = getAddressAtOffset(conn, 0);
AddressRange range = tb.range(address, address + 3); // length 4 AddressRange range = tb.range(address, address + 3); // length 4
breakRange.invoke(Map.of("process", proc, "range", range)); breakRange.invoke(Map.of("process", proc, "range", range));
String out = conn.executeCapture("list(util.get_breakpoints())"); String out = conn.executeCapture("print(list(util.get_breakpoints()))");
assertThat(out, containsString("%x".formatted(address))); assertThat(out, containsString("%x".formatted(address)));
assertThat(out, containsString("sz=4")); assertThat(out, containsString("sz=4"));
assertThat(out, containsString("type=r")); assertThat(out, containsString("type=r"));
@ -766,7 +790,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic")); breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
long address = getAddressAtOffset(conn, 0); long address = getAddressAtOffset(conn, 0);
String out = conn.executeCapture("list(util.get_breakpoints())"); String out = conn.executeCapture("print(list(util.get_breakpoints()))");
assertThat(out, containsString(Long.toHexString(address >> 24))); assertThat(out, containsString(Long.toHexString(address >> 24)));
assertThat(out, containsString("sz=1")); assertThat(out, containsString("sz=1"));
assertThat(out, containsString("type=r")); assertThat(out, containsString("type=r"));
@ -783,14 +807,14 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
RemoteMethod breakRange = conn.getMethod("break_write_range"); RemoteMethod breakRange = conn.getMethod("break_write_range");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped("Missed initial stop");
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]")); TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = getAddressAtOffset(conn, 0); long address = getAddressAtOffset(conn, 0);
AddressRange range = tb.range(address, address + 3); // length 4 AddressRange range = tb.range(address, address + 3); // length 4
breakRange.invoke(Map.of("process", proc, "range", range)); breakRange.invoke(Map.of("process", proc, "range", range));
String out = conn.executeCapture("list(util.get_breakpoints())"); String out = conn.executeCapture("print(list(util.get_breakpoints()))");
assertThat(out, containsString("%x".formatted(address))); assertThat(out, containsString("%x".formatted(address)));
assertThat(out, containsString("sz=4")); assertThat(out, containsString("sz=4"));
assertThat(out, containsString("type=w")); assertThat(out, containsString("type=w"));
@ -811,7 +835,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic")); breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
long address = getAddressAtOffset(conn, 0); long address = getAddressAtOffset(conn, 0);
String out = conn.executeCapture("list(util.get_breakpoints())"); String out = conn.executeCapture("print(list(util.get_breakpoints()))");
assertThat(out, containsString(Long.toHexString(address >> 24))); assertThat(out, containsString(Long.toHexString(address >> 24)));
assertThat(out, containsString("sz=1")); assertThat(out, containsString("sz=1"));
assertThat(out, containsString("type=w")); assertThat(out, containsString("type=w"));
@ -828,14 +852,14 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
RemoteMethod breakRange = conn.getMethod("break_access_range"); RemoteMethod breakRange = conn.getMethod("break_access_range");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped("Missed initial stop");
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]")); TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = getAddressAtOffset(conn, 0); long address = getAddressAtOffset(conn, 0);
AddressRange range = tb.range(address, address + 3); // length 4 AddressRange range = tb.range(address, address + 3); // length 4
breakRange.invoke(Map.of("process", proc, "range", range)); breakRange.invoke(Map.of("process", proc, "range", range));
String out = conn.executeCapture("list(util.get_breakpoints())"); String out = conn.executeCapture("print(list(util.get_breakpoints()))");
assertThat(out, containsString("%x".formatted(address))); assertThat(out, containsString("%x".formatted(address)));
assertThat(out, containsString("sz=4")); assertThat(out, containsString("sz=4"));
assertThat(out, containsString("type=rw")); assertThat(out, containsString("type=rw"));
@ -856,7 +880,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic")); breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
long address = getAddressAtOffset(conn, 0); long address = getAddressAtOffset(conn, 0);
String out = conn.executeCapture("list(util.get_breakpoints())"); String out = conn.executeCapture("print(list(util.get_breakpoints()))");
assertThat(out, containsString(Long.toHexString(address >> 24))); assertThat(out, containsString(Long.toHexString(address >> 24)));
assertThat(out, containsString("sz=1")); assertThat(out, containsString("sz=1"));
assertThat(out, containsString("type=rw")); assertThat(out, containsString("type=rw"));
@ -884,7 +908,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
toggleBreakpoint.invoke(Map.of("breakpoint", bpt, "enabled", false)); toggleBreakpoint.invoke(Map.of("breakpoint", bpt, "enabled", false));
String out = conn.executeCapture("list(util.get_breakpoints())"); String out = conn.executeCapture("print(list(util.get_breakpoints()))");
assertThat(out, containsString("disabled")); assertThat(out, containsString("disabled"));
} }
} }
@ -910,7 +934,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
deleteBreakpoint.invoke(Map.of("breakpoint", bpt)); deleteBreakpoint.invoke(Map.of("breakpoint", bpt));
String out = conn.executeCapture("list(util.get_breakpoints())"); String out = conn.executeCapture("print(list(util.get_breakpoints()))");
assertThat(out, containsString("[]")); assertThat(out, containsString("[]"));
} }
} }
@ -941,19 +965,19 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
} }
private String getInstAtOffset(PythonAndConnection conn, int offset) { private String getInstAtOffset(PythonAndConnection conn, int offset) {
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+" + offset + ")"; String inst = "print(util.get_inst(util.get_pc()+" + offset + "))";
String ret = conn.executeCapture(inst); String ret = conn.executeCapture(inst).strip();
return ret.substring(1, ret.length() - 1); // remove <> return ret.substring(1, ret.length() - 1); // remove <>
} }
private String getInstSizeAtOffset(PythonAndConnection conn, int offset) { private String getInstSizeAtOffset(PythonAndConnection conn, int offset) {
String instSize = "util.get_inst_sz(util.get_debugger().reg.get_pc()+" + offset + ")"; String instSize = "print(util.get_inst_sz(util.get_pc()+" + offset + "))";
return conn.executeCapture(instSize); return conn.executeCapture(instSize).strip();
} }
private long getAddressAtOffset(PythonAndConnection conn, int offset) { private long getAddressAtOffset(PythonAndConnection conn, int offset) {
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+" + offset + ")"; String inst = "print(util.get_inst(util.get_pc()+" + offset + "))";
String ret = conn.executeCapture(inst); String ret = conn.executeCapture(inst).strip();
String[] split = ret.split("\\s+"); // get target String[] split = ret.split("\\s+"); // get target
return Long.decode(split[1]); return Long.decode(split[1]);
} }