GP-5407: from review

GP-5407: from review
GP-5407: rebase
GP-5407: minor fix
GP-5407: new launcherGP-5407: new launcherGP-5407: working (?) open traceGP-5407: modules sort of worksGP-5407: mostly sane (threads+modules)GP-5407: start on methodsGP-5407: refresh fixGP-5407: update on refreshGP-5407: need a better fix for displaysGP-5407: backwards methodsGP-5407: add actionGP-5407: add actionGP-5407: working back buttonGP-5407: experimentingGP-5407: events workingGP-5407: clearer optionsGP-5407: minorGP-5407: actions->methods (step_ext)GP-5407: iconsGP-5407: iconsGP-5407: icons pt.2GP-5407: fix for KMEM/UMEMGP-5407: deprecate pyttdGP-5407: deprecate pyttdGP-5407: launchers updateGP-5407: ??
This commit is contained in:
d-millar 2025-03-04 13:02:21 -05:00
parent 136a944796
commit a2e42f5fe2
32 changed files with 829 additions and 3419 deletions

View file

@ -6,8 +6,8 @@ README.md||GHIDRA||||END|
data/debugger-launchers/kernel-dbgeng.bat||GHIDRA||||END|
data/debugger-launchers/local-dbgeng-attach.bat||GHIDRA||||END|
data/debugger-launchers/local-dbgeng-ext.bat||GHIDRA||||END|
data/debugger-launchers/local-dbgeng-trace.bat||GHIDRA||||END|
data/debugger-launchers/local-dbgeng.bat||GHIDRA||||END|
data/debugger-launchers/local-ttd.bat||GHIDRA||||END|
data/debugger-launchers/remote-dbgeng.bat||GHIDRA||||END|
data/debugger-launchers/svrcx-dbgeng.bat||GHIDRA||||END|
src/main/py/LICENSE||GHIDRA||||END|
@ -17,4 +17,3 @@ src/main/py/pyproject.toml||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_exdi.xml||GHIDRA||||END|
src/main/py/src/ghidrattd/schema.xml||GHIDRA||||END|

View file

@ -0,0 +1,21 @@
::@title dbgeng-trace
::@desc <html><body width="300px">
::@desc <h3>Open trace with <tt>dbgeng</tt> (in a Python interpreter)</h3>
::@desc <p>
::@desc This will open a WinDbg TTD trace of the target on the local machine using <tt>dbgeng.dll</tt>.
::@desc For setup instructions, press <b>F1</b>.
::@desc </p>
::@desc </body></html>
::@menu-group local
::@icon icon.debugger
::@help TraceRmiLauncherServicePlugin#dbgeng_trace
::@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
::@env OPT_TARGET_TRACE:file="" "Trace (.run)" "The target trace image"
::@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)."
@echo off
set USE_TTD=true
"%OPT_PYTHON_EXE%" -i ..\support\local-dbgeng-trace.py

View file

@ -1,21 +0,0 @@
::@title ttd
::@desc <html><body width="300px">
::@desc <h3>Launch with <tt>ttd</tt> (in a Python interpreter)</h3>
::@desc <p>
::@desc This will launch the target on the local machine for time-travel debugging.
::@desc For setup instructions, press <b>F1</b>.
::@desc </p>
::@desc </body></html>
::@menu-group local
::@icon icon.debugger
::@help TraceRmiLauncherServicePlugin#dbgeng_ttd
::@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
::@env OPT_TARGET_IMG:file!="" "Trace (.run)" "A trace associated with the target binary executable"
::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target"
::@env OPT_USE_DBGMODEL:bool=true "Use dbgmodel" "Load and use dbgmodel.dll if it is available."
::@env OPT_DBGMODEL_PATH:dir="" "Path to dbgeng.dll & \\ttd" "Path containing dbgeng and associated DLLS (if not Windows Kits)."
@echo off
"%OPT_PYTHON_EXE%" -i ..\support\local-ttd.py

View file

@ -0,0 +1,73 @@
## ###
# 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 os
import sys
home = os.getenv('GHIDRA_HOME')
if os.path.isdir(f'{home}\\ghidra\\.git'):
sys.path.append(
f'{home}\\ghidra\\Ghidra\\Debug\\Debugger-agent-dbgeng\\build\\pypkg\\src')
sys.path.append(
f'{home}\\ghidra\\Ghidra\\Debug\\Debugger-rmi-trace\\build\\pypkg\\src')
elif os.path.isdir(f'{home}\\.git'):
sys.path.append(
f'{home}\\Ghidra\\Debug\\Debugger-agent-dbgeng\\build\\pypkg\\src')
sys.path.append(
f'{home}\\Ghidra\\Debug\\Debugger-rmi-trace\\build\\pypkg\\src')
else:
sys.path.append(
f'{home}\\Ghidra\\Debug\\Debugger-agent-dbgeng\\pypkg\\src')
sys.path.append(f'{home}\\Ghidra\\Debug\\Debugger-rmi-trace\\pypkg\\src')
def main():
# Delay these imports until sys.path is patched
from ghidradbg import commands as cmd
from pybag.dbgeng import core as DbgEng
from ghidradbg.hooks import on_state_changed
from ghidradbg.util import dbg
# So that the user can re-enter by typing repl()
global repl
repl = cmd.repl
cmd.ghidra_trace_connect(os.getenv('GHIDRA_TRACE_RMI_ADDR'))
target = os.getenv('OPT_TARGET_TRACE')
if target is None or target == "":
print("dbgeng requires a target trace - please try again.")
cmd.ghidra_trace_disconnect()
return
cmd.ghidra_trace_open(target, start_trace=False)
# TODO: HACK
try:
dbg.wait()
except KeyboardInterrupt as ki:
dbg.interrupt()
cmd.ghidra_trace_start(target)
cmd.ghidra_trace_sync_enable()
on_state_changed(DbgEng.DEBUG_CES_EXECUTION_STATUS, DbgEng.DEBUG_STATUS_BREAK)
cmd.repl()
if __name__ == '__main__':
main()

View file

@ -1,58 +0,0 @@
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
import os
import sys
home = os.getenv('GHIDRA_HOME')
if os.path.isdir(f'{home}\\ghidra\\.git'):
sys.path.append(
f'{home}\\ghidra\\Ghidra\\Debug\\Debugger-agent-dbgeng\\build\\pypkg\\src')
sys.path.append(
f'{home}\\ghidra\\Ghidra\\Debug\\Debugger-rmi-trace\\build\\pypkg\\src')
elif os.path.isdir(f'{home}\\.git'):
sys.path.append(
f'{home}\\Ghidra\\Debug\\Debugger-agent-dbgeng\\build\\pypkg\\src')
sys.path.append(
f'{home}\\Ghidra\\Debug\\Debugger-rmi-trace\\build\\pypkg\\src')
else:
sys.path.append(
f'{home}\\Ghidra\\Debug\\Debugger-agent-dbgeng\\pypkg\\src')
sys.path.append(f'{home}\\Ghidra\\Debug\\Debugger-rmi-trace\\pypkg\\src')
def main():
# Delay these imports until sys.path is patched
from ghidrattd import commands as cmd
from ghidrattd import hooks
###from ghidrattd.util import dbg
cmd.ghidra_trace_connect(os.getenv('GHIDRA_TRACE_RMI_ADDR'))
args = os.getenv('OPT_TARGET_ARGS')
if args:
args = ' ' + args
cmd.ghidra_trace_create(
os.getenv('OPT_TARGET_IMG') + args, start_trace=True)
cmd.ghidra_trace_sync_enable()
hooks.on_stop()
cmd.repl()
if __name__ == '__main__':
main()

View file

@ -24,13 +24,14 @@ import time
from comtypes import c_ulong
from ghidratrace import sch
from ghidratrace.client import Client, Address, AddressRange, TraceObject
from ghidratrace.client import Client, Address, AddressRange, Lifespan, TraceObject
from pybag import pydbg, userdbg, kerneldbg
from pybag.dbgeng import core as DbgEng
from pybag.dbgeng import exception
from . import util, arch, methods, hooks
from .dbgmodel.imodelobject import ModelObjectKind
if util.is_exdi():
from .exdi import exdi_commands, exdi_methods
@ -65,6 +66,9 @@ SECTIONS_ADD_PATTERN = '.Sections'
SECTION_KEY_PATTERN = '[{secname}]'
SECTION_ADD_PATTERN = SECTIONS_ADD_PATTERN + SECTION_KEY_PATTERN
GENERIC_KEY_PATTERN = '[{key}]'
TTD_PATTERN = 'State.DebuggerVariables.{var}.TTD'
DESCRIPTION_PATTERN = '[{major}] {type}'
# TODO: Symbols
@ -221,7 +225,8 @@ def start_trace(name):
variant = " (dbgmodel)" if using_dbgmodel else " (dbgeng)"
with STATE.trace.open_tx("Create Root Object"):
root = STATE.trace.create_root_object(schema_xml, 'DbgRoot')
root.set_value('_display', util.DBG_VERSION.full + ' via pybag' + variant)
root.set_value('_display', util.DBG_VERSION.full +
' via pybag' + variant)
if util.dbg.use_generics:
put_generic(root)
util.set_convenience_variable('_ghidra_tracing', "true")
@ -294,7 +299,8 @@ def ghidra_trace_create_ext(command=None, initialDirectory='.', envVariables="\0
envVariables = None
if envVariables is not None and envVariables.endswith("/0/0") is False:
envVariables += "/0/0"
dbg._client.CreateProcess2(command, options, initialDirectory, envVariables)
dbg._client.CreateProcess2(
command, options, initialDirectory, envVariables)
dbg._control.AddEngineOptions(int(engine_options))
if start_trace:
ghidra_trace_start(command)
@ -348,6 +354,19 @@ def ghidra_trace_connect_server(options=None):
dbg._client.ConnectProcessServer(enc_options)
@util.dbg.eng_thread
def ghidra_trace_open(command=None, initial_break=True, timeout=DbgEng.WAIT_INFINITE, start_trace=True):
"""
Create a session.
"""
dbg = util.dbg._base
if command != None:
util.open_trace_or_dump(command)
if start_trace:
ghidra_trace_start(command)
@util.dbg.eng_thread
def ghidra_trace_kill():
"""
@ -905,7 +924,8 @@ def activate(path=None):
if frame is None:
path = THREAD_PATTERN.format(procnum=nproc, tnum=nthrd)
else:
path = FRAME_PATTERN.format(procnum=nproc, tnum=nthrd, level=frame)
path = FRAME_PATTERN.format(
procnum=nproc, tnum=nthrd, level=frame)
trace.proxy_object_path(path).activate()
@ -1201,8 +1221,8 @@ def put_regions():
if hasattr(r, 'Name') and r.Name is not None:
regobj.set_value('_display', r.Name)
regobj.insert()
STATE.trace.proxy_object_path(
MEMORY_PATTERN.format(procnum=nproc)).retain_values(keys)
#STATE.trace.proxy_object_path(
# MEMORY_PATTERN.format(procnum=nproc)).retain_values(keys)
def ghidra_trace_put_regions():
@ -1257,6 +1277,24 @@ def put_modules():
procnum=nproc)).retain_values(mod_keys)
def get_module(key, mod):
nproc = util.selected_process()
modmap = util.get_attributes(mod)
base = util.get_value(modmap["Address"])
size = util.get_value(modmap["Size"])
name = util.get_value(modmap["Name"])
mpath = MODULE_PATTERN.format(procnum=nproc, modpath=hex(base))
modobj = STATE.trace.create_object(mpath)
mapper = STATE.trace.memory_mapper
base_base, base_addr = mapper.map(nproc, base)
if base_base != base_addr.space:
STATE.trace.create_overlay_space(base_base, base_addr.space)
modobj.set_value('Range', base_addr.extend(size))
modobj.set_value('Name', name)
modobj.set_value('_display','{} {:x} {}'.format(key, base, name))
return modobj
def ghidra_trace_put_modules():
"""
Gather object files, if applicable, and write to the trace's Modules
@ -1334,6 +1372,24 @@ def put_event_thread(nthrd=None):
STATE.trace.proxy_object_path('').set_value('_event_thread', tobj)
def get_thread(key, thread):
pid = util.selected_process()
tmap = util.get_attributes(thread)
tid = int(key[1:len(key)-1])
radix = util.get_convenience_variable('output-radix')
if radix == 'auto':
radix = 16
tpath = THREAD_PATTERN.format(procnum=pid, tnum=tid)
tobj = STATE.trace.create_object(tpath)
tobj.set_value('TID', tid, span=Lifespan(0))
tidstr = ('0x{:x}' if radix == 16 else '0{:o}' if radix ==
8 else '{}').format(tid)
tobj.set_value('_short_display', '[{}:{}]'.format(
pid, tidstr), span=Lifespan(0))
tobj.set_value('_display', '[{}]'.format(tidstr), span=Lifespan(0))
return tobj
def ghidra_trace_put_threads():
"""
Put the current process's threads into the Ghidra trace
@ -1402,35 +1458,50 @@ def ghidra_trace_put_frames():
put_frames()
def update_by_container(np, keyval, obj):
index = keyval[0]
key = ''
def update_key(np, keyval):
"""
This should set the modified key
"""
key = keyval[0]
if np.endswith("Modules"):
key = '[{:d}]'.format(key)
mo = util.get_object(np+key+".BaseAddress")
key = hex(util.get_value(mo))
return key
def update_by_container(np, keyval, to):
"""
Sets non-generic variables by container
"""
key = keyval[0]
disp = ''
if np.endswith("Processes") or np.endswith("Threads"):
istate = compute_proc_state(index)
obj.set_value('State', istate)
istate = compute_proc_state(key)
to.set_value('State', istate)
if np.endswith("Sessions"):
key = '[{:x}]'.format(index)
disp = '[{:x}]'.format(key)
if np.endswith("Processes"):
create_generic(obj.path)
obj.set_value('PID', index)
create_generic(obj.path + ".Memory")
create_generic(to.path)
to.set_value('PID', key)
create_generic(to.path + ".Memory")
if util.is_kernel():
key = '[{:x}]'.format(index)
disp = '[{:x}]'.format(key)
else:
id = util.get_proc_id(index)
key = '{:x} [{:x}]'.format(id, index)
id = util.get_proc_id(key)
disp = '{:x} [{:x}]'.format(id, key)
if np.endswith("Breakpoints"):
create_generic(obj.path)
create_generic(to.path)
if np.endswith("Threads"):
create_generic(obj.path)
obj.set_value('TID', index)
create_generic(to.path)
to.set_value('TID', key)
if util.is_kernel():
key = '[{:x}]'.format(index)
disp = '[{:x}]'.format(key)
else:
id = util.get_thread_id(index)
key = '{:x} [{:x}]'.format(id, index)
id = util.get_thread_id(key)
disp = '{:x} [{:x}]'.format(id, key)
if np.endswith("Frames"):
mo = util.get_object(obj.path)
mo = util.get_object(to.path)
map = util.get_attributes(mo)
if 'Attributes' in map:
attr = map["Attributes"]
@ -1438,41 +1509,52 @@ def update_by_container(np, keyval, obj):
map = util.get_attributes(attr)
pc = util.get_value(map["InstructionOffset"])
(pc_base, pc_addr) = map_address(pc)
obj.set_value('Instruction Offset', pc_addr)
key = '#{:x} 0x{:x}'.format(index, pc)
to.set_value('Instruction Offset', pc_addr)
disp = '#{:x} 0x{:x}'.format(key, pc)
if np.endswith("Modules"):
create_generic(obj.path)
mo = util.get_object(obj.path)
modobjpath=np+'[{:d}]'.format(key)
create_generic(to.path, modobjpath=modobjpath)
mo = util.get_object(modobjpath)
map = util.get_attributes(mo)
base = util.get_value(map["BaseAddress"])
size = util.get_value(map["Size"])
name = util.get_value(map["Name"])
obj.set_value('Name', '{}'.format(name))
to.set_value('Name', '{}'.format(name))
(base_base, base_addr) = map_address(base)
obj.set_value('Range', base_addr.extend(size))
key = '{:x} {:x} {}'.format(index, base, name)
disp = util.to_display_string(keyval[1])
if disp is not None:
key += " " + disp
if key is not None and key != "":
obj.set_value('_display', key)
to.set_value('Range', base_addr.extend(size))
disp = '{:x} {:x} {}'.format(key, base, name)
disp0 = util.to_display_string(keyval[1])
if disp0 is not None:
disp += " " + disp0
if disp is not None and disp != "":
to.set_value('_display', disp)
def create_generic(path):
def create_generic(path, modobjpath=None):
obj = STATE.trace.create_object(path)
result = put_generic(obj, modobjpath)
obj.insert()
result = put_generic(obj)
return result
def put_generic(node):
def put_generic_from_node(node):
obj = STATE.trace.create_object(node.path)
result = put_generic(obj, None)
obj.insert()
return result
def put_generic(node, modobjpath=None):
# print(f"put_generic: {node}")
nproc = util.selected_process()
if nproc is None:
return
nthrd = util.selected_thread()
if modobjpath is None:
mo = util.get_object(node.path)
else:
mo = util.get_object(modobjpath)
mapper = STATE.trace.register_mapper
attributes = util.get_attributes(mo)
@ -1508,8 +1590,8 @@ def put_generic(node):
keys = []
if elements is not None:
for el in elements:
index = el[0]
key = GENERIC_KEY_PATTERN.format(key=index)
key = update_key(node.path, el)
key = GENERIC_KEY_PATTERN.format(key=key)
lpath = node.path + key
lobj = STATE.trace.create_object(lpath)
update_by_container(node.path, el, lobj)
@ -1557,7 +1639,132 @@ def ghidra_trace_put_generic(node):
STATE.require_tx()
with STATE.client.batch() as b:
put_generic(node)
put_generic_from_node(node)
def init_ttd():
# print(f"put_events: {node}")
with open_tracked_tx('Init TTDState'):
ttd = util.ttd
nproc = util.selected_process()
path = TTD_PATTERN.format(var="curprocess")+".Lifetime"
(values, keys) = create_generic(path)
lifetime = util.get_object(path)
map = util.get_attributes(lifetime)
ttd._first = map["MinPosition"]
ttd._last = map["MaxPosition"]
ttd._lastmajor = util.pos2split(ttd._last)[0]
ttd._lastpos = ttd._first
ttd.MAX_STEP = 0xFFFFFFFFFFFFFFFE
ghidra_trace_set_snap(util.pos2snap(ttd._first))
def put_events():
ttd = util.ttd
nproc = util.selected_process()
path = TTD_PATTERN.format(var="curprocess")+".Events"
(values, keys) = create_generic(path)
for k in keys:
event = util.get_object(path+k)
map = util.get_attributes(event)
type = util.get_value(map["Type"])
pos = map["Position"]
(major, minor) = util.pos2split(pos)
ttd.events[major] = event
ttd.evttypes[major] = type
with open_tracked_tx('Populate events'):
index = util.pos2snap(pos)
STATE.trace.snapshot(DESCRIPTION_PATTERN.format(major=major, type=type), snap=index)
if type == "ModuleLoaded" or type == "ModuleUnloaded":
mod = map["Module"]
mobj = get_module(k, mod)
if type == "ModuleLoaded":
mobj.insert(span=Lifespan(index))
else:
mobj.remove(span=Lifespan(index))
if type == "ThreadCreated" or type == "ThreadTerminated":
t = map["Thread"]
tobj = get_thread(k, t)
if type == "ThreadCreated":
tobj.insert(span=Lifespan(index))
else:
tobj.remove(span=Lifespan(index))
hooks.on_stop()
def ghidra_trace_put_events(node):
"""
Put the event set the Ghidra trace
"""
STATE.require_tx()
with STATE.client.batch() as b:
put_events()
def put_events_custom(prefix, cmd):
result = util.dbg.cmd("{prefix}.{cmd}".format(prefix=prefix, cmd=cmd))
if result.startswith("Error"):
print(result)
return
nproc = util.selected_process()
mapper = STATE.trace.memory_mapper
path = TTD_PATTERN.format(var="cursession")+".CustomEvents"
obj = STATE.trace.create_object(path)
index = 0
addr = size = start = stop = None
attrs = {}
keys = []
for l in result.split('\n'):
split = l.split(":")
id = split[0].strip()
if id == "Address":
addr = int(split[1].strip(),16)
elif id == "Size":
size = int(split[1].strip(),16)
elif id == "TimeStart":
start = util.mm2snap(int(split[1],16), int(split[2],16))
elif id == "TimeEnd":
stop = util.mm2snap(int(split[1],16), int(split[2],16))
elif " : " in l:
attrs[id] = l[l.index(":"):].strip()
if addr is not None and size is not None and start is not None and stop is not None:
with open_tracked_tx('Populate events'):
key = "[{:x}]".format(addr)
STATE.trace.snapshot("[{:x}] EventCreated {} ".format(start, key), snap=start)
if start > stop:
print(f"ERROR: {start}:{stop}")
continue
span=Lifespan(start, stop)
rpath = REGION_PATTERN.format(procnum=nproc, start=addr)
keys.append(REGION_KEY_PATTERN.format(start=addr))
regobj = STATE.trace.create_object(rpath)
(start_base, start_addr) = map_address(addr)
rng = start_addr.extend(size)
regobj.set_value('Range', rng, span=span)
regobj.set_value('_range', rng, span=span)
regobj.set_value('_display', hex(addr), span=span)
regobj.set_value('_cmd', cmd)
for (k,v) in attrs.items():
regobj.set_value(k, v, span=span)
regobj.insert(span=span)
keys.append(key)
index += 1
addr = size = start = stop = None
attrs = {}
obj.insert()
STATE.trace.proxy_object_path(TTD_PATTERN.format(var="cursession")).retain_values(keys)
hooks.on_stop()
def ghidra_trace_put_events_custom(prefix, cmd):
"""
Generate events by cmd and put them into the Ghidra trace
"""
STATE.require_tx()
with STATE.client.batch() as b:
put_events_custom(prefix, cmd)
def ghidra_trace_put_all():

View file

@ -26,6 +26,7 @@ from . import imodelobject as mo
class ModelIterator(object):
def __init__(self, iter):
self._iter = iter
self._index = 0
iter.AddRef()
# ModelIterator
@ -39,10 +40,17 @@ class ModelIterator(object):
byref(indexer), byref(metadata))
except COMError as ce:
return None
if "ptr=0x0" in str(indexer):
next = (self._index, mo.ModelObject(object))
self._index += 1
return next
index = mo.ModelObject(indexer)
ival = index.GetIntrinsicValue()
if ival is None:
return (0, mo.ModelObject(object))
next = (self._index, mo.ModelObject(object))
self._index += 1
return next
return (ival.value, mo.ModelObject(object))
def Reset(self):

View file

@ -0,0 +1,45 @@
## ###
# 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 ctypes import *
from comtypes import COMError
from comtypes.gen import DbgMod
from comtypes.hresult import S_OK, S_FALSE
from pybag.dbgeng import exception
from . import imodelobject as mo
class ModelMethod(object):
def __init__(self, method):
self._method = method
method.AddRef()
# ModelMethod
def Call(self, object, argcount=0, arguments=None):
if argcount == 0:
arguments = POINTER(DbgMod.IModelObject)()
result = POINTER(DbgMod.IModelObject)()
metadata = POINTER(DbgMod.IKeyStore)()
try:
self._method.Call(byref(object), argcount, byref(arguments),
byref(result), byref(metadata))
except COMError as ce:
return None
return mo.ModelObject(result)

View file

@ -35,7 +35,7 @@ class RawEnumerator(object):
self._keys = None
return cnt
# KeyEnumerator
# RawEnumerator
def GetNext(self):
key = BSTR()

View file

@ -57,12 +57,12 @@ class ProcessState(object):
self.visited = set()
self.waiting = False
def record(self, description=None):
def record(self, description=None, snap=None):
# print("RECORDING")
first = self.first
self.first = False
if description is not None:
commands.STATE.trace.snapshot(description)
commands.STATE.trace.snapshot(description, snap=snap)
if first:
if util.is_kernel():
commands.create_generic("Sessions")
@ -71,6 +71,9 @@ class ProcessState(object):
commands.put_processes()
commands.put_environment()
commands.put_threads()
if util.is_trace():
commands.init_ttd()
#commands.put_events()
if self.threads:
commands.put_threads()
self.threads = False
@ -106,10 +109,10 @@ class ProcessState(object):
commands.put_processes(running=True)
commands.put_threads(running=True)
def record_exited(self, exit_code, description=None):
def record_exited(self, exit_code, description=None, snap=None):
# print("RECORD_EXITED")
if description is not None:
commands.STATE.trace.snapshot(description)
commands.STATE.trace.snapshot(description, snap=snap)
proc = util.selected_process()
ipath = commands.PROCESS_PATTERN.format(procnum=proc)
procobj = commands.STATE.trace.proxy_object_path(ipath)
@ -381,13 +384,37 @@ def on_stop(*args):
return
state = PROC_STATE[proc]
state.visited.clear()
snap = update_position()
with commands.STATE.client.batch():
with trace.open_tx("Stopped"):
state.record("Stopped")
state.record("Stopped", snap)
commands.put_event_thread()
commands.activate()
def update_position():
"""Update the position"""
cursor = util.get_cursor()
if cursor is None:
return None
pos = cursor.get_position()
lpos = util.get_last_position()
rng = range(pos.major, lpos.major)
if pos.major > lpos.major:
rng = range(lpos.major, pos.major)
for i in rng:
type = util.get_event_type(i)
if type == "modload" or type == "modunload":
on_modules_changed()
break
for i in rng:
type = util.get_event_type(i)
if type == "threadcreated" or type == "threadterm":
on_threads_changed()
util.set_last_position(pos)
return util.pos2snap(pos)
def on_exited(proc):
# print("ON EXITED")
if proc not in PROC_STATE:

View file

@ -26,7 +26,6 @@ from pybag.dbgeng import core as DbgEng, exception
from . import util, commands
REGISTRY = MethodRegistry(ThreadPoolExecutor(
max_workers=1, thread_name_prefix='MethodRegistry'))
@ -214,7 +213,7 @@ def evaluate(
@REGISTRY.method(action='refresh', display="Refresh", condition=util.dbg.use_generics)
def refresh_generic(node: sch.OBJECT):
"""List processes on pydbg's host system."""
"""List the children for a generic node."""
with commands.open_tracked_tx('Refresh Generic'):
commands.ghidra_trace_put_generic(node)
@ -294,6 +293,15 @@ def refresh_modules(node: sch.Schema('ModuleContainer')):
commands.ghidra_trace_put_modules()
@REGISTRY.method(action='refresh', display='Refresh Events')
def refresh_events(node: sch.Schema('State')):
"""
Refresh the events list for a trace.
"""
with commands.open_tracked_tx('Refresh Events'):
commands.ghidra_trace_put_events(node)
@REGISTRY.method(action='activate')
def activate_process(process: sch.Schema('Process')):
"""Switch to the process."""
@ -411,6 +419,14 @@ def go(process: sch.Schema('Process')):
util.dbg.run_async(lambda: dbg().go())
@REGISTRY.method(action='step_ext', display='Go (backwards)', icon='icon.debugger.resume.back', condition=util.dbg.IS_TRACE)
@util.dbg.eng_thread
def go_back(thread: sch.Schema('Process')):
"""Continue execution of the process backwards."""
dbg().cmd("g-")
dbg().wait()
@REGISTRY.method
def interrupt(process: sch.Schema('Process')):
"""Interrupt the execution of the debugged program."""
@ -433,6 +449,22 @@ def step_over(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
util.dbg.run_async(lambda: dbg().stepo(n))
@REGISTRY.method(action='step_ext', display='Step Into (backwards)', icon='icon.debugger.step.back.into', condition=util.dbg.IS_TRACE)
@util.dbg.eng_thread
def step_back_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
"""Step one instruction backward exactly."""
dbg().cmd("t- " + str(n))
dbg().wait()
@REGISTRY.method(action='step_ext', display='Step Over (backwards)', icon='icon.debugger.step.back.over', condition=util.dbg.IS_TRACE)
@util.dbg.eng_thread
def step_back_over(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
"""Step one instruction backward, but proceed through subroutine calls."""
dbg().cmd("p- " + str(n))
dbg().wait()
@REGISTRY.method(action='step_out')
def step_out(thread: sch.Schema('Thread')):
"""Execute until the current stack frame returns."""
@ -448,6 +480,14 @@ def step_to(thread: sch.Schema('Thread'), address: Address, max=None):
util.dbg.run_async(lambda: dbg().stepto(address.offset, max))
@REGISTRY.method(action='go_to_time', display='Go To (event)', condition=util.dbg.IS_TRACE)
@util.dbg.eng_thread
def go_to_time(node: sch.Schema('State'), evt: ParamDesc(str, display='Event')):
"""Reset the trace to a specific time."""
dbg().cmd("!tt " + evt)
dbg().wait()
@REGISTRY.method(action='break_sw_execute')
@util.dbg.eng_thread
def break_address(process: sch.Schema('Process'), address: Address):
@ -578,5 +618,14 @@ def write_reg(frame: sch.Schema('StackFrame'), name: str, value: bytes):
dbg().reg._set_register(name, value)
@REGISTRY.method(display='Refresh Events (custom)', condition=util.dbg.IS_TRACE)
@util.dbg.eng_thread
def refresh_events_custom(node: sch.Schema('State'), cmd: ParamDesc(str, display='Cmd'),
prefix: ParamDesc(str, display='Prefix')="dx -r2 @$cursession.TTD"):
"""Parse TTD objects generated from a LINQ command."""
with commands.open_tracked_tx('Put Events (custom)'):
commands.ghidra_trace_put_events_custom(prefix, cmd)
def dbg():
return util.dbg._base

View file

@ -2,7 +2,7 @@
<schema name="DbgRoot" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<attribute name="Sessions" schema="SessionContainer" required="yes" fixed="yes" />
<attribute name="Settings" schema="ANY" />
<attribute name="State" schema="ANY" />
<attribute name="State" schema="State" />
<attribute-alias from="_state" to="State" />
<attribute name="Utility" schema="ANY" />
<attribute name="_display" schema="STRING" hidden="yes" />
@ -28,6 +28,11 @@
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY"/>
</schema>
<schema name="State" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<element schema="VOID" />
<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" />

View file

@ -38,9 +38,9 @@ from pybag.dbgeng.callbacks import DbgEngCallbacks
from pybag.dbgeng.idebugclient import DebugClient
from ghidradbg.dbgmodel.ihostdatamodelaccess import HostDataModelAccess
from ghidradbg.dbgmodel.imodelmethod import ModelMethod
from _winapi import STILL_ACTIVE
DbgVersion = namedtuple('DbgVersion', ['full', 'name', 'dotted', 'arch'])
@ -81,6 +81,7 @@ class StdInputCallbacks(CoClass):
class _Worker(threading.Thread):
def __init__(self, new_base, work_queue, dispatch):
super().__init__(name='DbgWorker', daemon=True)
self.new_base = new_base
@ -109,6 +110,7 @@ class _Worker(threading.Thread):
# https://github.com/python/cpython/blob/main/Lib/concurrent/futures/thread.py
# accessed 9 Jan 2024
class _WorkItem(object):
def __init__(self, future, fn, args, kwargs):
self.future = future
self.fn = fn
@ -131,6 +133,7 @@ class DebuggeeRunningException(BaseException):
class DbgExecutor(object):
def __init__(self, ghidra_dbg):
self._ghidra_dbg = ghidra_dbg
self._work_queue = queue.SimpleQueue()
@ -199,6 +202,7 @@ class AllDbg(pydbg.DebuggerBase):
class GhidraDbg(object):
def __init__(self):
self._queue = DbgExecutor(self)
self._thread = self._queue._thread
@ -242,6 +246,7 @@ class GhidraDbg(object):
self.IS_KERNEL = False
self.IS_EXDI = False
self.IS_REMOTE = os.getenv('OPT_CONNECT_STRING') is not None
self.IS_TRACE = os.getenv('USE_TTD') == "true"
def _new_base(self):
remote = os.getenv('OPT_CONNECT_STRING')
@ -252,7 +257,6 @@ class GhidraDbg(object):
else:
self._protected_base = AllDbg()
def _generate_client(self, original):
cli = POINTER(DbgEng.IDebugClient)()
cliptr = POINTER(POINTER(DbgEng.IDebugClient))(cli)
@ -260,7 +264,6 @@ class GhidraDbg(object):
exception.check_err(hr)
return DebugClient(client=cli)
@property
def _base(self):
if threading.current_thread() is not self._thread:
@ -289,12 +292,14 @@ class GhidraDbg(object):
For methods inside of GhidraDbg, ensure it runs on the dbgeng
thread
'''
@functools.wraps(func)
def _func(self, *args, **kwargs):
if threading.current_thread() is self._thread:
return func(self, *args, **kwargs)
else:
return self.run(func, self, *args, **kwargs)
return _func
def eng_thread(self, func):
@ -302,12 +307,14 @@ class GhidraDbg(object):
For methods and functions outside of GhidraDbg, ensure it
runs on this GhidraDbg's dbgeng thread
'''
@functools.wraps(func)
def _func(*args, **kwargs):
if threading.current_thread() is self._thread:
return func(*args, **kwargs)
else:
return self.run(func, *args, **kwargs)
return _func
def _ces_exec_status(self, argument):
@ -325,6 +332,7 @@ class GhidraDbg(object):
def _dispatch_events(self, timeout=DbgEng.WAIT_INFINITE):
# NB: pybag's impl doesn't heed standalone
self._protected_base._client.DispatchCallbacks(timeout)
dispatch_events = check_thread(_dispatch_events)
# no check_thread. Must allow reentry
@ -393,7 +401,23 @@ class GhidraDbg(object):
return None
class TTDState(object):
def __init__(self):
self._cursor = None
self._first = None
self._last = None
self._lastmajor = None
self._lastpos = None
self.breakpoints = []
self.events = {}
self.evttypes = {}
self.starts = {}
self.stops = {}
dbg = GhidraDbg()
ttd = TTDState()
@dbg.eng_thread
@ -750,18 +774,12 @@ def get_proc_id(pid):
def full_mem():
sizeptr = 64; #int(gdb.parse_and_eval('sizeof(void*)')) * 8
infoLow = DbgEng._MEMORY_BASIC_INFORMATION64()
infoLow.BaseAddress = 0
infoLow.RegionSize = (1 << (sizeptr-1))
infoLow.Protect = 0xFFF
infoLow.Name = "UMEM"
infoHigh = DbgEng._MEMORY_BASIC_INFORMATION64()
infoHigh.BaseAddress = 1 << (sizeptr-1)
infoHigh.RegionSize = (1 << (sizeptr-1))
infoHigh.Protect = 0xFFF
infoHigh.Name = "KMEM"
return [ infoLow, infoHigh ]
info = DbgEng._MEMORY_BASIC_INFORMATION64()
info.BaseAddress = 0
info.RegionSize = (1 << 64) - 1
info.Protect = 0xFFF
info.Name = "full memory"
return [ info ]
@dbg.eng_thread
@ -780,6 +798,16 @@ def get_thread_id(tid):
return None
@dbg.eng_thread
def open_trace_or_dump(filename):
"""Open a trace or dump file"""
_cli = dbg._base._client._cli
if isinstance(filename, str):
filename = filename.encode()
hr = _cli.OpenDumpFile(filename)
exception.check_err(hr)
def split_path(pathString):
list = []
segs = pathString.split(".")
@ -800,6 +828,11 @@ def IHostDataModelAccess():
dbg._base._client._cli.QueryInterface(interface=DbgMod.IHostDataModelAccess))
def IModelMethod(method_ptr):
return ModelMethod(
method_ptr.GetIntrinsicValue().value.QueryInterface(interface=DbgMod.IModelMethod))
@dbg.eng_thread
def get_object(relpath):
"""Get the list of all threads"""
@ -816,6 +849,21 @@ def get_object(relpath):
return root.GetOffspring(path)
@dbg.eng_thread
def get_method(context_path, method_name):
"""Get the list of all threads"""
obj = get_object(context_path)
keys = obj.EnumerateKeys()
(k, v) = keys.GetNext()
while k is not None:
if k.value == method_name:
break
(k, v) = keys.GetNext()
if k is None:
return None
return IModelMethod(v)
@dbg.eng_thread
def get_attributes(obj):
"""Get the list of attributes"""
@ -826,7 +874,7 @@ def get_attributes(obj):
@dbg.eng_thread
def get_elements(obj):
"""Get the list of all threads"""
"""Get the list of elements"""
if obj is None:
return None
return obj.GetElements()
@ -834,7 +882,7 @@ def get_elements(obj):
@dbg.eng_thread
def get_kind(obj):
"""Get the list of all threads"""
"""Get the kind"""
if obj is None:
return None
kind = obj.GetKind()
@ -845,7 +893,7 @@ def get_kind(obj):
@dbg.eng_thread
def get_type(obj):
"""Get the list of all threads"""
"""Get the type"""
if obj is None:
return None
return obj.GetTypeKind()
@ -853,7 +901,7 @@ def get_type(obj):
@dbg.eng_thread
def get_value(obj):
"""Get the list of all threads"""
"""Get the value"""
if obj is None:
return None
return obj.GetValue()
@ -861,7 +909,7 @@ def get_value(obj):
@dbg.eng_thread
def get_intrinsic_value(obj):
"""Get the list of all threads"""
"""Get the intrinsic value"""
if obj is None:
return None
return obj.GetIntrinsicValue()
@ -869,7 +917,7 @@ def get_intrinsic_value(obj):
@dbg.eng_thread
def get_target_info(obj):
"""Get the list of all threads"""
"""Get the target info"""
if obj is None:
return None
return obj.GetTargetInfo()
@ -877,7 +925,7 @@ def get_target_info(obj):
@dbg.eng_thread
def get_type_info(obj):
"""Get the list of all threads"""
"""Get the type info"""
if obj is None:
return None
return obj.GetTypeInfo()
@ -885,7 +933,7 @@ def get_type_info(obj):
@dbg.eng_thread
def get_name(obj):
"""Get the list of all threads"""
"""Get the name"""
if obj is None:
return None
return obj.GetName().value
@ -893,7 +941,7 @@ def get_name(obj):
@dbg.eng_thread
def to_display_string(obj):
"""Get the list of all threads"""
"""Get the display string"""
if obj is None:
return None
return obj.ToDisplayString()
@ -901,7 +949,7 @@ def to_display_string(obj):
@dbg.eng_thread
def get_location(obj):
"""Get the list of all threads"""
"""Get the location"""
if obj is None:
return None
try:
@ -925,6 +973,45 @@ def get_convenience_variable(id):
return val
def get_cursor():
return ttd._cursor
def get_last_position():
return ttd._lastpos
def set_last_position(pos):
ttd._lastpos = pos
def get_event_type(rng):
if ttd.evttypes.__contains__(rng):
return ttd.evttypes[rng]
def pos2snap(pos):
pmap = get_attributes(pos)
major = get_value(pmap["Sequence"])
minor = get_value(pmap["Steps"])
return mm2snap(major, minor)
def mm2snap(major, minor):
index = int(major)
if index < 0 or index >= ttd.MAX_STEP:
return int(ttd._lastmajor) # << 32
snap = index # << 32 + int(minor)
return snap
def pos2split(pos):
pmap = get_attributes(pos)
major = get_value(pmap["Sequence"])
minor = get_value(pmap["Steps"])
return (major, minor)
def set_convenience_variable(id, value):
conv_map[id] = value
@ -953,3 +1040,10 @@ def is_remote():
return dbg.IS_REMOTE
def set_trace(value):
dbg.IS_TRACE = value
def is_trace():
return dbg.IS_TRACE

View file

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

View file

@ -1,212 +0,0 @@
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
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():
return "x86_64"
def get_endian():
return 'little'
def get_osabi():
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]

View file

@ -1,441 +0,0 @@
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
import 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', 'waiting')
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()
self.waiting = True
def record(self, description=None, snap=None):
first = self.first
self.first = False
if description is not None:
commands.STATE.trace.snapshot(description, snap=snap)
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", display_result=False)
commands.putmem("$sp", "1", display_result=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:
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, snap=None):
if description is not None:
commands.STATE.trace.snapshot(description, snap)
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")
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:
proc = util.selected_process()
if args[1] & DbgEng.DEBUG_STATUS_INSIDE_WAIT:
PROC_STATE[proc].waiting = True
return DbgEng.DEBUG_STATUS_GO
PROC_STATE[proc].waiting = False
commands.put_state(proc)
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)):
commands.put_state(nproc)
state = PROC_STATE[nproc]
if state.waiting:
state.record_continued()
else:
state.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()
pos = dbg().get_position()
rng = range(pos.major, util.lastpos.major)
if pos.major > util.lastpos.major:
rng = range(util.lastpos.major, pos.major)
for i in rng:
if util.evttypes.__contains__(i):
type = util.evttypes[i]
if type == "modload" or type == "modunload":
on_modules_changed()
if type == "threadcreated" or type == "threadterm":
on_threads_changed()
util.lastpos = pos
with commands.STATE.client.batch():
with trace.open_tx("Stopped"):
state.record("Stopped", util.pos2snap(pos))
commands.put_state(proc)
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.id)):
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(bpt):
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=bpt.id)
with commands.STATE.client.batch():
with trace.open_tx("Breakpoint {} deleted".format(bpt.id)):
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
def remove_hooks():
if not HOOK_STATE.installed:
return
HOOK_STATE.installed = False
def enable_current_process():
proc = util.selected_process()
PROC_STATE[proc] = ProcessState()
def disable_current_process():
proc = util.selected_process()
if proc in PROC_STATE:
# Silently ignore already disabled
del PROC_STATE[proc]
def dbg():
return util.get_debugger()

View file

@ -1,78 +0,0 @@
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
import ctypes
import os
import platform
import comtypes
import comtypes.client
ctypes.windll.kernel32.SetErrorMode(0x0001 | 0x0002 | 0x8000)
if platform.architecture()[0] == '64bit':
dbgdirs = [os.getenv('OPT_DBGMODEL_PATH'),
r'C:\Program Files\Windows Kits\10\Debuggers\x64',
r'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64']
else:
dbgdirs = [os.getenv('OPT_DBGMODEL_PATH'),
r'C:\Program Files\Windows Kits\10\Debuggers\x86',
r'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86']
dbgdir = None
for _dir in dbgdirs:
if _dir is not None and os.path.exists(_dir):
dbgdir = _dir
break
if not dbgdir:
raise RuntimeError("Windbg install directory not found!")
print(f"Loading dbgeng and friends from {dbgdir}")
# preload these to get correct DLLs loaded
try:
ctypes.windll.LoadLibrary(os.path.join(dbgdir, 'dbghelp.dll'))
except Exception as exc:
print(fr"LoadLibrary failed: {dbgdir}\dbghelp.dll {exc}")
pass
try:
ctypes.windll.LoadLibrary(os.path.join(dbgdir, 'dbgeng.dll'))
except Exception as exc:
print(fr"LoadLibrary failed: {dbgdir}\dbgeng.dll {exc}")
pass
try:
ctypes.windll.LoadLibrary(os.path.join(dbgdir, 'DbgModel.dll'))
except Exception as exc:
print(fr"LoadLibrary failed: {dbgdir}\dbgmodel.dll {exc}")
pass
try:
ctypes.windll.LoadLibrary(os.path.join(dbgdir, 'ttd/TTDReplay.dll'))
except Exception as exc:
print(fr"LoadLibrary failed: {dbgdir}\ttd\TTDReplay.dll {exc}")
pass
try:
ctypes.windll.LoadLibrary(os.path.join(dbgdir, 'ttd/TTDReplayCPU.dll'))
except Exception as exc:
print(fr"LoadLibrary failed: {dbgdir}\ttd\TTDReplayCPU.dll {exc}")
pass
try:
from comtypes.gen import DbgMod
except:
tlb = os.path.join(dbgmodel.module_locator(), 'tlb', 'dbgmodel.tlb')
print(f"Loading TLB: {tlb}")
comtypes.client.GetModule(tlb)
from comtypes.gen import DbgMod

View file

@ -1,538 +0,0 @@
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
from concurrent.futures import Future, ThreadPoolExecutor
import re
import sys
from ghidratrace import sch
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
from pyttd import pyTTD
#from pybag import pydbg
#from pybag.dbgeng import core as DbgEng
from . import util, commands, hooks
from contextlib import redirect_stdout
from io import StringIO
REGISTRY = MethodRegistry(ThreadPoolExecutor(max_workers=1))
def extre(base, ext):
return re.compile(base.pattern + ext)
AVAILABLE_PATTERN = re.compile('Available\[(?P<pid>\\d*)\]')
WATCHPOINT_PATTERN = re.compile('Watchpoints\[(?P<watchnum>\\d*)\]')
BREAKPOINT_PATTERN = re.compile('Breakpoints\[(?P<breaknum>\\d*)\]')
BREAK_LOC_PATTERN = extre(BREAKPOINT_PATTERN, '\[(?P<locnum>\\d*)\]')
PROCESS_PATTERN = re.compile('Processes\[(?P<procnum>\\d*)\]')
PROC_BREAKS_PATTERN = extre(PROCESS_PATTERN, '\.Breakpoints')
PROC_BREAKBPT_PATTERN = extre(PROC_BREAKS_PATTERN, '\[(?P<breaknum>\\d*)\]')
ENV_PATTERN = extre(PROCESS_PATTERN, '\.Environment')
THREADS_PATTERN = extre(PROCESS_PATTERN, '\.Threads')
THREAD_PATTERN = extre(THREADS_PATTERN, '\[(?P<tnum>\\d*)\]')
STACK_PATTERN = extre(THREAD_PATTERN, '\.Stack')
FRAME_PATTERN = extre(STACK_PATTERN, '\[(?P<level>\\d*)\]')
REGS_PATTERN0 = extre(THREAD_PATTERN, '.Registers')
REGS_PATTERN = extre(FRAME_PATTERN, '.Registers')
MEMORY_PATTERN = extre(PROCESS_PATTERN, '\.Memory')
MODULES_PATTERN = extre(PROCESS_PATTERN, '\.Modules')
def find_availpid_by_pattern(pattern, object, err_msg):
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
pid = int(mat['pid'])
return pid
def find_availpid_by_obj(object):
return find_availpid_by_pattern(AVAILABLE_PATTERN, object, "an Available")
def find_proc_by_num(id):
if id != util.selected_process():
util.select_process(id)
return util.selected_process()
def find_proc_by_pattern(object, pattern, err_msg):
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
procnum = int(mat['procnum'])
return find_proc_by_num(procnum)
def find_proc_by_obj(object):
return find_proc_by_pattern(object, PROCESS_PATTERN, "an Process")
def find_proc_by_procbreak_obj(object):
return find_proc_by_pattern(object, PROC_BREAKS_PATTERN,
"a BreakpointLocationContainer")
def find_proc_by_procwatch_obj(object):
return find_proc_by_pattern(object, PROC_WATCHES_PATTERN,
"a WatchpointContainer")
def find_proc_by_env_obj(object):
return find_proc_by_pattern(object, ENV_PATTERN, "an Environment")
def find_proc_by_threads_obj(object):
return find_proc_by_pattern(object, THREADS_PATTERN, "a ThreadContainer")
def find_proc_by_mem_obj(object):
return find_proc_by_pattern(object, MEMORY_PATTERN, "a Memory")
def find_proc_by_modules_obj(object):
return find_proc_by_pattern(object, MODULES_PATTERN, "a ModuleContainer")
def find_thread_by_num(id):
if id != util.selected_thread():
util.select_thread(id)
return util.selected_thread()
def find_thread_by_pattern(pattern, object, err_msg):
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
pnum = int(mat['procnum'])
tnum = int(mat['tnum'])
find_proc_by_num(pnum)
return find_thread_by_num(tnum)
def find_thread_by_obj(object):
return find_thread_by_pattern(THREAD_PATTERN, object, "a Thread")
def find_thread_by_stack_obj(object):
return find_thread_by_pattern(STACK_PATTERN, object, "a Stack")
def find_thread_by_regs_obj(object):
return find_thread_by_pattern(REGS_PATTERN0, object, "a RegisterValueContainer")
def find_frame_by_level(level):
return dbg().backtrace_list()[level]
def find_frame_by_pattern(pattern, object, err_msg):
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
pnum = int(mat['procnum'])
tnum = int(mat['tnum'])
level = int(mat['level'])
find_proc_by_num(pnum)
find_thread_by_num(tnum)
return find_frame_by_level(level)
def find_frame_by_obj(object):
return find_frame_by_pattern(FRAME_PATTERN, object, "a StackFrame")
def find_bpt_by_number(breaknum):
try:
bp = util.breakpoints[breaknum]
return bp
except exception.E_NOINTERFACE_Error:
raise KeyError(f"Breakpoints[{breaknum}] does not exist")
def find_bpt_by_pattern(pattern, object, err_msg):
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
breaknum = int(mat['breaknum'])
return find_bpt_by_number(breaknum)
def find_bpt_by_obj(object):
return find_bpt_by_pattern(PROC_BREAKBPT_PATTERN, object, "a BreakpointSpec")
shared_globals = dict()
@REGISTRY.method
def execute(cmd: str, to_string: bool=False):
"""Execute a CLI command."""
# print("***{}***".format(cmd))
# sys.stderr.flush()
# sys.stdout.flush()
if to_string:
data = StringIO()
with redirect_stdout(data):
exec("{}".format(cmd), shared_globals)
return data.getvalue()
else:
exec("{}".format(cmd), shared_globals)
@REGISTRY.method(action='evaluate', display='Evaluate')
def evaluate(
session: sch.Schema('Session'),
expr: ParamDesc(str, display='Expr')):
"""Execute a CLI command."""
return str(eval("{}".format(expr), shared_globals))
@REGISTRY.method(action='refresh')
def refresh_available(node: sch.Schema('AvailableContainer')):
"""List processes on pydbg's host system."""
with commands.open_tracked_tx('Refresh Available'):
commands.ghidra_trace_put_available()
@REGISTRY.method(action='refresh')
def refresh_breakpoints(node: sch.Schema('BreakpointContainer')):
"""
Refresh the list of breakpoints (including locations for the current
process).
"""
with commands.open_tracked_tx('Refresh Breakpoints'):
commands.ghidra_trace_put_breakpoints()
@REGISTRY.method(action='refresh')
def refresh_processes(node: sch.Schema('ProcessContainer')):
"""Refresh the list of processes."""
with commands.open_tracked_tx('Refresh Processes'):
commands.ghidra_trace_put_threads()
def refresh_environment(node: sch.Schema('Environment')):
"""Refresh the environment descriptors (arch, os, endian)."""
with commands.open_tracked_tx('Refresh Environment'):
commands.ghidra_trace_put_environment()
@REGISTRY.method(action='refresh')
def refresh_threads(node: sch.Schema('ThreadContainer')):
"""Refresh the list of threads in the process."""
with commands.open_tracked_tx('Refresh Threads'):
commands.ghidra_trace_put_threads()
@REGISTRY.method(action='refresh')
def refresh_stack(node: sch.Schema('Stack')):
"""Refresh the backtrace for the thread."""
tnum = find_thread_by_stack_obj(node)
with commands.open_tracked_tx('Refresh Stack'):
commands.ghidra_trace_put_frames()
@REGISTRY.method(action='refresh')
def refresh_registers(node: sch.Schema('RegisterValueContainer')):
"""Refresh the register values for the frame."""
tnum = find_thread_by_regs_obj(node)
with commands.open_tracked_tx('Refresh Registers'):
commands.ghidra_trace_putreg()
@REGISTRY.method(action='refresh')
def refresh_mappings(node: sch.Schema('Memory')):
"""Refresh the list of memory regions for the process."""
with commands.open_tracked_tx('Refresh Memory Regions'):
commands.ghidra_trace_put_regions()
@REGISTRY.method(action='refresh')
def refresh_modules(node: sch.Schema('ModuleContainer')):
"""
Refresh the modules and sections list for the process.
This will refresh the sections for all modules, not just the selected one.
"""
with commands.open_tracked_tx('Refresh Modules'):
commands.ghidra_trace_put_modules()
@REGISTRY.method(action='activate')
def activate_process(process: sch.Schema('Process')):
"""Switch to the process."""
find_proc_by_obj(process)
@REGISTRY.method(action='activate')
def activate_thread(thread: sch.Schema('Thread')):
"""Switch to the thread."""
find_thread_by_obj(thread)
@REGISTRY.method(action='activate')
def activate_frame(frame: sch.Schema('StackFrame')):
"""Select the frame."""
find_frame_by_obj(frame)
@REGISTRY.method(action='delete')
def remove_process(process: sch.Schema('Process')):
"""Remove the process."""
find_proc_by_obj(process)
dbg().detach()
@REGISTRY.method(action='connect', display='Connect')
@util.dbg.eng_thread
def target(
session: sch.Schema('Session'),
cmd: ParamDesc(str, display='Command')):
"""Connect to a target machine or process."""
dbg().attach_kernel(cmd)
@REGISTRY.method(action='attach', display='Attach')
@util.dbg.eng_thread
def attach_obj(target: sch.Schema('Attachable')):
"""Attach the process to the given target."""
pid = find_availpid_by_obj(target)
dbg().attach(pid)
@REGISTRY.method(action='attach', display='Attach by pid')
@util.dbg.eng_thread
def attach_pid(
session: sch.Schema('Session'),
pid: ParamDesc(str, display='PID')):
"""Attach the process to the given target."""
dbg().attach_proc(int(pid))
@REGISTRY.method(action='attach', display='Attach by name')
@util.dbg.eng_thread
def attach_name(
session: sch.Schema('Session'),
name: ParamDesc(str, display='Name')):
"""Attach the process to the given target."""
dbg().attach_proc(name)
@REGISTRY.method(action='detach', display='Detach')
@util.dbg.eng_thread
def detach(process: sch.Schema('Process')):
"""Detach the process's target."""
dbg().detach()
@REGISTRY.method(action='launch', display='Launch')
def launch_loader(
session: sch.Schema('Session'),
file: ParamDesc(str, display='File'),
args: ParamDesc(str, display='Arguments')=''):
"""
Start a native process with the given command line, stopping at the ntdll initial breakpoint.
"""
command = file
if args != None:
command += " "+args
commands.ghidra_trace_create(command=file, start_trace=False)
@REGISTRY.method(action='launch', display='LaunchEx')
def launch(
session: sch.Schema('Session'),
file: ParamDesc(str, display='File'),
args: ParamDesc(str, display='Arguments')='',
initial_break: ParamDesc(bool, display='Initial Break')=True,
timeout: ParamDesc(int, display='Timeout')=-1):
"""
Run a native process with the given command line.
"""
command = file
if args != None:
command += " "+args
commands.ghidra_trace_create(
command, initial_break=initial_break, timeout=timeout, start_trace=False)
@REGISTRY.method
def kill(process: sch.Schema('Process')):
"""Kill execution of the process."""
dbg().terminate()
@REGISTRY.method(name='continue', action='resume')
def _continue(process: sch.Schema('Process')):
"""Continue execution of the process."""
dbg().replay_forward(pyTTD.MAX_STEP, util.last)
hooks.on_stop()
@REGISTRY.method
def interrupt(process: sch.Schema('Process')):
"""Interrupt the execution of the debugged program."""
print("'interrupt' is unsupported for TTD")
@REGISTRY.method(action='step_into')
def step_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
"""Step one instruction exactly."""
# find_thread_by_obj(thread)
dbg().replay_forward(n, util.last)
hooks.on_stop()
@REGISTRY.method(action='step_over')
def step_over(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
"""Step one instruction, but proceed through subroutine calls."""
# find_thread_by_obj(thread)
dbg().replay_backward(n, util.first)
hooks.on_stop()
@REGISTRY.method(action='step_out')
def step_out(thread: sch.Schema('Thread')):
"""Execute until the current stack frame returns."""
dbg().replay_backward(pyTTD.MAX_STEP, util.first)
hooks.on_stop()
@REGISTRY.method(action='step_to', display='Step To')
def step_to(thread: sch.Schema('Thread'), address: Address, max=None):
"""Continue execution up to the given address."""
find_thread_by_obj(thread)
return dbg().stepto(address.offset, max)
def gen_bpt(offset: int, size: int, flags: int):
bp = pyTTD.MemoryWatchpointData(addr=offset, size=size, flags=flags)
dbg().add_memory_watchpoint(bp)
bpt = util.Watchpoint(offset, size, flags, len(util.breakpoints), bp)
util.breakpoints.append(bpt)
hooks.on_breakpoint_created(bpt)
@REGISTRY.method(action='break_sw_execute')
def break_address(process: sch.Schema('Process'), address: Address):
"""Set a breakpoint."""
gen_bpt(offset=address.offset, size=4, flags=pyTTD.BP_FLAGS.EXEC)
@REGISTRY.method(action='break_sw_execute')
def break_expression(expression: str):
"""Set a breakpoint."""
# TODO: Escape?
dbg().bp(expr=expression)
@REGISTRY.method(action='break_hw_execute')
def break_hw_address(process: sch.Schema('Process'), address: Address):
"""Set a hardware-assisted breakpoint."""
gen_bpt(offset=address.offset, size=4, flags=pyTTD.BP_FLAGS.EXEC)
@REGISTRY.method(action='break_hw_execute')
def break_hw_expression(expression: str):
"""Set a hardware-assisted breakpoint."""
dbg().ba(expr=expression)
@REGISTRY.method(action='break_read')
def break_read_range(process: sch.Schema('Process'), range: AddressRange):
"""Set a read watchpoint."""
gen_bpt(offset=range.min, size=range.length(), flags=pyTTD.BP_FLAGS.READ)
@REGISTRY.method(action='break_read')
def break_read_expression(expression: str):
"""Set a read watchpoint."""
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ)
@REGISTRY.method(action='break_write')
def break_write_range(process: sch.Schema('Process'), range: AddressRange):
"""Set a watchpoint."""
gen_bpt(offset=range.min, size=range.length(), flags=pyTTD.BP_FLAGS.WRITE)
@REGISTRY.method(action='break_write')
def break_write_expression(expression: str):
"""Set a watchpoint."""
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_WRITE)
@REGISTRY.method(action='break_access')
def break_access_range(process: sch.Schema('Process'), range: AddressRange):
"""Set an access watchpoint."""
find_proc_by_obj(process)
break_read_range(process, range)
break_write_range(process, range)
@REGISTRY.method(action='break_access')
def break_access_expression(expression: str):
"""Set an access watchpoint."""
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ | DbgEng.DEBUG_BREAK_WRITE)
@REGISTRY.method(action='toggle')
def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
"""Toggle a breakpoint."""
bpt = find_bpt_by_obj(breakpoint)
if enabled:
dbg().be(bpt.GetId())
else:
dbg().bd(bpt.GetId())
@REGISTRY.method(action='delete')
def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
"""Delete a breakpoint."""
bpt = find_bpt_by_obj(breakpoint)
dbg().remove_memory_watchpoint(bpt.bp)
util.breakpoints.remove(bpt)
hooks.on_breakpoint_deleted(bpt)
@REGISTRY.method
def read_mem(process: sch.Schema('Process'), range: AddressRange):
"""Read memory."""
nproc = find_proc_by_obj(process)
offset_start = process.trace.memory_mapper.map_back(
nproc, Address(range.space, range.min))
with commands.open_tracked_tx('Read Memory'):
dbg().read_mem(range.min, range.length())
@REGISTRY.method
def write_mem(process: sch.Schema('Process'), address: Address, data: bytes):
"""Write memory."""
print("'write_mem' is unsupported for TTD")
@REGISTRY.method
def write_reg(frame: sch.Schema('StackFrame'), name: str, value: bytes):
"""Write a register."""
print("'write_reg' is unsupported for TTD")
def dbg():
return util.get_debugger()

View file

@ -1,233 +0,0 @@
<context>
<schema name="TTDSession" elementResync="NEVER" attributeResync="NEVER">
<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="_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="VOID" />
</schema>
<schema name="Selectable" elementResync="NEVER" attributeResync="NEVER">
<element schema="OBJECT" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="BreakpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<element schema="BreakpointSpec" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="AvailableContainer" canonical="yes" elementResync="ALWAYS" attributeResync="NEVER">
<element schema="Attachable" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="ProcessContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<element schema="Process" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="BreakpointSpec" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointSpec" />
<interface name="BreakpointLocation" />
<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="STRING" required="yes" hidden="yes" />
<attribute-alias from="_kinds" to="Kinds" />
<attribute name="_display" schema="STRING" hidden="yes" />
<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" 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">
<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" />
<element schema="VOID" />
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
<attribute name="Breakpoints" schema="BreakpointContainer" 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="Memory" required="yes" fixed="yes" />
<attribute name="Modules" schema="ModuleContainer" required="yes" 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="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</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="ModuleContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<element schema="Module" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Memory" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Memory" />
<element schema="MemoryRegion" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="ThreadContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<element schema="Thread" />
<attribute name="_order" schema="INT" hidden="yes" />
<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="Activatable" />
<interface name="Thread" />
<interface name="ExecutionStateful" />
<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" />
<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="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="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="VOID" />
</schema>
<schema name="MemoryRegion" 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">
<element schema="Section" />
<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="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="SymbolContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<element schema="Symbol" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Symbol" elementResync="NEVER" attributeResync="NEVER">
<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="Registers" schema="RegisterValueContainer" required="yes" fixed="yes" />
<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" />
<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="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
</context>

View file

@ -1,204 +0,0 @@
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
from collections import namedtuple
import os
import re
import sys
from ctypes import *
from pyttd import pyTTD
#from pybag import pydbg
#from pybag.dbgeng import core as DbgEng
#from pybag.dbgeng import exception
#from pybag.dbgeng import util as DbgUtil
base = False
eng = False
first = False
last = False
breakpoints = []
events = {}
evttypes = {}
starts = {}
stops = {}
lastpos = False
DbgVersion = namedtuple('DbgVersion', ['full', 'major', 'minor'])
class Watchpoint(object):
def __init__(self, addr, size, flags, id, bp):
self.addr = addr
self.size = size
self.flags = flags
self.id = id
self.bp = bp
self.expr = None
def _compute_pydbg_ver():
blurb = "" #get_debugger()._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_mem(addr, 15))
return str(ins)
def get_inst_sz(addr):
dbg = get_debugger()
ins = DbgUtil.disassemble_instruction(
dbg.bitness(), addr, dbg.read_mem(addr, 15))
return str(ins.size)
def get_breakpoints():
return None
def selected_process():
try:
return 0
# return current_process
except Exception:
return None
def selected_thread():
try:
dbg = get_debugger()
current = dbg.get_thread_info()
return current.threadid
except Exception:
return None
def selected_frame():
return 0 # selected_thread().GetSelectedFrame()
def select_process(id: int):
return None
def select_thread(id: int):
return None
def select_frame(id: int):
# TODO: this needs to be fixed
return None
def parse_and_eval(expr):
dbg = get_debugger()
if expr == "$pc":
return dbg.get_program_counter()
if expr == "$sp":
return dbg.get_context_x86_64().rsp
return int(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 process_list(running=False):
"""process_list() -> list of all processes"""
sysids = [0]
return sysids
def thread_list():
"""thread_list() -> list of all threads"""
dbg = get_debugger()
return dbg.get_thread_list()
def module_list():
"""thread_list() -> list of all threads"""
dbg = get_debugger()
return dbg.get_module_list()
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
def pos2snap(pos: int):
index = int(pos.major)
if index < 0 or index >= pyTTD.MAX_STEP:
return int(last.major)*1000
return index*1000+int(pos.minor)

View file

@ -1296,16 +1296,6 @@ def map_address(address):
return (base, addr)
# def ghidra_trace_put_generic(node):
# """
# Put the current thread's frames into the Ghidra trace
# """
#
# STATE.require_tx()
# with STATE.client.batch() as b:
# put_generic(node)
def ghidra_trace_put_all():
"""
Put everything currently selected into the Ghidra trace

View file

@ -955,38 +955,21 @@ exdi:CLSID={29f9906e-9dbe-4d4b-b0fb-6acf7fb6d014},Kd=Guess,DataBreaks=Exdi
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_trace"></A>TTD (Time-Travel Debugging)</H3>
<P>This is a nascent extension to our launcher for the Windows Debugger. The launcher itself
functions well, but lacks full integration. It is not yet properly packaged for the Ghidra
distribution, but is available in development environments. It also needs some additional
protocol support, namely to integrate its notion of time travel with Ghidra's notion. For the
time being, we map our time specifications as follows. MS TTD uses a tuple for its time specs,
both displayed in hexadecimal, e.g., "B5:1A". The first is the "major," which we believe counts
the events that warrant a snapshot. The second is the "minor," which we believe counts
instructions executed since the major event. Thus, we would like to map the major to a Ghidra
trace snapshot and the minor to steps of p-code emulation. For example, the "B5:1A" notation
would map to "181:26". It should be no surprise the notations are similar, since both MS TTD
and Ghidra (as well as several other "timeless" debuggers) use a snapshot-replay strategy to
recover past machine states. However, we have not yet worked out how to have Ghidra cooperate
with a back end in the replay part of this strategy. Currently, Ghidra will always apply p-code
emulation, despite having a perfectly good and performant back end available. So, for the time
being, we multiply the major number by 1000, thus reserving that many Ghidra snapshots between
major events for MS TTD minor steps. Thus, the notation "B5:1A" will actually map to "181026",
that is snapshot 181026 with no steps of p-code emulation. This hack will fall short if you
visit a time where the minor number exceeds 999.</P>
<P>Furthermore, if you use the Ghidra Debugger UI to visit a past snapshot, the back end is not
yet informed of your intent. You will probably only see stale records of the machine state.
Instead, please use the kd commands from the Terminal to navigate through time. The back end
(MS TTD) will perform the replay, record the snapshot, and command Ghidra to navigate
there.</P>
functions, but lacks full integration. In particular, Ghidra's concept of time is not
mapped directly to the TTD concept of time. TTD uses a major/minor scheme for ordering events,
where the major index changes when TTD must record a change in state. Events, including thread
creation/termination, module loads/unloads, syscalls, and other asynchronous changes, merit
new major indices. When you step forward or backward in a trace, the dbgeng API will increment
and decrement correspondingly. Ghidra, on the other hand, will only increment.</P>
<H4>Options</H4>
<P>This launcher has the same options as the WinDbg launcher, except that the DLL path must
contain <TT>dbgmodel.dll</TT> and the scripts that implement TTD. These are most easily
obtained by installing WinDbg Preview or later.</P>
<P>This launcher has basically the same options as the WinDbg launcher, except that arguments
are not included and the DLL path must contain <TT>TTDReplay.dll</TT>
and the scripts that implement TTD. These are most easily obtained by installing WinDbg Preview or later.</P>
<H2>Stock Java Launchers</H2>

View file

@ -110,10 +110,12 @@ src/main/resources/images/record.png||GHIDRA||||END|
src/main/resources/images/register-marker.png||GHIDRA||||END|
src/main/resources/images/registers.png||GHIDRA||||END|
src/main/resources/images/resume.png||GHIDRA||||END|
src/main/resources/images/resumeback.png||GHIDRA||||END|
src/main/resources/images/seek-present.png||GHIDRA||||END|
src/main/resources/images/select-registers.png||GHIDRA||||END|
src/main/resources/images/skipover.png||GHIDRA||||END|
src/main/resources/images/stepback.png||GHIDRA||||END|
src/main/resources/images/stepbackinto.png||GHIDRA||||END|
src/main/resources/images/stepinto.png||GHIDRA||||END|
src/main/resources/images/steplast.png||GHIDRA||||END|
src/main/resources/images/stepout.png||GHIDRA||||END|
@ -163,11 +165,13 @@ src/main/svg/record.svg||GHIDRA||||END|
src/main/svg/register-marker.svg||GHIDRA||||END|
src/main/svg/registers.svg||GHIDRA||||END|
src/main/svg/resume.svg||GHIDRA||||END|
src/main/svg/resumeback.svg||GHIDRA||||END|
src/main/svg/seek-present.svg||GHIDRA||||END|
src/main/svg/select-registers.svg||GHIDRA||||END|
src/main/svg/skipover.svg||GHIDRA||||END|
src/main/svg/stack.svg||GHIDRA||||END|
src/main/svg/stepback.svg||GHIDRA||||END|
src/main/svg/stepbackinto.svg||GHIDRA||||END|
src/main/svg/stepinto.svg||GHIDRA||||END|
src/main/svg/steplast.svg||GHIDRA||||END|
src/main/svg/stepout.svg||GHIDRA||||END|

View file

@ -104,6 +104,7 @@ icon.debugger.processor = memory16.gif // TODO this icon was missing 'kcmprocess
icon.debugger.launch = launch.png
icon.debugger.attach = attach.png
icon.debugger.resume = resume.png
icon.debugger.resume.back = resumeback.png
icon.debugger.interrupt = interrupt.png
icon.debugger.kill = kill.png
icon.debugger.detach = detach.png
@ -111,6 +112,8 @@ icon.debugger.record = record.png
icon.debugger.step.into = stepinto.png
icon.debugger.step.over = stepover.png
icon.debugger.step.back = stepback.png
icon.debugger.step.back.into = stepbackinto.png
icon.debugger.step.back.over = stepback.png
icon.debugger.step.finish = stepout.png
icon.debugger.step.last = steplast.png
icon.debugger.skip.over = skipover.png

View file

@ -115,8 +115,11 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener {
TraceObject obj = objRegion.getObject();
obj.getOrderedValues(Lifespan.ALL, TraceObjectMemoryRegion.KEY_RANGE, true).forEach(v -> {
if (region.getName(v.getMinSnap()).equals("full memory")) {
return;
}
MemoryBox box = new MemoryBox("Region " + region.getName(v.getMinSnap()),
MemviewBoxType.VIRTUAL_ALLOC, v.castValue(), v.getLifespan());
MemviewBoxType.REGION, v.castValue(), v.getLifespan());
updateList.add(box);
});
updateLabelDebouncer.contact(null);

View file

@ -38,8 +38,7 @@ class MemviewMapModel extends AbstractSortedTableModel<MemoryBox> {
private Map<String, MemoryBox> memMap = new HashMap<>();
private MemviewProvider provider;
private final static String COLUMN_NAMES[] =
{ NAME_COL, ASTART_COL, ASTOP_COL, TSTART_COL, TSTOP_COL };
private final static String COLUMN_NAMES[] = { NAME_COL, ASTART_COL, ASTOP_COL, TSTART_COL, TSTOP_COL };
public MemviewMapModel(MemviewProvider provider) {
super(ASTART);
@ -109,9 +108,9 @@ class MemviewMapModel extends AbstractSortedTableModel<MemoryBox> {
}
/**
* Convenience method for locating columns by name. Implementation is naive so this should be
* overridden if this method is to be called often. This method is not in the TableModel
* interface and is not used by the JTable.
* Convenience method for locating columns by name. Implementation is naive so
* this should be overridden if this method is to be called often. This method
* is not in the TableModel interface and is not used by the JTable.
*/
@Override
public int findColumn(String columnName) {
@ -143,9 +142,10 @@ class MemviewMapModel extends AbstractSortedTableModel<MemoryBox> {
}
/**
* Returns the number of records managed by the data source object. A <B>JTable</B> uses this
* method to determine how many rows it should create and display. This method should be quick,
* as it is call by <B>JTable</B> quite frequently.
* Returns the number of records managed by the data source object. A
* <B>JTable</B> uses this method to determine how many rows it should create
* and display. This method should be quick, as it is call by <B>JTable</B>
* quite frequently.
*
* @return the number or rows in the model
* @see #getColumnCount
@ -165,8 +165,7 @@ class MemviewMapModel extends AbstractSortedTableModel<MemoryBox> {
MemoryBox box = memList.get(rowIndex);
try {
box.getStart();
}
catch (ConcurrentModificationException e) {
} catch (ConcurrentModificationException e) {
update();
}
return memList.get(rowIndex);
@ -187,18 +186,17 @@ class MemviewMapModel extends AbstractSortedTableModel<MemoryBox> {
case ASTOP:
return box.getRange().getMaxAddress();
case TSTART:
return Long.toString(box.getStart());
return Long.toString(box.getStart(), 16);
case TSTOP:
long end = box.getEnd();
if (end == Long.MAX_VALUE) {
return "+" + '\u221e' + '\u2025';
}
return Long.toString(end);
return Long.toString(end, 16);
default:
return "UNKNOWN";
}
}
catch (ConcurrentModificationException e) {
} catch (ConcurrentModificationException e) {
update();
}
return null;

View file

@ -487,7 +487,8 @@ public class MemviewPanel extends JPanel implements MouseListener, MouseMotionLi
public String getTagForTick(long tick) {
String tval = "";
if (0 <= tick && tick < timesArray.length) {
tval = Long.toString(timesArray[(int) tick]);
Long time = timesArray[(int) tick];
tval = Long.toString(time, 16);
}
return tval;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 16 16"
version="1.1"
id="svg4819"
height="16"
width="16"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs4821" />
<metadata
id="metadata4824">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<path
id="path842"
d="M 12.5,2.1503906 V 13.849609 L 1.970703,8 Z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#fffffe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<path
id="path856"
d="M 12,3 V 13 L 3,8 Z"
style="opacity:1;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1.76383;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16"
width="16"
id="svg2"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata8">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6" />
<rect
y="10"
x="-15"
height="5"
width="14"
id="rect815"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.191062;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
transform="scale(-1,1)" />
<path
style="opacity:1;vector-effect:none;fill:#d4aa00;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="M 5,9.0004758 H 7 V 5.5 C 7,3 7,3 9.5,3 H 15 V 5 H 9.5 C 9,5 9,5 9,5.5 v 3.5004758 l 2,-4.759e-4 -3,3.0004761 z"
id="path834" />
<rect
y="11"
x="-14"
height="3"
width="3"
id="rect819"
style="fill:#0166a9;fill-opacity:1;stroke:none;stroke-width:2.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
transform="scale(-1,1)" />
<rect
y="11"
x="-5"
height="3"
width="3"
id="rect821"
style="fill:#0166a9;fill-opacity:1;stroke:none;stroke-width:2.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
transform="scale(-1,1)" />
<path
id="rect4163"
d="M 5,9.0004758 H 7 V 5.5 C 7,3 7,3 9.5,3 H 15 V 5 H 9.5 C 9,5 9,5 9,5.5 v 3.5004758 l 2,-4.759e-4 -3,3.0004761 z"
style="opacity:1;vector-effect:none;fill:#d4aa00;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB