From a2e42f5fe20386db8a21b9d542e944f968774216 Mon Sep 17 00:00:00 2001 From: d-millar <33498836+d-millar@users.noreply.github.com> Date: Tue, 4 Mar 2025 13:02:21 -0500 Subject: [PATCH] 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: ?? --- .../certification.manifest | 3 +- .../debugger-launchers/local-dbgeng-trace.bat | 21 + .../data/debugger-launchers/local-ttd.bat | 21 - .../data/support/local-dbgeng-trace.py | 73 + .../data/support/local-ttd.py | 58 - .../src/main/py/src/ghidradbg/commands.py | 347 ++++- .../src/ghidradbg/dbgmodel/imodeliterator.py | 36 +- .../py/src/ghidradbg/dbgmodel/imodelmethod.py | 45 + .../src/ghidradbg/dbgmodel/irawenumerator.py | 28 +- .../src/main/py/src/ghidradbg/hooks.py | 37 +- .../src/main/py/src/ghidradbg/methods.py | 59 +- .../src/main/py/src/ghidradbg/schema.xml | 7 +- .../src/main/py/src/ghidradbg/util.py | 158 +- .../src/main/py/src/ghidrattd/__init__.py | 19 - .../src/main/py/src/ghidrattd/arch.py | 212 --- .../src/main/py/src/ghidrattd/commands.py | 1387 ----------------- .../src/main/py/src/ghidrattd/hooks.py | 441 ------ .../src/main/py/src/ghidrattd/libraries.py | 78 - .../src/main/py/src/ghidrattd/methods.py | 538 ------- .../src/main/py/src/ghidrattd/schema.xml | 233 --- .../src/main/py/src/ghidrattd/util.py | 204 --- .../src/main/py/src/ghidradrgn/commands.py | 10 - .../TraceRmiLauncherServicePlugin.html | 37 +- Ghidra/Debug/Debugger/certification.manifest | 4 + .../Debugger/data/debugger.theme.properties | 3 + .../memview/DebuggerMemviewTraceListener.java | 7 +- .../debug/gui/memview/MemviewMapModel.java | 82 +- .../core/debug/gui/memview/MemviewPanel.java | 9 +- .../src/main/resources/images/resumeback.png | Bin 0 -> 354 bytes .../main/resources/images/stepbackinto.png | Bin 0 -> 367 bytes .../Debugger/src/main/svg/resumeback.svg | 34 + .../Debugger/src/main/svg/stepbackinto.svg | 57 + 32 files changed, 829 insertions(+), 3419 deletions(-) create mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-dbgeng-trace.bat delete mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-ttd.bat create mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-dbgeng-trace.py delete mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-ttd.py create mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/imodelmethod.py delete mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidrattd/__init__.py delete mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidrattd/arch.py delete mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidrattd/commands.py delete mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidrattd/hooks.py delete mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidrattd/libraries.py delete mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidrattd/methods.py delete mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidrattd/schema.xml delete mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidrattd/util.py create mode 100644 Ghidra/Debug/Debugger/src/main/resources/images/resumeback.png create mode 100644 Ghidra/Debug/Debugger/src/main/resources/images/stepbackinto.png create mode 100644 Ghidra/Debug/Debugger/src/main/svg/resumeback.svg create mode 100644 Ghidra/Debug/Debugger/src/main/svg/stepbackinto.svg diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/certification.manifest b/Ghidra/Debug/Debugger-agent-dbgeng/certification.manifest index f90bee9864..ac548173e4 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/certification.manifest +++ b/Ghidra/Debug/Debugger-agent-dbgeng/certification.manifest @@ -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| diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-dbgeng-trace.bat b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-dbgeng-trace.bat new file mode 100644 index 0000000000..55f5b46e65 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-dbgeng-trace.bat @@ -0,0 +1,21 @@ +::@title dbgeng-trace +::@desc
+::@desc+::@desc This will open a WinDbg TTD trace of the target on the local machine using dbgeng.dll. +::@desc For setup instructions, press F1. +::@desc
+::@desc +::@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 diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-ttd.bat b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-ttd.bat deleted file mode 100644 index 7d6e277819..0000000000 --- a/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-ttd.bat +++ /dev/null @@ -1,21 +0,0 @@ -::@title ttd -::@desc -::@desc-::@desc This will launch the target on the local machine for time-travel debugging. -::@desc For setup instructions, press F1. -::@desc
-::@desc -::@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 diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-dbgeng-trace.py b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-dbgeng-trace.py new file mode 100644 index 0000000000..82a223eaab --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-dbgeng-trace.py @@ -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() diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-ttd.py b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-ttd.py deleted file mode 100644 index d9f861a5b5..0000000000 --- a/Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-ttd.py +++ /dev/null @@ -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() diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py index 5ea94e43a3..2d9debe38d 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py @@ -24,15 +24,16 @@ 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 + from .exdi import exdi_commands, exdi_methods STILL_ACTIVE = 259 PAGE_SIZE = 4096 @@ -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) @@ -312,9 +318,9 @@ def ghidra_trace_attach(pid=None, attach_flags='0', initial_break=True, timeout= if attach_flags == None: attach_flags = '0' if pid != None: - dbg._client.AttachProcess(int(pid,0), int(attach_flags,0)) + dbg._client.AttachProcess(int(pid, 0), int(attach_flags, 0)) if start_trace: - ghidra_trace_start("pid_"+pid) + ghidra_trace_start("pid_" + pid) @util.dbg.eng_thread @@ -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(): """ @@ -370,7 +389,7 @@ def ghidra_trace_info(): print("Not connected to Ghidra") return host, port = STATE.client.s.getpeername() - print(f"Connected to {STATE.client.description} at {host}:{port}") + print(f"Connected to {STATE.client.description} at {host}: {port}") if STATE.trace is None: print("No trace") return @@ -585,7 +604,7 @@ def putreg(): nframe = util.selected_frame() # NB: We're going to update the Register View for non-zero stack frames if nframe == 0: - return {'missing': STATE.trace.put_registers(rpath, values)} + return {'missing': STATE.trace.put_registers(rpath, values)} nproc = util.selected_process() if nproc < 0: @@ -601,13 +620,13 @@ def putreg(): for i in range(0, len(regs)): name = regs._reg.GetDescription(i)[0] try: - value = regs._get_register_by_index(i) + value = regs._get_register_by_index(i) except Exception: - value = 0 + value = 0 try: values.append(mapper.map_value(nproc, name, value)) if util.dbg.use_generics is False: - robj.set_value(name, hex(value)) + robj.set_value(name, hex(value)) except Exception: pass return {'missing': STATE.trace.put_registers(space, values)} @@ -903,9 +922,10 @@ def activate(path=None): else: frame = util.selected_frame() if frame is None: - path = THREAD_PATTERN.format(procnum=nproc, tnum=nthrd) + 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() @@ -1198,11 +1218,11 @@ def put_regions(): regobj.set_value('AllocationBase', hex(r.AllocationBase)) regobj.set_value('Protect', hex(r.Protect)) regobj.set_value('Type', hex(r.Type)) - if hasattr(r, 'Name') and r.Name is not None: + 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 @@ -1358,7 +1414,7 @@ def put_frames(): (values, keys) = create_generic(path) STATE.trace.proxy_object_path(path).retain_values(keys) # NB: some flavors of dbgmodel lack Attributes, so we grab Instruction Offset regardless - #return + # return mapper = STATE.trace.memory_mapper keys = [] @@ -1373,18 +1429,18 @@ def put_frames(): STATE.trace.create_overlay_space(base, offset_inst.space) fobj.set_value('Instruction Offset', offset_inst) if not util.dbg.use_generics: - base, offset_stack = mapper.map(nproc, f.StackOffset) - if base != offset_stack.space: - STATE.trace.create_overlay_space(base, offset_stack.space) - base, offset_ret = mapper.map(nproc, f.ReturnOffset) - if base != offset_ret.space: - STATE.trace.create_overlay_space(base, offset_ret.space) - base, offset_frame = mapper.map(nproc, f.FrameOffset) - if base != offset_frame.space: - STATE.trace.create_overlay_space(base, offset_frame.space) - fobj.set_value('Stack Offset', offset_stack) - fobj.set_value('Return Offset', offset_ret) - fobj.set_value('Frame Offset', offset_frame) + base, offset_stack = mapper.map(nproc, f.StackOffset) + if base != offset_stack.space: + STATE.trace.create_overlay_space(base, offset_stack.space) + base, offset_ret = mapper.map(nproc, f.ReturnOffset) + if base != offset_ret.space: + STATE.trace.create_overlay_space(base, offset_ret.space) + base, offset_frame = mapper.map(nproc, f.FrameOffset) + if base != offset_frame.space: + STATE.trace.create_overlay_space(base, offset_frame.space) + fobj.set_value('Stack Offset', offset_stack) + fobj.set_value('Return Offset', offset_ret) + fobj.set_value('Frame Offset', offset_frame) fobj.set_value('_display', "#{} {}".format( f.FrameNumber, offset_inst.offset)) fobj.insert() @@ -1402,79 +1458,105 @@ 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"] if attr is not None: - map = util.get_attributes(attr) + 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() - mo = util.get_object(node.path) + 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) # print(f"ATTR={attributes}") values = [] @@ -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) @@ -1534,7 +1616,7 @@ def set_display(key, value, obj): if hloc is not None: key += " @ " + str(hloc) obj.set_value('_display', key) - (hloc_base, hloc_addr) = map_address(int(hloc,0)) + (hloc_base, hloc_addr) = map_address(int(hloc, 0)) obj.set_value('_address', hloc_addr, schema=Address) if vstr is not None: key += " : " + str(vstr) @@ -1557,9 +1639,134 @@ 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(): """ Put everything currently selected into the Ghidra trace diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/imodeliterator.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/imodeliterator.py index c5bf8451e8..6a90255a5d 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/imodeliterator.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/imodeliterator.py @@ -1,17 +1,17 @@ ## ### -# 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. +# 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 * @@ -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): diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/imodelmethod.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/imodelmethod.py new file mode 100644 index 0000000000..27e9880dbc --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/imodelmethod.py @@ -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) + diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/irawenumerator.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/irawenumerator.py index 3e284aec03..2e6afb40d0 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/irawenumerator.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/irawenumerator.py @@ -1,17 +1,17 @@ ## ### -# 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. +# 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 * @@ -35,7 +35,7 @@ class RawEnumerator(object): self._keys = None return cnt - # KeyEnumerator + # RawEnumerator def GetNext(self): key = BSTR() diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/hooks.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/hooks.py index dd01eab5a4..47b418fa99 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/hooks.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/hooks.py @@ -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: diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/methods.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/methods.py index b98cfa1c93..efcbdd2a71 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/methods.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/methods.py @@ -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.""" @@ -377,7 +385,7 @@ def launch_loader( """ command = file if args != None: - command += " "+args + command += " " + args commands.ghidra_trace_create(command=file, start_trace=False) @@ -393,7 +401,7 @@ def launch( """ command = file if args != None: - command += " "+args + command += " " + args commands.ghidra_trace_create( command, initial_break=initial_break, timeout=timeout, start_trace=False) @@ -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): @@ -557,7 +597,7 @@ def read_mem(process: sch.Schema('Process'), range: AddressRange): offset_start, offset_start + range.length() - 1, pages=True, display_result=False) if result['count'] == 0: commands.putmem_state( - offset_start, offset_start+range.length() - 1, 'error') + offset_start, offset_start + range.length() - 1, 'error') @REGISTRY.method @@ -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 diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema.xml b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema.xml index 872d38b53d..d4b7162e98 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema.xml +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema.xml @@ -2,7 +2,7 @@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.
- -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.
+ 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.This launcher has the same options as the WinDbg launcher, except that the DLL path must - contain dbgmodel.dll and the scripts that implement TTD. These are most easily - obtained by installing WinDbg Preview or later.
+This launcher has basically the same options as the WinDbg launcher, except that arguments + are not included and the DLL path must contain TTDReplay.dll + and the scripts that implement TTD. These are most easily obtained by installing WinDbg Preview or later.