Merge remote-tracking branch 'origin/GP-3754_d-millar_traceRmi_dbgeng--SQUASHED'

This commit is contained in:
Ryan Kurtz
2023-09-12 12:18:28 -04:00
20 changed files with 6266 additions and 2 deletions
@@ -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'
@@ -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|
@@ -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.
@@ -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
@@ -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"
@@ -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
@@ -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
@@ -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()
File diff suppressed because it is too large Load Diff
@@ -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>
@@ -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
@@ -249,7 +249,7 @@ public class TraceRmiHandler {
}
public boolean isClosed() {
return socket.isClosed();
return socket.isClosed() && closed.isDone();
}
public void waitClosed() throws InterruptedException, ExecutionException {
@@ -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) {
+1
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 }
}
@@ -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;
}
}
@@ -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]);
}
}
@@ -137,7 +137,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
"""