Merge remote-tracking branch 'origin/GP-5185_d-millar_exdi--SQUASHED'

This commit is contained in:
Ryan Kurtz 2024-12-17 11:39:03 -05:00
commit f0641fd72a
16 changed files with 855 additions and 37 deletions

View file

@ -15,4 +15,5 @@ src/main/py/README.md||GHIDRA||||END|
src/main/py/pyproject.toml||GHIDRA||||END| src/main/py/pyproject.toml||GHIDRA||||END|
src/main/py/src/ghidradbg/dbgmodel/DbgModel.idl||GHIDRA||||END| src/main/py/src/ghidradbg/dbgmodel/DbgModel.idl||GHIDRA||||END|
src/main/py/src/ghidradbg/schema.xml||GHIDRA||||END| src/main/py/src/ghidradbg/schema.xml||GHIDRA||||END|
src/main/py/src/ghidradbg/schema_exdi.xml||GHIDRA||||END|
src/main/py/src/ghidrattd/schema.xml||GHIDRA||||END| src/main/py/src/ghidrattd/schema.xml||GHIDRA||||END|

View file

@ -9,9 +9,11 @@
::@menu-group local ::@menu-group local
::@icon icon.debugger ::@icon icon.debugger
::@help TraceRmiLauncherServicePlugin#dbgeng_kernel ::@help TraceRmiLauncherServicePlugin#dbgeng_kernel
::@enum Connection:str Remote Local EXDI
::@env OPT_PYTHON_EXE:file!="python" "Python command" "The path to the Python 3 interpreter. Omit the full path to resolve using the system PATH." ::@env OPT_PYTHON_EXE:file!="python" "Python command" "The path to the Python 3 interpreter. Omit the full path to resolve using the system PATH."
:: Use env instead of args, because "all args except first" is terrible to implement in batch :: Use env instead of args, because "all args except first" is terrible to implement in batch
::@env OPT_TARGET_ARGS:str="" "Arguments" "Connection-string arguments (a la .server)" ::@env OPT_TARGET_ARGS:str="" "Arguments" "Connection-string arguments (a la .server)"
::@env OPT_TARGET_FLAGS:Connection="Remote" "Type" "Type/flags for connection (Remote/Local/EXDI)."
::@env OPT_USE_DBGMODEL:bool=true "Use dbgmodel" "Load and use dbgmodel.dll if it is available." ::@env OPT_USE_DBGMODEL:bool=true "Use dbgmodel" "Load and use dbgmodel.dll if it is available."
::@env WINDBG_DIR:dir="" "Path to dbgeng.dll directory" "Path containing dbgeng and associated DLLS (if not Windows Kits)." ::@env WINDBG_DIR:dir="" "Path to dbgeng.dll directory" "Path containing dbgeng and associated DLLS (if not Windows Kits)."

View file

@ -38,6 +38,7 @@ else:
def main(): def main():
# Delay these imports until sys.path is patched # Delay these imports until sys.path is patched
from ghidradbg import commands as cmd from ghidradbg import commands as cmd
from ghidradbg import util
from pybag.dbgeng import core as DbgEng from pybag.dbgeng import core as DbgEng
from ghidradbg.hooks import on_state_changed from ghidradbg.hooks import on_state_changed
from ghidradbg.util import dbg from ghidradbg.util import dbg
@ -47,8 +48,17 @@ def main():
repl = cmd.repl repl = cmd.repl
cmd.ghidra_trace_connect(os.getenv('GHIDRA_TRACE_RMI_ADDR')) cmd.ghidra_trace_connect(os.getenv('GHIDRA_TRACE_RMI_ADDR'))
type = os.getenv('OPT_TARGET_FLAGS')
flags = 0
if type == "Local":
flags = 1
if type == "EXDI":
print(f"ConfigFile: {os.getenv('EXDI_GDBSRV_XML_CONFIG_FILE')}")
print(f"RegMapFile: {os.getenv('EXDI_SYSTEM_REGISTERS_MAP_XML_FILE')}")
util.set_convenience_variable('output-radix', 16)
flags = 2
args = os.getenv('OPT_TARGET_ARGS') args = os.getenv('OPT_TARGET_ARGS')
cmd.ghidra_trace_attach_kernel(args, start_trace=False) cmd.ghidra_trace_attach_kernel(args, flags, start_trace=False)
# TODO: HACK # TODO: HACK
try: try:
@ -56,7 +66,8 @@ def main():
except KeyboardInterrupt as ki: except KeyboardInterrupt as ki:
dbg.interrupt() dbg.interrupt()
cmd.ghidra_trace_start(os.getenv('OPT_TARGET_IMG')) #cmd.ghidra_trace_start(os.getenv('OPT_TARGET_IMG'))
cmd.ghidra_trace_start("System")
cmd.ghidra_trace_sync_enable() cmd.ghidra_trace_sync_enable()
on_state_changed(DbgEng.DEBUG_CES_EXECUTION_STATUS, DbgEng.DEBUG_STATUS_BREAK) on_state_changed(DbgEng.DEBUG_CES_EXECUTION_STATUS, DbgEng.DEBUG_STATUS_BREAK)

View file

@ -38,6 +38,7 @@ data64_compiler_map = {
x86_compiler_map = { x86_compiler_map = {
'windows': 'windows', 'windows': 'windows',
'Cygwin': 'windows', 'Cygwin': 'windows',
'default': 'windows',
} }
default_compiler_map = { default_compiler_map = {

View file

@ -32,6 +32,7 @@ from pybag.dbgeng.win32.kernel32 import STILL_ACTIVE
from . import util, arch, methods, hooks from . import util, arch, methods, hooks
from .dbgmodel.imodelobject import ModelObjectKind from .dbgmodel.imodelobject import ModelObjectKind
from .exdi import exdi_commands, exdi_methods
PAGE_SIZE = 4096 PAGE_SIZE = 4096
@ -209,6 +210,9 @@ def start_trace(name):
STATE.trace.register_mapper = arch.compute_register_mapper(language) STATE.trace.register_mapper = arch.compute_register_mapper(language)
parent = os.path.dirname(inspect.getfile(inspect.currentframe())) parent = os.path.dirname(inspect.getfile(inspect.currentframe()))
if util.is_exdi():
schema_fn = os.path.join(parent, 'schema_exdi.xml')
else:
schema_fn = os.path.join(parent, 'schema.xml') schema_fn = os.path.join(parent, 'schema.xml')
with open(schema_fn, 'r') as schema_file: with open(schema_fn, 'r') as schema_file:
schema_xml = schema_file.read() schema_xml = schema_file.read()
@ -314,17 +318,19 @@ def ghidra_trace_attach(pid=None, attach_flags='0', initial_break=True, timeout=
@util.dbg.eng_thread @util.dbg.eng_thread
def ghidra_trace_attach_kernel(command=None, initial_break=True, timeout=DbgEng.WAIT_INFINITE, start_trace=True): def ghidra_trace_attach_kernel(command=None, flags=DbgEng.DEBUG_ATTACH_KERNEL_CONNECTION, initial_break=True, timeout=DbgEng.WAIT_INFINITE, start_trace=True):
""" """
Create a session. Create a session.
""" """
dbg = util.dbg._base dbg = util.dbg._base
util.set_kernel(True) util.set_kernel(True)
if flags == 2:
util.set_exdi(True)
if initial_break: if initial_break:
dbg._control.AddEngineOptions(DbgEng.DEBUG_ENGINITIAL_BREAK) dbg._control.AddEngineOptions(DbgEng.DEBUG_ENGINITIAL_BREAK)
if command != None: if command != None:
dbg._client.AttachKernel(command) dbg._client.AttachKernel(command, flags=int(flags))
if start_trace: if start_trace:
ghidra_trace_start(command) ghidra_trace_start(command)
@ -593,6 +599,9 @@ def putreg():
name = regs._reg.GetDescription(i)[0] name = regs._reg.GetDescription(i)[0]
try: try:
value = regs._get_register_by_index(i) value = regs._get_register_by_index(i)
except Exception:
value = 0
try:
values.append(mapper.map_value(nproc, name, value)) values.append(mapper.map_value(nproc, name, value))
robj.set_value(name, hex(value)) robj.set_value(name, hex(value))
except Exception: except Exception:

View file

@ -0,0 +1,244 @@
## ###
# 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 import sch
from ghidratrace.client import Client, Address, AddressRange, TraceObject
PAGE_SIZE = 4096
from ghidradbg import arch, commands, util
SESSION_PATH = 'Sessions[0]'
PROCESSES_PATH = SESSION_PATH + '.ExdiProcesses'
PROCESS_KEY_PATTERN = '[{pid}]'
PROCESS_PATTERN = PROCESSES_PATH + PROCESS_KEY_PATTERN
PROC_BREAKS_PATTERN = PROCESS_PATTERN + '.Breakpoints'
PROC_BREAK_KEY_PATTERN = '[{breaknum}.{locnum}]'
THREADS_PATTERN = PROCESS_PATTERN + '.Threads'
THREAD_KEY_PATTERN = '[{tnum}]'
THREAD_PATTERN = THREADS_PATTERN + THREAD_KEY_PATTERN
MEMORY_PATH = SESSION_PATH + '.Memory'
REGION_KEY_PATTERN = '[{start}]'
REGION_PATTERN = MEMORY_PATH + REGION_KEY_PATTERN
KMODULES_PATH = SESSION_PATH + '.Modules'
KMODULE_KEY_PATTERN = '[{modpath}]'
KMODULE_PATTERN = KMODULES_PATH + KMODULE_KEY_PATTERN
MODULES_PATTERN = PROCESS_PATTERN + '.Modules'
MODULE_KEY_PATTERN = '[{modpath}]'
MODULE_PATTERN = MODULES_PATTERN + MODULE_KEY_PATTERN
SECTIONS_ADD_PATTERN = '.Sections'
SECTION_KEY_PATTERN = '[{secname}]'
SECTION_ADD_PATTERN = SECTIONS_ADD_PATTERN + SECTION_KEY_PATTERN
@util.dbg.eng_thread
def ghidra_trace_put_processes_exdi():
"""
Put the list of processes into the trace's processes list.
"""
radix = util.get_convenience_variable('output-radix')
commands.STATE.require_tx()
with commands.STATE.client.batch() as b:
put_processes_exdi(commands.STATE, radix)
@util.dbg.eng_thread
def ghidra_trace_put_regions_exdi():
"""
Read the memory map, if applicable, and write to the trace's Regions
"""
commands.STATE.require_tx()
with commands.STATE.client.batch() as b:
put_regions_exdi(commands.STATE)
@util.dbg.eng_thread
def ghidra_trace_put_kmodules_exdi():
"""
Gather object files, if applicable, and write to the trace's Modules
"""
commands.STATE.require_tx()
with commands.STATE.client.batch() as b:
put_kmodules_exdi(commands.STATE)
@util.dbg.eng_thread
def ghidra_trace_put_threads_exdi(pid):
"""
Put the current process's threads into the Ghidra trace
"""
radix = util.get_convenience_variable('output-radix')
commands.STATE.require_tx()
with commands.STATE.client.batch() as b:
put_threads_exdi(commands.STATE, pid, radix)
@util.dbg.eng_thread
def ghidra_trace_put_all_exdi():
"""
Put everything currently selected into the Ghidra trace
"""
radix = util.get_convenience_variable('output-radix')
commands.STATE.require_tx()
with commands.STATE.client.batch() as b:
if util.dbg.use_generics == False:
put_processes_exdi(commands.STATE, radix)
put_regions_exdi(commands.STATE)
put_kmodules_exdi(commands.STATE)
@util.dbg.eng_thread
def put_processes_exdi(state, radix):
radix = util.get_convenience_variable('output-radix')
keys = []
result = util.dbg._base.cmd("!process 0 0")
lines = result.split("\n")
count = int((len(lines)-2)/5)
for i in range(0,count):
l1 = lines[i*5+1].strip().split() # PROCESS
l2 = lines[i*5+2].strip().split() # SessionId, Cid, Peb: ParentId
l3 = lines[i*5+3].strip().split() # DirBase, ObjectTable, HandleCount
l4 = lines[i*5+4].strip().split() # Image
id = int(l2[3], 16)
name = l4[1]
ppath = PROCESS_PATTERN.format(pid=id)
procobj = state.trace.create_object(ppath)
keys.append(PROCESS_KEY_PATTERN.format(pid=id))
pidstr = ('0x{:x}' if radix ==
16 else '0{:o}' if radix == 8 else '{}').format(id)
procobj.set_value('PID', id)
procobj.set_value('Name', name)
procobj.set_value('_display', '[{}] {}'.format(pidstr, name))
(base, addr) = commands.map_address(int(l1[1],16))
procobj.set_value('EPROCESS', addr, schema="ADDRESS")
(base, addr) = commands.map_address(int(l2[5],16))
procobj.set_value('PEB', addr, schema="ADDRESS")
(base, addr) = commands.map_address(int(l3[1],16))
procobj.set_value('DirBase', addr, schema="ADDRESS")
(base, addr) = commands.map_address(int(l3[3],16))
procobj.set_value('ObjectTable', addr, schema="ADDRESS")
#procobj.set_value('ObjectTable', l3[3])
tcobj = state.trace.create_object(ppath+".Threads")
procobj.insert()
tcobj.insert()
state.trace.proxy_object_path(PROCESSES_PATH).retain_values(keys)
@util.dbg.eng_thread
def put_regions_exdi(state):
radix = util.get_convenience_variable('output-radix')
keys = []
result = util.dbg._base.cmd("!address")
lines = result.split("\n")
init = False
for l in lines:
if "-------" in l:
init = True
continue
if init == False:
continue
fields = l.strip().replace('`','').split() # PROCESS
if len(fields) < 4:
continue
start = fields[0]
#finish = fields[1]
length = fields[2]
type = fields[3]
(sbase, saddr) = commands.map_address(int(start,16))
#(fbase, faddr) = commands.map_address(int(finish,16))
rng = saddr.extend(int(length,16))
rpath = REGION_PATTERN.format(start=start)
keys.append(REGION_KEY_PATTERN.format(start=start))
regobj = state.trace.create_object(rpath)
regobj.set_value('Range', rng, schema="RANGE")
regobj.set_value('Size', length)
regobj.set_value('Type', type)
regobj.set_value('_readable', True)
regobj.set_value('_writable', True)
regobj.set_value('_executable', True)
regobj.set_value('_display', '[{}] {}'.format(
start, type))
regobj.insert()
state.trace.proxy_object_path(MEMORY_PATH).retain_values(keys)
@util.dbg.eng_thread
def put_kmodules_exdi(state):
radix = util.get_convenience_variable('output-radix')
keys = []
result = util.dbg._base.cmd("lm")
lines = result.split("\n")
init = False
for l in lines:
if "start" in l:
continue
if "Unloaded" in l:
continue
fields = l.strip().replace('`','').split()
if len(fields) < 3:
continue
start = fields[0]
finish = fields[1]
name = fields[2]
sname = name.replace('.sys','').replace('.dll','')
(sbase, saddr) = commands.map_address(int(start,16))
(fbase, faddr) = commands.map_address(int(finish,16))
sz = faddr.offset - saddr.offset
rng = saddr.extend(sz)
mpath = KMODULE_PATTERN.format(modpath=sname)
keys.append(KMODULE_KEY_PATTERN.format(modpath=sname))
modobj = commands.STATE.trace.create_object(mpath)
modobj.set_value('Name', name)
modobj.set_value('Base', saddr, schema="ADDRESS")
modobj.set_value('Range', rng, schema="RANGE")
modobj.set_value('Size', hex(sz))
modobj.insert()
state.trace.proxy_object_path(KMODULES_PATH).retain_values(keys)
@util.dbg.eng_thread
def put_threads_exdi(state, pid, radix):
radix = util.get_convenience_variable('output-radix')
pidstr = ('0x{:x}' if radix == 16 else '0{:o}' if radix == 8 else '{}').format(pid)
keys = []
result = util.dbg._base.cmd("!process "+hex(pid)+" 4")
lines = result.split("\n")
for l in lines:
l = l.strip()
if "THREAD" not in l:
continue
fields = l.split()
cid = fields[3] # pid.tid (decimal)
tid = int(cid.split('.')[1],16)
tidstr = ('0x{:x}' if radix ==
16 else '0{:o}' if radix == 8 else '{}').format(tid)
tpath = THREAD_PATTERN.format(pid=pid, tnum=tid)
tobj = commands.STATE.trace.create_object(tpath)
keys.append(THREAD_KEY_PATTERN.format(tnum=tidstr))
tobj = state.trace.create_object(tpath)
tobj.set_value('PID', pidstr)
tobj.set_value('TID', tidstr)
tobj.set_value('_display', '[{}]'.format(tidstr))
tobj.set_value('ETHREAD', fields[1])
tobj.set_value('TEB', fields[5])
tobj.set_value('Win32Thread', fields[7])
tobj.set_value('State', fields[8])
tobj.insert()
commands.STATE.trace.proxy_object_path(
THREADS_PATTERN.format(pid=pidstr)).retain_values(keys)

View file

@ -0,0 +1,54 @@
## ###
# 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 re
from ghidratrace import sch
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
from ghidradbg import util, commands, methods
from ghidradbg.methods import REGISTRY, SESSIONS_PATTERN, SESSION_PATTERN, extre
from . import exdi_commands
XPROCESSES_PATTERN = extre(SESSION_PATTERN, '\.ExdiProcesses')
XPROCESS_PATTERN = extre(XPROCESSES_PATTERN, '\[(?P<procnum>\\d*)\]')
XTHREADS_PATTERN = extre(XPROCESS_PATTERN, '\.Threads')
def find_pid_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['procnum'])
return pid
def find_pid_by_obj(object):
return find_pid_by_pattern(XTHREADS_PATTERN, object, "an ExdiThreadsContainer")
@REGISTRY.method(action='refresh', display="Refresh Target Processes")
def refresh_exdi_processes(node: sch.Schema('ExdiProcessContainer')):
"""Refresh the list of processes in the target kernel."""
with commands.open_tracked_tx('Refresh Processes'):
exdi_commands.ghidra_trace_put_processes_exdi()
@REGISTRY.method(action='refresh', display="Refresh Process Threads")
def refresh_exdi_threads(node: sch.Schema('ExdiThreadContainer')):
"""Refresh the list of threads in the process."""
pid = find_pid_by_obj(node)
with commands.open_tracked_tx('Refresh Threads'):
exdi_commands.ghidra_trace_put_threads_exdi(pid)

View file

@ -27,6 +27,7 @@ from pybag.dbgeng.callbacks import EventHandler
from pybag.dbgeng.idebugbreakpoint import DebugBreakpoint from pybag.dbgeng.idebugbreakpoint import DebugBreakpoint
from . import commands, util from . import commands, util
from .exdi import exdi_commands
ALL_EVENTS = 0xFFFF ALL_EVENTS = 0xFFFF
@ -65,6 +66,8 @@ class ProcessState(object):
if first: if first:
if util.is_kernel(): if util.is_kernel():
commands.create_generic("Sessions") commands.create_generic("Sessions")
if util.is_exdi() and util.dbg.use_generics is False:
commands.create_generic("Sessions[0].ExdiProcesses")
commands.put_processes() commands.put_processes()
commands.put_environment() commands.put_environment()
commands.put_threads() commands.put_threads()
@ -86,9 +89,13 @@ class ProcessState(object):
if first or hashable_frame not in self.visited: if first or hashable_frame not in self.visited:
self.visited.add(hashable_frame) self.visited.add(hashable_frame)
if first or self.regions: if first or self.regions:
if util.is_exdi():
exdi_commands.put_regions_exdi(commands.STATE)
commands.put_regions() commands.put_regions()
self.regions = False self.regions = False
if first or self.modules: if first or self.modules:
if util.is_exdi():
exdi_commands.put_kmodules_exdi(commands.STATE)
commands.put_modules() commands.put_modules()
self.modules = False self.modules = False
if first or self.breaks: if first or self.breaks:

View file

@ -0,0 +1,410 @@
<context>
<schema name="DbgRoot" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Configurable" />
<attribute name="Sessions" schema="SessionContainer" required="yes" fixed="yes" />
<attribute name="Settings" schema="ANY" />
<attribute name="State" schema="ANY" />
<attribute-alias from="_state" to="State" />
<attribute name="Utility" schema="ANY" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY"/>
</schema>
<schema name="SessionContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Configurable" />
<element schema="Session" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY"/>
</schema>
<schema name="Session" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<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="ExdiProcesses" schema="ExdiProcessContainer" required="no" fixed="yes" />
<attribute name="Processes" schema="ProcessContainer" required="yes" fixed="yes" />
<attribute name="Available" schema="AvailableContainer" required="yes" fixed="yes" />
<attribute name="Memory" schema="Memory" required="no" fixed="yes" />
<attribute name="Modules" schema="ModuleContainer" required="no" fixed="yes" />
<attribute name="_event_thread" schema="OBJECT" hidden="yes" />
<attribute name="_focus" schema="Selectable" required="yes" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY"/>
</schema>
<schema name="Selectable" elementResync="NEVER" attributeResync="NEVER">
<element schema="OBJECT" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="DebugBreakpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Aggregate" />
<element schema="VOID" />
<attribute name="Breakpoints" schema="BreakpointContainer" required="yes" />
<attribute name="_order" schema="INT" 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="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="AvailableContainer" canonical="yes" elementResync="ALWAYS" attributeResync="NEVER">
<interface name="Configurable" />
<element schema="Attachable" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="ProcessContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Configurable" />
<element schema="Process" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</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="Expression" schema="STRING" required="yes" hidden="yes" />
<attribute-alias from="_expression" to="Expression" />
<attribute name="Kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
<attribute-alias from="_kinds" to="Kinds" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="Range" schema="RANGE" />
<attribute-alias from="_range" to="Range" />
<attribute name="Enabled" schema="BOOL" required="yes" />
<attribute-alias from="_enabled" to="Enabled" />
<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" />
<attribute-alias from="_pid" to="PID" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Process" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<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="Debug" schema="DebugBreakpointContainer" required="yes" fixed="yes" />
<!-- attribute name="Breakpoints" schema="BreakpointLocationContainer" required="yes" fixed="yes" /-->
<attribute name="Exit Code" schema="LONG" />
<attribute-alias from="_exit_code" to="Exit Code" />
<attribute name="Environment" schema="Environment" required="yes" fixed="yes" />
<attribute name="Memory" schema="ProcessMemory" required="yes" fixed="yes" />
<attribute name="Modules" schema="ProcessModuleContainer" required="yes" fixed="yes" />
<attribute name="Handle" schema="STRING" fixed="yes" />
<attribute name="Id" schema="STRING" fixed="yes" />
<attribute name="PID" schema="LONG" hidden="yes" />
<attribute-alias from="_pid" to="PID" />
<attribute name="State" schema="EXECUTION_STATE" required="yes" hidden="yes" />
<attribute-alias from="_state" to="State" />
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="Environment" elementResync="NEVER" attributeResync="NEVER">
<interface name="Environment" />
<element schema="VOID" />
<attribute name="OS" schema="STRING" />
<attribute name="Arch" schema="STRING" />
<attribute name="Endian" schema="STRING" />
<attribute name="Debugger" schema="STRING" />
<attribute-alias from="_os" to="OS" />
<attribute-alias from="_arch" to="Arch" />
<attribute-alias from="_endian" to="Endian" />
<attribute-alias from="_debugger" to="Debugger" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="ProcessModuleContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<!-- interface name="ModuleContainer" /-->
<element schema="ProcessModule" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="ProcessMemory" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<!-- interface name="Memory" /-->
<element schema="ProcessMemoryRegion" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="BreakpointLocation" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointLocation" />
<element schema="VOID" />
<attribute name="Range" schema="RANGE" />
<attribute-alias from="_range" to="Range" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="BreakpointLocationContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointLocationContainer" />
<element schema="BreakpointLocation" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="ThreadContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Configurable" />
<element schema="Thread" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</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="Activatable" />
<interface name="Thread" />
<interface name="ExecutionStateful" />
<interface name="Steppable" />
<interface name="Aggregate" />
<element schema="VOID" />
<attribute name="Stack" schema="StackFramesContainer" required="yes" fixed="yes" />
<attribute name="Registers" schema="RegisterValueContainer" required="yes" fixed="yes" />
<attribute name="Environment" schema="ANY" fixed="yes" />
<attribute name="Id" schema="STRING" fixed="yes" />
<attribute name="TID" schema="LONG" />
<attribute-alias from="_tid" to="TID" />
<attribute name="State" schema="EXECUTION_STATE" required="yes" hidden="yes" />
<attribute-alias from="_state" to="State" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="Advance" schema="Method" required="yes" fixed="yes" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="ProcessModule" 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="Name" schema="STRING" />
<attribute-alias from="_module_name" to="Name" />
<attribute-alias from="_range" to="Range" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="ToDisplayString" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="ProcessMemoryRegion" elementResync="NEVER" attributeResync="NEVER">
<!-- interface name="MemoryRegion" /-->
<element schema="VOID" />
<attribute name="Base" schema="LONG" required="yes" fixed="yes" />
<attribute name="Object File" schema="STRING" fixed="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-alias from="_range" to="Range" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="SectionContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="SectionContainer" />
<element schema="Section" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="StackFramesContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Aggregate" />
<element schema="VOID" />
<attribute name="Frames" schema="Stack" required="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Stack" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Stack" />
<element schema="StackFrame" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="SymbolContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<interface name="SymbolNamespace" />
<element schema="Symbol" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Symbol" elementResync="NEVER" attributeResync="NEVER">
<interface name="Symbol" />
<element schema="VOID" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="StackFrame" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="StackFrame" />
<interface name="Aggregate" />
<element schema="VOID" />
<attribute name="Function" schema="STRING" hidden="yes" />
<attribute-alias from="_function" to="Function" />
<attribute name="Instruction Offset" schema="ADDRESS" required="yes" />
<attribute-alias from="_pc" to="Instruction Offset" />
<attribute name="Stack Offset" schema="ADDRESS" />
<attribute name="Return Offset" schema="ADDRESS" />
<attribute name="Frame Offset" schema="ADDRESS" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="Section" elementResync="NEVER" attributeResync="NEVER">
<interface name="Section" />
<element schema="VOID" />
<attribute name="Range" schema="RANGE" />
<attribute-alias from="_range" to="Range" />
<attribute name="Offset" schema="STRING" fixed="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="RegisterValueContainer" attributeResync="ONCE">
<interface name="RegisterContainer" />
<interface name="RegisterBank" />
<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="FloatingPoint" schema="RegisterBank" />
<attribute name="SIMD" schema="RegisterBank" />
<attribute name="User" schema="RegisterBank" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<interface name="RegisterBank" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="ExdiProcessContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<element schema="ExdiProcess" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_base" schema="INT" />
<attribute schema="VOID" />
</schema>
<schema name="ExdiProcess" 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="ExdiThreadContainer" required="yes" fixed="yes" />
<!-- attribute name="Modules" schema="ModuleContainer" required="yes" fixed="yes" /-->
<attribute name="PID" schema="LONG" />
<attribute-alias from="_pid" to="PID" />
<attribute name="State" schema="EXECUTION_STATE" required="yes" hidden="yes" />
<attribute-alias from="_state" to="State" />
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="OBJECT" />
</schema>
<schema name="ModuleContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<element schema="Module" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_base" schema="INT" />
<attribute schema="VOID" />
</schema>
<schema name="Module" elementResync="NEVER" attributeResync="NEVER">
<interface name="Module" />
<element schema="VOID" />
<attribute name="Range" schema="RANGE" />
<attribute name="Name" schema="STRING" />
<attribute-alias from="_module_name" to="Name" />
<attribute-alias from="_range" to="Range" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="OBJECT" />
</schema>
<schema name="ExdiThreadContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<element schema="ExdiThread" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_base" schema="INT" />
<attribute schema="VOID" />
</schema>
<schema name="ExdiThread" elementResync="NEVER" attributeResync="NEVER">
<interface name="Thread" />
<interface name="ExecutionStateful" />
<interface name="Steppable" />
<interface name="Aggregate" />
<element schema="VOID" />
<attribute name="TID" schema="LONG" />
<attribute-alias from="_tid" to="TID" />
<attribute name="State" schema="EXECUTION_STATE" required="yes" hidden="yes" />
<attribute-alias from="_state" to="State" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="OBJECT" />
</schema>
<schema name="Memory" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<interface name="Memory" />
<element schema="MemoryRegion" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="MemoryRegion" elementResync="NEVER" attributeResync="NEVER">
<interface name="MemoryRegion" />
<element schema="VOID" />
<attribute name="_memory" schema="Memory" />
<attribute name="Range" schema="RANGE" required="yes" />
<attribute-alias from="_range" to="Range" />
<attribute name="Size" schema="STRING" fixed="yes" />
<attribute name="_readable" schema="BOOL" hidden="yes" />
<attribute name="_writable" schema="BOOL" hidden="yes" />
<attribute name="_executable" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
</context>

View file

@ -238,6 +238,7 @@ class GhidraDbg(object):
]: ]:
setattr(self, name, self.eng_thread(getattr(base, name))) setattr(self, name, self.eng_thread(getattr(base, name)))
self.IS_KERNEL = False self.IS_KERNEL = False
self.IS_EXDI = False
def _new_base(self): def _new_base(self):
self._protected_base = AllDbg() self._protected_base = AllDbg()
@ -455,6 +456,8 @@ def get_breakpoints():
@dbg.eng_thread @dbg.eng_thread
def selected_process(): def selected_process():
try: try:
if is_exdi():
return 0
if is_kernel(): if is_kernel():
do = dbg._base._systems.GetCurrentProcessDataOffset() do = dbg._base._systems.GetCurrentProcessDataOffset()
id = c_ulong() id = c_ulong()
@ -472,6 +475,8 @@ def selected_process():
@dbg.eng_thread @dbg.eng_thread
def selected_process_space(): def selected_process_space():
try: try:
if is_exdi():
return 0
if is_kernel(): if is_kernel():
return dbg._base._systems.GetCurrentProcessDataOffset() return dbg._base._systems.GetCurrentProcessDataOffset()
return selected_process() return selected_process()
@ -759,6 +764,8 @@ def split_path(pathString):
segs = pathString.split(".") segs = pathString.split(".")
for s in segs: for s in segs:
if s.endswith("]"): if s.endswith("]"):
if "[" not in s:
print(f"Missing terminator: {s}")
index = s.index("[") index = s.index("[")
list.append(s[:index]) list.append(s[:index])
list.append(s[index:]) list.append(s[index:])
@ -907,3 +914,9 @@ def set_kernel(value):
def is_kernel(): def is_kernel():
return dbg.IS_KERNEL return dbg.IS_KERNEL
def set_exdi(value):
dbg.IS_EXDI = value
def is_exdi():
return dbg.IS_EXDI

View file

@ -825,6 +825,44 @@ bdcedit /dbgsettings NET HOSTIP:IP PORT:54321 KEY:1.1.1.1
'net:port=54321,key=1.1.1.1'.'</LI> 'net:port=54321,key=1.1.1.1'.'</LI>
</UL> </UL>
<UL>
<LI><B>Type</B>: The type of kernel connection, either "Remote", "Local", or "EXDI".
"Remote", the most common type, indicates two-machine debugging over various
possible connection media, e.g. Ethernet, serial, USB, etc. "Local" is used for limited
introspection into the target on which the debugger is running. "EXDI" is arguably
the most exotic type - it essentially simulates the normal "Remote" connection using
the gdb Remote Serial Protocol. It can be used when connecting to gdbstubs in
platforms, such as QEMU, VMWare, Trace32, etc.</LI>
</UL>
<H4>EXDI</H4>
<P>Setup for EXDI connections is fairly complicated and difficult to get correct.
The argument string typically should be something like:</P>
<UL style='list-style-type: none'>
<LI>
<PRE>
exdi:CLSID={29f9906e-9dbe-4d4b-b0fb-6acf7fb6d014},Kd=Guess,DataBreaks=Exdi
</PRE>
</LI>
</UL>
<P>The CLSID here should match the CLSID in the <B>exdiConfigData.xml</B> file in the debugger architectural directory. If windbg has been
run using EXDI at some point, there will also be an entry in the System Registry for this CLSID. The InprocServer32 subentry
for this CLSID in the Registry should point to a copy of ExdiGdbSrv.dll, typically the one in the same directory. This DLL
must reside somewhere that the debugger has permission to load from, i.e. not in the WindowsApps directory tree.
The <B>exdiConfigData</B> file should be configured for the target you're using. We heavily recommend using <B>displayCommPackets==yes</B>,
as many of the tasks take considerable time, and this is the only indicator of progress.
</P>
<P>The <B>Kd=Guess</B> parameter causes the underlying engine to scan memory for the kernel's base address, which will probably not
be provided by the gdbstub. (<B>Kd=NtBaseAddr</B> is also a valid option, as is eliminating the parameter, but, currently, we have no
idea how to point the configuration at a correct value. Using this option will cause the load to spin pointlessly.) If you can,
we highly recommend breaking the target near the base address, as the search proceeds down through memory starting at the current
program counter. If the difference between the PC and the base address is large, the loading process will punt before useful
values are detected. If anyone understand how to extend this search (or knows how to set the base address to sidestep the scan),
we would really love some guidance.
</P>
<H3><A name="dbgeng_ttd"></A>TTD (Time-Travel Debugging)</H3> <H3><A name="dbgeng_ttd"></A>TTD (Time-Travel Debugging)</H3>
<P>This is a nascent extension to our launcher for the Windows Debugger. The launcher itself <P>This is a nascent extension to our launcher for the Windows Debugger. The launcher itself

View file

@ -197,11 +197,17 @@ public class DebuggerRegionsPanel extends AbstractObjectsTableBasedPanel<TraceOb
List<String> seedPath = object.getCanonicalPath().getKeyList(); List<String> seedPath = object.getCanonicalPath().getKeyList();
List<String> processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath); List<String> processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath);
if (processPath != null) { if (processPath != null) {
return successorRegions(rootSchema, processPath); ModelQuery result = successorRegions(rootSchema, processPath);
if (!result.isEmpty()) {
return result;
}
} }
List<String> memoryPath = rootSchema.searchForSuitable(TargetMemory.class, seedPath); List<String> memoryPath = rootSchema.searchForSuitable(TargetMemory.class, seedPath);
if (memoryPath != null) { if (memoryPath != null) {
return successorRegions(rootSchema, memoryPath); ModelQuery result = successorRegions(rootSchema, memoryPath);
if (!result.isEmpty()) {
return result;
}
} }
return successorRegions(rootSchema, List.of()); return successorRegions(rootSchema, List.of());
} }

View file

@ -240,4 +240,8 @@ public class ModelQuery {
} }
return false; return false;
} }
public boolean isEmpty() {
return predicates.isEmpty();
}
} }

View file

@ -242,13 +242,19 @@ public class DebuggerModulesPanel extends AbstractObjectsTableBasedPanel<TraceOb
List<String> seedPath = object.getCanonicalPath().getKeyList(); List<String> seedPath = object.getCanonicalPath().getKeyList();
List<String> processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath); List<String> processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath);
if (processPath != null) { if (processPath != null) {
return successorModules(rootSchema, processPath); ModelQuery result = successorModules(rootSchema, processPath);
if (!result.isEmpty()) {
return result;
}
} }
List<String> containerPath = List<String> containerPath =
rootSchema.searchForSuitableContainer(TargetModule.class, seedPath); rootSchema.searchForSuitableContainer(TargetModule.class, seedPath);
if (containerPath != null) { if (containerPath != null) {
return successorModules(rootSchema, containerPath); ModelQuery result = successorModules(rootSchema, containerPath);
if (!result.isEmpty()) {
return result;
}
} }
return successorModules(rootSchema, List.of()); return successorModules(rootSchema, List.of());
} }

View file

@ -221,14 +221,20 @@ public class DebuggerSectionsPanel extends AbstractObjectsTableBasedPanel<TraceO
List<String> seedPath = object.getCanonicalPath().getKeyList(); List<String> seedPath = object.getCanonicalPath().getKeyList();
List<String> processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath); List<String> processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath);
if (processPath != null) { if (processPath != null) {
return successorSections(rootSchema, processPath); ModelQuery result = successorSections(rootSchema, processPath);
if (!result.isEmpty()) {
return result;
}
} }
// Yes, anchor on the *module* container when searching for sections // Yes, anchor on the *module* container when searching for sections
List<String> containerPath = List<String> containerPath =
rootSchema.searchForSuitableContainer(TargetModule.class, seedPath); rootSchema.searchForSuitableContainer(TargetModule.class, seedPath);
if (containerPath != null) { if (containerPath != null) {
return successorSections(rootSchema, containerPath); ModelQuery result = successorSections(rootSchema, containerPath);
if (!result.isEmpty()) {
return result;
}
} }
return successorSections(rootSchema, List.of()); return successorSections(rootSchema, List.of());
} }

View file

@ -347,13 +347,19 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
List<String> seedPath = object.getCanonicalPath().getKeyList(); List<String> seedPath = object.getCanonicalPath().getKeyList();
List<String> processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath); List<String> processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath);
if (processPath != null) { if (processPath != null) {
return successorThreads(rootSchema, processPath); ModelQuery result = successorThreads(rootSchema, processPath);
if (!result.isEmpty()) {
return result;
}
} }
List<String> containerPath = List<String> containerPath =
rootSchema.searchForSuitableContainer(TargetThread.class, seedPath); rootSchema.searchForSuitableContainer(TargetThread.class, seedPath);
if (containerPath != null) { if (containerPath != null) {
return successorThreads(rootSchema, containerPath); ModelQuery result = successorThreads(rootSchema, containerPath);
if (!result.isEmpty()) {
return result;
}
} }
return successorThreads(rootSchema, List.of()); return successorThreads(rootSchema, List.of());
} }