Skip to main content

clr_assembler/formats/dll/reader/
mod.rs

1mod headers;
2mod metadata;
3mod program;
4mod utils;
5
6use crate::{
7    formats::dll::DllReadConfig,
8    program::{ClrHeader, ClrProgram, ClrVersion, DotNetAssemblyInfo, MetadataHeader, StreamHeader},
9};
10use gaia_types::{GaiaError, SourceLocation};
11use pe_assembler::{
12    helpers::PeReader,
13    types::{PeHeader, PeProgram, SectionHeader},
14};
15use std::io::{Read, Seek};
16
17#[derive(Debug)]
18pub struct DllReader<'config, R> {
19    /// Configuration options
20    options: &'config DllReadConfig,
21    reader: pe_assembler::formats::dll::reader::DllReader<R>,
22    /// Parsed CLR header information
23    clr_header: Option<ClrHeader>,
24    /// Parsed metadata header information
25    metadata_header: Option<MetadataHeader>,
26    /// Metadata stream header list (lazy loaded)
27    stream_headers: Option<Vec<StreamHeader>>,
28    /// Extracted basic assembly information (lazy loaded)
29    assembly_info: Option<DotNetAssemblyInfo>,
30    /// Fully parsed CLR program (lazy loaded)
31    clr_program: Option<ClrProgram>,
32}
33
34impl<'config, R: Read + Seek> PeReader<R> for DllReader<'config, R> {
35    fn get_viewer(&mut self) -> &mut R {
36        self.reader.get_viewer()
37    }
38
39    fn add_diagnostics(&mut self, error: impl Into<GaiaError>) {
40        self.reader.add_diagnostics(error)
41    }
42
43    fn get_section_headers(&mut self) -> Result<&[SectionHeader], GaiaError> {
44        self.reader.get_section_headers()
45    }
46
47    fn get_pe_header(&mut self) -> Result<&PeHeader, GaiaError> {
48        self.reader.get_pe_header()
49    }
50
51    fn get_program(&mut self) -> Result<&PeProgram, GaiaError> {
52        self.reader.get_program()
53    }
54}
55
56impl<'config, R> DllReader<'config, R> {
57    /// Constructs a .NET reader (DLL) using a generic PE reader.
58    ///
59    /// Note: This is a lazy constructor and will not immediately execute the parsing workflow.
60    pub fn new(reader: R, options: &'config DllReadConfig) -> Self {
61        Self {
62            reader: pe_assembler::formats::dll::reader::DllReader::new(reader),
63            clr_header: None,
64            metadata_header: None,
65            stream_headers: None,
66            assembly_info: None,
67            clr_program: None,
68            options,
69        }
70    }
71}
72
73impl<'config, R> DllReader<'config, R>
74where
75    R: Read + Seek,
76{
77    /// Lazily reads basic assembly information.
78    ///
79    /// Only reads basic identification information of the assembly without parsing the full type system.
80    /// Suitable for scenarios where you need to quickly get assembly name, version, etc.
81    ///
82    /// # Returns
83    /// * `Ok(DotNetAssemblyInfo)` - Basic assembly information.
84    /// * `Err(GaiaError)` - Error during the reading process.
85    pub fn get_assembly_info(&mut self) -> Result<DotNetAssemblyInfo, GaiaError> {
86        if self.assembly_info.is_none() {
87            self.ensure_assembly_info_parsed()?;
88        }
89
90        self.assembly_info
91            .as_ref()
92            .cloned()
93            .ok_or_else(|| GaiaError::syntax_error("程序集信息未解析".to_string(), SourceLocation::default()))
94    }
95
96    /// Parses as a full CLR program.
97    ///
98    /// Parses the entire .NET assembly, including all types, methods, fields, and other information.
99    /// This is a heavyweight operation that consumes significant memory and time.
100    ///
101    /// # Returns
102    /// * `Ok(ClrProgram)` - Full representation of the CLR program.
103    /// * `Err(GaiaError)` - Error during the parsing process.
104    pub fn to_clr_program(&mut self) -> Result<ClrProgram, GaiaError> {
105        if let Some(ref program) = self.clr_program {
106            return Ok(program.clone());
107        }
108
109        // 执行完整解析
110        let program = self.parse_full_program()?;
111        self.clr_program = Some(program.clone());
112        Ok(program)
113    }
114
115    /// Validates the integrity of the assembly.
116    ///
117    /// Checks if the parsed .NET assembly contains all required components:
118    /// - CLR Header: Contains runtime information.
119    /// - Metadata Header: Describes the type system.
120    /// - Metadata Streams: Contains the actual metadata.
121    ///
122    /// # Returns
123    /// * `Ok(Vec<String>)` - List of warning messages; an empty list indicates validation success.
124    /// * `Err(GaiaError)` - Error during the validation process.
125    pub fn validate_assembly(&mut self) -> Result<Vec<String>, GaiaError> {
126        let mut warnings = Vec::new();
127
128        // 确保基本信息已解析
129        self.ensure_assembly_info_parsed()?;
130
131        // 验证 CLR 头 - 必需的核心头信息
132        if self.clr_header.is_none() {
133            warnings.push("缺少 CLR 头".to_string());
134        }
135
136        // 验证元数据头 - 描述类型系统的元数据
137        if self.metadata_header.is_none() {
138            warnings.push("缺少元数据头".to_string());
139        }
140
141        // 验证流头 - 包含实际的元数据流
142        if self.stream_headers.as_ref().map_or(true, |h| h.is_empty()) {
143            warnings.push("缺少元数据流".to_string());
144        }
145
146        Ok(warnings)
147    }
148
149    /// Gets a summary of the assembly information.
150    ///
151    /// Returns the basic information of the assembly in a friendly format, suitable for display or logging.
152    /// If the assembly information is unavailable, it returns a corresponding error message.
153    ///
154    /// # Returns
155    /// * `String` - Formatted assembly information containing name, version, culture, public key token, and runtime version.
156    pub fn get_assembly_summary(&mut self) -> String {
157        match self.get_assembly_info() {
158            Ok(info) => {
159                format!(
160                    "Assembly: {}\nVersion: {}\nCulture: {}\nPublic Key Token: {}\nRuntime Version: {}",
161                    info.name,
162                    info.version,
163                    info.culture.as_deref().unwrap_or("neutral"),
164                    info.public_key_token.as_deref().unwrap_or("null"),
165                    info.runtime_version.as_deref().unwrap_or("unknown")
166                )
167            }
168            Err(_) => "Unable to get assembly information".to_string(),
169        }
170    }
171
172    /// Ensures that the assembly information is parsed (lazy loading helper method).
173    fn ensure_assembly_info_parsed(&mut self) -> Result<(), GaiaError> {
174        if self.assembly_info.is_some() {
175            return Ok(());
176        }
177
178        // Execute the parsing workflow on demand
179        self.parse_clr_header_lazy()?;
180        self.parse_metadata_lazy()?;
181        self.extract_assembly_info()?;
182
183        Ok(())
184    }
185
186    /// Parses the CLR header.
187    fn parse_clr_header_lazy(&mut self) -> Result<(), GaiaError> {
188        eprintln!("Starting to parse CLR header");
189        self.clr_header = self.find_and_read_clr_header()?;
190        eprintln!("find_and_read_clr_header returned: {:?}", self.clr_header.is_some());
191        if self.clr_header.is_none() {
192            eprintln!("CLR header is empty, returning error");
193            return Err(GaiaError::syntax_error("Missing CLR header", SourceLocation::default()));
194        }
195        eprintln!("CLR header parsed successfully");
196        Ok(())
197    }
198
199    /// Parses the metadata.
200    fn parse_metadata_lazy(&mut self) -> Result<(), GaiaError> {
201        if let Some(ref clr_header) = self.clr_header {
202            // Convert metadata RVA to file offset
203            let pe_program = self.reader.get_program()?.clone();
204            let metadata_offset = utils::rva_to_file_offset(&pe_program, clr_header.metadata_rva)?;
205            // Read metadata header
206            self.metadata_header = Some(self.read_metadata_header(metadata_offset)?);
207            // Read stream header information
208            self.stream_headers = Some(self.read_stream_headers(metadata_offset)?);
209        }
210
211        Ok(())
212    }
213}