GP-3754: post-review review

GP-3754: last pass
GP-3754: most review issues address (ymm0/testSave not); tests pass
GP-3754: review - eval/exec
GP-3754: moved registers to thread
GP-3754: review pass 7 - register w/o banks
GP-3754: review pass 6 - basic clean-up items
GP-3754: pass 5 - tests all pass (minus commented-out ones)
GP-3754: review pass 4(?)
GP-3754: review pass 3
GP-3754: review pass 2
GP-3754: review pass 1
GP-3754: ref Pybag
GP-3754: clean-up
GP-3754: testing post-changes in hooks
GP-3754: fix for env
GP-3754: convenience method for get_debugger
GP-3754: tests all running successfully
GP-3754: hook tests running
GP-3754: hook tests: memory changed not wokring, bpt modified does nothing
GP-3754: lot of work to get testExited to pass
GP-3754: start on hooks tests; mods to methods not-runnable while running
GP-3754: methods tests working
GP-3754: bulk of bpt tests running
GP-3754: whittling down the list
GP-3754: more passing method tests
GP-3754: at least a few method tests working (don't run batch)
GP-3754: cmd tests pass w/o closing stdin
GP-3754: command test basically running
GP-3754: 3 cmd tests failing; 3 commented out
GP-3754: ghidra_trace_set_values uses broken except for in testGetValues
GP-3754: whittling down the command tests
GP-3754: tests esp. SetValue
GP-3754: testMinimal works but cannot 'execute'
GP-3754: JUnits still don't run
GP-3754: breakpoints in the list
GP-3754: continued work on hooks; bpts not registering as bpts
GP-3754: templates for hooks
GP-3754: tests, first viable hook (module_load)'
GP-3754: first pass at methods
GP-3754: memory/regs working
GP-3754: most of the puts done
GP-3754: modules/regions working
GP-3754: process/threads working
GP-3754: added to manifest
GP-3754: minimal shell: arch faked out / thru activate w/o push
This commit is contained in:
d-millar 2023-09-12 11:46:10 -04:00
parent f64c38ef7f
commit abbc18f927
20 changed files with 6266 additions and 2 deletions

View file

@ -20,6 +20,7 @@ apply from: "$rootProject.projectDir/gradle/nativeProject.gradle"
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
apply from: "$rootProject.projectDir/gradle/debugger/hasNodepJar.gradle"
apply from: "$rootProject.projectDir/gradle/debugger/hasPythonPackage.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Debug Debugger-agent-dbgeng'

View file

@ -3,3 +3,7 @@
Module.manifest||GHIDRA||||END|
src/javaprovider/def/javaprovider.def||GHIDRA||||END|
src/javaprovider/rc/javaprovider.rc||GHIDRA||||END|
src/main/py/LICENSE||GHIDRA||||END|
src/main/py/README.md||GHIDRA||||END|
src/main/py/pyproject.toml||GHIDRA||||END|
src/main/py/src/ghidradbg/schema.xml||GHIDRA||||END|

View file

@ -0,0 +1,11 @@
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.

View file

@ -0,0 +1,8 @@
# Ghidra Trace RMI
Package for connecting dbgeng to Ghidra via Trace RMI.
This connector requires Pybag 2.2.8 or better:
https://pypi.org/project/Pybag
https://github.com/dshikashio/Pybag

View file

@ -0,0 +1,26 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "ghidradbg"
version = "10.4"
authors = [
{ name="Ghidra Development Team" },
]
description = "Ghidra's Plugin for dbgeng"
readme = "README.md"
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
]
dependencies = [
"ghidratrace==10.4",
"pybag>=2.2.8"
]
[project.urls]
"Homepage" = "https://github.com/NationalSecurityAgency/ghidra"
"Bug Tracker" = "https://github.com/NationalSecurityAgency/ghidra/issues"

View file

@ -0,0 +1,19 @@
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
import ctypes
ctypes.windll.kernel32.SetErrorMode(0x0001|0x0002|0x8000)
from . import util, commands, methods, hooks

View file

@ -0,0 +1,240 @@
## ###
# 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.
##
from ghidratrace.client import Address, RegVal
from pybag import pydbg
from . import util
language_map = {
'ARM': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A', 'ARM:BE:64:v8', 'ARM:LE:64:v8'],
'Itanium': [],
'x86': ['x86:LE:32:default'],
'x86_64': ['x86:LE:64:default'],
'EFI': ['x86:LE:64:default'],
}
data64_compiler_map = {
None: 'pointer64',
}
x86_compiler_map = {
'windows': 'windows',
'Cygwin': 'windows',
}
arm_compiler_map = {
'windows': 'windows',
}
compiler_map = {
'DATA:BE:64:default': data64_compiler_map,
'DATA:LE:64:default': data64_compiler_map,
'x86:LE:32:default': x86_compiler_map,
'x86:LE:64:default': x86_compiler_map,
'AARCH64:BE:64:v8A': arm_compiler_map,
'AARCH64:LE:64:AppleSilicon': arm_compiler_map,
'AARCH64:LE:64:v8A': arm_compiler_map,
'ARM:BE:64:v8': arm_compiler_map,
'ARM:LE:64:v8': arm_compiler_map,
}
def get_arch():
try:
type = util.get_debugger()._control.GetActualProcessorType()
except Exception:
return "Unknown"
if type is None:
return "x86_64"
if type == 0x8664:
return "x86_64"
if type == 0x014c:
return "x86"
if type == 0x01c0:
return "ARM"
if type == 0x0200:
return "Itanium"
if type == 0x0EBC:
return "EFI"
return "Unknown"
def get_endian():
parm = util.get_convenience_variable('endian')
if parm != 'auto':
return parm
return 'little'
def get_osabi():
parm = util.get_convenience_variable('osabi')
if not parm in ['auto', 'default']:
return parm
try:
os = util.get_debugger().cmd("vertarget")
if "Windows" not in os:
return "default"
except Exception:
pass
return "windows"
def compute_ghidra_language():
# First, check if the parameter is set
lang = util.get_convenience_variable('ghidra-language')
if lang != 'auto':
return lang
# Get the list of possible languages for the arch. We'll need to sift
# through them by endian and probably prefer default/simpler variants. The
# heuristic for "simpler" will be 'default' then shortest variant id.
arch = get_arch()
endian = get_endian()
lebe = ':BE:' if endian == 'big' else ':LE:'
if not arch in language_map:
return 'DATA' + lebe + '64:default'
langs = language_map[arch]
matched_endian = sorted(
(l for l in langs if lebe in l),
key=lambda l: 0 if l.endswith(':default') else len(l)
)
if len(matched_endian) > 0:
return matched_endian[0]
# NOTE: I'm disinclined to fall back to a language match with wrong endian.
return 'DATA' + lebe + '64:default'
def compute_ghidra_compiler(lang):
# First, check if the parameter is set
comp = util.get_convenience_variable('ghidra-compiler')
if comp != 'auto':
return comp
# Check if the selected lang has specific compiler recommendations
if not lang in compiler_map:
return 'default'
comp_map = compiler_map[lang]
osabi = get_osabi()
if osabi in comp_map:
return comp_map[osabi]
if None in comp_map:
return comp_map[None]
return 'default'
def compute_ghidra_lcsp():
lang = compute_ghidra_language()
comp = compute_ghidra_compiler(lang)
return lang, comp
class DefaultMemoryMapper(object):
def __init__(self, defaultSpace):
self.defaultSpace = defaultSpace
def map(self, proc: int, offset: int):
space = self.defaultSpace
return self.defaultSpace, Address(space, offset)
def map_back(self, proc: int, address: Address) -> int:
if address.space == self.defaultSpace:
return address.offset
raise ValueError(f"Address {address} is not in process {proc.GetProcessID()}")
DEFAULT_MEMORY_MAPPER = DefaultMemoryMapper('ram')
memory_mappers = {}
def compute_memory_mapper(lang):
if not lang in memory_mappers:
return DEFAULT_MEMORY_MAPPER
return memory_mappers[lang]
class DefaultRegisterMapper(object):
def __init__(self, byte_order):
if not byte_order in ['big', 'little']:
raise ValueError("Invalid byte_order: {}".format(byte_order))
self.byte_order = byte_order
self.union_winners = {}
def map_name(self, proc, name):
return name
def map_value(self, proc, name, value):
try:
### TODO: this seems half-baked
av = value.to_bytes(8, "big")
except Exception:
raise ValueError("Cannot convert {}'s value: '{}', type: '{}'"
.format(name, value, type(value)))
return RegVal(self.map_name(proc, name), av)
def map_name_back(self, proc, name):
return name
def map_value_back(self, proc, name, value):
return RegVal(self.map_name_back(proc, name), value)
class Intel_x86_64_RegisterMapper(DefaultRegisterMapper):
def __init__(self):
super().__init__('little')
def map_name(self, proc, name):
if name is None:
return 'UNKNOWN'
if name == 'efl':
return 'rflags'
if name.startswith('zmm'):
# Ghidra only goes up to ymm, right now
return 'ymm' + name[3:]
return super().map_name(proc, name)
def map_value(self, proc, name, value):
rv = super().map_value(proc, name, value)
if rv.name.startswith('ymm') and len(rv.value) > 32:
return RegVal(rv.name, rv.value[-32:])
return rv
def map_name_back(self, proc, name):
if name == 'rflags':
return 'eflags'
DEFAULT_BE_REGISTER_MAPPER = DefaultRegisterMapper('big')
DEFAULT_LE_REGISTER_MAPPER = DefaultRegisterMapper('little')
register_mappers = {
'x86:LE:64:default': Intel_x86_64_RegisterMapper()
}
def compute_register_mapper(lang):
if not lang in register_mappers:
if ':BE:' in lang:
return DEFAULT_BE_REGISTER_MAPPER
if ':LE:' in lang:
return DEFAULT_LE_REGISTER_MAPPER
return register_mappers[lang]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,439 @@
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
import sys
import time
import threading
from pybag import pydbg
from pybag.dbgeng.callbacks import EventHandler
from pybag.dbgeng import core as DbgEng
from pybag.dbgeng import exception
from pybag.dbgeng.idebugbreakpoint import DebugBreakpoint
from . import commands, util
ALL_EVENTS = 0xFFFF
class HookState(object):
__slots__ = ('installed', 'mem_catchpoint')
def __init__(self):
self.installed = False
self.mem_catchpoint = None
class ProcessState(object):
__slots__ = ('first', 'regions', 'modules', 'threads', 'breaks', 'watches', 'visited')
def __init__(self):
self.first = True
# For things we can detect changes to between stops
self.regions = False
self.modules = False
self.threads = False
self.breaks = False
self.watches = False
# For frames and threads that have already been synced since last stop
self.visited = set()
def record(self, description=None):
first = self.first
self.first = False
if description is not None:
commands.STATE.trace.snapshot(description)
if first:
commands.put_processes()
commands.put_environment()
if self.threads:
commands.put_threads()
self.threads = False
thread = util.selected_thread()
if thread is not None:
if first or thread not in self.visited:
commands.putreg()
commands.putmem("$pc", "1", from_tty=False)
commands.putmem("$sp", "1", from_tty=False)
commands.put_frames()
self.visited.add(thread)
frame = util.selected_frame()
hashable_frame = (thread, frame)
if first or hashable_frame not in self.visited:
self.visited.add(hashable_frame)
if first or self.regions or self.threads or self.modules:
commands.put_regions()
self.regions = False
if first or self.modules:
commands.put_modules()
self.modules = False
if first or self.breaks:
commands.put_breakpoints()
self.breaks = False
def record_continued(self):
commands.put_processes(running=True)
commands.put_threads(running=True)
def record_exited(self, exit_code, description=None):
if description is not None:
commands.STATE.trace.snapshot(description)
proc = util.selected_process()
ipath = commands.PROCESS_PATTERN.format(procnum=proc)
commands.STATE.trace.proxy_object_path(
ipath).set_value('_exit_code', exit_code)
class BrkState(object):
__slots__ = ('break_loc_counts',)
def __init__(self):
self.break_loc_counts = {}
def update_brkloc_count(self, b, count):
self.break_loc_counts[b.GetID()] = count
def get_brkloc_count(self, b):
return self.break_loc_counts.get(b.GetID(), 0)
def del_brkloc_count(self, b):
if b not in self.break_loc_counts:
return 0 # TODO: Print a warning?
count = self.break_loc_counts[b.GetID()]
del self.break_loc_counts[b.GetID()]
return count
HOOK_STATE = HookState()
BRK_STATE = BrkState()
PROC_STATE = {}
def on_state_changed(*args):
#print("ON_STATE_CHANGED")
#print(args[0])
if args[0] == DbgEng.DEBUG_CES_CURRENT_THREAD:
return on_thread_selected(args)
elif args[0] == DbgEng.DEBUG_CES_BREAKPOINTS:
return on_breakpoint_modified(args)
elif args[0] == DbgEng.DEBUG_CES_RADIX:
util.set_convenience_variable('output-radix', args[1])
return DbgEng.DEBUG_STATUS_GO
elif args[0] == DbgEng.DEBUG_CES_EXECUTION_STATUS:
if args[1] & DbgEng.DEBUG_STATUS_INSIDE_WAIT:
return DbgEng.DEBUG_STATUS_GO
if args[1] == DbgEng.DEBUG_STATUS_BREAK:
return on_stop(args)
else:
return on_cont(args)
return DbgEng.DEBUG_STATUS_GO
def on_debuggee_changed(*args):
#print("ON_DEBUGGEE_CHANGED")
trace = commands.STATE.trace
if trace is None:
return
if args[1] == DbgEng.DEBUG_CDS_REGISTERS:
on_register_changed(args[0][1])
#if args[1] == DbgEng.DEBUG_CDS_DATA:
# on_memory_changed(args[0][1])
return DbgEng.DEBUG_STATUS_GO
def on_session_status_changed(*args):
#print("ON_STATUS_CHANGED")
trace = commands.STATE.trace
if trace is None:
return
if args[0] == DbgEng.DEBUG_SESSION_ACTIVE or args[0] == DbgEng.DEBUG_SSESION_REBOOT:
with commands.STATE.client.batch():
with trace.open_tx("New Process {}".format(util.selected_process())):
commands.put_processes()
return DbgEng.DEBUG_STATUS_GO
def on_symbol_state_changed(*args):
#print("ON_SYMBOL_STATE_CHANGED")
trace = commands.STATE.trace
if trace is None:
return
if args[0] == 1 or args[0] == 2:
PROC_STATE[proc].modules = True
return DbgEng.DEBUG_STATUS_GO
def on_system_error(*args):
print("ON_SYSTEM_ERROR")
print(hex(args[0]))
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.open_tx("New Process {}".format(util.selected_process())):
commands.put_processes()
return DbgEng.DEBUG_STATUS_BREAK
def on_new_process(*args):
#print("ON_NEW_PROCESS")
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.open_tx("New Process {}".format(util.selected_process())):
commands.put_processes()
return DbgEng.DEBUG_STATUS_BREAK
def on_process_selected():
#print("PROCESS_SELECTED")
proc = util.selected_process()
if proc not in PROC_STATE:
return
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.open_tx("Process {} selected".format(proc)):
PROC_STATE[proc].record()
commands.activate()
def on_process_deleted(*args):
#print("ON_PROCESS_DELETED")
proc = args[0]
on_exited(proc)
if proc in PROC_STATE:
del PROC_STATE[proc]
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.open_tx("Process {} deleted".format(proc)):
commands.put_processes() # TODO: Could just delete the one....
return DbgEng.DEBUG_STATUS_BREAK
def on_threads_changed(*args):
#print("ON_THREADS_CHANGED")
proc = util.selected_process()
if proc not in PROC_STATE:
return DbgEng.DEBUG_STATUS_GO
PROC_STATE[proc].threads = True
return DbgEng.DEBUG_STATUS_GO
def on_thread_selected(*args):
#print("THREAD_SELECTED")
nthrd = args[0][1]
nproc = util.selected_process()
if nproc not in PROC_STATE:
return
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.open_tx("Thread {}.{} selected".format(nproc, nthrd)):
PROC_STATE[nproc].record()
commands.activate()
def on_register_changed(regnum):
#print("REGISTER_CHANGED")
proc = util.selected_process()
if proc not in PROC_STATE:
return
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.open_tx("Register {} changed".format(regnum)):
commands.putreg()
commands.activate()
def on_cont(*args):
proc = util.selected_process()
if proc not in PROC_STATE:
return
trace = commands.STATE.trace
if trace is None:
return
state = PROC_STATE[proc]
with commands.STATE.client.batch():
with trace.open_tx("Continued"):
state.record_continued()
return DbgEng.DEBUG_STATUS_GO
def on_stop(*args):
proc = util.selected_process()
if proc not in PROC_STATE:
print("not in state")
return
trace = commands.STATE.trace
if trace is None:
print("no trace")
return
state = PROC_STATE[proc]
state.visited.clear()
with commands.STATE.client.batch():
with trace.open_tx("Stopped"):
state.record("Stopped")
commands.put_event_thread()
commands.activate()
def on_exited(proc):
if proc not in PROC_STATE:
print("not in state")
return
trace = commands.STATE.trace
if trace is None:
return
state = PROC_STATE[proc]
state.visited.clear()
exit_code = util.GetExitCode()
description = "Exited with code {}".format(exit_code)
with commands.STATE.client.batch():
with trace.open_tx(description):
state.record_exited(exit_code, description)
commands.activate()
def on_modules_changed(*args):
#print("ON_MODULES_CHANGED")
proc = util.selected_process()
if proc not in PROC_STATE:
return DbgEng.DEBUG_STATUS_GO
PROC_STATE[proc].modules = True
return DbgEng.DEBUG_STATUS_GO
def on_breakpoint_created(bp):
proc = util.selected_process()
if proc not in PROC_STATE:
return
PROC_STATE[proc].breaks = True
trace = commands.STATE.trace
if trace is None:
return
ibpath = commands.PROC_BREAKS_PATTERN.format(procnum=proc)
with commands.STATE.client.batch():
with trace.open_tx("Breakpoint {} created".format(bp.GetId())):
ibobj = trace.create_object(ibpath)
# Do not use retain_values or it'll remove other locs
commands.put_single_breakpoint(bp, ibobj, proc, [])
ibobj.insert()
def on_breakpoint_modified(*args):
#print("BREAKPOINT_MODIFIED")
proc = util.selected_process()
if proc not in PROC_STATE:
return
PROC_STATE[proc].breaks = True
trace = commands.STATE.trace
if trace is None:
return
ibpath = commands.PROC_BREAKS_PATTERN.format(procnum=proc)
ibobj = trace.create_object(ibpath)
bpid = args[0][1]
try:
bp = dbg()._control.GetBreakpointById(bpid)
except exception.E_NOINTERFACE_Error:
dbg().breakpoints._remove_stale(bpid)
return on_breakpoint_deleted(bpid)
return on_breakpoint_created(bp)
def on_breakpoint_deleted(bpid):
proc = util.selected_process()
if proc not in PROC_STATE:
return
PROC_STATE[proc].breaks = True
trace = commands.STATE.trace
if trace is None:
return
bpath = commands.PROC_BREAK_PATTERN.format(procnum=proc, breaknum=bpid)
with commands.STATE.client.batch():
with trace.open_tx("Breakpoint {} deleted".format(bpid)):
trace.proxy_object_path(bpath).remove(tree=True)
def on_breakpoint_hit(*args):
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.open_tx("New Process {}".format(util.selected_process())):
commands.put_processes()
return DbgEng.DEBUG_STATUS_GO
def on_exception(*args):
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.open_tx("New Process {}".format(util.selected_process())):
commands.put_processes()
return DbgEng.DEBUG_STATUS_GO
def install_hooks():
if HOOK_STATE.installed:
return
HOOK_STATE.installed = True
events = dbg().events
events.engine_state(handler=on_state_changed)
events.debuggee_state(handler=on_debuggee_changed)
events.session_status(handler=on_session_status_changed)
events.symbol_state(handler=on_symbol_state_changed)
events.system_error(handler=on_system_error)
events.create_process(handler=on_new_process)
events.exit_process(handler=on_process_deleted)
events.create_thread(handler=on_threads_changed)
events.exit_thread(handler=on_threads_changed)
events.module_load(handler=on_modules_changed)
events.unload_module(handler=on_modules_changed)
#events.breakpoint(handler=on_breakpoint_hit)
#events.exception(handler=on_exception)
def remove_hooks():
if not HOOK_STATE.installed:
return
HOOK_STATE.installed = False
dbg()._reset_callbacks()
def enable_current_process():
proc = util.selected_process()
PROC_STATE[proc] = ProcessState()
def disable_current_process():
proc = util.selected_process()
if proc in PROC_STATE:
# Silently ignore already disabled
del PROC_STATE[proc]
def dbg():
return util.get_debugger()

View file

@ -0,0 +1,522 @@
## ###
# 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.
##
from concurrent.futures import Future, ThreadPoolExecutor
import re
import sys
from ghidratrace import sch
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
from pybag import pydbg
from pybag.dbgeng import core as DbgEng
from . import util, commands
from contextlib import redirect_stdout
from io import StringIO
REGISTRY = MethodRegistry(ThreadPoolExecutor(max_workers=1))
def extre(base, ext):
return re.compile(base.pattern + ext)
AVAILABLE_PATTERN = re.compile('Available\[(?P<pid>\\d*)\]')
WATCHPOINT_PATTERN = re.compile('Watchpoints\[(?P<watchnum>\\d*)\]')
BREAKPOINT_PATTERN = re.compile('Breakpoints\[(?P<breaknum>\\d*)\]')
BREAK_LOC_PATTERN = extre(BREAKPOINT_PATTERN, '\[(?P<locnum>\\d*)\]')
PROCESS_PATTERN = re.compile('Processes\[(?P<procnum>\\d*)\]')
PROC_BREAKS_PATTERN = extre(PROCESS_PATTERN, '\.Breakpoints')
PROC_BREAKBPT_PATTERN = extre(PROC_BREAKS_PATTERN, '\[(?P<breaknum>\\d*)\]')
ENV_PATTERN = extre(PROCESS_PATTERN, '\.Environment')
THREADS_PATTERN = extre(PROCESS_PATTERN, '\.Threads')
THREAD_PATTERN = extre(THREADS_PATTERN, '\[(?P<tnum>\\d*)\]')
STACK_PATTERN = extre(THREAD_PATTERN, '\.Stack')
FRAME_PATTERN = extre(STACK_PATTERN, '\[(?P<level>\\d*)\]')
REGS_PATTERN0 = extre(THREAD_PATTERN, '.Registers')
REGS_PATTERN = extre(FRAME_PATTERN, '.Registers')
MEMORY_PATTERN = extre(PROCESS_PATTERN, '\.Memory')
MODULES_PATTERN = extre(PROCESS_PATTERN, '\.Modules')
def find_availpid_by_pattern(pattern, object, err_msg):
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
pid = int(mat['pid'])
return pid
def find_availpid_by_obj(object):
return find_availpid_by_pattern(AVAILABLE_PATTERN, object, "an Available")
def find_proc_by_num(id):
if id != util.selected_process():
util.select_process(id)
return util.selected_process()
def find_proc_by_pattern(object, pattern, err_msg):
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
procnum = int(mat['procnum'])
return find_proc_by_num(procnum)
def find_proc_by_obj(object):
return find_proc_by_pattern(object, PROCESS_PATTERN, "an Process")
def find_proc_by_procbreak_obj(object):
return find_proc_by_pattern(object, PROC_BREAKS_PATTERN,
"a BreakpointLocationContainer")
def find_proc_by_procwatch_obj(object):
return find_proc_by_pattern(object, PROC_WATCHES_PATTERN,
"a WatchpointContainer")
def find_proc_by_env_obj(object):
return find_proc_by_pattern(object, ENV_PATTERN, "an Environment")
def find_proc_by_threads_obj(object):
return find_proc_by_pattern(object, THREADS_PATTERN, "a ThreadContainer")
def find_proc_by_mem_obj(object):
return find_proc_by_pattern(object, MEMORY_PATTERN, "a Memory")
def find_proc_by_modules_obj(object):
return find_proc_by_pattern(object, MODULES_PATTERN, "a ModuleContainer")
def find_thread_by_num(id):
if id != util.selected_thread():
util.select_thread(id)
return util.selected_thread()
def find_thread_by_pattern(pattern, object, err_msg):
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
pnum = int(mat['procnum'])
tnum = int(mat['tnum'])
find_proc_by_num(pnum)
return find_thread_by_num(tnum)
def find_thread_by_obj(object):
return find_thread_by_pattern(THREAD_PATTERN, object, "a Thread")
def find_thread_by_stack_obj(object):
return find_thread_by_pattern(STACK_PATTERN, object, "a Stack")
def find_thread_by_regs_obj(object):
return find_thread_by_pattern(REGS_PATTERN0, object, "a RegisterValueContainer")
def find_frame_by_level(level):
return dbg().backtrace_list()[level]
def find_frame_by_pattern(pattern, object, err_msg):
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
pnum = int(mat['procnum'])
tnum = int(mat['tnum'])
level = int(mat['level'])
find_proc_by_num(pnum)
find_thread_by_num(tnum)
return find_frame_by_level(level)
def find_frame_by_obj(object):
return find_frame_by_pattern(FRAME_PATTERN, object, "a StackFrame")
def find_bpt_by_number(breaknum):
try:
bp = dbg()._control.GetBreakpointById(breaknum)
return bp
except exception.E_NOINTERFACE_Error:
raise KeyError(f"Breakpoints[{breaknum}] does not exist")
def find_bpt_by_pattern(pattern, object, err_msg):
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
breaknum = int(mat['breaknum'])
return find_bpt_by_number(breaknum)
def find_bpt_by_obj(object):
return find_bpt_by_pattern(PROC_BREAKBPT_PATTERN, object, "a BreakpointSpec")
shared_globals = dict()
@REGISTRY.method
def execute(cmd: str, to_string: bool=False):
"""Execute a CLI command."""
#print("***{}***".format(cmd))
#sys.stderr.flush()
#sys.stdout.flush()
if to_string:
data = StringIO()
with redirect_stdout(data):
exec("{}".format(cmd), shared_globals)
return data.getvalue()
else:
exec("{}".format(cmd), shared_globals)
@REGISTRY.method
def evaluate(expr: str):
"""Execute a CLI command."""
return str(eval("{}".format(expr), shared_globals))
@REGISTRY.method(action='refresh')
def refresh_available(node: sch.Schema('AvailableContainer')):
"""List processes on pydbg's host system."""
with commands.open_tracked_tx('Refresh Available'):
commands.ghidra_trace_put_available()
@REGISTRY.method(action='refresh')
def refresh_breakpoints(node: sch.Schema('BreakpointContainer')):
"""
Refresh the list of breakpoints (including locations for the current
process).
"""
with commands.open_tracked_tx('Refresh Breakpoints'):
commands.ghidra_trace_put_breakpoints()
@REGISTRY.method(action='refresh')
def refresh_processes(node: sch.Schema('ProcessContainer')):
"""Refresh the list of processes."""
with commands.open_tracked_tx('Refresh Processes'):
commands.ghidra_trace_put_threads()
@REGISTRY.method(action='refresh')
def refresh_proc_breakpoints(node: sch.Schema('BreakpointLocationContainer')):
"""
Refresh the breakpoint locations for the process.
In the course of refreshing the locations, the breakpoint list will also be
refreshed.
"""
with commands.open_tracked_tx('Refresh Breakpoint Locations'):
commands.ghidra_trace_put_breakpoints()
@REGISTRY.method(action='refresh')
def refresh_environment(node: sch.Schema('Environment')):
"""Refresh the environment descriptors (arch, os, endian)."""
with commands.open_tracked_tx('Refresh Environment'):
commands.ghidra_trace_put_environment()
@REGISTRY.method(action='refresh')
def refresh_threads(node: sch.Schema('ThreadContainer')):
"""Refresh the list of threads in the process."""
with commands.open_tracked_tx('Refresh Threads'):
commands.ghidra_trace_put_threads()
@REGISTRY.method(action='refresh')
def refresh_stack(node: sch.Schema('Stack')):
"""Refresh the backtrace for the thread."""
tnum = find_thread_by_stack_obj(node)
with commands.open_tracked_tx('Refresh Stack'):
commands.ghidra_trace_put_frames()
@REGISTRY.method(action='refresh')
def refresh_registers(node: sch.Schema('RegisterValueContainer')):
"""Refresh the register values for the frame."""
tnum = find_thread_by_regs_obj(node)
with commands.open_tracked_tx('Refresh Registers'):
commands.ghidra_trace_putreg()
@REGISTRY.method(action='refresh')
def refresh_mappings(node: sch.Schema('Memory')):
"""Refresh the list of memory regions for the process."""
with commands.open_tracked_tx('Refresh Memory Regions'):
commands.ghidra_trace_put_regions()
@REGISTRY.method(action='refresh')
def refresh_modules(node: sch.Schema('ModuleContainer')):
"""
Refresh the modules and sections list for the process.
This will refresh the sections for all modules, not just the selected one.
"""
with commands.open_tracked_tx('Refresh Modules'):
commands.ghidra_trace_put_modules()
@REGISTRY.method(action='activate')
def activate_process(process: sch.Schema('Process')):
"""Switch to the process."""
find_proc_by_obj(process)
@REGISTRY.method(action='activate')
def activate_thread(thread: sch.Schema('Thread')):
"""Switch to the thread."""
find_thread_by_obj(thread)
@REGISTRY.method(action='activate')
def activate_frame(frame: sch.Schema('StackFrame')):
"""Select the frame."""
find_frame_by_obj(frame)
@REGISTRY.method(action='delete')
def remove_process(process: sch.Schema('Process')):
"""Remove the process."""
find_proc_by_obj(process)
dbg().detach()
@REGISTRY.method(action='connect')
def target(process: sch.Schema('Process'), spec: str):
"""Connect to a target machine or process."""
find_proc_by_obj(process)
dbg().attach(spec)
@REGISTRY.method(action='attach')
def attach_obj(target: sch.Schema('Attachable')):
"""Attach the process to the given target."""
pid = find_availpid_by_obj(target)
dbg().attach(pid)
@REGISTRY.method(action='attach')
def attach_pid(pid: int):
"""Attach the process to the given target."""
dbg().attach(pid)
@REGISTRY.method(action='attach')
def attach_name(process: sch.Schema('Process'), name: str):
"""Attach the process to the given target."""
dbg().atach(name)
@REGISTRY.method
def detach(process: sch.Schema('Process')):
"""Detach the process's target."""
dbg().detach()
@REGISTRY.method(action='launch')
def launch_loader(
file: ParamDesc(str, display='File'),
args: ParamDesc(str, display='Arguments')=''):
"""
Start a native process with the given command line, stopping at the ntdll initial breakpoint.
"""
command = file
if args != None:
command += " "+args
commands.ghidra_trace_create(command=file, start_trace=False)
@REGISTRY.method(action='launch')
def launch(
timeout: ParamDesc(int, display='Timeout'),
file: ParamDesc(str, display='File'),
args: ParamDesc(str, display='Arguments')=''):
"""
Run a native process with the given command line.
"""
command = file
if args != None:
command += " "+args
commands.ghidra_trace_create(command, initial_break=False, timeout=timeout, start_trace=False)
@REGISTRY.method
def kill(process: sch.Schema('Process')):
"""Kill execution of the process."""
dbg().terminate()
@REGISTRY.method(name='continue', action='resume')
def _continue(process: sch.Schema('Process')):
"""Continue execution of the process."""
dbg().go()
@REGISTRY.method
def interrupt():
"""Interrupt the execution of the debugged program."""
dbg()._control.SetInterrupt(DbgEng.DEBUG_INTERRUPT_ACTIVE)
@REGISTRY.method(action='step_into')
def step_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
"""Step on instruction exactly."""
find_thread_by_obj(thread)
dbg().stepi(n)
@REGISTRY.method(action='step_over')
def step_over(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
"""Step one instruction, but proceed through subroutine calls."""
find_thread_by_obj(thread)
dbg().stepo(n)
@REGISTRY.method(action='step_out')
def step_out(thread: sch.Schema('Thread')):
"""Execute until the current stack frame returns."""
find_thread_by_obj(thread)
dbg().stepout()
@REGISTRY.method(action='step_to')
def step_to(thread: sch.Schema('Thread'), address: Address, max=None):
"""Continue execution up to the given address."""
find_thread_by_obj(thread)
return dbg().stepto(address.offset, max)
@REGISTRY.method(action='break_sw_execute')
def break_address(process: sch.Schema('Process'), address: Address):
"""Set a breakpoint."""
find_proc_by_obj(process)
dbg().bp(expr=address.offset)
@REGISTRY.method(action='break_sw_execute')
def break_expression(expression: str):
"""Set a breakpoint."""
# TODO: Escape?
dbg().bp(expr=expression)
@REGISTRY.method(action='break_hw_execute')
def break_hw_address(process: sch.Schema('Process'), address: Address):
"""Set a hardware-assisted breakpoint."""
find_proc_by_obj(process)
dbg().ba(expr=address.offset)
@REGISTRY.method(action='break_hw_execute')
def break_hw_expression(expression: str):
"""Set a hardware-assisted breakpoint."""
dbg().ba(expr=expression)
@REGISTRY.method(action='break_read')
def break_read_range(process: sch.Schema('Process'), range: AddressRange):
"""Set a read watchpoint."""
find_proc_by_obj(process)
dbg().ba(expr=range.min, size=range.length(), access=DbgEng.DEBUG_BREAK_READ)
@REGISTRY.method(action='break_read')
def break_read_expression(expression: str):
"""Set a read watchpoint."""
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ)
@REGISTRY.method(action='break_write')
def break_write_range(process: sch.Schema('Process'), range: AddressRange):
"""Set a watchpoint."""
find_proc_by_obj(process)
dbg().ba(expr=range.min, size=range.length(), access=DbgEng.DEBUG_BREAK_WRITE)
@REGISTRY.method(action='break_write')
def break_write_expression(expression: str):
"""Set a watchpoint."""
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_WRITE)
@REGISTRY.method(action='break_access')
def break_access_range(process: sch.Schema('Process'), range: AddressRange):
"""Set an access watchpoint."""
find_proc_by_obj(process)
dbg().ba(expr=range.min, size=range.length(), access=DbgEng.DEBUG_BREAK_READ|DbgEng.DEBUG_BREAK_WRITE)
@REGISTRY.method(action='break_access')
def break_access_expression(expression: str):
"""Set an access watchpoint."""
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ|DbgEng.DEBUG_BREAK_WRITE)
@REGISTRY.method(action='toggle')
def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
"""Toggle a breakpoint."""
bpt = find_bpt_by_obj(breakpoint)
if enabled:
dbg().be(bpt.GetId())
else:
dbg().bd(bpt.GetId())
@REGISTRY.method(action='delete')
def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
"""Delete a breakpoint."""
bpt = find_bpt_by_obj(breakpoint)
dbg().cmd("bc {}".format(bpt.GetId()))
@REGISTRY.method
def read_mem(process: sch.Schema('Process'), range: AddressRange):
"""Read memory."""
nproc = find_proc_by_obj(process)
offset_start = process.trace.memory_mapper.map_back(
nproc, Address(range.space, range.min))
with commands.open_tracked_tx('Read Memory'):
dbg().read(range.min, range.length())
@REGISTRY.method
def write_mem(process: sch.Schema('Process'), address: Address, data: bytes):
"""Write memory."""
nproc = find_proc_by_obj(process)
offset = process.trace.memory_mapper.map_back(nproc, address)
dbg().write(offset, data)
@REGISTRY.method
def write_reg(frame: sch.Schema('Frame'), name: str, value: bytes):
"""Write a register."""
util.select_frame()
nproc = pydbg.selected_process()
dbg().reg._set_register(name, value)
def dbg():
return util.get_debugger()

View file

@ -0,0 +1,434 @@
<context>
<schema name="Session" elementResync="NEVER" attributeResync="NEVER">
<interface name="Access" />
<interface name="Attacher" />
<interface name="Interpreter" />
<interface name="Interruptible" />
<interface name="Launcher" />
<interface name="ActiveScope" />
<interface name="EventScope" />
<interface name="FocusScope" />
<interface name="Aggregate" />
<element schema="VOID" />
<attribute name="Processes" schema="ProcessContainer" required="yes" fixed="yes" />
<attribute name="Available" schema="AvailableContainer" required="yes" fixed="yes" />
<attribute name="_accessible" schema="BOOL" required="yes" hidden="yes" />
<attribute name="_supported_attach_kinds" schema="SET_ATTACH_KIND" required="yes" hidden="yes" />
<attribute name="_prompt" schema="STRING" required="yes" hidden="yes" />
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" hidden="yes" />
<attribute name="_event_thread" schema="OBJECT" hidden="yes" />
<attribute name="_focus" schema="Selectable" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Selectable" elementResync="NEVER" attributeResync="NEVER">
<element schema="OBJECT" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="BreakpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointLocationContainer" />
<interface name="BreakpointSpecContainer" />
<element schema="BreakpointSpec" />
<attribute name="_supported_breakpoint_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="AvailableContainer" canonical="yes" elementResync="ALWAYS" attributeResync="NEVER">
<interface name="Configurable" />
<element schema="Attachable" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_base" schema="INT" />
<attribute schema="VOID" />
</schema>
<schema name="ProcessContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Configurable" />
<element schema="Process" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_base" schema="INT" />
<attribute schema="VOID" />
</schema>
<schema name="BreakpointSpec" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointSpec" />
<interface name="BreakpointLocation" />
<interface name="Deletable" />
<interface name="Togglable" />
<element schema="VOID" />
<attribute name="_container" schema="BreakpointContainer" required="yes" hidden="yes" />
<attribute name="_expression" schema="STRING" required="yes" hidden="yes" />
<attribute name="_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_spec" schema="BreakpointSpec" />
<attribute name="_range" schema="RANGE" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" />
<attribute name="Commands" schema="STRING" />
<attribute name="Condition" schema="STRING" />
<attribute name="Hit Count" schema="INT" />
<attribute name="Ignore Count" schema="INT" />
<attribute name="Pending" schema="BOOL" />
<attribute name="Silent" schema="BOOL" />
<attribute name="Temporary" schema="BOOL" />
<attribute schema="VOID" />
</schema>
<schema name="Attachable" elementResync="NEVER" attributeResync="NEVER">
<interface name="Attachable" />
<element schema="VOID" />
<attribute name="_pid" schema="LONG" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Process" elementResync="NEVER" attributeResync="NEVER">
<interface name="Process" />
<interface name="Aggregate" />
<interface name="ExecutionStateful" />
<interface name="Attacher" />
<interface name="Deletable" />
<interface name="Detachable" />
<interface name="Killable" />
<interface name="Launcher" />
<interface name="Resumable" />
<interface name="Steppable" />
<interface name="Interruptible" />
<element schema="VOID" />
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
<attribute name="Breakpoints" schema="BreakpointContainer" required="yes" fixed="yes" />
<!-- attribute name="Breakpoints" schema="BreakpointLocationContainer" required="yes" fixed="yes" /-->
<attribute name="_exit_code" schema="LONG" />
<attribute name="Environment" schema="Environment" required="yes" fixed="yes" />
<attribute name="Memory" schema="Memory" required="yes" fixed="yes" />
<attribute name="Modules" schema="ModuleContainer" required="yes" fixed="yes" />
<attribute name="_pid" schema="LONG" hidden="yes" />
<attribute name="_state" schema="EXECUTION_STATE" required="yes" hidden="yes" />
<attribute name="_supported_attach_kinds" schema="SET_ATTACH_KIND" required="yes" hidden="yes" />
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" hidden="yes" />
<attribute name="_supported_step_kinds" schema="SET_STEP_KIND" required="yes" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Environment" elementResync="NEVER" attributeResync="NEVER">
<interface name="Environment" />
<element schema="VOID" />
<attribute name="arch" schema="STRING" />
<attribute name="os" schema="STRING" />
<attribute name="endian" schema="STRING" />
<attribute name="_arch" schema="STRING" hidden="yes" />
<attribute name="_debugger" schema="STRING" hidden="yes" />
<attribute name="_os" schema="STRING" hidden="yes" />
<attribute name="_endian" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="ModuleContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<interface name="ModuleContainer" />
<element schema="Module" />
<attribute name="_supports_synthetic_modules" schema="BOOL" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Memory" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Memory" />
<element schema="MemoryRegion" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="BreakpointLocation" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointLocation" />
<element schema="VOID" />
<attribute name="_range" schema="RANGE" hidden="yes" />
<attribute name="_spec" schema="BreakpointSpec" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="BreakpointLocationContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointLocationContainer" />
<element schema="BreakpointLocation" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="ThreadContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Configurable" />
<element schema="Thread" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_base" schema="INT" />
<attribute schema="VOID" />
</schema>
<schema name="Method" elementResync="NEVER" attributeResync="NEVER">
<interface name="Method" />
<element schema="VOID" />
<attribute name="_display" schema="STRING" required="yes" fixed="yes" hidden="yes" />
<attribute name="_return_type" schema="TYPE" required="yes" fixed="yes" hidden="yes" />
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" fixed="yes" hidden="yes" />
<attribute schema="VOID" fixed="yes" hidden="yes" />
</schema>
<schema name="Thread" elementResync="NEVER" attributeResync="NEVER">
<interface name="Thread" />
<interface name="ExecutionStateful" />
<interface name="Steppable" />
<interface name="Aggregate" />
<element schema="VOID" />
<attribute name="Stack" schema="Stack" required="yes" fixed="yes" />
<attribute name="Registers" schema="RegisterValueContainer" required="yes" fixed="yes" />
<attribute name="_tid" schema="LONG" hidden="yes" />
<attribute name="_state" schema="EXECUTION_STATE" required="yes" hidden="yes" />
<attribute name="_supported_step_kinds" schema="SET_STEP_KIND" required="yes" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="Advance" schema="Method" required="yes" fixed="yes" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Module" elementResync="NEVER" attributeResync="NEVER">
<interface name="Module" />
<element schema="VOID" />
<attribute name="Sections" schema="SectionContainer" required="yes" fixed="yes" />
<attribute name="Symbols" schema="SymbolContainer" required="yes" fixed="yes" />
<attribute name="range" schema="RANGE" />
<attribute name="module name" schema="STRING" />
<attribute name="_module_name" schema="STRING" required="yes" hidden="yes" />
<attribute name="_range" schema="RANGE" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="MemoryRegion" elementResync="NEVER" attributeResync="NEVER">
<interface name="MemoryRegion" />
<element schema="VOID" />
<attribute name="_offset" schema="LONG" required="yes" fixed="yes" hidden="yes" />
<attribute name="_objfile" schema="STRING" required="yes" fixed="yes" hidden="yes" />
<attribute name="_readable" schema="BOOL" required="yes" hidden="yes" />
<attribute name="_writable" schema="BOOL" required="yes" hidden="yes" />
<attribute name="_executable" schema="BOOL" required="yes" hidden="yes" />
<attribute name="_range" schema="RANGE" required="yes" hidden="yes" />
<attribute name="_memory" schema="Memory" required="yes" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="SectionContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="SectionContainer" />
<element schema="Section" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Stack" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Stack" />
<element schema="StackFrame" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="SymbolContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<interface name="SymbolNamespace" />
<element schema="Symbol" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Symbol" elementResync="NEVER" attributeResync="NEVER">
<interface name="Symbol" />
<element schema="VOID" />
<attribute name="_size" schema="LONG" fixed="yes" hidden="yes" />
<attribute name="_namespace" schema="SymbolContainer" required="yes" fixed="yes" hidden="yes" />
<attribute name="_data_type" schema="DATA_TYPE" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ADDRESS" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_bpt" schema="STRING" />
<attribute schema="VOID" />
</schema>
<schema name="StackFrame" elementResync="NEVER" attributeResync="NEVER">
<interface name="StackFrame" />
<interface name="Aggregate" />
<element schema="VOID" />
<attribute name="_function" schema="STRING" hidden="yes" />
<attribute name="Registers" schema="RegisterValueContainer" required="yes" fixed="yes" />
<attribute name="_pc" schema="ADDRESS" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Section" elementResync="NEVER" attributeResync="NEVER">
<interface name="Section" />
<element schema="VOID" />
<attribute name="range" schema="RANGE" />
<attribute name="_module" schema="Module" required="yes" fixed="yes" hidden="yes" />
<attribute name="_range" schema="RANGE" required="yes" fixed="yes" />
<attribute name="_offset" schema="INT" required="no" fixed="yes" />
<attribute name="_objfile" schema="STRING" required="no" fixed="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="RegisterValueContainer" canonical="yes" elementResync="ONCE" attributeResync="ONCE">
<interface name="RegisterContainer" />
<interface name="RegisterBank" />
<element schema="RegisterValue" />
<attribute name="General Purpose Registers" schema="RegisterBank" />
<attribute name="Floating Point Registers" schema="RegisterBank" />
<attribute name="Advanced Vector Extensions" schema="RegisterBank" />
<attribute name="Memory Protection Extensions" schema="RegisterBank" />
<attribute name="_descriptions" schema="RegisterValueContainer" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<interface name="RegisterBank" />
<element schema="RegisterValue" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="RegisterValue" elementResync="NEVER" attributeResync="NEVER">
<interface name="Register" />
<element schema="VOID" />
<attribute name="_container" schema="OBJECT" required="yes" fixed="yes" hidden="yes" />
<attribute name="_length" schema="INT" required="yes" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
</context>

View file

@ -0,0 +1,260 @@
## ###
# 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.
##
from collections import namedtuple
import os
import re
import sys
from ctypes import *
from pybag import pydbg
from pybag.dbgeng import core as DbgEng
from pybag.dbgeng import exception
from pybag.dbgeng import util as DbgUtil
base = pydbg.DebuggerBase()
DbgVersion = namedtuple('DbgVersion', ['full', 'major', 'minor'])
def _compute_pydbg_ver():
blurb = "" #base._control.GetActualProcessorType()
full = ""
major = 0
minor = 0
return DbgVersion(full, int(major), int(minor))
DBG_VERSION = _compute_pydbg_ver()
def get_debugger():
return base
def get_target():
return 0 #get_debugger()._systems.GetCurrentSystemId()
def get_inst(addr):
dbg = get_debugger()
ins = DbgUtil.disassemble_instruction(dbg.bitness(), addr, dbg.read(addr, 15))
return str(ins)
def get_inst_sz(addr):
dbg = get_debugger()
ins = DbgUtil.disassemble_instruction(dbg.bitness(), addr, dbg.read(addr, 15))
return str(ins.size)
def get_breakpoints():
ids = [bpid for bpid in get_debugger().breakpoints]
offset_set = []
expr_set = []
prot_set = []
width_set = []
stat_set = []
for bpid in ids:
try:
bp = get_debugger()._control.GetBreakpointById(bpid)
except exception.E_NOINTERFACE_Error:
continue
if bp.GetFlags() & DbgEng.DEBUG_BREAKPOINT_DEFERRED:
offset = "[Deferred]"
expr = bp.GetOffsetExpression()
else:
offset = "%016x" % bp.GetOffset()
expr = get_debugger().get_name_by_offset(bp.GetOffset())
if bp.GetType()[0] == DbgEng.DEBUG_BREAKPOINT_DATA:
width, prot = bp.GetDataParameters()
width = ' sz={}'.format(str(width))
prot = {4: 'type=x', 3: 'type=rw', 2: 'type=w', 1: 'type=r'}[prot]
else:
width = ''
prot = ''
if bp.GetFlags() & DbgEng.DEBUG_BREAKPOINT_ENABLED:
status = 'enabled'
else:
status = 'disabled'
offset_set.append(offset)
expr_set.append(expr)
prot_set.append(prot)
width_set.append(width)
stat_set.append(status)
return zip(offset_set, expr_set, prot_set, width_set, stat_set)
def selected_process():
try:
return get_debugger()._systems.GetCurrentProcessId()
#return current_process
except Exception:
return None
def selected_thread():
try:
return get_debugger()._systems.GetCurrentThreadId()
except Exception:
return None
def selected_frame():
return 0 #selected_thread().GetSelectedFrame()
def select_process(id: int):
return get_debugger()._systems.SetCurrentProcessId(id)
def select_thread(id: int):
return get_debugger()._systems.SetCurrentThreadId(id)
def select_frame(id: int):
#TODO: this needs to be fixed
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):
ctrl = get_debugger()._control._ctrl
ctrl.SetExpressionSyntax(1)
value = DbgEng._DEBUG_VALUE()
index = c_ulong()
if type == None:
type = DbgEng.DEBUG_VALUE_INT64
hr = ctrl.Evaluate(Expression="{}".format(expr).encode(),DesiredType=type,Value=byref(value),RemainderIndex=byref(index))
exception.check_err(hr)
if type == DbgEng.DEBUG_VALUE_INT8:
return value.u.I8
if type == DbgEng.DEBUG_VALUE_INT16:
return value.u.I16
if type == DbgEng.DEBUG_VALUE_INT32:
return value.u.I32
if type == DbgEng.DEBUG_VALUE_INT64:
return value.u.I64.I64
if type == DbgEng.DEBUG_VALUE_FLOAT32:
return value.u.F32
if type == DbgEng.DEBUG_VALUE_FLOAT64:
return value.u.F64
if type == DbgEng.DEBUG_VALUE_FLOAT80:
return value.u.F80Bytes
if type == DbgEng.DEBUG_VALUE_FLOAT82:
return value.u.F82Bytes
if type == DbgEng.DEBUG_VALUE_FLOAT128:
return value.u.F128Bytes
def GetProcessIdsByIndex(count=0):
if count == 0:
try :
count = get_debugger()._systems.GetNumberProcesses()
except Exception:
count = 0
ids = (c_ulong * count)()
sysids = (c_ulong * count)()
if count != 0:
hr = get_debugger()._systems._sys.GetProcessIdsByIndex(0, count, ids, sysids)
exception.check_err(hr)
return (tuple(ids), tuple(sysids))
def GetCurrentProcessExecutableName():
dbg = get_debugger()
size = c_ulong()
exesize = c_ulong()
hr = dbg._systems._sys.GetCurrentProcessExecutableName(None, size, byref(exesize))
exception.check_err(hr)
buffer = create_string_buffer(exesize.value)
size = exesize
hr = dbg._systems._sys.GetCurrentProcessExecutableName(buffer, size, None)
exception.check_err(hr)
buffer = buffer[:size.value]
buffer = buffer.rstrip(b'\x00')
return buffer
def GetCurrentProcessPeb():
dbg = get_debugger()
offset = c_ulonglong()
hr = dbg._systems._sys.GetCurrentProcessPeb(byref(offset))
exception.check_err(hr)
return offset.value
def GetExitCode():
exit_code = c_ulong()
hr = get_debugger()._client._cli.GetExitCode(byref(exit_code))
return exit_code.value
def process_list(running=False):
"""process_list() -> list of all processes"""
dbg = get_debugger()
ids, sysids = GetProcessIdsByIndex()
pebs = []
names = []
try :
curid = dbg._systems.GetCurrentProcessId()
if running == False:
for id in ids:
dbg._systems.SetCurrentProcessId(id)
names.append(GetCurrentProcessExecutableName())
pebs.append(GetCurrentProcessPeb())
if running == False:
dbg._systems.SetCurrentProcessId(curid)
return zip(sysids, names, pebs)
except Exception:
pass
return zip(sysids)
def thread_list(running=False):
"""thread_list() -> list of all threads"""
dbg = get_debugger()
try :
ids, sysids = dbg._systems.GetThreadIdsByIndex()
except Exception:
return zip([])
tebs = []
syms = []
curid = dbg._systems.GetCurrentThreadId()
if running == False:
for id in ids:
dbg._systems.SetCurrentThreadId(id)
tebs.append(dbg._systems.GetCurrentThreadTeb())
addr = dbg.reg.get_pc()
syms.append(dbg.get_name_by_offset(addr))
if running == False:
dbg._systems.SetCurrentThreadId(curid)
return zip(sysids, tebs, syms)
return zip(sysids)
conv_map = {}
def get_convenience_variable(id):
#val = get_target().GetEnvironment().Get(id)
if id not in conv_map:
return "auto"
val = conv_map[id]
if val is None:
return "auto"
return val
def set_convenience_variable(id, value):
#env = get_target().GetEnvironment()
#return env.Set(id, value, True)
conv_map[id] = value

View file

@ -249,7 +249,7 @@ public class TraceRmiHandler {
}
public boolean isClosed() {
return socket.isClosed();
return socket.isClosed() && closed.isDone();
}
public void waitClosed() throws InterruptedException, ExecutionException {

View file

@ -52,6 +52,12 @@ public class DummyProc implements AutoCloseable {
if (platformExe.exists() && platformExe.getFile(false).canExecute()) {
return platformExe.getAbsolutePath();
}
platformExe = new ResourceFile(modRoot,
"build/exe/" + cmd + "/" + Platform.CURRENT_PLATFORM.getDirectoryName() + "/" +
cmd + ".exe");
if (platformExe.exists() && platformExe.getFile(false).canExecute()) {
return platformExe.getAbsolutePath();
}
}
}
catch (Exception e) {

View file

@ -82,4 +82,5 @@ integrationTest {
dependsOn { project(':Debugger-rmi-trace').assemblePyPackage }
dependsOn { project(':Debugger-agent-gdb').assemblePyPackage }
dependsOn { project(':Debugger-agent-lldb').assemblePyPackage }
dependsOn { project(':Debugger-agent-dbgeng').assemblePyPackage }
}

View file

@ -0,0 +1,424 @@
/* ###
* 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 agent.dbgeng.rmi;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.junit.Before;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteAsyncResult;
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteMethod;
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiAcceptor;
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler;
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiPlugin;
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
import ghidra.app.services.TraceRmiService;
import ghidra.dbg.testutil.DummyProc;
import ghidra.framework.Application;
import ghidra.framework.OperatingSystem;
import ghidra.framework.TestApplicationUtils;
import ghidra.framework.main.ApplicationLevelOnlyPlugin;
import ghidra.framework.model.DomainFile;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginsConfiguration;
import ghidra.framework.plugintool.util.PluginDescription;
import ghidra.framework.plugintool.util.PluginException;
import ghidra.framework.plugintool.util.PluginPackage;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRangeImpl;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectValue;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDebuggerGUITest {
/**
* Some features have to be disabled to avoid permissions issues in the test container. Namely,
* don't try to disable ASLR.
*/
public static final String PREAMBLE = """
from ghidradbg.commands import *
""";
// Connecting should be the first thing the script does, so use a tight timeout.
protected static final int CONNECT_TIMEOUT_MS = 3000;
protected static final int TIMEOUT_SECONDS = 300;
protected static final int QUIT_TIMEOUT_MS = 1000;
protected TraceRmiService traceRmi;
private Path pythonPath;
private Path outFile;
private Path errFile;
//@BeforeClass
public static void setupPython() throws Throwable {
new ProcessBuilder("gradle", "Debugger-agent-dbgeng:assemblePyPackage")
.directory(TestApplicationUtils.getInstallationDirectory())
.inheritIO()
.start()
.waitFor();
}
protected void setPythonPath(ProcessBuilder pb) throws IOException {
String sep =
OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS ? ";" : ":";
String rmiPyPkg = Application.getModuleSubDirectory("Debugger-rmi-trace",
"build/pypkg/src").getAbsolutePath();
String gdbPyPkg = Application.getModuleSubDirectory("Debugger-agent-dbgeng",
"build/pypkg/src").getAbsolutePath();
String add = rmiPyPkg + sep + gdbPyPkg;
pb.environment().compute("PYTHONPATH", (k, v) -> v == null ? add : (v + sep + add));
}
@Before
public void setupTraceRmi() throws Throwable {
traceRmi = addPlugin(tool, TraceRmiPlugin.class);
try {
pythonPath = Paths.get(DummyProc.which("python3"));
}
catch (RuntimeException e) {
pythonPath = Paths.get(DummyProc.which("python"));
}
outFile = Files.createTempFile("pydbgout", null);
errFile = Files.createTempFile("pydbgerr", null);
}
protected void addAllDebuggerPlugins() throws PluginException {
PluginsConfiguration plugConf = new PluginsConfiguration() {
@Override
protected boolean accepts(Class<? extends Plugin> pluginClass) {
return !ApplicationLevelOnlyPlugin.class.isAssignableFrom(pluginClass);
}
};
for (PluginDescription pd : plugConf
.getPluginDescriptions(PluginPackage.getPluginPackage("Debugger"))) {
addPlugin(tool, pd.getPluginClass());
}
}
protected static String addrToStringForPython(InetAddress address) {
if (address.isAnyLocalAddress()) {
return "127.0.0.1"; // Can't connect to 0.0.0.0 as such. Choose localhost.
}
return address.getHostAddress();
}
protected static String sockToStringForPython(SocketAddress address) {
if (address instanceof InetSocketAddress tcp) {
return addrToStringForPython(tcp.getAddress()) + ":" + tcp.getPort();
}
throw new AssertionError("Unhandled address type " + address);
}
protected record PythonResult(boolean timedOut, int exitCode, String stdout, String stderr) {
protected String handle() {
if (stderr.contains("Error") || (0 != exitCode && 1 != exitCode && 143 != exitCode)) {
throw new PythonError(exitCode, stdout, stderr);
}
return stdout;
}
}
protected record ExecInPython(Process python, CompletableFuture<PythonResult> future) {
}
@SuppressWarnings("resource") // Do not close stdin
protected ExecInPython execInPython(String script) throws IOException {
ProcessBuilder pb = new ProcessBuilder(pythonPath.toString(), "-i");
setPythonPath(pb);
// If commands come from file, Python will quit after EOF.
Msg.info(this, "outFile: " + outFile);
Msg.info(this, "errFile: " + errFile);
//pb.inheritIO();
pb.redirectInput(ProcessBuilder.Redirect.PIPE);
pb.redirectOutput(outFile.toFile());
pb.redirectError(errFile.toFile());
Process pyproc = pb.start();
OutputStream stdin = pyproc.getOutputStream();
stdin.write(script.getBytes());
stdin.flush();
//stdin.close();
return new ExecInPython(pyproc, CompletableFuture.supplyAsync(() -> {
try {
if (!pyproc.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
Msg.error(this, "Timed out waiting for Python");
pyproc.destroyForcibly();
pyproc.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS);
return new PythonResult(true, -1, Files.readString(outFile),
Files.readString(errFile));
}
Msg.info(this, "Python exited with code " + pyproc.exitValue());
return new PythonResult(false, pyproc.exitValue(), Files.readString(outFile),
Files.readString(errFile));
}
catch (Exception e) {
return ExceptionUtils.rethrow(e);
}
finally {
pyproc.destroyForcibly();
}
}));
}
public static class PythonError extends RuntimeException {
public final int exitCode;
public final String stdout;
public final String stderr;
public PythonError(int exitCode, String stdout, String stderr) {
super("""
exitCode=%d:
----stdout----
%s
----stderr----
%s
""".formatted(exitCode, stdout, stderr));
this.exitCode = exitCode;
this.stdout = stdout;
this.stderr = stderr;
}
}
protected String runThrowError(String script) throws Exception {
CompletableFuture<PythonResult> result = execInPython(script).future;
return result.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle();
}
protected record PythonAndHandler(ExecInPython exec, TraceRmiHandler handler)
implements AutoCloseable {
protected RemoteMethod getMethod(String name) {
return Objects.requireNonNull(handler.getMethods().get(name));
}
public void execute(String cmd) {
RemoteMethod execute = getMethod("execute");
execute.invoke(Map.of("cmd", cmd));
}
public RemoteAsyncResult executeAsync(String cmd) {
RemoteMethod execute = getMethod("execute");
return execute.invokeAsync(Map.of("cmd", cmd));
}
public String executeCapture(String expr) {
RemoteMethod execute = getMethod("evaluate");
return (String) execute.invoke(Map.of("expr", expr));
}
@Override
public void close() throws Exception {
Msg.info(this, "Cleaning up python");
exec.python().destroy();
try {
PythonResult r = exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
r.handle();
waitForPass(() -> assertTrue(handler.isClosed()));
}
finally {
exec.python.destroyForcibly();
}
}
}
protected PythonAndHandler startAndConnectPython(Function<String, String> scriptSupplier)
throws Exception {
TraceRmiAcceptor acceptor = traceRmi.acceptOne(null);
ExecInPython exec =
execInPython(scriptSupplier.apply(sockToStringForPython(acceptor.getAddress())));
acceptor.setTimeout(CONNECT_TIMEOUT_MS);
try {
TraceRmiHandler handler = acceptor.accept();
return new PythonAndHandler(exec, handler);
}
catch (SocketTimeoutException e) {
exec.python.destroyForcibly();
exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle();
throw e;
}
}
protected PythonAndHandler startAndConnectPython() throws Exception {
return startAndConnectPython(addr -> """
%s
ghidra_trace_connect('%s')
""".formatted(PREAMBLE, addr));
}
@SuppressWarnings("resource")
protected String runThrowError(Function<String, String> scriptSupplier)
throws Exception {
PythonAndHandler conn = startAndConnectPython(scriptSupplier);
PythonResult r = conn.exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
String stdout = r.handle();
waitForPass(() -> assertTrue(conn.handler.isClosed()));
return stdout;
}
protected void waitStopped() {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
waitForPass(() -> assertEquals("STOPPED", tb.objValue(proc, 0, "_state")));
waitTxDone();
}
protected void waitRunning() {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
waitForPass(() -> assertEquals("RUNNING", tb.objValue(proc, 0, "_state")));
waitTxDone();
}
protected String extractOutSection(String out, String head) {
String[] split = out.split("\n");
String xout = "";
for (String s : split) {
if (!s.startsWith("(python)") && !s.equals("")) {
xout += s + "\n";
}
}
return xout.split(head)[1].split("---")[0].replace("(python)", "").trim();
}
record MemDump(long address, byte[] data) {
}
protected MemDump parseHexDump(String dump) throws IOException {
// First, get the address. Assume contiguous, so only need top line.
List<String> lines = List.of(dump.split("\n"));
List<String> toksLine0 = List.of(lines.get(0).split("\\s+"));
String addrstr = toksLine0.get(0);
if (addrstr.contains(":")) {
addrstr = addrstr.substring(0, addrstr.indexOf(":"));
}
long address = Long.parseLong(addrstr, 16);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
for (String l : lines) {
List<String> parts = List.of(l.split(":"));
assertEquals(2, parts.size());
String hex = parts.get(1).substring(0, 48);
byte[] lineData = NumericUtilities.convertStringToBytes(hex);
assertNotNull("Converted to null: " + hex, parts.get(1));
buf.write(lineData);
}
return new MemDump(address, buf.toByteArray());
}
record RegDump() {
}
protected RegDump parseRegDump(String dump) {
return new RegDump();
}
protected ManagedDomainObject openDomainObject(String path) throws Exception {
DomainFile df = env.getProject().getProjectData().getFile(path);
assertNotNull(df);
return new ManagedDomainObject(df, false, false, monitor);
}
protected ManagedDomainObject waitDomainObject(String path) throws Exception {
DomainFile df;
long start = System.currentTimeMillis();
while (true) {
df = env.getProject().getProjectData().getFile(path);
if (df != null) {
return new ManagedDomainObject(df, false, false, monitor);
}
Thread.sleep(1000);
if (System.currentTimeMillis() - start > 30000) {
throw new TimeoutException("30 seconds expired waiting for domain file");
}
}
}
protected void assertBreakLoc(TraceObjectValue locVal, String key, Address addr, int len,
Set<TraceBreakpointKind> kinds, String expression) throws Exception {
assertEquals(key, locVal.getEntryKey());
TraceObject loc = locVal.getChild();
TraceObject spec = loc;
assertEquals(new AddressRangeImpl(addr, len), loc.getValue(0, "_range").getValue());
assertEquals(TraceBreakpointKindSet.encode(kinds), spec.getValue(0, "_kinds").getValue());
assertTrue(spec.getValue(0, "_expression").getValue().toString().contains(expression));
}
protected void assertWatchLoc(TraceObjectValue locVal, String key, Address addr, int len,
Set<TraceBreakpointKind> kinds, String expression) throws Exception {
assertEquals(key, locVal.getEntryKey());
TraceObject loc = locVal.getChild();
assertEquals(new AddressRangeImpl(addr, len), loc.getValue(0, "_range").getValue());
assertEquals(TraceBreakpointKindSet.encode(kinds), loc.getValue(0, "_kinds").getValue());
}
protected void waitTxDone() {
waitFor(() -> tb.trace.getCurrentTransactionInfo() == null);
}
public static void waitForPass(Runnable runnable, long timeoutMs, long retryDelayMs) {
long start = System.currentTimeMillis();
AssertionError lastError = null;
while (System.currentTimeMillis() - start < timeoutMs) {
try {
runnable.run();
return;
}
catch (AssertionError e) {
lastError = e;
}
try {
Thread.sleep(retryDelayMs);
}
catch (InterruptedException e) {
// Retry sooner, I guess.
}
}
if (lastError == null) {
throw new AssertionError("Timed out before first try?");
}
throw lastError;
}
}

View file

@ -0,0 +1,380 @@
/* ###
* 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 agent.dbgeng.rmi;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Objects;
import org.junit.Ignore;
import org.junit.Test;
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteMethod;
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
import ghidra.dbg.testutil.DummyProc;
import ghidra.dbg.util.PathPattern;
import ghidra.dbg.util.PathPredicates;
import ghidra.program.model.address.AddressSpace;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
private static final long RUN_TIMEOUT_MS = 20000;
private static final long RETRY_MS = 500;
record PythonAndTrace(PythonAndHandler conn, ManagedDomainObject mdo) implements AutoCloseable {
public void execute(String cmd) {
conn.execute(cmd);
}
public String executeCapture(String cmd) {
return conn.executeCapture(cmd);
}
@Override
public void close() throws Exception {
conn.close();
mdo.close();
}
}
@SuppressWarnings("resource")
protected PythonAndTrace startAndSyncPython(String exec) throws Exception {
PythonAndHandler conn = startAndConnectPython();
try {
ManagedDomainObject mdo;
conn.execute("from ghidradbg.commands import *");
conn.execute(
"util.set_convenience_variable('ghidra-language', 'x86:LE:64:default')");
if (exec != null) {
start(conn, exec);
mdo = waitDomainObject("/New Traces/pydbg/"+exec);
}
else {
conn.execute("ghidra_trace_start()");
mdo = waitDomainObject("/New Traces/pydbg/noname");
}
tb = new ToyDBTraceBuilder((Trace) mdo.get());
return new PythonAndTrace(conn, mdo);
}
catch (Exception e) {
conn.close();
throw e;
}
}
protected long lastSnap(PythonAndTrace conn) {
return conn.conn.handler().getLastSnapshot(tb.trace);
}
@Test // The 10s wait makes this a pretty expensive test
public void testOnNewThread() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
conn.execute("from ghidradbg.commands import *");
txPut(conn, "processes");
waitForPass(() -> {
TraceObject proc = tb.objAny("Processes[]");
assertNotNull(proc);
assertEquals("STOPPED", tb.objValue(proc, lastSnap(conn), "_state"));
}, RUN_TIMEOUT_MS, RETRY_MS);
txPut(conn, "threads");
waitForPass(() -> assertEquals(4,
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("dbg().go(10)");
waitForPass(() -> assertTrue(tb.objValues(lastSnap(conn), "Processes[].Threads[]").size() > 4),
RUN_TIMEOUT_MS, RETRY_MS);
}
}
@Test
public void testOnThreadSelected() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
txPut(conn, "processes");
waitForPass(() -> {
TraceObject inf = tb.objAny("Processes[]");
assertNotNull(inf);
assertEquals("STOPPED", tb.objValue(inf, lastSnap(conn), "_state"));
}, RUN_TIMEOUT_MS, RETRY_MS);
txPut(conn, "threads");
waitForPass(() -> assertEquals(4,
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
RUN_TIMEOUT_MS, RETRY_MS);
// Now the real test
conn.execute("util.select_thread(1)");
waitForPass(() -> {
String tnum = conn.executeCapture("util.selected_thread()");
assertTrue(tnum.contains("1"));
String threadIndex = threadIndex(traceManager.getCurrentObject());
assertTrue(tnum.contains(threadIndex));
}, RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("util.select_thread(2)");
waitForPass(() -> {
String tnum = conn.executeCapture("util.selected_thread()");
assertTrue(tnum.contains("2"));
String threadIndex = threadIndex(traceManager.getCurrentObject());
assertTrue(tnum.contains(threadIndex));
}, RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("util.select_thread(0)");
waitForPass(() -> {
String tnum = conn.executeCapture("util.selected_thread()");
assertTrue(tnum.contains("0"));
String threadIndex = threadIndex(traceManager.getCurrentObject());
assertTrue(tnum.contains(threadIndex));
}, RUN_TIMEOUT_MS, RETRY_MS);
}
}
protected String getIndex(TraceObject object, String pattern, int n) {
if (object == null) {
return null;
}
PathPattern pat = PathPredicates.parse(pattern).getSingletonPattern();
// if (pat.countWildcards() != 1) {
// throw new IllegalArgumentException("Exactly one wildcard required");
// }
List<String> path = object.getCanonicalPath().getKeyList();
if (path.size() < pat.asPath().size()) {
return null;
}
List<String> matched = pat.matchKeys(path.subList(0, pat.asPath().size()));
if (matched == null) {
return null;
}
if (matched.size() <= n) {
return null;
}
return matched.get(n);
}
protected String threadIndex(TraceObject object) {
return getIndex(object, "Processes[].Threads[]", 1);
}
protected String frameIndex(TraceObject object) {
return getIndex(object, "Processes[].Threads[].Stack[]", 2);
}
@Test
@Ignore
public void testOnSyscallMemory() throws Exception {
// TODO: Need a specimen
// 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
public void testOnMemoryChanged() throws Exception {
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);
conn.execute("util.get_debugger().write("+address+", b'\\x7f')");
waitForPass(() -> {
ByteBuffer buf = ByteBuffer.allocate(10);
tb.trace.getMemoryManager().getBytes(lastSnap(conn), tb.addr(address), buf);
assertEquals(0x7f, buf.get(0));
}, RUN_TIMEOUT_MS, RETRY_MS);
}
}
@Test
public void testOnRegisterChanged() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
conn.execute("ghidra_trace_txstart('Tx')");
conn.execute("ghidra_trace_putreg()");
conn.execute("ghidra_trace_txcommit()");
conn.execute("util.get_debugger().reg._set_register('rax', 0x1234)");
conn.execute("util.get_debugger().stepi()");
String path = "Processes[].Threads[].Registers";
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
AddressSpace space = tb.trace.getBaseAddressFactory()
.getAddressSpace(registers.getCanonicalPath().toString());
TraceMemorySpace regs = tb.trace.getMemoryManager().getMemorySpace(space, false);
waitForPass(() -> assertEquals("1234",
regs.getValue(lastSnap(conn), tb.reg("RAX")).getUnsignedValue().toString(16)));
}
}
@Test
public void testOnCont() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
txPut(conn, "processes");
conn.execute("util.get_debugger()._control.SetExecutionStatus(DbgEng.DEBUG_STATUS_GO)");
waitRunning();
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
waitForPass(() -> {
assertEquals("RUNNING", tb.objValue(proc, lastSnap(conn), "_state"));
}, RUN_TIMEOUT_MS, RETRY_MS);
}
}
@Test
public void testOnStop() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
txPut(conn, "processes");
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
waitForPass(() -> {
assertEquals("STOPPED", tb.objValue(proc, lastSnap(conn), "_state"));
}, RUN_TIMEOUT_MS, RETRY_MS);
}
}
@Test
public void testOnExited() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("netstat.exe")) {
txPut(conn, "processes");
waitStopped();
conn.execute("util.get_debugger().go()");
waitForPass(() -> {
TraceSnapshot snapshot =
tb.trace.getTimeManager().getSnapshot(lastSnap(conn), false);
assertNotNull(snapshot);
assertEquals("Exited with code 0", snapshot.getDescription());
TraceObject proc = tb.objAny("Processes[]");
assertNotNull(proc);
Object val = tb.objValue(proc, lastSnap(conn), "_exit_code");
assertThat(val, instanceOf(Number.class));
assertEquals(0, ((Number) val).longValue());
}, RUN_TIMEOUT_MS, RETRY_MS);
}
}
@Test
public void testOnBreakpointCreated() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
conn.execute("dbg = util.get_debugger()");
conn.execute("pc = dbg.reg.get_pc()");
conn.execute("dbg.bp(expr=pc)");
conn.execute("dbg.stepi()");
waitForPass(() -> {
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
assertEquals(1, brks.size());
return (TraceObject) brks.get(0);
});
}
}
@Test
public void testOnBreakpointModified() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
conn.execute("dbg = util.get_debugger()");
conn.execute("pc = dbg.reg.get_pc()");
conn.execute("dbg.bp(expr=pc)");
conn.execute("dbg.stepi()");
TraceObject brk = waitForPass(() -> {
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
assertEquals(1, brks.size());
return (TraceObject) brks.get(0);
});
assertEquals(true, tb.objValue(brk, lastSnap(conn), "Enabled"));
conn.execute("dbg.bd(0)");
conn.execute("dbg.stepi()");
assertEquals(false, tb.objValue(brk, lastSnap(conn), "Enabled"));
/* Not currently enabled
assertEquals("", tb.objValue(brk, lastSnap(conn), "Command"));
conn.execute("dbg.bp(expr=pc, windbgcmd='bl')");
conn.execute("dbg.stepi()");
assertEquals("bl", tb.objValue(brk, lastSnap(conn), "Command"));
*/
}
}
@Test
public void testOnBreakpointDeleted() throws Exception {
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
conn.execute("dbg = util.get_debugger()");
conn.execute("pc = dbg.reg.get_pc()");
conn.execute("dbg.bp(expr=pc)");
conn.execute("dbg.stepi()");
TraceObject brk = waitForPass(() -> {
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
assertEquals(1, brks.size());
return (TraceObject) brks.get(0);
});
conn.execute("dbg.cmd('bc %s')".formatted(brk.getCanonicalPath().index()));
conn.execute("dbg.stepi()");
waitForPass(
() -> assertEquals(0,
tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size()));
}
}
private void start(PythonAndHandler conn, String obj) {
conn.execute("from ghidradbg.commands import *");
if (obj != null)
conn.execute("ghidra_trace_create('"+obj+"')");
else
conn.execute("ghidra_trace_create()");
conn.execute("ghidra_trace_sync_enable()");
}
private void txPut(PythonAndTrace conn, String obj) {
conn.execute("ghidra_trace_txstart('Tx" + obj + "')");
conn.execute("ghidra_trace_put_" + obj +"()");
conn.execute("ghidra_trace_txcommit()");
}
private long getAddressAtOffset(PythonAndTrace conn, int offset) {
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+"+offset+")";
String ret = conn.executeCapture(inst);
String[] split = ret.split("\\s+"); // get target
return Long.decode(split[1]);
}
}

View file

@ -0,0 +1,959 @@
/* ###
* 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 agent.dbgeng.rmi;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.*;
import java.util.*;
import org.junit.Test;
import generic.Unique;
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteMethod;
import ghidra.app.plugin.core.debug.service.rmi.trace.ValueDecoder;
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
import ghidra.dbg.testutil.DummyProc;
import ghidra.dbg.util.PathPattern;
import ghidra.dbg.util.PathPredicates;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectValue;
public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
@Test
public void testEvaluate() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
RemoteMethod evaluate = conn.getMethod("evaluate");
assertEquals("11",
evaluate.invoke(Map.of("expr", "3+4*2")));
}
}
@Test
public void testExecuteCapture() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
RemoteMethod execute = conn.getMethod("execute");
assertEquals(false,
execute.parameters().get("to_string").defaultValue().get(ValueDecoder.DEFAULT));
assertEquals("11\n",
execute.invoke(Map.of("cmd", "print(3+4*2)", "to_string", true)));
}
}
@Test
public void testExecute() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
conn.execute("ghidra_trace_kill()");
}
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
// Just confirm it's present
}
}
@Test
public void testRefreshAvailable() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, null);
txCreate(conn, "Available");
RemoteMethod refreshAvailable = conn.getMethod("refresh_available");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject available = Objects.requireNonNull(tb.objAny("Available"));
refreshAvailable.invoke(Map.of("node", available));
// Would be nice to control / validate the specifics
List<TraceObject> list = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0), PathPredicates.parse("Available[]"))
.map(p -> p.getDestination(null))
.toList();
assertThat(list.size(), greaterThan(2));
}
}
}
@Test
public void testRefreshBreakpoints() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod refreshBreakpoints = conn.getMethod("refresh_breakpoints");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
conn.execute("dbg = util.get_debugger()");
conn.execute("pc = dbg.reg.get_pc()");
conn.execute("dbg.bp(expr=pc)");
conn.execute("dbg.ba(expr=pc+4)");
txPut(conn, "breakpoints");
TraceObject breakpoints = Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
refreshBreakpoints.invoke(Map.of("node", breakpoints));
List<TraceObjectValue> procBreakLocVals = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0),
PathPredicates.parse("Processes[].Breakpoints[]"))
.map(p -> p.getLastEntry())
.toList();
assertEquals(2, procBreakLocVals.size());
AddressRange rangeMain =
procBreakLocVals.get(0).getChild().getValue(0, "_range").castValue();
Address main = rangeMain.getMinAddress();
assertBreakLoc(procBreakLocVals.get(0), "[0]", main, 1,
Set.of(TraceBreakpointKind.SW_EXECUTE),
"ntdll!LdrInit");
assertBreakLoc(procBreakLocVals.get(1), "[1]", main.add(4), 1,
Set.of(TraceBreakpointKind.HW_EXECUTE),
"ntdll!LdrInit");
}
}
}
@Test
public void testRefreshBreakpoints2() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "all");
RemoteMethod refreshProcWatchpoints = conn.getMethod("refresh_breakpoints");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
conn.execute("dbg = util.get_debugger()");
conn.execute("pc = dbg.reg.get_pc()");
conn.execute("dbg.ba(expr=pc, access=DbgEng.DEBUG_BREAK_EXECUTE)");
conn.execute("dbg.ba(expr=pc+4, access=DbgEng.DEBUG_BREAK_READ)");
conn.execute("dbg.ba(expr=pc+8, access=DbgEng.DEBUG_BREAK_WRITE)");
TraceObject locations =
Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
refreshProcWatchpoints.invoke(Map.of("node", locations));
List<TraceObjectValue> procBreakVals = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0),
PathPredicates.parse("Processes[].Breakpoints[]"))
.map(p -> p.getLastEntry())
.toList();
assertEquals(3, procBreakVals.size());
AddressRange rangeMain0 =
procBreakVals.get(0).getChild().getValue(0, "_range").castValue();
Address main0 = rangeMain0.getMinAddress();
AddressRange rangeMain1 =
procBreakVals.get(1).getChild().getValue(0, "_range").castValue();
Address main1 = rangeMain1.getMinAddress();
AddressRange rangeMain2 =
procBreakVals.get(2).getChild().getValue(0, "_range").castValue();
Address main2 = rangeMain2.getMinAddress();
assertWatchLoc(procBreakVals.get(0), "[0]", main0, (int) rangeMain0.getLength(),
Set.of(TraceBreakpointKind.HW_EXECUTE),
"main");
assertWatchLoc(procBreakVals.get(1), "[1]", main1, (int) rangeMain1.getLength(),
Set.of(TraceBreakpointKind.WRITE),
"main+4");
assertWatchLoc(procBreakVals.get(2), "[2]", main2, (int) rangeMain1.getLength(),
Set.of(TraceBreakpointKind.READ),
"main+8");
}
}
}
@Test
public void testRefreshProcesses() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, null);
txCreate(conn, "Processes");
txCreate(conn, "Processes[1]");
RemoteMethod refreshProcesses = conn.getMethod("refresh_processes");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject processes = Objects.requireNonNull(tb.objAny("Processes"));
refreshProcesses.invoke(Map.of("node", processes));
// Would be nice to control / validate the specifics
List<TraceObject> list = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0), PathPredicates.parse("Processes[]"))
.map(p -> p.getDestination(null))
.toList();
assertEquals(1, list.size());
}
}
}
@Test
public void testRefreshEnvironment() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
String path = "Processes[].Environment";
start(conn, "notepad.exe");
txPut(conn, "all");
RemoteMethod refreshEnvironment = conn.getMethod("refresh_environment");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject env = Objects.requireNonNull(tb.objAny(path));
refreshEnvironment.invoke(Map.of("node", env));
// Assumes pydbg on Windows amd64
assertEquals("pydbg", env.getValue(0, "_debugger").getValue());
assertEquals("x86_64", env.getValue(0, "_arch").getValue());
assertEquals("windows", env.getValue(0, "_os").getValue());
assertEquals("little", env.getValue(0, "_endian").getValue());
}
}
}
@Test
public void testRefreshThreads() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
String path = "Processes[].Threads";
start(conn, "notepad.exe");
txCreate(conn, path);
RemoteMethod refreshThreads = conn.getMethod("refresh_threads");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject threads = Objects.requireNonNull(tb.objAny(path));
refreshThreads.invoke(Map.of("node", threads));
// Would be nice to control / validate the specifics
int listSize = tb.trace.getThreadManager().getAllThreads().size();
assertEquals(4, listSize);
}
}
}
@Test
public void testRefreshStack() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
String path = "Processes[].Threads[].Stack";
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod refreshStack = conn.getMethod("refresh_stack");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
txPut(conn, "frames");
TraceObject stack = Objects.requireNonNull(tb.objAny(path));
refreshStack.invoke(Map.of("node", stack));
// Would be nice to control / validate the specifics
List<TraceObject> list = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0),
PathPredicates.parse("Processes[].Threads[].Stack[]"))
.map(p -> p.getDestination(null))
.toList();
assertTrue(list.size() > 1);
}
}
}
@Test
public void testRefreshRegisters() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
String path = "Processes[].Threads[].Registers";
start(conn, "notepad.exe");
conn.execute("ghidra_trace_txstart('Tx')");
conn.execute("ghidra_trace_putreg()");
conn.execute("ghidra_trace_txcommit()");
RemoteMethod refreshRegisters = conn.getMethod("refresh_registers");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
conn.execute("regs = util.get_debugger().reg");
conn.execute("regs._set_register('rax', int(0xdeadbeef))");
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
refreshRegisters.invoke(Map.of("node", registers));
long snap = 0;
AddressSpace t1f0 = tb.trace.getBaseAddressFactory()
.getAddressSpace(registers.getCanonicalPath().toString());
TraceMemorySpace regs = tb.trace.getMemoryManager().getMemorySpace(t1f0, false);
RegisterValue rax = regs.getValue(snap, tb.reg("rax"));
assertEquals("deadbeef", rax.getUnsignedValue().toString(16));
}
}
}
@Test
public void testRefreshMappings() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
String path = "Processes[].Memory";
start(conn, "notepad.exe");
txCreate(conn, path);
RemoteMethod refreshMappings = conn.getMethod("refresh_mappings");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject memory = Objects.requireNonNull(tb.objAny(path));
refreshMappings.invoke(Map.of("node", memory));
// Would be nice to control / validate the specifics
Collection<? extends TraceMemoryRegion> all =
tb.trace.getMemoryManager().getAllRegions();
assertThat(all.size(), greaterThan(2));
}
}
}
@Test
public void testRefreshModules() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
String path = "Processes[].Modules";
start(conn, "notepad.exe");
txCreate(conn, path);
RemoteMethod refreshModules = conn.getMethod("refresh_modules");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject modules = Objects.requireNonNull(tb.objAny(path));
refreshModules.invoke(Map.of("node", modules));
// Would be nice to control / validate the specifics
Collection<? extends TraceModule> all = tb.trace.getModuleManager().getAllModules();
TraceModule modBash =
Unique.assertOne(all.stream().filter(m -> m.getName().contains("notepad.exe")));
assertNotEquals(tb.addr(0), Objects.requireNonNull(modBash.getBase()));
}
}
}
@Test
public void testActivateThread() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod activateThread = conn.getMethod("activate_thread");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
txPut(conn, "threads");
PathPattern pattern =
PathPredicates.parse("Processes[].Threads[]").getSingletonPattern();
List<TraceObject> list = tb.trace.getObjectManager()
.getValuePaths(Lifespan.at(0), pattern)
.map(p -> p.getDestination(null))
.toList();
assertEquals(4, list.size());
for (TraceObject t : list) {
activateThread.invoke(Map.of("thread", t));
String out = conn.executeCapture("util.get_debugger().get_thread()");
List<String> indices = pattern.matchKeys(t.getCanonicalPath().getKeyList());
assertEquals(out, "%s".formatted(indices.get(1)));
}
}
}
}
@Test
public void testRemoveProcess() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "netstat.exe");
txPut(conn, "processes");
RemoteMethod removeProcess = conn.getMethod("remove_process");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/netstat.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject proc2 = Objects.requireNonNull(tb.objAny("Processes[]"));
removeProcess.invoke(Map.of("process", proc2));
String out = conn.executeCapture("list(util.process_list())");
assertThat(out, containsString("[]"));
}
}
}
@Test
public void testAttachObj() throws Exception {
try (DummyProc dproc = DummyProc.run("notepad.exe")) {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, null);
txPut(conn, "available");
RemoteMethod attachObj = conn.getMethod("attach_obj");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject target =
Objects.requireNonNull(tb.obj("Available[%d]".formatted(dproc.pid)));
attachObj.invoke(Map.of("target", target));
String out = conn.executeCapture("list(util.process_list())");
assertThat(out, containsString("%d".formatted(dproc.pid)));
}
}
}
}
@Test
public void testAttachPid() throws Exception {
try (DummyProc dproc = DummyProc.run("notepad.exe")) {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, null);
txPut(conn, "available");
RemoteMethod attachPid = conn.getMethod("attach_pid");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
Objects.requireNonNull(tb.objAny("Available["+dproc.pid+"]", Lifespan.at(0)));
attachPid.invoke(Map.of("pid", dproc.pid));
String out = conn.executeCapture("list(util.process_list())");
assertThat(out, containsString("%d".formatted(dproc.pid)));
}
}
}
}
@Test
public void testDetach() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "netstat.exe");
txPut(conn, "processes");
RemoteMethod detach = conn.getMethod("detach");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/netstat.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
detach.invoke(Map.of("process", proc));
String out = conn.executeCapture("list(util.process_list())");
assertThat(out, containsString("[]"));
}
}
}
@Test
public void testLaunchEntry() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, null);
txPut(conn, "processes");
RemoteMethod launch = conn.getMethod("launch_loader");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
launch.invoke(Map.ofEntries(
Map.entry("file", "notepad.exe")));
String out = conn.executeCapture("list(util.process_list())");
assertThat(out, containsString("notepad.exe"));
}
}
}
@Test //Can't do this test because create(xxx, initial_break=False) doesn't return
public void testLaunch() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, null);
txPut(conn, "processes");
RemoteMethod launch = conn.getMethod("launch");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
launch.invoke(Map.ofEntries(
Map.entry("timeout", 1L),
Map.entry("file", "notepad.exe")));
txPut(conn, "processes");
String out = conn.executeCapture("list(util.process_list())");
assertThat(out, containsString("notepad.exe"));
}
}
}
@Test
public void testKill() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod kill = conn.getMethod("kill");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
kill.invoke(Map.of("process", proc));
String out = conn.executeCapture("list(util.process_list())");
assertThat(out, containsString("[]"));
}
}
}
@Test
public void testStepInto() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod step_into = conn.getMethod("step_into");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
txPut(conn, "threads");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
while (!getInst(conn).contains("call")) {
step_into.invoke(Map.of("thread", thread));
}
String disCall = getInst(conn);
// lab0:
// -> addr0
//
// lab1:
// addr1
String[] split = disCall.split("\\s+"); // get target
long pcCallee = Long.decode(split[split.length-1]);
step_into.invoke(Map.of("thread", thread));
long pc = getAddressAtOffset(conn, 0);
assertEquals(pcCallee, pc);
}
}
}
@Test
public void testStepOver() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod step_over = conn.getMethod("step_over");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
txPut(conn, "threads");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
while (!getInst(conn).contains("call")) {
step_over.invoke(Map.of("thread", thread));
}
String disCall = getInst(conn);
String[] split = disCall.split("\\s+"); // get target
long pcCallee = Long.decode(split[split.length-1]);
step_over.invoke(Map.of("thread", thread));
long pc = getAddressAtOffset(conn, 0);
assertNotEquals(pcCallee, pc);
}
}
}
@Test
public void testStepTo() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
RemoteMethod step_into = conn.getMethod("step_into");
RemoteMethod step_to = conn.getMethod("step_to");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
txPut(conn, "threads");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
while (!getInst(conn).contains("call")) {
step_into.invoke(Map.of("thread", thread));
}
step_into.invoke(Map.of("thread", thread));
int sz = Integer.parseInt(getInstSizeAtOffset(conn, 0));
for (int i = 0; i < 4; i++) {
sz += Integer.parseInt(getInstSizeAtOffset(conn, sz));
}
long pcNext = getAddressAtOffset(conn, sz);
boolean success = (boolean) step_to.invoke(Map.of("thread", thread, "address", tb.addr(pcNext), "max", 10));
assertTrue(success);
long pc = getAddressAtOffset(conn, 0);
assertEquals(pcNext, pc);
}
}
}
@Test
public void testStepOut() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod step_into = conn.getMethod("step_into");
RemoteMethod step_out = conn.getMethod("step_out");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
txPut(conn, "threads");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
while (!getInst(conn).contains("call")) {
step_into.invoke(Map.of("thread", thread));
}
int sz = Integer.parseInt(getInstSizeAtOffset(conn, 0));
long pcNext = getAddressAtOffset(conn, sz);
step_into.invoke(Map.of("thread", thread));
step_out.invoke(Map.of("thread", thread));
long pc = getAddressAtOffset(conn, 0);
assertEquals(pcNext, pc);
}
}
}
@Test
public void testBreakAddress() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakAddress = conn.getMethod("break_address");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = getAddressAtOffset(conn, 0);
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString(Long.toHexString(address)));
}
}
}
@Test
public void testBreakExpression() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
breakExpression.invoke(Map.of("expression", "entry"));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString("entry"));
}
}
}
@Test
public void testBreakHardwareAddress() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakAddress = conn.getMethod("break_hw_address");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = getAddressAtOffset(conn, 0);
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString(Long.toHexString(address)));
}
}
}
@Test
public void testBreakHardwareExpression() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_hw_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
breakExpression.invoke(Map.of("expression", "entry"));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString("entry"));
}
}
}
@Test
public void testBreakReadRange() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakRange = conn.getMethod("break_read_range");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = getAddressAtOffset(conn, 0);
AddressRange range = tb.range(address, address + 3); // length 4
breakRange.invoke(Map.of("process", proc, "range", range));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString("%x".formatted(address)));
assertThat(out, containsString("sz=4"));
assertThat(out, containsString("type=r"));
}
}
}
@Test
public void testBreakReadExpression() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_read_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
long address = getAddressAtOffset(conn, 0);
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString(Long.toHexString(address>>24)));
assertThat(out, containsString("sz=1"));
assertThat(out, containsString("type=r"));
}
}
}
@Test
public void testBreakWriteRange() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakRange = conn.getMethod("break_write_range");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = getAddressAtOffset(conn, 0);
AddressRange range = tb.range(address, address + 3); // length 4
breakRange.invoke(Map.of("process", proc, "range", range));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString("%x".formatted(address)));
assertThat(out, containsString("sz=4"));
assertThat(out, containsString("type=w"));
}
}
}
@Test
public void testBreakWriteExpression() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_write_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
long address = getAddressAtOffset(conn, 0);
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString(Long.toHexString(address>>24)));
assertThat(out, containsString("sz=1"));
assertThat(out, containsString("type=w"));
}
}
}
@Test
public void testBreakAccessRange() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakRange = conn.getMethod("break_access_range");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped();
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = getAddressAtOffset(conn, 0);
AddressRange range = tb.range(address, address + 3); // length 4
breakRange.invoke(Map.of("process", proc, "range", range));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString("%x".formatted(address)));
assertThat(out, containsString("sz=4"));
assertThat(out, containsString("type=rw"));
}
}
}
@Test
public void testBreakAccessExpression() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_access_expression");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
long address = getAddressAtOffset(conn, 0);
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString(Long.toHexString(address>>24)));
assertThat(out, containsString("sz=1"));
assertThat(out, containsString("type=rw"));
}
}
}
@Test
public void testToggleBreakpoint() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakAddress = conn.getMethod("break_address");
RemoteMethod toggleBreakpoint = conn.getMethod("toggle_breakpoint");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
long address = getAddressAtOffset(conn, 0);
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
txPut(conn, "breakpoints");
TraceObject bpt = Objects.requireNonNull(tb.objAny("Processes[].Breakpoints[]"));
toggleBreakpoint.invoke(Map.of("breakpoint", bpt, "enabled", false));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString("disabled"));
}
}
}
@Test
public void testDeleteBreakpoint() throws Exception {
try (PythonAndHandler conn = startAndConnectPython()) {
start(conn, "notepad.exe");
txPut(conn, "processes");
RemoteMethod breakAddress = conn.getMethod("break_address");
RemoteMethod deleteBreakpoint = conn.getMethod("delete_breakpoint");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
long address = getAddressAtOffset(conn, 0);
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
txPut(conn, "breakpoints");
TraceObject bpt = Objects.requireNonNull(tb.objAny("Processes[].Breakpoints[]"));
deleteBreakpoint.invoke(Map.of("breakpoint", bpt));
String out = conn.executeCapture("list(util.get_breakpoints())");
assertThat(out, containsString("[]"));
}
}
}
private void start(PythonAndHandler conn, String obj) {
conn.execute("from ghidradbg.commands import *");
if (obj != null)
conn.execute("ghidra_trace_create('"+obj+"')");
else
conn.execute("ghidra_trace_create()"); }
private void txPut(PythonAndHandler conn, String obj) {
conn.execute("ghidra_trace_txstart('Tx')");
conn.execute("ghidra_trace_put_" + obj + "()");
conn.execute("ghidra_trace_txcommit()");
}
private void txCreate(PythonAndHandler conn, String path) {
conn.execute("ghidra_trace_txstart('Fake')");
conn.execute("ghidra_trace_create_obj('%s')".formatted(path));
conn.execute("ghidra_trace_txcommit()");
}
private String getInst(PythonAndHandler conn) {
return getInstAtOffset(conn, 0);
}
private String getInstAtOffset(PythonAndHandler conn, int offset) {
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+"+offset+")";
String ret = conn.executeCapture(inst);
return ret.substring(1, ret.length()-1); // remove <>
}
private String getInstSizeAtOffset(PythonAndHandler conn, int offset) {
String instSize = "util.get_inst_sz(util.get_debugger().reg.get_pc()+"+offset+")";
return conn.executeCapture(instSize);
}
private long getAddressAtOffset(PythonAndHandler conn, int offset) {
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+"+offset+")";
String ret = conn.executeCapture(inst);
String[] split = ret.split("\\s+"); // get target
return Long.decode(split[1]);
}
}

View file

@ -134,7 +134,7 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest {
ghidra_trace_connect %s
file bash
script ghidralldb.util.set_convenience_variable('ghidra-language','Toy:BE:64:default')
script ghidralldb.util.set_convenience_varaible('ghidra-compiler','default')
script ghidralldb.util.set_convenience_variable('ghidra-compiler','default')
ghidra_trace_start myToy
quit
"""