Skip to main content

d3d9_reader/
debug.rs

1//! The debug information fxc writes with `/Zi` as a comment (`DBUG`): the source files, the source line of each
2//! instruction, and which components of which variables each instruction writes. A Direct3D 9 program compiled on its
3//! own carries it, and so does the Direct3D 9 program of a feature level 9 container.
4//!
5//! The layout below was read from programs fxc 10.0.26100.4654 built with `/Zi`, with and without `/Od`: pixel and
6//! vertex programs of Shader Model 2 and 3, an included file, structs, matrices, arrays, loops and branches, each checked
7//! against `D3DDisassemble`'s printing of them. Offsets are from the byte after the FourCC. The header is ten words:
8//!
9//! - 0 and 1: the offsets of the main source file's path and of the compiler's name;
10//! - 2: 0 in every program seen;
11//! - 3 and 4: the number of source files and the offset of their table (a word each: the offset of a path);
12//! - 5 and 6: the number of line records and the offset of their table (8 bytes a record), one record for every
13//!   declaration, definition and instruction, in stream order;
14//! - 7 and 8: the number of variables and the offset of their table (20 bytes a record), in name order;
15//! - 9: the offset of the entry point's name.
16use crate::ctab::Reader;
17use crate::{Result, Type, error};
18
19/// The source line of one declaration, definition or instruction.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct SourceLine {
22    /// The line number (u16 at offset 0 of the record); 0 where `file` is `None`.
23    pub line: u16,
24    /// The source file, by its place in [`DebugInfo::files`] (u16 at offset 2); `None` (`0xffff`) for a record with
25    /// no source line, as a declaration has.
26    pub file: Option<u16>,
27    /// The byte offset of the record's token from the program's version token (u32 at offset 4).
28    pub offset: u32,
29}
30
31/// Where an instruction writes a variable: the variable's components in its four destination lanes.
32#[derive(Clone, Debug, PartialEq, Eq)]
33pub struct Location {
34    /// The instruction, by its place in [`DebugInfo::lines`] (u32 at offset 0 of the 12-byte record).
35    pub instruction: u32,
36    /// The variable's component each of the lanes x, y, z and w holds (u16 at offsets 4 to 10), counting the
37    /// components of a struct's members in member order; `None` (`0xffff`) for a lane that holds none of it.
38    pub components: [Option<u16>; 4],
39}
40
41/// A variable: a parameter, a local, the function's return value or a constant.
42#[derive(Clone, Debug, PartialEq, Eq)]
43pub struct Variable {
44    /// The name of the function or constant buffer it is in (offset at 0 of the record); `None` (offset 0) for a
45    /// local of the entry point and the return value of a function, named by the function's name.
46    pub scope: Option<String>,
47    /// The variable's name (offset at 4).
48    pub name: String,
49    /// The variable's type (offset at 8), in the layout of the constant table's types.
50    pub ty: Type,
51    /// The instructions that write it (the count at 12 and the table's offset at 16).
52    pub locations: Vec<Location>,
53}
54
55/// The `DBUG` comment decoded.
56#[derive(Clone, Debug, PartialEq, Eq)]
57pub struct DebugInfo {
58    /// The main source file's path (header word 0).
59    pub path: String,
60    /// The compiler's name (header word 1).
61    pub creator: String,
62    /// Header word 2.
63    pub reserved: u32,
64    /// The source files' paths.
65    pub files: Vec<String>,
66    /// The source lines, a record for each declaration, definition and instruction in stream order.
67    pub lines: Vec<SourceLine>,
68    /// The variables, in name order.
69    pub variables: Vec<Variable>,
70    /// The entry point's name (header word 9).
71    pub entry: String,
72}
73
74/// The comment's bytes after the FourCC, whose byte 0 lies in program word `word`. Refuses tables outside the
75/// comment, a line record naming a file that isn't in the table, and a location naming an instruction that has no
76/// line record.
77pub(crate) fn read(data: &[u8], word: usize) -> Result<DebugInfo> {
78    let r = Reader {
79        data,
80        word,
81        tag: "DBUG",
82    };
83    let header = |n: usize| r.offset(n.saturating_mul(4));
84    let table = |count: usize, size: usize| -> Result<usize> {
85        let at = header(count.saturating_add(1))?;
86        r.range(at, r.bytes(at, header(count)?, size)?)?;
87        Ok(at)
88    };
89    let (file_count, files_at) = (header(3)?, table(3, 4)?);
90    let mut files = Vec::new();
91    for n in 0..file_count {
92        files.push(r.string(r.offset(r.plus(files_at, r.bytes(files_at, n, 4)?)?)?)?);
93    }
94    let (line_count, lines_at) = (header(5)?, table(5, 8)?);
95    let mut lines = Vec::new();
96    for n in 0..line_count {
97        let at = r.plus(lines_at, r.bytes(lines_at, n, 8)?)?;
98        let file = r.u16(r.plus(at, 2)?)?;
99        if file != 0xffff && usize::from(file) >= file_count {
100            return Err(error(
101                r.word_of(at),
102                format!("DBUG line record {n} names file {file}, where there are {file_count}"),
103            ));
104        }
105        lines.push(SourceLine {
106            line: r.u16(at)?,
107            file: (file != 0xffff).then_some(file),
108            offset: r.u32(r.plus(at, 4)?)?,
109        });
110    }
111    let (variable_count, variables_at) = (header(7)?, table(7, 20)?);
112    let mut variables = Vec::new();
113    let mut budget = 4096;
114    for n in 0..variable_count {
115        let at = r.plus(variables_at, r.bytes(variables_at, n, 20)?)?;
116        let scope = r.offset(at)?;
117        let location_count = r.offset(r.plus(at, 12)?)?;
118        let locations_at = r.offset(r.plus(at, 16)?)?;
119        r.range(locations_at, r.bytes(locations_at, location_count, 12)?)?;
120        let mut locations = Vec::new();
121        for l in 0..location_count {
122            let record = r.plus(locations_at, r.bytes(locations_at, l, 12)?)?;
123            let instruction = r.u32(record)?;
124            if usize::try_from(instruction).map_or(true, |i| i >= line_count) {
125                return Err(error(
126                    r.word_of(record),
127                    format!(
128                        "DBUG location of variable {n} names instruction {instruction}, where there are {line_count}"
129                    ),
130                ));
131            }
132            let mut components = [None; 4];
133            for (lane, slot) in components.iter_mut().enumerate() {
134                let c = r.u16(r.plus(record, lane.saturating_mul(2).saturating_add(4))?)?;
135                *slot = (c != 0xffff).then_some(c);
136            }
137            locations.push(Location {
138                instruction,
139                components,
140            });
141        }
142        variables.push(Variable {
143            scope: if scope == 0 { None } else { Some(r.string(scope)?) },
144            name: r.string(r.offset(r.plus(at, 4)?)?)?,
145            ty: r.ty(r.offset(r.plus(at, 8)?)?, 0, &mut budget)?,
146            locations,
147        });
148    }
149    Ok(DebugInfo {
150        path: r.string(header(0)?)?,
151        creator: r.string(header(1)?)?,
152        reserved: r.u32(8)?,
153        files,
154        lines,
155        variables,
156        entry: r.string(header(9)?)?,
157    })
158}