Time Travel Debugging — Recording and Rewinding the Bugs That Never Reproduce in Long-Running Apps

· Updated: · · WinDbg, Time Travel Debugging, Debugging, Bug Investigation, Windows, Windows Development, .NET, C++, Technical Consulting

Revision history (first version, published Sep 2, 2026)
First published
Cite this article(DOI: 10.5281/zenodo.22640286)

This article is archived on Zenodo. Below are both the DOI that always resolves to the latest version and the DOI pinned to the version you are reading.

Go Komura (2026). Time Travel Debugging — Recording and Rewinding the Bugs That Never Reproduce in Long-Running Apps. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22640286 https://comcomponent.com/en/blog/windows-time-travel-debugging-ttd/

DOI (latest version)
10.5281/zenodo.22640286
DOI (this version)
10.5281/zenodo.22640287

“It crashes once a month, only in the middle of the night.” “The same operation never reproduces it on my machine.” “We got a dump, but looking at the crash site does not tell us why the value became what it is.” Among the bug investigations of long-running Windows apps, these are the cases that consume the most time. In an earlier article, “Reading Crash Dumps with WinDbg + SOS,” we looked at how to read a crash dump, which is a single photograph. This article continues from there and covers the tool for the situations where a photograph is not enough: Time Travel Debugging (TTD).

TTD is a WinDbg feature that records the entire execution of a process and lets you replay it later, forward and backward. Instead of trying over and over to reproduce a bug, you can “rewind” the debugger session.1 The intended readers are developers and maintainers of Windows, .NET, and C++ applications who have already done their share of dump and log investigation and still have cases they cannot get to the root cause of. The prerequisite environment is Windows 10/11 or Windows Server 2016 or later with the current WinDbg, and recording requires administrator privileges.12 The difficulty is intermediate.

Assumptions of This Article

Item Content
Intended readers Developers and maintainers of Windows apps with long-running or intermittent bugs that dumps and logs cannot get to the root cause of
Prerequisite knowledge Experience opening a dump in WinDbg and running !analyze -v or !clrstack. The content of the SOS analysis article is assumed
Prerequisite environment Windows 10/11 or Windows Server 2016/2019/2022/2025, WinDbg (current version), TTD.exe, administrator privileges2
Out of scope Integration with the Visual Studio Enterprise Snapshot Debugger; kernel mode (TTD is user mode only3)

1. The Bottom Line First

  • A dump preserves “state”; TTD preserves the “path.” The official documentation states that dumps tend to miss the state and execution path that led to the failure.1 If a photograph of the moment of the crash does not tell you the cause, what you need next is not more photographs but a recording.
  • Recording is heavy. While recording, the target process runs 5 to 20 times slower or worse, and the trace file grows by 5 to 50 MB per second while the process is active, with no cap.24 It is not a tool you attach unconditionally to a long-running app.
  • For long-running apps, design “what to record.” Chapter 5 covers the four entry points TTD.exe provides: -ring/-maxFile (keep only the last N MB), -module (record only while your own module is executing), -recordmode Manual (let the app specify the recording interval), and -monitor (record every launch).2
  • Replay revolves around three things: positions (!tt), events (dx @$curprocess.TTD.Events), and queries (TTD.Calls / TTD.Memory). Combine ba (a break-on-access breakpoint) with g- (reverse execution), and the debugger answers “who last wrote this value?” directly.5
  • A trace contains the contents of memory. It can include personal and confidential information such as file paths, registry data, and the contents of memory and files.1 Design sharing and storage as you would for a confidential file.
  • Know what TTD cannot do before you start. It cannot record kernel mode, it cannot inject into protected processes (PPL), it cannot detach itself once attached, and memory cannot be modified during replay.32

In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (32 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle

2. Where a Dump Is Not Enough

A crash dump is a copy of memory and registers at the moment the process crashed (or was stopped). As described in the SOS analysis article, !clrstack tells you where it crashed and !dumpheap -stat tells you what is consuming the heap. What it cannot tell you is what happened on the way to that state.

What a dump captures and what TTD capturesA crash dump captures only the state at the moment of the crash, not the path that led there. TTD keeps the entire instruction execution from the start of recording to the end, so the path is preserved along with the stateCrash dump: state at the moment of the crashDoes not show why the value became what it isTTD trace: instruction execution of the recorded intervalCan go back to the position where the value was writtenThis gap is what drags out long-running investigations

Figure 1: A dump is state; TTD is the path. In long-running bugs, what you want is usually the latter.

Typical cases look like these.

  • One field of a structure on the heap holds an impossible value. The dump shows the corrupted value, but not who wrote it or when.
  • The location of the exception is known, but not why the argument passed to it was invalid. Walking further up the callers, the function that produced the value along the way is no longer on the stack.
  • Handles or memory grow over the course of a month. A single dump can only say “it is growing”; the call path that grew it is not there.
What a dump misses in typical long-running casesThe corrupted value is captured but not who wrote it, the exception position is captured but the function that produced the argument is no longer on the stack, and the resource growth is captured but not the path that grew it; the three kinds of case share thisCorrupted fieldNo record of who wrote it or whenException on an invalid argumentFunction that produced the value is no longer on the stackResource that grows over a monthNo record of the call path that grew itCommon point: state exists, but no path

Figure 2: All three cases have “state but no path.” More photographs do not fill in the path.

Microsoft’s documentation compares the strengths and weaknesses of the investigation methods as follows.1

Method Strengths Weaknesses
Live debugging Interactive, shows the flow of execution, and lets you change state Stops the user’s work. Takes effort to reproduce repeatedly. Often unusable in production. Hard to go back from the point of failure to the cause
Dumps No code changes required in advance. Low intrusiveness, can be collected on a trigger. Near-zero overhead when not in use Even with consecutive snapshots, the view of “elapsed time” is coarse
Telemetry and logs Lightweight. Tied to business scenarios No logs on unexpected code paths. Insufficient depth of data, and statically embedded in the code
TTD Strong on complex bugs. No code changes required in advance. Can be replayed offline any number of times and records everything Large overhead while recording. May collect more data than needed. Files get large

There is one more important property that the official TTD walkthrough points out. When the debugger stops at the point of failure, that point is often inside error-handling code several steps past the real cause.5 A dump is always taken at this “several steps later” position. With TTD, you can step back from there one instruction at a time.

The gap between the point of failure and the real causeThe point of failure where a dump is taken is often inside error handling several steps past the real cause, and with TTD you can rewind from that point instruction by instruction back to the causeDumpTTDReal cause (the instruction that corrupted the value)Several steps forwardPoint of failure (exception, error handling)Frozen hereGo back with p- / t- / g-

Figure 3: A dump is frozen at the point of failure. TTD can walk back from the point of failure to the cause.

3. How TTD Works and What It Costs

3.1 What Is Being Recorded

TTD injects a recording engine into the target process and records the executed instructions, instruction by instruction. In the words of the official documentation, it “encodes a complete instruction-level trace at an average of less than one byte per instruction”; in practice it lands somewhere between one bit and one byte per instruction. Programs that execute few kinds of functions and handle little data produce smaller traces; the opposite produces larger ones.24

A recording produces two files.1

File Role Approximate size
.run The trace itself. Stores the instruction execution during recording Grows by 5 to 50 MB per second while active. Does not grow while idle4
.idx The index. Auxiliary data that lets WinDbg replay and query memory efficiently. Created when recording stops, and also generated automatically when WinDbg opens the .run file 1 to 2 times the size of the trace4
The flow from TTD recording to replayA recording engine is injected into the target process and instruction execution is recorded to a .run file; when WinDbg opens the .run file it creates an .idx index and replays forward and backward using positions, events, and queriesTarget processRecording engine injected (TTDRecordCPU).run (record of instruction execution)Open in WinDbgGenerate .idx (index)Replay forward and backward with positions, events, and queries

Figure 4: Recording is done by TTD.exe or WinDbg; reading is done by WinDbg. Sharing only the .run file is enough.

Time inside a .run file is expressed as a “position.” It takes the form of two hexadecimal numbers separated by a colon, such as 12:0 or 1A0:12F; the first half is the sequencing number (corresponding to a sequencing event) and the second half is the approximate number of instructions since that event.6 FFFFFFFFFFFFFFFE:0 means the end of the trace.7 Positions take center stage in Chapter 6.

How positions in a trace are expressedA position is a hexadecimal sequencing number and step count separated by a colon; the start is near 0, the end is expressed as FFFFFFFFFFFFFFFE:0, and you can also move to an approximate position by percentagePosition xx:yy (hexadecimal)xx: sequencing numberyy: instructions since that eventEnd is FFFFFFFFFFFFFFFE:0Percentages such as !tt 50 also work

Figure 5: A position is “event number:instruction count.” It is not wall-clock time, but it can be converted to wall-clock time as shown in Chapter 6.

3.2 The Cost

The official documentation itself describes TTD as an “invasive technology.”2 Here is the cost in numbers.

Item Content
Speed The target process runs 5 to 20 times slower or worse while recording (depending on the app and recording options). It may go unnoticed in the UI, but it is perceptible in heavy operations such as an Open File dialog23
File growth 5 to 50 MB per second while active. A few minutes of recording can reach several GB. No cap is set4
Running out of disk If the disk fills up during recording, TTD writes the last page and then effectively waits until it can write again. WinDbg keeps showing the recording dialog and emits neither an error nor a warning. The result is an incomplete trace4
Memory Recording adds the overhead of virtual CPUs to the target process’s memory (by default 55 on x64/ARM64, 32 on x86). Reduce it with -numVCpu only when memory is short2
Cannot detach Once attached, TTD cannot detach itself. When you finish recording, close the app or terminate the process. If the process is essential to the system, the OS must be rebooted2
The cost of recording and its effect on long-running appsThe slowdown while recording, file growth, the silent wait when the disk runs out, and the inability to detach after attaching are the four costs that make it impossible to attach TTD unconditionally to a long-running appTTD recording5 to 20 times slowerGrows 5 to 50 MB per second, no capWaits silently when the disk runs outCannot detach after attachingUnconditional continuous recording is not viable

Figure 6: Any one of the four costs rules out continuous recording. That is why the “recording design” of Chapter 5 is needed.

3.3 What It Cannot Do

  • User mode only. It can record only the user-mode execution of a process; code that runs in kernel mode, such as drivers, cannot be debugged.3
  • Protected processes. TTD cannot inject itself into Windows protected processes such as Protected Process Light (PPL).3
  • Replay is read-only. You can go back in time, but you cannot change history. Commands that read memory work; commands that modify it do not.3
  • Incompatibility with antivirus and memory-monitoring software. Because TTD hooks into the process, it conflicts with software that tracks or shadows system memory calls. If recording produces an error that looks like insufficient permissions, disable such software temporarily to isolate the problem. The Electron framework is a known conflict as well; even if a recording succeeds, the target process may deadlock or crash.3
  • UWP apps cannot be launched and recorded (attaching to an already-running UWP app is possible). “Unusual processes” running in another session or a different security context are currently not supported either.8
What TTD cannot record or doKernel-mode code, protected processes, launch recording of UWP apps, and processes in another session or security context cannot be recorded; memory cannot be modified during replay; and antivirus software and Electron can conflictTTD limitationsCannot be recordedLimitations and conflictsKernel-mode code (drivers, etc.)Protected processes (PPL)Launch recording of UWP (attach is possible)Other sessions and security contextsReplay is read-onlyMay conflict with antivirus and Electron

Figure 7: The limitations come in two kinds: “cannot record” and “conflicts.” The latter has to be isolated per environment.

The last item trips up anyone who wants to record a service. The TTD.exe documentation describes -attach as intended for “investigating services and long-running apps” and -monitor as recording “every time a program or service starts,”2 which does not simply square with the statement on the troubleshooting page. In practice, the safe approach is to verify per environment in this order: confirm that ping.exe or cmd.exe can be recorded in the same configuration as production, then try the target process.8

4. Recording — The WinDbg UI and TTD.exe

There are two entry points for recording: the WinDbg UI, or the command-line TTD.exe.

4.1 Recording from the WinDbg UI

Run WinDbg as administrator (elevation is mandatory for TTD1), choose File > Start debugging > Launch executable (advanced), specify the executable, and check Record with Time Travel Debugging. Choosing Configure and Record lets you set the location of the trace file and Record subset of execution (limit the recorded modules with a comma-separated list such as notepad.exe,kernelbase.dll). For a process that is already running, choose File > Start debugging > Attach to process and likewise check Record Process with Time Travel Debugging.9

While recording, a small dialog with “Stop and Debug” and “Cancel” buttons appears. When the app exits (or crashes), the trace is closed and WinDbg opens it automatically and builds the index.5

The flow of recording from the WinDbg UIIn WinDbg started as administrator, choose Launch executable (advanced) or Attach to process, check Record with Time Travel Debugging, set the save location and module filter in Configure and Record, go through the recording dialog, and when the app exits the trace is closed and indexed automaticallyStart WinDbg as administratorLaunch executable (advanced) / Attach to processCheck Record with Time Travel DebuggingConfigure and Record: save location, module filterRecording dialog (Stop and Debug)App exits, trace is closed and indexed automatically

Figure 8: Recording from the UI takes five steps. Skip “start as administrator” and you stop at the first dialog.

4.2 Recording with TTD.exe

When you need to record on a PC where WinDbg cannot be installed, or to automate recording, use TTD.exe on its own. It installs through App Installer from https://aka.ms/ttd/download, and after installation you can verify it with ttd.exe -help. For offline environments, Microsoft also provides an official procedure (with a PowerShell script) for unpacking the MSIX bundle by hand and extracting only the binaries.2 Recording requires administrator privileges and is normally run from an administrator command prompt.2

There are three recording modes.2

The three recording modes of TTD.exelaunch starts a new process with arguments and records it, but it runs with elevated privileges. attach attaches to a running process by PID. monitor records every time the specified program starts, and the launch happens with normal privilegesTTD.exe recording modes-launch: launch and record-attach: attach to a running PID-monitor: record every launchLaunched with administrator privilegesKeeps normal privilegesNormal launch path, suited to automation

Figure 9: Only -launch can pass arguments, but it runs with elevated privileges. To record behavior close to production, use -attach or -monitor.

:: Launch and record (default mode; -launch can be omitted)
TTD.exe -out C:\traces MyApp.exe --config prod.json

:: Attach to a running process (create the output directory first)
TTD.exe -attach 21440 -out C:\traces\MyApp.run

:: Record every launch (Ctrl+C ends monitoring; -out requires a full path)
TTD.exe -out C:\traces\ -monitor MyApp.exe
  • -launch is the only mode that can pass arguments, but the program is launched with the same (administrator) privileges as TTD.exe. For apps whose behavior changes with privileges, record with -attach or -monitor to keep normal privileges.2
  • -attach requires the output directory to exist already. If you specify a file name, no file with that name may exist.2
  • -monitor installs a process-launch monitoring driver and records every time the specified program (more than one can be given) starts. It stays in effect until reboot and is stopped with Ctrl+C. Its advantages are that you do not have to assemble the launch yourself, the target runs with normal privileges, and it suits scripted automation. Adding -cmdLineFilter "string" records only launches whose command line contains that string.2
  • -children records child processes too, but each process gets its own .run file, and WinDbg can open only one at a time.2

While recording, a small UI with two buttons appears: “Tracing Off” (stop recording and let the app continue) and “Exit App” (close the app and end recording). For automation, hide it with -noUI and accept the EULA with -accepteula.2 The recording log is kept in an .out file in the same location as the .run file, where you can read the wall-clock start and end times of the recording, the length of the recording session (simulation time), whether it was a launch or an attach, and the OS version. When a recording fails, some error messages appear only in the .out file.2

5. Recording Design for Long-Running Apps

This is the heart of the article. Given the costs in Chapter 3, catching a bug that occurs at an unknown time in a process that runs for days requires a recording design. TTD.exe provides four means, and you choose by the nature of the symptom.

Choosing the recording scope by the symptom of a long-running appIf you do not know when it will occur, keep only the last part with a ring buffer; if you know which module is suspect, record only that module; if you can modify the app, specify the interval with the manual recording API; if it appears only at startup or on particular launches, record every launch with monitor modeUnknown when it occursOnly at startup or on particular launchesSuspect module is clearApp can be modifiedNature of the symptom?-ring / -maxFile: keep only the end-monitor: record every launchAdd -moduleSpecify the interval with -recordmode Manual

Figure 10: The four are not mutually exclusive. Combining -ring with -module is the most practical combination for long-running apps.

5.1 Ring Buffer — Keep Only the Last N MB

With -ring, the trace is written to a ring buffer of the size given by -maxFile, and the file never grows beyond that limit. What remains is only the last part of the recording that fits in that size.2 The unit of -maxFile is MB; in ring-buffer mode the default is 2,048 MB, the minimum 1 MB, and the maximum 32,768 MB (the default for the in-memory ring of a 32-bit process is 256 MB).2

:: Attach to a running monitoring app and keep only the most recent 4 GB of execution
TTD.exe -accepteula -noUI -attach 21440 -ring -maxFile 4096 -out C:\traces\MyApp.run

:: When the symptom is detected, stop recording (the app keeps running)
TTD.exe -stop 21440

-stop accepts a process name, a PID, or all, and stops that recording. -wait <seconds> waits until every recording session on the system has finished (-1 for indefinitely), and is used in automation scripts to enforce the order “stop, then collect the file.”2

Timeline of a ring-buffer recordingRecording starts on attach, older parts are pushed out of the ring buffer, and when the symptom appears and stop is issued, only the most recent maxFile worth remains as the traceStart recording with -attach -ringOlder intervals are pushed outSymptom appearsStop recording with -stopOnly the most recent maxFile worth remainsSize it so the moments before the symptom fit in the buffer

Figure 11: The ring buffer is a device for keeping “the moments before the symptom.” Work -maxFile backward from “time from noticing the symptom to stopping, multiplied by growth per second.”

There are two design points.

  1. Make the buffer large enough to absorb the time from noticing to stopping. In an active process the trace grows by 5 to 50 MB per second,4 so a 4 GB ring corresponds to one to two minutes under heavy load and a little over ten minutes under light load. You need a mechanism that keeps the lag between detecting the symptom (a specific log line, a counter threshold, an alert from monitoring) and -stop within that window.
  2. Stopping does not detach. -stop stops the recording, but TTD does not detach itself from the target process.2 Ending the recording completely requires terminating the process, so include “restart at the next maintenance window after collecting the trace” in the operating procedure.
Stopping a ring-buffer recording in coordination with monitoringWhen the monitoring side detects the symptom through logs or counters, it calls TTD.exe stop, collects the finalized .run file, and restarts the process at the next maintenance window to remove TTDTarget processTTD.exeMonitoring (logs, counters)Target processTTD.exeMonitoring (logs, counters)Detect the symptom-stop PIDStop recording (process continues).run is finalizedCollect .run, encrypt, and storeRestart at the next maintenance window

Figure 12: Only when the lag between detection and -stop fits in the buffer do the moments before the symptom remain in the trace. The restart is part of the operation.

5.2 Module Filter — Record Only While Your Own Code Runs

-module <module name> records only the specified module (the executable itself or a loaded DLL; more than one can be given) and the code that module calls. The target process runs at full speed until code in the specified module executes; recording starts when execution enters the module, stops when it leaves, and the process returns to full speed. Because turning recording on and off is expensive, recording stays on while the specified module is calling other modules in the process.2

:: Record only while our measurement-logic DLL is running (combined with a ring)
TTD.exe -accepteula -noUI -attach 21440 -module MeasureCore.dll -ring -maxFile 2048 -out C:\traces\

A trace made this way simply skips the intervals where recording was off, treating “the next instruction” as the first instruction after recording resumed, and you debug it no differently from a trace of the whole process.2 In long-running apps, the UI idle loop and the framework’s internal processing tend to account for most of the executed instructions, so restricting recording to your own module alone reduces overhead and file size substantially.

How module-filtered recording behavesThe target process runs at full speed outside the specified module; recording starts when code in the specified module is entered, continues while that module calls other modules, and stops when execution leaves the moduleOutside the specified module: full speedEnter the specified module: recording startsOther modules it calls: recording continuesLeave the specified module: recording stops

Figure 13: Because the Win32 APIs and runtime that your DLL calls are recorded as well, this is enough to trace “what our code handed to the OS.”

5.3 Manual Recording — Let the App Specify the Interval

With -recordmode Manual, the process keeps running at full speed even after TTD is injected, and recording happens only when the program calls TTD’s in-process recording API (the default, Automatic, records from the moment of injection).2 The API documentation and samples are in the WinDbg-Samples repository on GitHub.10

If you can modify the app, this is the least wasteful approach. You can build in logic that starts recording when the app itself knows an anomaly is imminent and stops when things return to normal: “three consecutive communication retries failed,” “the queue backlog exceeded the threshold,” and so on. In a supervisor-process configuration like the one covered in the Job Object article, the supervisor can decide the interval instead.

5.4 Monitor Mode — Record Every Launch

For bugs that appear only right after startup or on particular launches, -monitor is the right fit. The official comparison table also describes monitor mode as intended for “catching intermittent problems and startup problems.”2 When recording the same program many times, the default sequential file names (MyApp01.run, MyApp02.run, and so on) become inefficient because existing files have to be scanned, so use -timestampFilename for timestamped names. The number of simultaneous recordings can be capped with -maxConcurrentRecordings.2

How monitor mode behavesThe monitor option installs a process-launch monitoring driver, narrows the target with the command-line filter every time the specified program starts, records it, creates a separate trace file per launch, and continues until Ctrl+C or a rebootYesNoInstall the launch-monitoring driverDetect a launch of the specified programMatches -cmdLineFilter?Record that launch (separate file per launch)Do not recordWait for the next launch (until Ctrl+C or reboot)

Figure 14: Monitor mode “lies in wait for the launch.” It suits bugs that appear on every launch and bugs that can be narrowed by launch conditions.

5.5 Where to Put the Disk

For long-running recordings, put the traces on a dedicated volume and include the growth of the .run file in your monitoring. As noted in Section 3.2, when the disk fills up, recording just waits silently with no error. The official workaround is equally primitive: “check free space in Explorer” and “check that the .run file is growing regularly.”4 Without free-space monitoring, you end up with an incomplete trace in which the moment you wanted most was never written.

How running out of disk produces an incomplete traceWhen the disk runs out during recording, TTD writes the last page and waits silently with neither an error nor a warning, so the symptom that occurs afterward is not recorded, leaving an incomplete trace that opens but lacks the crucial part. Prevent it with a dedicated volume and monitoring of .run growthpreventsDisk runs outWrites the last page and waits silentlyNo error, no warningSymptom occurs afterwardIncomplete trace without the symptomDedicated volume + monitoring of .run growth

Figure 15: A trace that “opens but lacks the crucial part” is born from a gap in disk monitoring.

6. Replaying — Positions, Events, and Rewinding

6.1 Opening

When you open a .run file in WinDbg, if there is no index, !index runs automatically and builds the .idx file while counting keyframes (positions in the trace generated automatically for indexing; larger traces have more of them). The larger the trace, the longer it takes.5 The state of the index can be checked with !index -status; if it reports anything other than “Index file loaded,” rebuild it with !index -force. If that still fails, close the debugger, delete the .idx file, and reopen the .run file. Rebuilding the index does not modify the .run file, so no data is lost.8

One caveat: when indexing of large traces was improved in TTD 1.11.611, the index format changed, and existing traces need to be re-indexed.11 If you carry old .idx files around, this is where you stumble.

Checking and rebuilding the indexAfter opening the trace, check the state with !index -status; if it is anything other than Index file loaded, rebuild with !index -force, and if that still fails, close the debugger, delete the .idx file, and reopen the .run file. Rebuilding does not modify the .run fileIndex file loadedAnything elseFailsSucceedsOpen the trace!index -statusProceed to analysisRebuild with !index -forceClose, delete .idx, reopen .run

Figure 16: Rebuilding the index does not touch the .run file. When in doubt, delete it and reopen.

6.2 Moving by Position

Passing a position to !tt moves to that point in time.6

!tt 0          ; start of the trace
!tt 50         ; approximately the 50% position
!tt 100        ; end of the trace
!tt 1A0:12F    ; to position 1A0:12F

A position is a pair of hexadecimal numbers, sequencing number:step count.6 The Position object has the properties Percent (the fraction of the trace), Sequence, and Steps, plus SeekTo(), which moves to that position, and ToSystemTime(), which returns the approximate wall-clock time (UTC).7 In long-running investigations, ToSystemTime() is what makes the difference, because it lets you match the timestamps in the app’s log against positions in the trace. TTD.Calls results also carry SystemTimeStart / SystemTimeEnd.12

Matching positions against log timestampsStarting from a timestamp in the app's log, follow the approximate wall-clock time of the Position object to identify the position in the trace, move there with SeekTo, and read the execution around itApp log: time of the anomalyFind a position whose ToSystemTime is closeMove there with SeekToRead the surrounding calls and values

Figure 17: “What was it doing just before this log line?” can be looked up through the correspondence between positions and wall-clock time.

Positions are also useful for sharing. When you hand a trace to a colleague, attach the !tt x:y position and they can start looking from the same point in time. Writing position ranges in bug reports is an officially recommended practice as well.2

6.3 Rewinding

Appending - to the usual stepping commands moves backward in time.13

Command Meaning Ribbon button
p- Go back one instruction (or one source line). A function call counts as one step Step Over Back
t- Go back one instruction (or one source line). Steps into function calls Step Into Back
g- Execute in reverse. Stops when a breakpoint is hit, on an event, or at the start of the trace Go Back

The events that stop g- are the same ones that stop a forward g.13 In other words, set a ba (break-on-access breakpoint) or bp and run g-, and you go straight back to “the last position where that condition held.” This is the basic move of Chapter 7.

Choosing among the reverse commandsp- goes back one step across function calls, t- goes back one instruction into functions, and g- goes straight back to a breakpoint, an event, or the start of the trace. Whatever stops a forward g also stops g-p-t-g-Current positionGo back one step across callsGo back one instruction into functionsGo straight back to the next stopping conditionba / bp / event / start of trace

Figure 18: To look carefully nearby, use t-; to jump to a distant cause, use ba + g-.

6.4 Entering Through Events

If you are unsure where to start reading, begin with the event list. @$curprocess.TTD.Events lists thread creation and termination, module load and unload, and exceptions as events.14

dx -g @$curprocess.TTD.Events
dx @$curprocess.TTD.Events.Where(t => t.Type == "Exception").Select(e => e.Exception)

An exception event contains the position, the type (Software / Hardware), the exception code, and the program counter at the time, and clicking the [Time Travel] link in the output moves to that position.15 The official walkthrough follows this flow: it jumps to the position of an access violation (0xc0000005), suspects stack corruption because the stack pointer and base pointer disagree, and steps back three instructions with t- to check the values.5 WinDbg’s Timelines window visualizes exceptions, breakpoints, memory accesses, and function calls as a timeline, and double-clicking an exception issues the same SeekTo().16

6.5 Threads and Positions

!positions shows every active thread at the current position together with each thread’s position in the trace.17 There is one pitfall here. Switching threads with ~<number>s does not move the position in the trace. The position the debugger uses to read memory does not change, so to look at another thread’s memory “at that point in time,” move there with the position link in the !positions output or with !tt x:y.13

The basic path through a replayOpen the trace and build the index, move from the event list to the position of the exception, step backward to the cause, and if needed move to another thread's position with positionsOpen the trace and index itFind the exception in TTD.EventsMove to the position with [Time Travel]Go back with t- / p- / g-Check other threads' positions with !positions~s does not move the trace position

Figure 19: “Enter through an event, walk backward” is the basic path through a replay. Switching threads is not moving positions.

7. Finding “When” with Queries — TTD.Calls and TTD.Memory

The real strength of replay is that you can query the whole trace. TTD’s objects are exposed through the debugger data model (the dx command), and you can filter, sort, and aggregate them LINQ-style.18

7.1 TTD.Calls — Searching for Function Calls

@$cursession.TTD.Calls("module!symbol") collects the calls to the specified function from the whole trace. Wildcards are allowed, and each call carries its start and end positions (TimeStart / TimeEnd), the thread ID (with a UniqueThreadId that is never reused), the arguments (Parameters[]), the return value (ReturnValue), and the return address (ReturnAddress).12

The example in the official documentation is GetLastError. Aggregate the calls whose return value is nonzero by error code, and you get a list of which errors occurred how many times during the trace.18

dx -g @$cursession.TTD.Calls("kernelbase!GetLastError").Where(x => x.ReturnValue != 0).GroupBy(x => x.ReturnValue).Select(x => new { ErrorNumber = x.First().ReturnValue, ErrorCount = x.Count() }).OrderByDescending(p => p.ErrorCount),d

For “where was the last MessageBox called from?”, take the last call with OrderBy(c => c.TimeStart).Last() and move there through the [Time Travel] link of its TimeStart.18

Building a TTD.Calls queryCollect calls by function name, filter by return value or arguments, aggregate by error code and the like, sort by time, and move to the position of the call you want through the Time Travel linkTTD.Calls (function name, wildcards)Where: filter by return value or argumentsGroupBy: aggregate by error code, etc.OrderBy: sort by timeMove through [Time Travel] on TimeStart

Figure 20: The way to use queries is to start not from “where” but from “when, how many times, and with which arguments.”

A word on symbols. TTD determines the number and types of a function’s arguments, its return type, and its calling convention from the PDB symbol information. With private symbols you get the function name and the correct arguments. With public symbols only, you get the function name and default arguments (four 64-bit unsigned integers). A module with no symbols at all gets the function name UnknownOrMissingSymbols.1218 For the kinds of PDB and how to store them, see “What Is a PDB?”.

Symbol availability and TTD.Calls resultsWith private symbols you get the function name and the correct arguments; with public symbols only you get the function name and the default four 64-bit integer arguments; with no symbols the function name becomes UnknownOrMissingSymbolsprivate symbolspublic symbolsnoneSymbols for the module?Function name + correct arguments and return valueFunction name + default arguments (4 x 64-bit integers)UnknownOrMissingSymbols

Figure 21: Whether you have kept the PDBs of your own modules directly determines how useful the queries are.

Calls involves computation, so the larger the trace, the longer it takes and the higher the CPU usage. Results are cached in memory, so the second and later queries for the same function are faster.12 When a query returns nothing, there are four causes: how the call is written (check the module name with the x command; if it comes back in uppercase, use that), the target DLL is not yet loaded at that position (move to a position after the load and run it again), the function is inlined (cannot be tracked), or the wildcard is too broad (narrow it).18

7.2 TTD.Memory — Searching for Memory Accesses

@$cursession.TTD.Memory(start address, end address, "access type") collects accesses to the specified memory range from the whole trace. The types are r (read), w (write), rw, e (execute), rwe, and ec (execute/change).19 “Who last wrote this variable?” is answered by collecting with "w", taking .Last(), and moving to that position.5

dx -g @$cursession.TTD.Memory(0x00a4fca0, 0x00a4fca4, "w")
dx @$cursession.TTD.Memory(0x00a4fca0, 0x00a4fca4, "w").Last().TimeStart.SeekTo()

If you only need to search forward or backward from the current position, @$curprocess.TTD.PrevMemoryAccess("w", address, size) / NextMemoryAccess are lighter and accept multiple ranges at once. The position where a register changed can be found with @$curthread.TTD.PrevRegisterWrite("rcx").6

7.3 ba + g- — Making the Debugger Answer “Who Corrupted It?”

Condensing the procedure shown in the official walkthrough into a form usable for long-running investigations gives the following.5

  1. Move to the position of the exception with TTD.Events
  2. Step back with t- and hypothesize which variable holds the corrupted value
  3. Get the address of that variable with dx &variable
  4. Set a write breakpoint with ba w4 <address>
  5. Run g- to go straight back to the position where that variable was last written
  6. Check whether that point (or a few instructions earlier) is the cause. If the written value came from another variable, set ba on that variable and run g- again
  7. Repeat until you reach the instruction that corrupted it
Tracing the origin of a value with ba and g-Set a write breakpoint on the address of the corrupted value and execute in reverse, stop at the instruction that last wrote it, and if that value came from another variable repeat the same steps until you reach the instruction that corrupted itYesNoIdentify the address of the corrupted valueSet a write breakpoint with ba wExecute in reverse with g-Stop at the instruction that last wrote itDid the value come from another variable?The corrupting instruction = the cause

Figure 22: An investigation that ends at “it is corrupted” with a dump proceeds mechanically to “who corrupted it” with TTD.

TTD 1.11.553 and later also add @$curframe.TTD.VariableHistory(), which returns the history of a frame’s local variable values. It shows a table of the variable names and which values each variable held over which position ranges.11 In situations like stack corruption where you want to know “since when has the value been wrong,” it is useful for narrowing down where to set ba first.

8. Applying This to Long-Running Bugs

Now we apply these tools to the three kinds of case listed at the beginning.

Types of long-running bugs and the TTD entry point for eachFor intermittent exceptions and data corruption, go back from the event with ba and g-; for resource growth, match acquisition and release calls with TTD.Calls; for not responding and chains of waits, follow positions and the wall-clock times of wait API callsIntermittent exceptions, data corruptionTTD.Events, then ba + g-Handle and memory growthMatch acquisition and release with TTD.CallsNot responding, chains of waits!positions and wall-clock times of wait APIsRecord with -module limited to your own DLL

Figure 23: The entry point differs by type. What they share is narrowing the recording scope before you start reading.

Type 1: Intermittent exceptions and data corruption. Keep the moments before the symptom with the ring buffer of Section 5.1, jump to the position of the exception from the event list of Section 6.4, and trace the origin of the value with the ba + g- of Section 7.3. The difference from a dump investigation is that the investigation does not end when you find “the corrupted variable”; from there you proceed mechanically backward.

Type 2: Handle and memory growth. This is the kind of case dissected in “A Handle Leak That Crashes After a Month.” You cannot record a month’s worth, so restrict recording to your own DLL with the -module of Section 5.2, combine it with -ring, and keep “the few minutes while it is growing.” On the trace, collect TTD.Calls("kernelbase!CreateFileW") and TTD.Calls("kernelbase!CloseHandle") and match the return value of CreateFileW (the handle value) against the first argument of CloseHandle (Parameters[0]). The ReturnValue of CloseHandle is a Boolean success flag, so it is useless for matching. If a handle value is left unclosed, the ReturnAddress (the caller) of that CreateFileW call gives you the caller that is leaking.12 That said, observing whether a leak exists and how large it is is cheaper with Application Verifier or handle counts; the correct division of labor is to bring in TTD at the stage of pinning down which path is leaking. Note that recording with Application Verifier enabled noticeably degrades replay performance because of how memory is used, so disable it while recording.8

Type 3: “Not responding” and chains of waits. As written in “What “Not Responding” Really Is,” a hang is a question of who is waiting for whom. A trace does not grow while idle,4 so the waiting time of a thread that is waiting in the kernel does not appear as instructions. What does appear is the call just before entering the wait and the instructions after it returned. Look at each thread’s position with !positions,17 and line up “which thread started waiting for what, and when” from the SystemTimeStart / SystemTimeEnd of TTD.Calls("kernelbase!WaitForSingleObject") and the like; matched against the log timestamps, that lets you reconstruct the chain of waits.12 “Order-dependent bugs” like DllMain and the loader lock or spurious wakeups of condition variables are an area where TTD, which records the order itself, is a good fit.

9. TTD with .NET Apps

The official TTD documentation states that managed code can be debugged with TTD in WinDbg using the SOS extension (sos.dll) running in 64-bit mode.1 Loading it is the same as in Chapter 3 of the SOS analysis article (.loadby sos coreclr or automatic loading), and !clrstack and !pe work at each position in the trace. The basic path is to move to the position of the exception with TTD.Events and then read the managed stack with !clrstack.

Setup for reading a TTD trace of a .NET appOpen the TTD trace in WinDbg, load the 64-bit SOS extension, move to the position of the exception event, and read the managed state with !clrstack and !pe. Use TTD.Calls for calls across native boundariesTTD trace (.run)WinDbgSOS extension (64-bit)Move to the exception position with TTD.EventsRead with !clrstack / !peTTD.Calls for calls across native boundaries

Figure 24: Managed state through SOS, call searches at native boundaries. Separate the roles and you will not get lost.

Two caveats. First, what Microsoft guarantees is “SOS in 64-bit mode.” Nothing is stated about .NET apps built for x86, so run the investigation target as x64 if you can. Second, TTD.Calls relies on PDB symbol information.12 Do not count on searching JIT-compiled managed methods by name; the reliable use is to follow calls across native boundaries such as P/Invoke targets in native DLLs, COM, and Win32 APIs. Managed-heap problems like those in “Telling GC Lag from a Memory Leak in .NET” are pursued first with dotnet-counters / dotnet-gcdump / !gcroot on a dump, and TTD comes out at the stage where a native boundary is involved.

10. Choosing Among Dumps, Logs, ETW, and TTD

TTD is not a cure-all, and it does not replace the existing tools. Here it is lined up against the tools covered in our articles.

What you want to know Tool to use first When TTD comes in
The state at the moment of the crash Crash dump (WER LocalDumps / ProcDump) When the dump shows “it is corrupted” but not who corrupted it
Which file or registry key failed Process Monitor When you also need how the arguments passed to the failing API were built
Whole-PC, long-duration performance WPR/WPA, PerfView (ETW) When it is a correctness problem rather than performance and you need instruction-level order
The business-level sequence of events The app’s logs When it happened on a code path with no logging (TTD records everything without prior code changes1)
Who wrote that value, the order of calls TTD
Choosing an investigation methodFirst grasp the state with lightweight dumps and logs, investigate failing APIs with ProcMon and performance with ETW, and move on to TTD only when you still need who passed what and whenYesNoSymptomGrasp the state with dumps and logs (lightweight)Failing APIs: ProcMonPerformance: WPR/WPA, PerfViewNeed who, when, and what was passed?Design the recording scope and record with TTDSettled with lightweight tools

Figure 25: Grasp the “result” with lightweight tools first, and bring out TTD only for the cases where the “path” turns out to be needed.

The order is “lightest first.” Dumps and logs cost almost nothing to collect, so keep them in place permanently; bring out TTD, after the design of Chapter 5, for the cases where reading the dump showed that the path is needed. Given TTD’s overhead and the fact that traces contain confidential information, there is no reason to reverse this order.

11. Operational Notes

  • Treat traces as confidential files. A recording contains memory contents and can include personal and security-related information such as file paths, registry data, and the contents of memory and files.12 When recording in a customer environment, understand in advance what may be included (connection strings, tokens, customer data) and decide on an encrypted transfer channel, a storage location, and a retention period.
  • Share only the .run file. The .idx file is about as large as the .run file and is generated automatically when WinDbg opens it. .run files compress well. When reporting a bug in TTD itself, attach the .out file too.2
  • Keep versions aligned. TTD keeps being updated together with WinDbg; 1.11.611 includes a fix for recording crashes in programs that use AVX/AVX512 and a change in the index format.11 Mixing an old version on the recording side or the replay side means re-indexing or re-recording.
  • Check -replayCpuSupport when replaying on a different CPU. The default favors portability, and MostConservative is provided for cases where the recording CPU and the replay CPU differ (such as replaying an Intel trace on arm64). Conversely, if you know the replay CPU is equal or better, you can choose a smaller, faster recording.2
  • Windows Server can be recorded too. TTD.exe supports Windows Server 2016/2019/2022/2025.2
  • Isolating a failure to record. First try whether ping.exe or cmd.exe can be recorded; if not, suspect a conflict with invasive software such as antivirus or application virtualization.8
Procedure for handling tracesBecause a recorded trace contains memory contents, understand what information it may contain, compress only the .run file and hand it over through an encrypted channel, decide on the storage location and retention period, and on the analysis side open it with the same version of WinDbg and build the indexRecording complete (.run / .idx / .out)Understand what information it may containCompress only .run, encrypt, and hand overDecide the storage location and retention periodOpen with the same version of WinDbg and generate .idx

Figure 26: Handing over a trace is handing over a confidential file. Decide the procedure before you record.

12. Summary

  • A dump is “state”; TTD is the “path.” In cases that need the origin of a corrupted value or the order of calls, TTD turns the investigation into mechanical work
  • Recording is 5 to 20 times slower, grows 5 to 50 MB per second, and cannot detach. For long-running apps, design the recording scope first with -ring/-maxFile, -module, -recordmode Manual, and -monitor
  • Replay is “enter through an event, walk backward”: TTD.Events, then [Time Travel], then t-/g-, and ba + g- to make the debugger answer “who corrupted it”
  • TTD.Calls and TTD.Memory are queries over the whole trace. Match against log timestamps with ToSystemTime() and SystemTimeStart
  • For .NET, read the state with 64-bit SOS and use TTD.Calls at native boundaries
  • A trace is a confidential file. Share only the .run file, and decide the channel and storage first

Cases where a dump was captured but the cause is out of reach, or where work has stalled because the bug does not reproduce, are quite often settled with TTD once the recording scope can be designed. We can also take on everything from the recording design to the analysis of the .run file, so feel free to get in touch with your dumps and logs.

KomuraSoft LLC handles root-cause investigation of Windows app bugs that appear only after long runs or intermittently, combining crash dumps, logs, and TTD traces; building an investigation setup including the design of the recording scope; and isolating failures that involve native boundaries (COM, P/Invoke, device SDKs). Get in touch as early as the stage of “we have a dump but cannot reach the cause.”

References

  1. Microsoft Learn, Time Travel Debugging - Overview. On TTD recording a process’s execution and replaying it forward and backward, dumps tending to miss the state and execution path that led to the failure, recording requiring administrator privileges, recordings possibly containing personal and security-related information, the comparison table of investigation methods, the roles of .run/.idx, and debugging managed code with the SOS extension in 64-bit mode.  2 3 4 5 6 7 8 9 10

  2. Microsoft Learn, Time Travel Debugging - TTD.exe command line utility. On the 5- to 20-fold or greater slowdown, inability to detach itself after attaching, support for Windows Server 2016 to 2025, installation and offline deployment, the three modes -launch/-attach/-monitor, the options -out/-noUI/-accepteula/-stop/-wait/-tracingOff/-children/-cmdLineFilter/-timestampFilename/-ring/-maxFile/-maxConcurrentRecordings/-numVCpu/-replayCpuSupport/-module/-recordmode, reading the .out file, and advice on sharing traces.  2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

  3. Microsoft Learn, Time Travel Debugging - Overview - Things to look out for. On incompatibility with antivirus and memory-monitoring software and Electron, user mode only, replay being read-only, inability to inject into protected processes (PPL), and the roughly 10- to 20-fold performance impact while recording.  2 3 4 5 6 7

  4. Microsoft Learn, Time Travel Debugging - Working with Trace Files. On the factors of trace size (one bit to one byte per instruction), growth of 5 to 50 MB per second while active and none while idle, the absence of a maximum size cap, the index being 1 to 2 times the trace, and the behavior of recording and indexing when the disk runs out along with the workaround.  2 3 4 5 6 7 8 9

  5. Microsoft Learn, Time Travel Debugging - Sample App Walkthrough. On the general procedure of moving to the position of an exception event and going back with ba and g- to the position where an invalid value was last written, the point of failure often being inside error handling several steps past the real cause, the trace being closed on a crash and WinDbg indexing it automatically, and the use of TTD.Memory and .Last() 2 3 4 5 6 7

  6. Microsoft Learn, !tt (time travel). On specifying positions for !tt (percentage or xx:yy), the meaning of the two components of a position (sequencing number and step count), and TTD.PrevRegisterWrite/PrevMemoryAccess/NextMemoryAccess 2 3 4

  7. Microsoft Learn, TTD Position Objects. On the Position object’s Percent/Sequence/Steps, SeekTo(), ToSystemTime() returning the approximate wall-clock time (UTC), and FFFFFFFFFFFFFFFE:0 denoting the end of the trace.  2

  8. Microsoft Learn, Time Travel Debugging - Troubleshooting. On elevation being required, launch recording of UWP apps being unsupported, “unusual processes” in another session or security context being out of scope, isolating with ping.exe/cmd.exe, replay slowing down when Application Verifier is used at the same time, and rebuilding the index with !index -status/!index -force 2 3 4 5

  9. Microsoft Learn, Time Travel Debugging - Record a trace. On recording from Launch executable (advanced) / Attach to process in the WinDbg UI, the Record with Time Travel Debugging check box, setting the save location with Configure and Record, and limiting modules with Record subset of execution

  10. Microsoft, WinDbg-Samples - TTD in-process recording API (GitHub). On the documentation of the in-process recording API that, combined with -recordmode Manual, lets the program control the start and stop of recording. 

  11. Microsoft Learn, Time travel debugging release notes. On the recording fix for programs that use AVX/AVX512 and the index format change (re-indexing required) in 1.11.611, and @$curframe.TTD.VariableHistory() added in 1.11.553.  2 3

  12. Microsoft Learn, TTD Calls Objects. On the arguments of TTD.Calls, the properties ThreadId/UniqueThreadId/Function/ReturnValue/ReturnAddress/Parameters[]/TimeStart/TimeEnd/SystemTimeStart/SystemTimeEnd, the defaults when PDB symbol information is missing (four 64-bit unsigned integer arguments, UnknownOrMissingSymbols), and the computation taking time with results being cached.  2 3 4 5 6 7

  13. Microsoft Learn, Time Travel Debugging - Replay a trace. On reverse execution with p-/t-/g-, g- stopping on the same events as forward execution, !positions, and ~s not changing the position in the trace.  2 3

  14. Microsoft Learn, TTD Event Objects. On the event types (ThreadCreated/ThreadTerminated/ModuleLoaded/ModuleUnloaded/Exception) and the child objects Position, Module, Thread, and Exception. 

  15. Microsoft Learn, TTD Exception Objects. On the exception object’s Type (Software/Hardware), ProgramCounter, Code, Flags, and Position

  16. Microsoft Learn, WinDbg: Timelines. On the Timelines window visualizing exceptions, breakpoints, memory accesses, and function calls, and double-clicking an exception issuing Position.SeekTo()

  17. Microsoft Learn, !positions. On displaying every active thread and each thread’s position in the trace.  2

  18. Microsoft Learn, Introduction to Time Travel Debugging objects. On the @$curprocess.TTD / @$cursession.TTD objects, queries with OrderBy/Where/Select/GroupBy, the examples of aggregating GetLastError errors and finding the last call to MessageBoxW, the meaning of UnknownOrMissingSymbols, and the four reasons Calls returns nothing.  2 3 4 5

  19. Microsoft Learn, TTD Memory Objects. On the access types of TTD.Memory (r/w/rw/e/rwe/ec) and moving to a position through the [Time Travel] link in the results. 

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Frequently Asked Questions

Common questions about the topic of this article.

What is the difference between Time Travel Debugging (TTD) and a crash dump?
A crash dump is a photograph of memory at the moment of the crash: it shows the state at that instant, but not the path that led there. A TTD trace is a complete recording of the process's instruction execution that can later be replayed forward and backward, so you can rewind and directly check who last wrote to a variable or what was called just before an exception. Microsoft's official documentation also states that dumps tend to miss the state and execution path that led to the failure. The price is that the process runs 5 to 20 times slower while recording, and the trace file grows by roughly 5 to 50 MB per second while the process is active.
Can I leave TTD attached to a production app that runs for days?
Not as is. TTD slows the process considerably while recording, the trace grows by 5 to 50 MB per second in an active process, and there is no cap on the file size. Using it on a long-running app presupposes a recording design: TTD.exe's -ring (ring buffer) with -maxFile to keep only the last N MB, -module to record only while your own module is running, or -recordmode Manual to let the app choose the recording interval. Also, once attached, TTD cannot detach itself, so you must decide in advance how you will end the recording, which means terminating (restarting) the process.
Can services and processes in other sessions be recorded?
The TTD.exe documentation describes -attach as intended for investigating services and long-running apps, and -monitor as recording every time a program or service starts. On the other hand, the troubleshooting page states that unusual processes running in another session or a different security context are currently not supported for recording. Whether it actually works depends on the environment, so first record a simple process such as ping.exe or cmd.exe in the same configuration as production, then try the target process, and only then build it into your operation.
Can TTD be used on traces of .NET apps?
Yes. The official documentation states that the SOS extension (sos.dll) running in 64-bit mode can be used on a TTD trace in WinDbg to debug managed code. The basic pattern is to run SOS commands such as !clrstack and !pe at each position in the trace, moving to the position of an exception event and then reading the managed stack. The TTD.Calls query, which searches for calls by symbol name, relies on PDB symbol information, so in .NET apps the reliable use is to follow calls across native boundaries such as P/Invoke, COM, and Win32 APIs.
Is it safe to send a trace file (.run) to another company or outside the organization?
Not as is. A TTD recording contains the process's memory contents, and the official documentation states explicitly that it can include personal or confidential information such as file paths, registry data, and the contents of memory and files. If you send one, first understand what was recorded (connection strings, tokens, customer data, and so on), then decide on an encrypted transfer channel and storage location. Sharing only the .run file is sufficient; the index file (.idx) is generated automatically when WinDbg opens it.
TTD.Calls returns nothing when I search for a function. Why?
There are four main causes. First, symbols: functions in a module without a PDB are named UnknownOrMissingSymbols, and the module name may be uppercase, so check the actual symbol name with the x command. Second, the target DLL may not be loaded yet at that position; move to a position after the DLL was loaded and query again. Third, if the function is inlined, the query engine cannot track it. Fourth, the wildcard may be too broad and match too many functions; narrow the pattern.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog