Skip to main content

dxil_reader/disasm/
mod.rs

1//! A disassembler of DXIL programs: the listing `dxc -dumpbin` (dxc 1.9.2609) prints of a container, byte for byte.
2//!
3//! The listing is dxc's comment header (`dxcdisassembler.cpp`: the feature flags, the signature parts, the debug name
4//! and hash, the pipeline state validation runtime info, and from the program's metadata its signatures, buffer
5//! layouts, resource bindings, view-id state and a library's subobjects), then the program's module as LLVM 3.7 prints
6//! it, with dxc's comment after each call of a DXIL operation. What the reader can't read is an error, never a listing
7//! that differs from dxc's.
8//!
9//! ```no_run
10//! # fn main() -> Result<(), dxil_reader::Error> {
11//! let container = std::fs::read("shader.dxil").expect("a compiled shader");
12//! print!("{}", dxil_reader::disasm::disassemble(&container)?);
13//! # Ok(()) }
14//! ```
15//!
16//! [`disassemble_with_positions`] gives the same text with, for each instruction of a function body, the line that
17//! lists it and the bits of the container that store it.
18mod annotate;
19mod asm;
20mod header;
21mod layout;
22#[rustfmt::skip] // written by `cargo xtask dxil-ops`, as it is
23mod ops;
24
25use crate::{Module, Result, bitstream, error, psv, rdat};
26
27/// The listing `dxc -dumpbin` prints of a DXBC container holding a DXIL program, to the newline it ends with.
28pub fn disassemble(container: &[u8]) -> Result<String> {
29    Ok(listing(container)?.text)
30}
31
32/// The listing of [`disassemble`], with where each instruction of the listed module's function bodies is listed and
33/// stored.
34#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct Positioned {
36    /// The listing, byte for byte the text [`disassemble`] gives.
37    pub text: String,
38    /// Each instruction of a function body, in listing order: the bodies as the listing defines them, each body's
39    /// instructions block by block. Nothing else of the listing has an entry: not the header, globals, declarations
40    /// or metadata.
41    pub instructions: Vec<Listed>,
42    /// The part whose module is listed: `ILDB`, the debug module, where the container has one, else `DXIL`. The two
43    /// modules are stored apart, so an instruction's bits in one are not its bits in the other.
44    pub part: [u8; 4],
45    /// The byte of the container where that part's bitcode starts (its `BC` magic): bit `b` of [`Listed::at`] and
46    /// [`Listed::end`] is bit `b % 8` of container byte `bitcode + b / 8`, counting from the least significant bit, as
47    /// LLVM's bitstream does.
48    pub bitcode: usize,
49}
50
51/// One instruction of a [`Positioned`] listing.
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub struct Listed {
54    /// The line of the text that lists it, from 0. It is one line, but for a `switch`, whose cases and closing bracket
55    /// follow on lines of their own.
56    pub line: usize,
57    /// The bit its record starts at in the part's bitcode: [`crate::module::Instruction::at`] of the module read from
58    /// that bitcode ([`crate::module()`] of `(chunks, part)`), so an instruction is found by that value.
59    pub at: usize,
60    /// The bit just past its record ([`crate::bitstream::Record::end`]): the record is bits `at..end`.
61    pub end: usize,
62}
63
64/// [`disassemble`]'s listing, with the line and the bits of each instruction of a function body.
65pub fn disassemble_with_positions(container: &[u8]) -> Result<Positioned> {
66    let l = listing(container)?;
67    // each record's bits, by where it starts: an instruction is a record of a function block
68    let mut records = Vec::new();
69    for b in &l.blocks {
70        function_records(b, &mut records);
71    }
72    records.sort_unstable();
73    let mut instructions = Vec::with_capacity(l.marks.len());
74    let (mut line, mut counted) = (0usize, 0usize);
75    for mark in &l.marks {
76        let before = l
77            .text
78            .get(counted..mark.byte)
79            .ok_or_else(|| error(mark.at, "an instruction's text"))?;
80        line = line.saturating_add(before.bytes().filter(|&c| c == b'\n').count());
81        counted = mark.byte;
82        let end = records
83            .binary_search_by_key(&mark.at, |&(at, _)| at)
84            .ok()
85            .and_then(|i| records.get(i))
86            .map(|&(_, end)| end)
87            .ok_or_else(|| error(mark.at, "an instruction that is not a record of a function block"))?;
88        instructions.push(Listed {
89            line,
90            at: mark.at,
91            end,
92        });
93    }
94    Ok(Positioned {
95        text: l.text,
96        instructions,
97        part: l.part,
98        bitcode: l.bitcode,
99    })
100}
101
102/// The records of every function block in `block` and the blocks it holds, as (start bit, end bit).
103fn function_records(block: &bitstream::Block, out: &mut Vec<(usize, usize)>) {
104    // LLVM's FUNCTION_BLOCK_ID
105    if block.id == 12 {
106        out.extend(block.records().map(|r| (r.at, r.end)));
107    }
108    for b in block.blocks() {
109        function_records(b, out);
110    }
111}
112
113/// A listing and what [`disassemble_with_positions`] needs to place its instructions.
114struct Listing {
115    text: String,
116    /// Each instruction's record bit and the byte of `text` its line starts at.
117    marks: Vec<asm::Mark>,
118    /// The listed module's blocks.
119    blocks: Vec<bitstream::Block>,
120    part: [u8; 4],
121    bitcode: usize,
122}
123
124fn listing(container: &[u8]) -> Result<Listing> {
125    let parts = crate::parts(container)?;
126    let find = |kind: &[u8; 4]| parts.iter().find(|c| &c.kind == kind).map(|c| c.data);
127    let mut s = String::new();
128    if let Some(sfi0) = find(b"SFI0") {
129        header::feature_info(&mut s, sfi0)?;
130    }
131    for (kind, name, input) in [
132        (b"ISG1", "Input", true),
133        (b"OSG1", "Output", false),
134        (b"PSG1", "Patch Constant", false),
135    ] {
136        if let Some(part) = find(kind) {
137            header::part_signature(&mut s, name, part, input)?;
138        }
139    }
140    if let Some(ildn) = find(b"ILDN") {
141        header::debug_name(&mut s, ildn);
142    }
143    if let Some(hash) = find(b"HASH") {
144        header::hash(&mut s, hash)?;
145    }
146    // the debug module, where the container has one, is the one listed
147    let (part, program) = [*b"ILDB", *b"DXIL"]
148        .into_iter()
149        .find_map(|kind| find(&kind).map(|data| (kind, data)))
150        .ok_or_else(|| error(0, "the container has no DXIL part"))?;
151    let program = crate::program(program)?;
152    // the bitcode is a slice of the container's own bytes
153    let bitcode = program
154        .bitcode
155        .as_ptr()
156        .addr()
157        .checked_sub(container.as_ptr().addr())
158        .filter(|&b| b < container.len())
159        .ok_or_else(|| error(0, "the program's bitcode outside the container"))?;
160    if let Some(part) = find(b"PSV0") {
161        // the shader kind is the high half of the program header's version word; dxc passes its low byte
162        let kind = u8::try_from(program.program_version >> 16 & 0xFF).unwrap_or(u8::MAX);
163        header::psv_runtime(&mut s, &psv::read(part)?, kind)?;
164    }
165    let blocks = bitstream::read(program.bitcode, *b"BC\xc0\xde")?;
166    let module = Module::read(&blocks)?;
167    // the STAT part's module holds the reflection the program part's may be stripped of; dxc ignores one whose header
168    // it can't read
169    let reflection = match find(b"STAT").map(crate::program) {
170        Some(Ok(part)) => Some(read(part.bitcode)?),
171        _ => None,
172    };
173    if module.named_metadata.iter().any(|(n, _)| n == "dx.version") {
174        let kind = crate::reflection::shader_model(&module)?
175            .map(|(k, _, _)| k)
176            .ok_or_else(|| error(module.at, "the module has no dx.shaderModel metadata"))?;
177        if kind != "lib" {
178            header::dxil_signatures(&mut s, &module, &kind)?;
179        }
180        header::reflection_sections(&mut s, reflection.as_ref().unwrap_or(&module))?;
181        let subobjects = match find(b"RDAT") {
182            Some(part) => rdat::subobjects(part)?,
183            None => Vec::new(),
184        };
185        if subobjects.is_empty() && module.named_metadata.iter().any(|(n, _)| n == "dx.subobjects") {
186            return Err(error(
187                module.at,
188                "subobjects in dx.subobjects metadata are not supported",
189            ));
190        }
191        header::subobjects(&mut s, &subobjects);
192    }
193    let marks = asm::write(&mut s, &module)?;
194    // dxc's console writer ends the text with a newline of its own
195    s.push('\n');
196    Ok(Listing {
197        text: s,
198        marks,
199        blocks,
200        part,
201        bitcode,
202    })
203}
204
205/// The module part of the listing: from `target datalayout` on, the program's IR as dxc prints it.
206pub fn module_text(m: &Module) -> Result<String> {
207    let mut s = String::new();
208    asm::write(&mut s, m)?;
209    Ok(s)
210}
211
212fn read(bitcode: &[u8]) -> Result<Module> {
213    Module::read(&crate::bitstream::read(bitcode, *b"BC\xc0\xde")?)
214}