tdinfo
Tool
tools/dos/tdinfo.py(program, 16-bit DOS). Read Borland Turbo Debugger symbols (TDINFO, signature FB52) from a DOS or NE executable.
Run it with ./gameframe run tdinfo ARGS or python3 tools/dos/tdinfo.py. Source on GitHub.
Its own help
This is the help text in the tool itself, as ./gameframe tool tdinfo prints it.
text
Read Borland Turbo Debugger symbols (TDINFO, signature FB52) from a DOS or NE executable.
Usage: tdinfo.py BINARY COMMAND [ARGS]
Commands:
summary where the symbols are, their version and counts, and the modules (units)
labels TSV for labels/names.tsv: every procedure and global as seg:off, name, evidence
procs [TEXT] procedures: address, size, module, parameters and locals with their types
globals [TEXT] global variables and typed constants: address, size, type
types [TEXT] named types; records show each field with its offset (summed from sizes)
lines [MODULE] line numbers: address -> source file and line
json OUT everything above as one JSON file
rust TYPE ... Rust structs and readers for record types (and arrays of records), with a
comment on every field giving its Pascal type and offset
rust-globals DIR a module directory: one file per Pascal unit with its variables at their
data-segment offsets, types/ (one file per record type), and mod.rs's Globals with load/store to
and from a 64 KB image of the segment (checks ported code against the
original's memory after an emulated run)
rust-locals PROC a struct of a procedure's locals at their BP offsets, with load/store on an
image of the stack segment (for code nested in a big procedure)
rust-reader the small PascalReader / PascalWriter module the generated code uses
Borland Pascal and Borland C++ write these symbols when a program is linked with debug
information (TPC /V, BPC /V, TLINK /v), either appended to the program or in a .TDS file. Many
shipped games kept them. They give the original names of procedures, globals, parameters,
locals and types, and a map from code addresses to source lines: run this before tracing
anything by hand, and load the labels into Ghidra (scripts/ghidra-run.sh BINARY labels ...).
Addresses use Ghidra's numbering: NE segment N is selector 0x1000 + 8 * (N - 1); in an MZ
program the symbol's segment is relative to the load image, which Ghidra loads at 0x1000.
Format notes (from reading real files; Borland's own description is not public in full):
- The block starts with the 16-bit magic 0x52FB and a version word (0x0300 = 3.0). A CodeView
style "NBxx" trailer at the end of the file may point to it.
- Tables follow the header and its extension, in this order: symbols (9 bytes), modules (16),
source files (6), line numbers (4), scopes (12), segments (16), correlations (8), types (8),
members (5, one more than the header's count), then the name pool (zero-terminated names,
numbered from 1).
- A type is one 8-byte slot: id, name, size, then 3 bytes whose meaning depends on the id.
Subranges and arrays take a second slot (bounds, index type); type numbers are slot numbers.
- Record fields (members) store a name and a type but no offset: field offsets here are the
running sum of field sizes (Pascal records are not padded). Treat them as *inferred* and
check one against the code before relying on it. Variant records are not decoded.
It does not read Turbo Debugger 4+ (32-bit) symbols, CodeView or Microsoft/Watcom formats.Named by
Skills: re-start, re-inventory, re-ghidra, re-trace, re-format, re-port.