Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2456b50
Add a TTD Behavior sidebar showing the API calls a trace made
xusheng6 Jul 24, 2026
11f31d9
Navigate to the instruction pointer after time traveling to a call
xusheng6 Jul 24, 2026
40a65e2
Fix the TTD Behavior filter hanging on large reports
xusheng6 Jul 27, 2026
d172ddf
Save extracted reports next to the trace, and show truncated buffers …
xusheng6 Jul 27, 2026
027e7e6
Default the buffer capture limit to 64 KiB, and bound the hex dump
xusheng6 Jul 27, 2026
5fe38b9
Keep the UI responsive during extraction, and size the narrow columns…
xusheng6 Jul 27, 2026
162ece7
Read the binary report format, and show save and load times
xusheng6 Jul 27, 2026
68848d7
Show each call's return address in a new column
xusheng6 Jul 28, 2026
ce24e8c
Let the filter scope terms to a field
xusheng6 Jul 28, 2026
5f22fc8
Give the TTD Behavior widget query tabs
xusheng6 Jul 28, 2026
0338cca
Move TTD behavior reports into core, with a Python binding
xusheng6 Jul 29, 2026
20ae223
Drop the UI's copy of the report reader and use the core API
xusheng6 Jul 29, 2026
169a6c0
Decode parameters by default when querying
xusheng6 Jul 29, 2026
64f1c24
Recover unread strings when extracting from the sidebar
xusheng6 Jul 29, 2026
e3a6f60
Default TTD Behavior string recovery to off
xusheng6 Jul 29, 2026
980a252
Tell the extractor where WinDbg's TTD replay DLLs are
xusheng6 Jul 30, 2026
a695aba
Ship the TTD behavior extractor and use it by default
xusheng6 Jul 30, 2026
9a68432
Vendor the extractor as a flat ttd-extract folder
xusheng6 Jul 30, 2026
f132b39
Show a buffer's leading bytes in the table again
xusheng6 Jul 30, 2026
db02e47
Remove the UI's orphaned parameter renderer
xusheng6 Jul 30, 2026
fbc516d
Clean up CMake comments
xusheng6 Jul 30, 2026
8a25ba5
Hide TTD behavior extraction off Windows, and document the feature
xusheng6 Jul 30, 2026
306f883
Allow TTD behavior extraction from the Python API
xusheng6 Jul 30, 2026
6eceb06
Add a screenshot to the TTD behavior guide
xusheng6 Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions api/debuggerapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -983,4 +983,70 @@ namespace BinaryNinjaDebuggerAPI {
std::string GetWinDbgInstalledVersion(const std::string& installPath = "");
std::string GetWinDbgLatestVersion();

// A memory-mapped report of the Windows API calls extracted from a TTD trace.
//
// Thin wrapper over the core reader: opening is a header validation, and rows are
// decoded out of the mapping only when asked for. RunQuery filters the whole report
// on the core side and hands back matching row indices in one crossing, which is
// what keeps filtering millions of calls affordable through the FFI.
struct TTDApiCallParam
{
std::string name;
std::string type;
std::string kind;
uint64_t value = 0;
std::string str;
std::vector<std::string> flags;
std::vector<uint8_t> bytes;
uint64_t bytesTotal = 0; // the buffer's real length when `bytes` was capped
uint64_t deref = 0;
bool hasDeref = false;
bool out = false;
bool atReturn = false;
};

struct TTDApiCall
{
uint64_t seq = 0;
uint64_t tid = 0;
uint64_t positionSequence = 0;
uint64_t positionSteps = 0;
std::string module;
std::string api;
uint64_t ret = 0;
uint64_t returnAddress = 0;
std::string paramSummary;
bool decoded = false;
std::vector<TTDApiCallParam> params;
};

class TTDBehaviorReport
{
BNTTDBehaviorReport* m_object = nullptr;

public:
TTDBehaviorReport() = default;
~TTDBehaviorReport();
TTDBehaviorReport(const TTDBehaviorReport&) = delete;
TTDBehaviorReport& operator=(const TTDBehaviorReport&) = delete;

bool Open(const std::string& path, std::string& error);
void Close();
bool IsOpen() const { return m_object != nullptr; }

uint64_t GetCallCount() const;
uint64_t GetDecodedCount() const;
uint64_t GetProcessId() const;
uint64_t GetMaxSequence() const;
uint32_t GetMaxPositionChars() const;
std::string GetTracePath() const;
std::string GetArchitecture() const;

// Row indices of every call matching `query`.
std::vector<uint64_t> RunQuery(const std::string& query) const;

// `withParams` is the expensive half, so a table can skip it.
bool GetCall(uint64_t index, TTDApiCall& out, bool withParams) const;
};

}; // namespace BinaryNinjaDebuggerAPI
67 changes: 67 additions & 0 deletions api/ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,73 @@ extern "C"
DEBUGGER_FFI_API char* BNDebuggerGetWinDbgInstalledVersion(const char* installPath);
DEBUGGER_FFI_API char* BNDebuggerGetWinDbgLatestVersion(void);

// TTD behavior reports: the Windows API calls extracted from a TTD trace.
//
// The boundary is drawn so that no loop over the call list crosses it.
// BNTTDBehaviorRunQuery evaluates a whole filter core-side and returns the matching
// row indices in one call, which measures within noise of filtering in-process;
// crossing per row instead costs 30-50%. Callers then fetch only the rows they show.
typedef struct BNTTDBehaviorReport BNTTDBehaviorReport;

typedef struct BNTTDApiCallParam
{
char* m_name;
char* m_type;
char* m_kind;
uint64_t m_value;
char* m_str; // empty when the parameter is not a string
char** m_flags; // symbolic names for enum/flag parameters
size_t m_flagCount;
uint8_t* m_bytes; // captured buffer contents, possibly only a prefix
size_t m_byteCount;
uint64_t m_bytesTotal; // the buffer's real length when capped, else 0
uint64_t m_deref;
bool m_hasDeref;
bool m_out;
bool m_atReturn; // re-read at the call's return position
} BNTTDApiCallParam;

typedef struct BNTTDApiCall
{
uint64_t m_seq;
uint64_t m_tid;
uint64_t m_positionSequence;
uint64_t m_positionSteps;
char* m_module;
char* m_api;
uint64_t m_ret;
uint64_t m_returnAddress; // the instruction after the CALL, i.e. the call site
char* m_paramSummary;
bool m_decoded;
BNTTDApiCallParam* m_params;
size_t m_paramCount;
} BNTTDApiCall;

// Returns NULL on failure, with *errorMessage set to a string the caller frees with
// BNDebuggerFreeString.
DEBUGGER_FFI_API BNTTDBehaviorReport* BNTTDBehaviorOpenReport(const char* path, char** errorMessage);
DEBUGGER_FFI_API void BNTTDBehaviorCloseReport(BNTTDBehaviorReport* report);

DEBUGGER_FFI_API uint64_t BNTTDBehaviorGetCallCount(BNTTDBehaviorReport* report);
DEBUGGER_FFI_API uint64_t BNTTDBehaviorGetDecodedCount(BNTTDBehaviorReport* report);
DEBUGGER_FFI_API uint64_t BNTTDBehaviorGetProcessId(BNTTDBehaviorReport* report);
DEBUGGER_FFI_API uint64_t BNTTDBehaviorGetMaxSequence(BNTTDBehaviorReport* report);
DEBUGGER_FFI_API uint32_t BNTTDBehaviorGetMaxPositionChars(BNTTDBehaviorReport* report);
// Both must be freed with BNDebuggerFreeString.
DEBUGGER_FFI_API char* BNTTDBehaviorGetTracePath(BNTTDBehaviorReport* report);
DEBUGGER_FFI_API char* BNTTDBehaviorGetArchitecture(BNTTDBehaviorReport* report);

// Row indices of every call matching `query`. Free with BNTTDBehaviorFreeQueryResult.
DEBUGGER_FFI_API uint64_t* BNTTDBehaviorRunQuery(
BNTTDBehaviorReport* report, const char* query, size_t* count);
DEBUGGER_FFI_API void BNTTDBehaviorFreeQueryResult(uint64_t* result);

// Fills `out`, which the caller frees with BNTTDBehaviorFreeCall. Decoding the
// parameters is the expensive half, so a table can pass false and a detail view true.
DEBUGGER_FFI_API bool BNTTDBehaviorGetCall(
BNTTDBehaviorReport* report, uint64_t index, bool withParams, BNTTDApiCall* out);
DEBUGGER_FFI_API void BNTTDBehaviorFreeCall(BNTTDApiCall* call);

#ifdef __cplusplus
}
#endif
2 changes: 2 additions & 0 deletions api/python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
from .debuggercontroller import *
from .debugadaptertype import *
from .debugger_enums import *
from .ttdbehavior import *
else:
if Settings().get_bool('corePlugins.debugger') and (os.environ.get('BN_DISABLE_CORE_DEBUGGER') is None):
from .debuggercontroller import *
from .debugadaptertype import *
from .debugger_enums import *
from .ttdbehavior import *
Loading