Skip to main content

wamex_cli/analysis/
symbols.rs

1use std::{
2    borrow::Cow,
3    collections::{BTreeMap, BTreeSet, VecDeque},
4    ops::Range,
5};
6
7use anyhow::{Result, ensure};
8pub use diff::{DiffEntry, DiffResult, Differ, StaticModuleInfo};
9use smallvec::SmallVec;
10
11use crate::{
12    InputModule, analysis,
13    helpers::{RangeComp, RangeExt},
14    index::{DataSegmentId, Id, IdMap, IdVec, InputFuncId, InputGlobalId, SymbolId, TableId},
15};
16mod diff;
17
18#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19pub enum SymbolKind {
20    Func {
21        input_id: InputFuncId,
22    },
23    DataDefined {
24        segment_id: DataSegmentId,
25        offset: usize,
26        length: usize,
27    },
28    Global(InputGlobalId),
29    Table(TableId),
30    Duplicate(SymbolId),
31}
32
33#[derive(PartialEq, Eq, Debug, Clone)]
34pub struct SymbolRecord<'a> {
35    /// From Name + linking section, fail in case of conflicts.
36    /// undefined and non-named
37    pub name: Cow<'a, str>,
38    pub linking_name: Option<Cow<'a, str>>,
39    pub flags: wasmparser::SymbolFlags,
40    /// Relocation entries inside this symbol
41    /// Every index in relocation entry is corresponding to one symbol in symbol table.
42    /// Except for TypeIndexLeb relocations, which is in type index space.
43    pub relocs: Vec<wasmparser::RelocationEntry>,
44    pub kind: SymbolKind,
45}
46
47impl SymbolRecord<'_> {
48    fn apply_empty_relocs(body: &mut [u8], relocations: &[wasmparser::RelocationEntry]) {
49        for rel in relocations {
50            let reloc_range = rel.relocation_range();
51            body[reloc_range].fill(0);
52        }
53    }
54
55    pub fn stable_name(&self) -> Option<&str> {
56        match self.kind {
57            SymbolKind::Func { .. } => Some(&self.name),
58            _ => None,
59        }
60    }
61
62    // Return content with cleared relocations.
63    pub fn stable_content(&self, input: &analysis::ModuleInfo) -> Option<Vec<u8>> {
64        match self.kind {
65            SymbolKind::Func { input_id } => {
66                let Some(defined_id) = input.as_defined_function_id(input_id) else {
67                    // No content for imported functions
68                    return None;
69                };
70
71                let func = &input.wasm.code.defined_funcs[defined_id];
72                let mut body = func.body.as_bytes().to_vec();
73                Self::apply_empty_relocs(&mut body, &self.relocs);
74                Some(body)
75            }
76            SymbolKind::DataDefined {
77                segment_id,
78                offset,
79                length,
80            } => {
81                let segment = &input.wasm.data.data_segments[segment_id];
82                let start = offset;
83                let end = start + length;
84                let mut data = segment.data[start..end].to_vec();
85                Self::apply_empty_relocs(&mut data, &self.relocs);
86                Some(data)
87            }
88            SymbolKind::Global(_) | SymbolKind::Table(_) | SymbolKind::Duplicate(_) => {
89                // No content for globals/tables/duplicates
90                None
91            }
92        }
93    }
94
95    pub fn childs(&self) -> impl Iterator<Item = SymbolId> + '_ {
96        let filter_non_types = |reloc: &&wasmparser::RelocationEntry| {
97            !matches!(reloc.ty, wasmparser::RelocationType::TypeIndexLeb)
98        };
99
100        let duplicate_iter = match self.kind {
101            SymbolKind::Duplicate(original_id) => Some(original_id),
102            _ => None,
103        };
104        self.relocs
105            .iter()
106            .filter(filter_non_types)
107            .map(|reloc| Id::from_index(reloc.index))
108            .chain(duplicate_iter)
109    }
110}
111
112#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
113struct DataSymbolKey {
114    data_segment: DataSegmentId,
115    offset: usize,
116    symbol_id: SymbolId,
117}
118
119#[derive(Clone, Default, Debug)]
120pub struct SymbolMap<'src> {
121    symbols: IdVec<SymbolRecord<'src>>,
122    funcs_ids: IdMap<InputFuncId, SymbolId>,
123    datas_ids: BTreeSet<DataSymbolKey>,
124}
125
126impl<'src> SymbolMap<'src> {
127    pub fn empty() -> Self {
128        Self {
129            symbols: IdVec::new(),
130            funcs_ids: IdMap::new(),
131            datas_ids: BTreeSet::new(),
132        }
133    }
134    pub fn new(wasm: &'_ crate::read::InputModule<'src>, num_imports_fn: usize) -> Result<Self> {
135        use wasmparser::SymbolInfo;
136
137        #[derive(Debug)]
138        struct SymbolRange {
139            id: SymbolId,
140            range: Range<usize>,
141        }
142        struct SymIm<'src> {
143            flags: wasmparser::SymbolFlags,
144            name: Cow<'src, str>,
145            linking_name: Option<&'src str>,
146            symbol_kind: SymbolKind,
147        }
148
149        let (code_relocs, data_relocs) = Self::collect_ordered_relocs(wasm)?;
150
151        type DupIds = SmallVec<[SymbolId; 4]>;
152        let mut func_ids = IdMap::<InputFuncId, (SymbolRange, DupIds)>::new();
153        // TODO: Handle symbols that overlap in data segments.
154        let mut data_ids = BTreeMap::<DataSymbolKey, SymbolRange>::new();
155
156        let mut symbols = IdVec::new();
157
158        let data_section_start = wasm.data.starting_offset;
159
160        for (symbol_id, symbol) in wasm.linking.linking_symbols.symbols.iter().enumerate() {
161            let symbol_id = Id::from_index(symbol_id);
162
163            let sym = match *symbol {
164                SymbolInfo::Func { index, flags, name } => {
165                    let input_function_id = Id::from_index(index);
166                    let fn_name = Self::get_or_create_name(
167                        name,
168                        wasm.names.functions.get(input_function_id).map(|n| *n),
169                        || panic!("function {index} does not have a name"),
170                    );
171
172                    let fn_range = if input_function_id.as_raw_index() >= num_imports_fn {
173                        let defined_index = input_function_id.as_raw_index() - num_imports_fn;
174                        let func = wasm
175                            .code
176                            .defined_funcs
177                            .get(Id::from_index(defined_index))
178                            .expect("defined function id should be valid");
179                        func.body.range().shift_left(wasm.code.starting_offset)
180                    } else {
181                        0..0
182                    };
183
184                    // TODO: Handle weak symbols.
185                    func_ids
186                        .entry(input_function_id)
187                        .or_insert_with(|| {
188                            (
189                                SymbolRange {
190                                    id: symbol_id,
191                                    range: fn_range,
192                                },
193                                DupIds::new(),
194                            )
195                        })
196                        .1
197                        .push(symbol_id);
198
199                    SymIm {
200                        flags,
201                        name: fn_name,
202                        linking_name: name,
203                        symbol_kind: SymbolKind::Func {
204                            input_id: input_function_id,
205                        },
206                    }
207                }
208                SymbolInfo::Data {
209                    flags,
210                    name,
211                    symbol,
212                } => {
213                    let Some(defined) = symbol.as_ref() else {
214                        log::warn!(
215                            "Skipping undefined data symbol '{}' in linking section",
216                            name
217                        );
218                        continue;
219                    };
220
221                    let segment_id = Id::from_index(defined.index);
222                    let segment = &wasm.data.data_segments[segment_id];
223
224                    // Remove header size from segment range.
225                    let segment_data_start = segment.range.end - segment.data.len();
226                    // offset of segment relative to data section start
227                    let segment_offset = segment_data_start - data_section_start;
228                    let segment_end = segment_offset + segment.data.len();
229
230                    let start = segment_offset + defined.offset as usize;
231                    let end = start + defined.size as usize;
232
233                    ensure!(
234                        end <= segment_end,
235                        "Data symbol '{}' extends beyond segment",
236                        name
237                    );
238
239                    let data_range = start..end;
240                    data_ids.insert(
241                        DataSymbolKey {
242                            data_segment: segment_id,
243                            offset: start,
244                            symbol_id,
245                        },
246                        SymbolRange {
247                            id: symbol_id,
248                            range: data_range,
249                        },
250                    );
251
252                    SymIm {
253                        flags,
254                        name: name.into(),
255                        linking_name: name.into(),
256                        symbol_kind: SymbolKind::DataDefined {
257                            segment_id,
258                            offset: defined.offset as usize,
259                            length: defined.size as usize,
260                        },
261                    }
262                }
263                SymbolInfo::Global { flags, name, index } => {
264                    let global_id = Id::from_index(index);
265                    let global_name = Self::get_or_create_name(
266                        name,
267                        wasm.names.globals.get(global_id).map(|n| *n),
268                        || format!("global_{index}"),
269                    );
270                    SymIm {
271                        flags,
272                        name: global_name,
273                        linking_name: name,
274                        symbol_kind: SymbolKind::Global(global_id),
275                    }
276                }
277                SymbolInfo::Table { index, name, flags } => {
278                    let table_id = Id::from_index(index);
279                    let table_name = Self::get_or_create_name(
280                        name,
281                        wasm.names.tables.get(table_id).map(|n| *n),
282                        || format!("table_{}", table_id),
283                    );
284                    SymIm {
285                        flags,
286                        name: table_name,
287                        linking_name: name,
288                        symbol_kind: SymbolKind::Table(table_id),
289                    }
290                }
291                // TODO: Handle other symbol types.
292                SymbolInfo::Event { .. } | SymbolInfo::Section { .. } => {
293                    continue;
294                }
295            };
296
297            let id = symbols.push(SymbolRecord {
298                name: sym.name,
299                linking_name: sym.linking_name.map(From::from),
300                flags: sym.flags,
301                kind: sym.symbol_kind,
302                relocs: Vec::new(),
303            });
304            debug_assert_eq!(id, symbol_id)
305        }
306
307        fn move_relocs<'a, U: 'a>(
308            symbols: &mut IdVec<SymbolRecord>,
309            iterator: impl IntoIterator<Item = (U, &'a SymbolRange)>,
310            mut relocations: VecDeque<wasmparser::RelocationEntry>,
311        ) {
312            'next_sym: for (_, symbol) in iterator {
313                while let Some(reloc) = relocations.front() {
314                    match symbol.range.cmp_range(reloc.relocation_range()) {
315                        RangeComp::Equal | RangeComp::Overlap => {}
316                        RangeComp::Left => continue 'next_sym,
317                        RangeComp::Right | RangeComp::NonComparable | RangeComp::Within => {
318                            panic!(
319                                "BUG: Relocation entry is not related to symbols: {symbol:?} and {reloc:?}",
320                            );
321                        }
322                    }
323                    // Collect relocations with offset relative to symbol start
324                    let mut reloc = relocations.pop_front().unwrap();
325                    reloc.offset -= symbol.range.start as u32;
326                    symbols[symbol.id].relocs.push(reloc);
327                }
328            }
329        }
330        // Move relocs from duplicate symbols to main symbol, and mark duplicates.
331        fn move_dup_symbols(
332            symbols: &mut IdVec<SymbolRecord>,
333            func_ids: &IdMap<InputFuncId, (SymbolRange, DupIds)>,
334        ) {
335            for (_input_id, (sym_range, dup_ids)) in func_ids.iter() {
336                if dup_ids.len() <= 1 {
337                    continue;
338                }
339
340                for &dup_id in dup_ids.iter() {
341                    if dup_id == sym_range.id {
342                        continue;
343                    }
344                    // take relocs, and mark as duplicate
345                    let dup_sym = &mut symbols[dup_id];
346                    let relocs = std::mem::take(&mut dup_sym.relocs);
347                    dup_sym.kind = SymbolKind::Duplicate(sym_range.id);
348                    // extend main symbol with
349                    symbols[sym_range.id].relocs.extend(relocs.into_iter());
350                }
351            }
352        }
353
354        // 1. Get ordered relocs (data segments, code).
355        // 2. Iterate symbols by function_id, and get pop relocs untill it in range of function body.
356        // 3. same for data relocs.
357        move_relocs(
358            &mut symbols,
359            func_ids.iter().map(|(k, (v, _))| (k, v)),
360            code_relocs,
361        );
362        move_relocs(&mut symbols, data_ids.iter(), data_relocs);
363        move_dup_symbols(&mut symbols, &func_ids);
364
365        Ok(Self {
366            symbols,
367            funcs_ids: func_ids.into_iter().map(|(k, (v, _))| (k, v.id)).collect(),
368            datas_ids: data_ids.into_iter().map(|(k, _)| k).collect(),
369        })
370    }
371
372    pub fn clone_owned(&self) -> SymbolMap<'static> {
373        let symbols = self
374            .symbols
375            .iter()
376            .map(|(_id, sym)| SymbolRecord {
377                name: sym.name.clone().into_owned().into(),
378                linking_name: sym.linking_name.clone().map(|n| Cow::Owned(n.into_owned())),
379                flags: sym.flags,
380                relocs: sym.relocs.clone(),
381                kind: sym.kind,
382            })
383            .collect();
384        SymbolMap {
385            symbols,
386            funcs_ids: self.funcs_ids.clone(),
387            datas_ids: self.datas_ids.clone(),
388        }
389    }
390
391    // TODO: Build remap index instead of handling duplicates here.
392    pub fn get(&self, id: SymbolId) -> Option<&SymbolRecord<'src>> {
393        self.symbols.get(id).map(|sym| match &sym.kind {
394            SymbolKind::Duplicate(original_id) => &self.symbols[*original_id],
395            _ => sym,
396        })
397    }
398    pub fn get_function_symbol(&self, func_id: InputFuncId) -> Option<SymbolId> {
399        self.funcs_ids.get(func_id).copied()
400    }
401
402    // pub fn get_data_symbol(&self, segment_id: DataSegmentId, offset: usize) -> Option<SymbolId> {
403    //     self.datas_ids
404    //         .get(&DataSymbolKey {
405    //             data_segment: segment_id,
406    //             offset,
407    //         })
408    //         .copied()
409    // }
410
411    pub fn as_input_function(&self, id: SymbolId) -> Option<InputFuncId> {
412        match &self.symbols.get(id)?.kind {
413            SymbolKind::Func { input_id } => Some(*input_id),
414            _ => None,
415        }
416    }
417    pub fn as_duplicate_mapped(&self, id: SymbolId) -> Option<SymbolId> {
418        match &self.symbols.get(id)?.kind {
419            SymbolKind::Duplicate(original_id) => Some(*original_id),
420            _ => None,
421        }
422    }
423
424    pub fn is_function(&self, id: SymbolId) -> bool {
425        self.as_input_function(id).is_some()
426    }
427    pub fn is_data(&self, id: SymbolId) -> bool {
428        matches!(self.symbols[id].kind, SymbolKind::DataDefined { .. })
429    }
430
431    fn get_or_create_name<'a>(
432        linking_name: Option<&'a str>,
433        name_section_name: Option<&'a str>,
434        create_name: impl Fn() -> String,
435    ) -> Cow<'a, str> {
436        match (linking_name, name_section_name) {
437            (Some(linking), Some(name_section)) => {
438                if linking != name_section {
439                    // This is not an error - linking section contain internal name of symbol, while name_section may contain #[demangle] name.
440                    log::trace!(
441                        "Conflicting names for symbol: linking section name '{linking}', name section name '{name_section}'"
442                    );
443                }
444                name_section.into()
445            }
446            (Some(linking), None) => linking.into(),
447            (None, Some(name_section)) => name_section.into(),
448            (None, None) => create_name().into(),
449        }
450    }
451
452    // Return a list of all relocations in the module, ordered by their offset.
453    fn collect_ordered_relocs(
454        input: &'_ InputModule<'src>,
455    ) -> Result<(
456        VecDeque<wasmparser::RelocationEntry>,
457        VecDeque<wasmparser::RelocationEntry>,
458    )> {
459        // Build a flat list of all relocations, adjusting offsets to be relative to the start of the module
460        let mut code = input.relocs.relocs[input.code.section_index]
461            .entries
462            .clone();
463        let mut data = input.relocs.relocs[input.data.section_index]
464            .entries
465            .clone();
466
467        code.sort_by_key(|r| r.offset);
468        data.sort_by_key(|r| r.offset);
469        // make sure entries do not overlap
470        #[cfg(debug_assertions)]
471        {
472            for pair in code.windows(2) {
473                let first = &pair[0];
474                let second = &pair[1];
475                let first_end = first.relocation_range().end as u32;
476                if first_end > second.offset {
477                    panic!(
478                        "Overlapping relocations found: first={first:?} (end={first_end}), second={second:?}"
479                    );
480                }
481            }
482            for pair in data.windows(2) {
483                let first = &pair[0];
484                let second = &pair[1];
485                let first_end = first.relocation_range().end as u32;
486                if first_end > second.offset {
487                    panic!(
488                        "Overlapping relocations found: first={first:?} (end={first_end}), second={second:?}"
489                    );
490                }
491            }
492        }
493
494        Ok((code.into(), data.into()))
495    }
496
497    pub fn print_debug(&self) {
498        for (id, symbol) in self.symbols.iter() {
499            println!("---{id} <{name}>", name = &symbol.name);
500            println!("    record: {:?}", symbol);
501            for reloc in &symbol.relocs {
502                let id = Id::from_index(reloc.index);
503                println!(
504                    "-->{id} <{name}> reloc{:?}",
505                    reloc,
506                    name = self.symbols[id].name,
507                );
508            }
509        }
510    }
511
512    pub fn iter_data_symbols(
513        &self,
514    ) -> impl Iterator<Item = (DataSegmentId, SymbolId, &SymbolRecord<'src>)> {
515        self.datas_ids.iter().map(|key| {
516            (
517                key.data_segment,
518                key.symbol_id,
519                &self.symbols[key.symbol_id],
520            )
521        })
522    }
523    pub fn iter(&self) -> impl Iterator<Item = (SymbolId, &SymbolRecord<'src>)> {
524        self.symbols.iter()
525    }
526}