summaryrefslogtreecommitdiff
path: root/plugins/bntl_utils/src/winmd/info.rs
blob: 4db30c7a72c55401ba1748e73e6d47d4b62df74a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
//! Metadata information extracted from Windows metadata files.
//!
//! While we could use the direct representation, this is easier to work with.

use std::collections::{HashMap, HashSet};

#[derive(Debug, Default, Clone)]
pub struct MetadataInfo {
    pub types: Vec<MetadataTypeInfo>,
    pub functions: Vec<MetadataFunctionInfo>,
    pub constants: Vec<MetadataConstantInfo>,
}

impl MetadataInfo {
    /// Partitions the metadata into a map of libraries, where each library contains types and functions
    /// that belong to that library. This is used when mapping metadata info to type libraries.
    pub fn partitioned(&self) -> PartitionedMetadataInfo {
        let mut result_map: HashMap<LibraryName, LibraryInfo> = HashMap::new();

        // Map of namespace to module names that use it.
        let mut namespace_dependencies: HashMap<String, HashSet<String>> = HashMap::new();
        for func in &self.functions {
            if let Some(import) = &func.import_info {
                namespace_dependencies
                    .entry(func.namespace.clone())
                    .or_default()
                    .insert(import.module.name.clone());
            }
        }

        let namespace_to_library_name = |ns: &str| -> LibraryName {
            match namespace_dependencies.get(ns) {
                Some(modules) if modules.len() == 1 => {
                    LibraryName::Module(modules.iter().next().unwrap().clone())
                }
                _ => LibraryName::Namespace(ns.to_string()),
            }
        };

        for func in &self.functions {
            let dest_lib = match &func.import_info {
                Some(info) => LibraryName::Module(info.module.name.clone()),
                None => LibraryName::Namespace(func.namespace.clone()),
            };
            let entry = result_map.entry(dest_lib.clone()).or_default();
            func.ty.visit_references(&mut |ns, name| {
                let library_name = namespace_to_library_name(ns);
                if dest_lib != library_name {
                    entry
                        .external_references
                        .insert(name.to_string(), library_name);
                }
            });
            entry.metadata.functions.push(func.clone());
        }

        for ty in &self.types {
            let dest_lib = namespace_to_library_name(&ty.namespace);
            let entry = result_map.entry(dest_lib.clone()).or_default();
            ty.kind.visit_references(&mut |ns, name| {
                let library_name = namespace_to_library_name(ns);
                if dest_lib != library_name {
                    entry
                        .external_references
                        .insert(name.to_string(), library_name);
                }
            });
            entry.metadata.types.push(ty.clone());
        }

        for constant in &self.constants {
            let dest_lib = namespace_to_library_name(&constant.namespace);
            let entry = result_map.entry(dest_lib.clone()).or_default();
            constant.ty.visit_references(&mut |ns, name| {
                let library_name = namespace_to_library_name(ns);
                if dest_lib != library_name {
                    entry
                        .external_references
                        .insert(name.to_string(), library_name);
                }
            });
            entry.metadata.constants.push(constant.clone());
        }

        PartitionedMetadataInfo {
            libraries: result_map,
        }
    }

    pub fn create_constant_enums(&self) -> Vec<MetadataTypeInfo> {
        // Group constants by their type, if there are multiple constants with the same type, we
        // will make an enum out of them, once that is done, we will take overlapping constants
        // and prioritize certain namespaces over others.
        // TODO: Add some more structured types here, this is a crazy map.
        let mut grouped_constants: HashMap<
            (String, String),
            HashMap<u64, Vec<MetadataConstantInfo>>,
        > = HashMap::new();
        for constant in &self.constants {
            let MetadataTypeKind::Reference { name, namespace } = &constant.ty else {
                // TODO: We should optionally provide a way to group constants like these into an enumeration.
                // Skipping constant `WDS_MC_TRACE_VERBOSE` with non-reference type `Integer { size: Some(4), is_signed: false }`
                // Skipping constant `WDS_MC_TRACE_INFO` with non-reference type `Integer { size: Some(4), is_signed: false }`
                // Skipping constant `WDS_MC_TRACE_WARNING` with non-reference type `Integer { size: Some(4), is_signed: false }`
                // Skipping constant `WDS_MC_TRACE_ERROR` with non-reference type `Integer { size: Some(4), is_signed: false }`
                // Skipping constant `WDS_MC_TRACE_FATAL` with non-reference type `Integer { size: Some(4), is_signed: false }`
                tracing::debug!(
                    "Skipping constant `{}` with non-reference type `{:?}`",
                    constant.name,
                    constant.ty
                );
                continue;
            };
            grouped_constants
                .entry((namespace.clone(), name.clone()))
                .or_default()
                .entry(constant.value)
                .or_default()
                .push(constant.clone());
        }

        let mut enums = Vec::new();
        for ((enum_namespace, enum_name), mapped_values) in grouped_constants {
            let mut variants = Vec::new();
            for (_, group_variants) in mapped_values {
                let sorted_group_variants =
                    sort_metadata_constants_by_proximity(&enum_namespace, group_variants);
                let enum_variants: Vec<_> = sorted_group_variants
                    .iter()
                    .map(|info| (info.name.clone(), info.value))
                    .collect();
                variants.extend(enum_variants);
            }

            let enum_kind = MetadataTypeKind::Enum {
                ty: Box::new(MetadataTypeKind::Void),
                variants,
            };

            enums.push(MetadataTypeInfo {
                name: enum_name,
                kind: enum_kind,
                namespace: enum_namespace,
            });
        }
        enums
    }

    #[allow(dead_code)]
    fn update_stale_references(&mut self) {
        let mut valid_type_map = HashMap::new();
        for ty in self.types.iter() {
            valid_type_map.insert(ty.name.clone(), ty.clone());
        }

        for ty in self.types.iter_mut() {
            ty.kind.visit_references_mut(&mut |node| {
                let MetadataTypeKind::Reference { name, namespace } = node else {
                    tracing::error!(
                        "`visit_references_mut` did not return a reference! {:?}",
                        node
                    );
                    return;
                };
                if let Some(survivor) = valid_type_map.get(name) {
                    if namespace != &survivor.namespace {
                        tracing::debug!(
                            "Updating stale namespace reference `{}` to `{}` for `{}`",
                            namespace,
                            survivor.namespace,
                            name
                        );
                        *namespace = survivor.namespace.clone();
                    }
                }
            });
        }
    }
}

#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub enum LibraryName {
    /// A synthetic library with no associated module name.
    ///
    /// The shared library is "synthetic" in the sense that a binary view cannot reference it directly.
    Namespace(String),
    /// A real module with a name (e.g. "info.dll"), these libraries can be referenced directly by a binary view.
    Module(String),
}

#[derive(Debug, Clone, Default)]
pub struct LibraryInfo {
    pub metadata: MetadataInfo,
    /// A map of externally referenced names to their library names.
    ///
    /// This is required when resolving type references to other libraries.
    pub external_references: HashMap<String, LibraryName>,
}

#[derive(Debug, Default)]
pub struct PartitionedMetadataInfo {
    pub libraries: HashMap<LibraryName, LibraryInfo>,
}

// TODO: ModuleRef (computable from ModuleInfo and the underlying core module)
// TODO: Put a ModuleRef in all places where a module is associated.
#[derive(Debug, Clone)]
pub struct MetadataModuleInfo {
    /// The modules name on disk, this is used to determine the imported
    /// function name when loading type information from a type library.
    pub name: String,
}

#[derive(Debug, Clone)]
pub struct MetadataTypeInfo {
    pub name: String,
    pub kind: MetadataTypeKind,
    /// The namespace of the type, e.x. "Windows.Win32.Foundation"
    ///
    /// This is used to help determine what library this information belongs to. When we go to import
    /// this information (along with others), we will build a tree of information where each node
    /// corresponds to the namespace, and each child node corresponds to a sub-namespace. Then import
    /// info will be enumerated to determine if the type can only ever belong to a single import module
    /// if the type is only used in a single module, we will place it in that type library. If the namespace
    /// can reference more than one module, we will place it in a common type library named after
    /// the namespace itself, it can only ever be referenced by another type library and as such should
    /// only contain types and no functions.
    ///
    /// For more information see [`PartitionedMetadataInfo`].
    pub namespace: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MetadataTypeKind {
    Void,
    Bool {
        // NOTE: Weird optional, if None we actually default the size to integer size!
        size: Option<usize>,
    },
    Integer {
        size: Option<usize>,
        is_signed: bool,
    },
    Character {
        size: usize,
    },
    Float {
        size: usize,
    },
    Pointer {
        is_const: bool,
        is_pointee_const: bool,
        target: Box<MetadataTypeKind>,
    },
    Array {
        element: Box<MetadataTypeKind>,
        count: usize,
    },
    Struct {
        fields: Vec<MetadataFieldInfo>,
        is_packed: bool,
    },
    Union {
        fields: Vec<MetadataFieldInfo>,
    },
    Enum {
        ty: Box<MetadataTypeKind>,
        variants: Vec<(String, u64)>,
    },
    Function {
        params: Vec<MetadataParameterInfo>,
        return_type: Box<MetadataTypeKind>,
        is_vararg: bool,
    },
    Reference {
        // TODO: Generics may also be passed here.
        /// The namespace of the referenced type, e.x. "Windows.Win32.Foundation"
        namespace: String,
        /// The referenced type name, e.x. "BOOL"
        name: String,
    },
}

impl MetadataTypeKind {
    pub(crate) fn visit_references<F>(&self, callback: &mut F)
    where
        F: FnMut(&str, &str),
    {
        match self {
            MetadataTypeKind::Reference { namespace, name } => {
                callback(namespace, name);
            }
            MetadataTypeKind::Pointer { target, .. } => {
                target.visit_references(callback);
            }
            MetadataTypeKind::Array { element, .. } => {
                element.visit_references(callback);
            }
            MetadataTypeKind::Struct { fields, .. } => {
                for field in fields {
                    field.ty.visit_references(callback);
                }
            }
            MetadataTypeKind::Enum { ty, .. } => {
                ty.visit_references(callback);
            }
            MetadataTypeKind::Function {
                params,
                return_type,
                ..
            } => {
                for param in params {
                    param.ty.visit_references(callback);
                }
                return_type.visit_references(callback);
            }
            _ => {}
        }
    }

    #[allow(dead_code)]
    pub(crate) fn visit_references_mut<F>(&mut self, callback: &mut F)
    where
        F: FnMut(&mut MetadataTypeKind),
    {
        match self {
            MetadataTypeKind::Reference { .. } => {
                callback(self);
            }
            MetadataTypeKind::Pointer { target, .. } => {
                target.visit_references_mut(callback);
            }
            MetadataTypeKind::Array { element, .. } => {
                element.visit_references_mut(callback);
            }
            MetadataTypeKind::Struct { fields, .. } | MetadataTypeKind::Union { fields, .. } => {
                for field in fields {
                    field.ty.visit_references_mut(callback);
                }
            }
            MetadataTypeKind::Enum { ty, .. } => {
                ty.visit_references_mut(callback);
            }
            MetadataTypeKind::Function {
                params,
                return_type,
                ..
            } => {
                for param in params {
                    param.ty.visit_references_mut(callback);
                }
                return_type.visit_references_mut(callback);
            }
            _ => {}
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MetadataFieldInfo {
    pub name: String,
    pub ty: MetadataTypeKind,
    pub is_const: bool,
    /// This is only set for bitfields, The first value is the bit position within the associated byte,
    /// and the second is the bit width.
    ///
    /// NOTE: The bit position can never be greater than `7`.
    pub bitfield: Option<(u8, u8)>,
    // TODO: Attributes ( virtual, static, etc...)
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MetadataParameterInfo {
    pub name: String,
    pub ty: MetadataTypeKind,
    // TODO: Attributes (in, out, etc...)
}

#[allow(dead_code)]
#[derive(Debug, Clone)]
pub enum MetadataImportMethod {
    ByName(String),
    ByOrdinal(u32),
}

#[derive(Debug, Clone)]
pub struct MetadataImportInfo {
    #[allow(dead_code)]
    pub method: MetadataImportMethod,
    pub module: MetadataModuleInfo,
}

#[derive(Debug, Clone)]
pub struct MetadataFunctionInfo {
    pub name: String,
    /// This will only ever be [`MetadataTypeKind::Function`].
    pub ty: MetadataTypeKind,
    pub namespace: String,
    pub import_info: Option<MetadataImportInfo>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MetadataConstantInfo {
    pub name: String,
    pub namespace: String,
    pub ty: MetadataTypeKind,
    pub value: u64,
}

pub fn sort_metadata_constants_by_proximity(
    reference: &str,
    mut candidates: Vec<MetadataConstantInfo>,
) -> Vec<MetadataConstantInfo> {
    let ref_parts: Vec<&str> = reference.split('.').collect();
    candidates.sort_by_cached_key(|info| {
        // Extract the namespace string from the metadata info
        let ns = &info.namespace;
        let cand_parts = ns.split('.');

        let score = ref_parts
            .iter()
            .zip(cand_parts)
            .take_while(|(a, b)| *a == b)
            .count();

        // Sort by highest score first, then alphabetically by namespace
        (std::cmp::Reverse(score), ns.clone())
    });
    candidates
}