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}