Skip to main content

objdiff_core/arch/ppc/
mod.rs

1use alloc::{
2    boxed::Box,
3    collections::{BTreeMap, BTreeSet},
4    string::{String, ToString},
5    vec,
6    vec::Vec,
7};
8
9use anyhow::{Result, anyhow, bail, ensure};
10use cwextab::{ExceptionTableData, decode_extab};
11use flagset::Flags;
12use object::{Object as _, ObjectSection as _, ObjectSymbol as _, elf, pe};
13
14use crate::{
15    arch::{Arch, DataType, RelocationOverride, RelocationOverrideTarget},
16    diff::{
17        DiffObjConfig,
18        data::resolve_relocation,
19        display::{ContextItem, HoverItem, HoverItemColor, InstructionPart, SymbolNavigationKind},
20    },
21    obj::{
22        FlowAnalysisResult, InstructionRef, Object, Relocation, RelocationFlags,
23        ResolvedInstructionRef, ResolvedRelocation, Section, Symbol, SymbolFlag, SymbolFlagSet,
24        SymbolKind,
25    },
26};
27
28mod flow_analysis;
29
30// Relative relocation, can be Simm, Offset or BranchDest
31fn is_relative_arg(arg: &powerpc::Argument) -> bool {
32    matches!(
33        arg,
34        powerpc::Argument::Simm(_)
35            | powerpc::Argument::Offset(_)
36            | powerpc::Argument::BranchDest(_)
37    )
38}
39
40// Relative or absolute relocation, can be Uimm, Simm or Offset
41fn is_rel_abs_arg(arg: &powerpc::Argument) -> bool {
42    matches!(
43        arg,
44        powerpc::Argument::Uimm(_) | powerpc::Argument::Simm(_) | powerpc::Argument::Offset(_)
45    )
46}
47
48fn is_offset_arg(arg: &powerpc::Argument) -> bool { matches!(arg, powerpc::Argument::Offset(_)) }
49
50#[derive(Debug)]
51pub struct ArchPpc {
52    pub extensions: powerpc::Extensions,
53    /// Exception info
54    pub extab: Option<BTreeMap<usize, ExceptionInfo>>,
55}
56
57impl ArchPpc {
58    pub fn new(file: &object::File) -> Result<Self> {
59        let extensions = match file.flags() {
60            object::FileFlags::Coff { .. } => powerpc::Extensions::xenon(),
61            object::FileFlags::Elf { e_flags, .. }
62                if (e_flags & elf::EF_PPC_EMB) == elf::EF_PPC_EMB =>
63            {
64                powerpc::Extensions::gekko_broadway()
65            }
66            _ => {
67                if file.is_64() {
68                    powerpc::Extension::Ppc64 | powerpc::Extension::AltiVec
69                } else {
70                    // Gekko/Broadway objects often use the EF_PPC_EMB flag,
71                    // but ProDG in particular does not emit it.
72                    powerpc::Extensions::gekko_broadway()
73                }
74            }
75        };
76        let extab = decode_exception_info(file)?;
77        Ok(Self { extensions, extab })
78    }
79
80    fn parse_ins_ref(&self, resolved: ResolvedInstructionRef) -> Result<powerpc::Ins> {
81        let mut code = u32::from_be_bytes(resolved.code.try_into()?);
82        if let Some(reloc) = resolved.relocation {
83            code = zero_reloc(code, reloc.relocation);
84        }
85        let op = powerpc::Opcode::from(resolved.ins_ref.opcode);
86        Ok(powerpc::Ins { code, op })
87    }
88
89    fn find_reloc_arg(
90        &self,
91        ins: &powerpc::ParsedIns,
92        resolved: Option<ResolvedRelocation>,
93    ) -> Option<usize> {
94        match resolved?.relocation.flags {
95            RelocationFlags::Elf(elf::R_PPC_EMB_SDA21) => Some(1),
96            RelocationFlags::Elf(elf::R_PPC_REL24 | elf::R_PPC_REL14)
97            | RelocationFlags::Coff(pe::IMAGE_REL_PPC_REL24 | pe::IMAGE_REL_PPC_REL14) => {
98                ins.args.iter().rposition(is_relative_arg)
99            }
100            RelocationFlags::Elf(
101                elf::R_PPC_ADDR16_HI | elf::R_PPC_ADDR16_HA | elf::R_PPC_ADDR16_LO,
102            )
103            | RelocationFlags::Coff(pe::IMAGE_REL_PPC_REFHI | pe::IMAGE_REL_PPC_REFLO) => {
104                ins.args.iter().rposition(is_rel_abs_arg)
105            }
106            RelocationFlags::Elf(elf::R_PPC64_TOC16) => ins.args.iter().rposition(is_offset_arg),
107            _ => None,
108        }
109    }
110}
111
112impl Arch for ArchPpc {
113    fn scan_instructions_internal(
114        &self,
115        address: u64,
116        code: &[u8],
117        _section_index: usize,
118        _relocations: &[Relocation],
119        _diff_config: &DiffObjConfig,
120    ) -> Result<Vec<InstructionRef>> {
121        ensure!(code.len() & 3 == 0, "Code length must be a multiple of 4");
122        let ins_count = code.len() / 4;
123        let mut insts = Vec::<InstructionRef>::with_capacity(ins_count);
124        for (cur_addr, ins) in powerpc::InsIter::new(code, address as u32, self.extensions) {
125            insts.push(InstructionRef {
126                address: cur_addr as u64,
127                size: 4,
128                opcode: u16::from(ins.op),
129                branch_dest: ins.branch_dest(cur_addr).map(u64::from),
130            });
131        }
132        Ok(insts)
133    }
134
135    fn display_instruction(
136        &self,
137        resolved: ResolvedInstructionRef,
138        _diff_config: &DiffObjConfig,
139        cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
140    ) -> Result<()> {
141        let ins = self.parse_ins_ref(resolved)?.simplified();
142
143        cb(InstructionPart::opcode(ins.mnemonic, resolved.ins_ref.opcode))?;
144
145        let reloc_arg = self.find_reloc_arg(&ins, resolved.relocation);
146
147        let mut writing_offset = false;
148        for (idx, arg) in ins.args_iter().enumerate() {
149            if idx > 0 && !writing_offset {
150                cb(InstructionPart::separator())?;
151            }
152
153            if reloc_arg == Some(idx) {
154                let reloc = resolved.relocation.unwrap();
155                display_reloc(reloc, cb)?;
156                // For @sda21, we can omit the register argument
157                if matches!(reloc.relocation.flags, RelocationFlags::Elf(elf::R_PPC_EMB_SDA21))
158                    // Sanity check: the next argument should be r0
159                    && matches!(ins.args.get(idx + 1), Some(powerpc::Argument::GPR(powerpc::GPR(0))))
160                {
161                    break;
162                }
163            } else {
164                match arg {
165                    powerpc::Argument::Simm(simm) => cb(InstructionPart::signed(simm.0)),
166                    powerpc::Argument::Uimm(uimm) => cb(InstructionPart::unsigned(uimm.0)),
167                    powerpc::Argument::Offset(offset) => cb(InstructionPart::signed(offset.0)),
168                    powerpc::Argument::BranchDest(dest) => cb(InstructionPart::branch_dest(
169                        (resolved.ins_ref.address as u32).wrapping_add_signed(dest.0),
170                    )),
171                    _ => cb(InstructionPart::opaque(arg.to_string())),
172                }?;
173            }
174
175            if writing_offset {
176                cb(InstructionPart::basic(")"))?;
177                writing_offset = false;
178            }
179            if is_offset_arg(arg) {
180                cb(InstructionPart::basic("("))?;
181                writing_offset = true;
182            }
183        }
184
185        Ok(())
186    }
187
188    // Could be replaced by data_flow_analysis once that feature stabilizes
189    fn generate_pooled_relocations(
190        &self,
191        address: u64,
192        code: &[u8],
193        relocations: &[Relocation],
194        symbols: &[Symbol],
195    ) -> Vec<Relocation> {
196        generate_fake_pool_relocations_for_function(
197            address,
198            code,
199            relocations,
200            symbols,
201            self.extensions,
202        )
203    }
204
205    fn data_flow_analysis(
206        &self,
207        obj: &Object,
208        symbol: &Symbol,
209        code: &[u8],
210        relocations: &[Relocation],
211    ) -> Option<Box<dyn FlowAnalysisResult>> {
212        Some(flow_analysis::ppc_data_flow_analysis(obj, symbol, code, relocations, self.extensions))
213    }
214
215    fn relocation_override(
216        &self,
217        file: &object::File<'_>,
218        section: &object::Section,
219        address: u64,
220        relocation: &object::Relocation,
221    ) -> Result<Option<RelocationOverride>> {
222        match relocation.flags() {
223            // IMAGE_REL_PPC_PAIR contains the REF{HI,LO} displacement instead of a symbol index
224            object::RelocationFlags::Coff {
225                typ: pe::IMAGE_REL_PPC_REFHI | pe::IMAGE_REL_PPC_REFLO,
226            } => section
227                .relocations()
228                .skip_while(|&(a, _)| a < address)
229                .take_while(|&(a, _)| a == address)
230                .find(|(_, reloc)| {
231                    matches!(reloc.flags(), object::RelocationFlags::Coff {
232                        typ: pe::IMAGE_REL_PPC_PAIR
233                    })
234                })
235                .map_or(
236                    Ok(Some(RelocationOverride {
237                        target: RelocationOverrideTarget::Keep,
238                        addend: 0,
239                    })),
240                    |(_, reloc)| match reloc.target() {
241                        object::RelocationTarget::Symbol(_) => Ok(Some(RelocationOverride {
242                            target: RelocationOverrideTarget::Keep,
243                            addend: 0,
244                        })),
245                        target => Err(anyhow!("Unsupported IMAGE_REL_PPC_PAIR target {target:?}")),
246                    },
247                ),
248            // Skip PAIR relocations as they are handled by the previous case
249            object::RelocationFlags::Coff { typ: pe::IMAGE_REL_PPC_PAIR } => {
250                Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Skip, addend: 0 }))
251            }
252            // Any other COFF relocation has an addend of 0
253            object::RelocationFlags::Coff { .. } => {
254                Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend: 0 }))
255            }
256            // Handle ELF implicit relocations
257            flags @ object::RelocationFlags::Elf { r_type } => {
258                ensure!(
259                    !relocation.has_implicit_addend(),
260                    "Unsupported implicit relocation {:?}",
261                    flags
262                );
263                match r_type {
264                    elf::R_PPC64_TOC16 => {
265                        let offset = u64::try_from(relocation.addend())
266                            .map_err(|_| anyhow!("Negative addend for R_PPC64_TOC16 relocation"))?;
267                        let Some(toc_section) = file.section_by_name(".toc") else {
268                            bail!("Missing .toc section for R_PPC64_TOC16 relocation");
269                        };
270                        // If TOC target is a relocation, replace it with the target symbol
271                        let Some((_, toc_relocation)) =
272                            toc_section.relocations().find(|&(a, _)| a == offset)
273                        else {
274                            return Ok(None);
275                        };
276                        if toc_relocation.has_implicit_addend() {
277                            log::warn!(
278                                "Unsupported implicit addend for R_PPC64_TOC16 relocation: {toc_relocation:?}"
279                            );
280                            return Ok(None);
281                        }
282                        let addend = toc_relocation.addend();
283                        match toc_relocation.target() {
284                            object::RelocationTarget::Symbol(symbol_index) => {
285                                Ok(Some(RelocationOverride {
286                                    target: RelocationOverrideTarget::Symbol(symbol_index),
287                                    addend,
288                                }))
289                            }
290                            object::RelocationTarget::Section(section_index) => {
291                                Ok(Some(RelocationOverride {
292                                    target: RelocationOverrideTarget::Section(section_index),
293                                    addend,
294                                }))
295                            }
296                            target => {
297                                log::warn!(
298                                    "Unsupported R_PPC64_TOC16 relocation target {target:?}"
299                                );
300                                Ok(None)
301                            }
302                        }
303                    }
304                    _ => Ok(None),
305                }
306            }
307            _ => Ok(None),
308        }
309    }
310
311    fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
312        match flags {
313            RelocationFlags::Elf(r_type) => match r_type {
314                elf::R_PPC_NONE => Some("R_PPC_NONE"), // We use this for fake pool relocs
315                elf::R_PPC_ADDR16_LO => Some("R_PPC_ADDR16_LO"),
316                elf::R_PPC_ADDR16_HI => Some("R_PPC_ADDR16_HI"),
317                elf::R_PPC_ADDR16_HA => Some("R_PPC_ADDR16_HA"),
318                elf::R_PPC_EMB_SDA21 => Some("R_PPC_EMB_SDA21"),
319                elf::R_PPC_ADDR32 => Some("R_PPC_ADDR32"),
320                elf::R_PPC_UADDR32 => Some("R_PPC_UADDR32"),
321                elf::R_PPC_REL24 => Some("R_PPC_REL24"),
322                elf::R_PPC_REL14 => Some("R_PPC_REL14"),
323                elf::R_PPC64_TOC16 => Some("R_PPC64_TOC16"),
324                _ => None,
325            },
326            RelocationFlags::Coff(r_type) => match r_type {
327                pe::IMAGE_REL_PPC_ADDR32 => Some("IMAGE_REL_PPC_ADDR32"),
328                pe::IMAGE_REL_PPC_REFHI => Some("IMAGE_REL_PPC_REFHI"),
329                pe::IMAGE_REL_PPC_REFLO => Some("IMAGE_REL_PPC_REFLO"),
330                pe::IMAGE_REL_PPC_REL24 => Some("IMAGE_REL_PPC_REL24"),
331                pe::IMAGE_REL_PPC_REL14 => Some("IMAGE_REL_PPC_REL14"),
332                pe::IMAGE_REL_PPC_PAIR => Some("IMAGE_REL_PPC_PAIR"),
333                _ => None,
334            },
335        }
336    }
337
338    fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
339        match flags {
340            RelocationFlags::Elf(r_type) => match r_type {
341                elf::R_PPC_ADDR32 => 4,
342                elf::R_PPC_UADDR32 => 4,
343                _ => 1,
344            },
345            RelocationFlags::Coff(r_type) => match r_type {
346                pe::IMAGE_REL_PPC_ADDR32 => 4,
347                _ => 1,
348            },
349        }
350    }
351
352    fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet {
353        if self.extab.as_ref().is_some_and(|extab| extab.contains_key(&(symbol.index().0 - 1))) {
354            SymbolFlag::HasExtra.into()
355        } else {
356            SymbolFlag::none()
357        }
358    }
359
360    fn guess_data_type(
361        &self,
362        ins: Option<ResolvedInstructionRef>,
363        reloc: Option<ResolvedRelocation>,
364        bytes: &[u8],
365    ) -> Option<DataType> {
366        if reloc.is_some_and(|r| {
367            r.symbol.name.starts_with("@stringBase") // MWCC
368                || r.symbol.name.starts_with("@wstringBase") // MWCC
369                || r.symbol.name.starts_with("$SG")
370                || r.symbol.name.starts_with("??_C") // MSVC
371        }) {
372            // Compiler-generated symbol name for a string or a pool of strings.
373            return Some(DataType::String);
374        }
375        if let Some(ins) = ins {
376            let opcode = powerpc::Opcode::from(ins.ins_ref.opcode);
377            if let Some(ty) = flow_analysis::guess_data_type_from_load_store_inst_op(opcode) {
378                // Numeric type.
379                return Some(ty);
380            }
381        }
382        if reloc.is_some_and(|r| r.symbol.name.starts_with("$LC")) {
383            // GCC compiler-generated symbol name for a literal.
384            // This could be a float literal instead of a string literal, so only check this after the opcode.
385            return Some(DataType::String);
386        }
387        if bytes.len() >= 2 && bytes.iter().position(|&c| c == b'\0') == Some(bytes.len() - 1) {
388            // It may be an unpooled string if the symbol contains exactly one null byte at the end of the symbol.
389            return Some(DataType::String);
390        }
391        None
392    }
393
394    fn symbol_hover(&self, _obj: &Object, symbol_index: usize) -> Vec<HoverItem> {
395        let mut out = Vec::new();
396        if let Some(extab) = self.extab_for_symbol(symbol_index) {
397            out.push(HoverItem::Text {
398                label: "extab symbol".into(),
399                value: extab.etb_symbol.name.clone(),
400                color: HoverItemColor::Special,
401            });
402            out.push(HoverItem::Text {
403                label: "extabindex symbol".into(),
404                value: extab.eti_symbol.name.clone(),
405                color: HoverItemColor::Special,
406            });
407        }
408        out
409    }
410
411    fn symbol_context(&self, _obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
412        let mut out = Vec::new();
413        if let Some(_extab) = self.extab_for_symbol(symbol_index) {
414            out.push(ContextItem::Navigate {
415                label: "Decode exception table".to_string(),
416                symbol_index,
417                kind: SymbolNavigationKind::Extab,
418            });
419        }
420        out
421    }
422
423    fn instruction_hover(&self, _obj: &Object, resolved: ResolvedInstructionRef) -> Vec<HoverItem> {
424        let Ok(ins) = self.parse_ins_ref(resolved) else {
425            return Vec::new();
426        };
427        let orig = ins.basic().to_string();
428        let simplified = ins.simplified().to_string();
429        let show_orig = orig != simplified;
430        let rlwinm_decoded = rlwinmdec::decode(&orig);
431        let mut out = Vec::with_capacity(2);
432        if show_orig {
433            out.push(HoverItem::Text {
434                label: "Original".into(),
435                value: orig,
436                color: HoverItemColor::Normal,
437            });
438        }
439        if let Some(decoded) = rlwinm_decoded {
440            for line in decoded.lines() {
441                out.push(HoverItem::Text {
442                    label: Default::default(),
443                    value: line.to_string(),
444                    color: HoverItemColor::Special,
445                });
446            }
447        }
448        out
449    }
450
451    fn instruction_context(
452        &self,
453        _obj: &Object,
454        resolved: ResolvedInstructionRef,
455    ) -> Vec<ContextItem> {
456        let Ok(ins) = self.parse_ins_ref(resolved) else {
457            return Vec::new();
458        };
459        let orig = ins.basic().to_string();
460        let simplified = ins.simplified().to_string();
461        let show_orig = orig != simplified;
462        let mut out = Vec::with_capacity(2);
463        out.push(ContextItem::Copy { value: simplified, label: None, copy_string: None });
464        if show_orig {
465            out.push(ContextItem::Copy {
466                value: orig,
467                label: Some("original".to_string()),
468                copy_string: None,
469            });
470        }
471        out
472    }
473
474    fn infer_function_size(
475        &self,
476        symbol: &Symbol,
477        section: &Section,
478        mut next_address: u64,
479    ) -> Result<u64> {
480        // Trim any trailing 4-byte zeroes from the end (padding)
481        while next_address >= symbol.address + 4
482            && let Some(data) = section.data_range(next_address - 4, 4)
483            && data == [0u8; 4]
484            && section.relocation_at(next_address - 4, 4).is_none()
485        {
486            next_address -= 4;
487        }
488        Ok(next_address.saturating_sub(symbol.address))
489    }
490
491    fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol], symbol_indices: &[usize]) {
492        // Change the indices used as keys from the original symbol indices to the new symbol array indices
493        self.extab = Self::convert_extab_map_indices(self, symbol_indices);
494    }
495}
496
497impl ArchPpc {
498    pub fn extab_for_symbol(&self, symbol_index: usize) -> Option<&ExceptionInfo> {
499        self.extab.as_ref()?.get(&symbol_index)
500    }
501
502    pub fn convert_extab_map_indices(
503        &self,
504        symbol_indices: &[usize],
505    ) -> Option<BTreeMap<usize, ExceptionInfo>> {
506        let new_map: BTreeMap<usize, ExceptionInfo> =
507            self.extab.as_ref()?.iter().map(|e| (symbol_indices[*e.0 + 1], e.1.clone())).collect();
508
509        Some(new_map)
510    }
511}
512
513fn zero_reloc(code: u32, reloc: &Relocation) -> u32 {
514    match reloc.flags {
515        RelocationFlags::Elf(elf::R_PPC_EMB_SDA21) => code & !0x1FFFFF,
516        RelocationFlags::Elf(elf::R_PPC_REL24) | RelocationFlags::Coff(pe::IMAGE_REL_PPC_REL24) => {
517            code & !0x3FFFFFC
518        }
519        RelocationFlags::Elf(elf::R_PPC_REL14) | RelocationFlags::Coff(pe::IMAGE_REL_PPC_REL14) => {
520            code & !0xFFFC
521        }
522        RelocationFlags::Elf(
523            elf::R_PPC_ADDR16_HI | elf::R_PPC_ADDR16_HA | elf::R_PPC_ADDR16_LO,
524        )
525        | RelocationFlags::Coff(pe::IMAGE_REL_PPC_REFHI | pe::IMAGE_REL_PPC_REFLO) => {
526            code & !0xFFFF
527        }
528        _ => code,
529    }
530}
531
532fn display_reloc(
533    resolved: ResolvedRelocation,
534    cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
535) -> Result<()> {
536    match resolved.relocation.flags {
537        RelocationFlags::Elf(elf::R_PPC_ADDR16_LO)
538        | RelocationFlags::Coff(pe::IMAGE_REL_PPC_REFLO) => {
539            cb(InstructionPart::reloc())?;
540            cb(InstructionPart::basic("@l"))?;
541        }
542        RelocationFlags::Elf(elf::R_PPC_ADDR16_HI)
543        | RelocationFlags::Coff(pe::IMAGE_REL_PPC_REFHI) => {
544            cb(InstructionPart::reloc())?;
545            cb(InstructionPart::basic("@h"))?;
546        }
547        RelocationFlags::Elf(elf::R_PPC_ADDR16_HA) => {
548            cb(InstructionPart::reloc())?;
549            cb(InstructionPart::basic("@ha"))?;
550        }
551        RelocationFlags::Elf(elf::R_PPC_EMB_SDA21) => {
552            cb(InstructionPart::reloc())?;
553            cb(InstructionPart::basic("@sda21"))?;
554        }
555        RelocationFlags::Elf(
556            elf::R_PPC_ADDR32 | elf::R_PPC_UADDR32 | elf::R_PPC_REL24 | elf::R_PPC_REL14,
557        )
558        | RelocationFlags::Coff(
559            pe::IMAGE_REL_PPC_ADDR32 | pe::IMAGE_REL_PPC_REL24 | pe::IMAGE_REL_PPC_REL14,
560        ) => {
561            cb(InstructionPart::reloc())?;
562        }
563        RelocationFlags::Elf(elf::R_PPC_NONE) => {
564            // Fake pool relocation.
565            cb(InstructionPart::basic("<"))?;
566            cb(InstructionPart::reloc())?;
567            cb(InstructionPart::basic(">"))?;
568        }
569        _ => cb(InstructionPart::reloc())?,
570    };
571    Ok(())
572}
573
574#[derive(Debug, Clone)]
575pub struct ExtabSymbolRef {
576    pub original_index: usize,
577    pub name: String,
578    pub demangled_name: Option<String>,
579}
580
581#[derive(Debug, Clone)]
582pub struct ExceptionInfo {
583    pub eti_symbol: ExtabSymbolRef,
584    pub etb_symbol: ExtabSymbolRef,
585    pub data: ExceptionTableData,
586    pub dtors: Vec<ExtabSymbolRef>,
587}
588
589fn decode_exception_info(
590    file: &object::File<'_>,
591) -> Result<Option<BTreeMap<usize, ExceptionInfo>>> {
592    let Some(extab_section) = file.section_by_name("extab") else {
593        return Ok(None);
594    };
595    let Some(extabindex_section) = file.section_by_name("extabindex") else {
596        return Ok(None);
597    };
598
599    let mut result = BTreeMap::new();
600    let extab_relocations =
601        extab_section.relocations().collect::<BTreeMap<u64, object::Relocation>>();
602    let extabindex_relocations =
603        extabindex_section.relocations().collect::<BTreeMap<u64, object::Relocation>>();
604
605    for extabindex in file.symbols().filter(|symbol| {
606        symbol.section_index() == Some(extabindex_section.index())
607            && symbol.kind() == object::SymbolKind::Data
608    }) {
609        if extabindex.size() != 12 {
610            log::warn!("Invalid extabindex entry size {}", extabindex.size());
611            continue;
612        }
613
614        // Each extabindex entry has two relocations:
615        // - 0x0: The function that the exception table is for
616        // - 0x8: The relevant entry in extab section
617        let Some(extab_func_reloc) = extabindex_relocations.get(&extabindex.address()) else {
618            log::warn!("Failed to find function relocation for extabindex entry");
619            continue;
620        };
621        let Some(extab_reloc) = extabindex_relocations.get(&(extabindex.address() + 8)) else {
622            log::warn!("Failed to find extab relocation for extabindex entry");
623            continue;
624        };
625
626        // Resolve the function and extab symbols
627        let Some(extab_func) = relocation_symbol(file, extab_func_reloc)? else {
628            log::warn!("Failed to find function symbol for extabindex entry");
629            continue;
630        };
631        let extab_func_name = extab_func.name()?;
632        let Some(extab) = relocation_symbol(file, extab_reloc)? else {
633            log::warn!("Failed to find extab symbol for extabindex entry");
634            continue;
635        };
636
637        let extab_start_addr = extab.address() - extab_section.address();
638        let extab_end_addr = extab_start_addr + extab.size();
639
640        // All relocations in the extab section are dtors
641        let mut dtors: Vec<ExtabSymbolRef> = vec![];
642        for (_, reloc) in extab_relocations.range(extab_start_addr..extab_end_addr) {
643            let Some(symbol) = relocation_symbol(file, reloc)? else {
644                log::warn!("Failed to find symbol for extab relocation");
645                continue;
646            };
647            dtors.push(make_symbol_ref(&symbol)?);
648        }
649
650        // Decode the extab data
651        let Some(extab_data) = extab_section.data_range(extab_start_addr, extab.size())? else {
652            log::warn!("Failed to get extab data for function {extab_func_name}");
653            continue;
654        };
655        let data = match decode_extab(extab_data) {
656            Ok(decoded_data) => decoded_data,
657            Err(e) => {
658                log::warn!(
659                    "Exception table decoding failed for function {extab_func_name}, reason: {e}"
660                );
661                return Ok(None);
662            }
663        };
664
665        //Add the new entry to the list
666        result.insert(extab_func.index().0 - 1, ExceptionInfo {
667            eti_symbol: make_symbol_ref(&extabindex)?,
668            etb_symbol: make_symbol_ref(&extab)?,
669            data,
670            dtors,
671        });
672    }
673
674    Ok(Some(result))
675}
676
677fn relocation_symbol<'data, 'file>(
678    file: &'file object::File<'data>,
679    relocation: &object::Relocation,
680) -> Result<Option<object::Symbol<'data, 'file>>> {
681    let addend = relocation.addend();
682    match relocation.target() {
683        object::RelocationTarget::Symbol(idx) => {
684            ensure!(addend == 0, "Symbol relocations must have zero addend");
685            Ok(Some(file.symbol_by_index(idx)?))
686        }
687        object::RelocationTarget::Section(idx) => {
688            ensure!(addend >= 0, "Section relocations must have non-negative addend");
689            let addend = addend as u64;
690            Ok(file
691                .symbols()
692                .find(|symbol| symbol.section_index() == Some(idx) && symbol.address() == addend))
693        }
694        target => bail!("Unsupported relocation target: {target:?}"),
695    }
696}
697
698fn make_symbol_ref(symbol: &object::Symbol) -> Result<ExtabSymbolRef> {
699    let name = symbol.name()?.to_string();
700    let demangled_name = cwdemangle::demangle(&name, &cwdemangle::DemangleOptions::default());
701    Ok(ExtabSymbolRef { original_index: symbol.index().0 - 1, name, demangled_name })
702}
703
704#[derive(Debug)]
705struct PoolReference {
706    addr_src_gpr: powerpc::GPR,
707    addr_offset: i64,
708    addr_dst_gpr: Option<powerpc::GPR>,
709}
710
711// Given an instruction, check if it could be accessing pooled data at the address in a register.
712// If so, return information pertaining to where the instruction is getting that address from and
713// what it's doing with the address (e.g. copying it into another register, adding an offset, etc).
714fn get_pool_reference_for_inst(
715    ins: powerpc::Ins,
716    simplified: &powerpc::ParsedIns,
717) -> Option<PoolReference> {
718    use powerpc::{Argument, Opcode};
719    let args = &simplified.args;
720    if flow_analysis::guess_data_type_from_load_store_inst_op(ins.op).is_some() {
721        match (args[1], args[2]) {
722            (Argument::Offset(offset), Argument::GPR(addr_src_gpr)) => {
723                // e.g. lwz. Immediate offset.
724                Some(PoolReference {
725                    addr_src_gpr,
726                    addr_offset: offset.0 as i64,
727                    addr_dst_gpr: None,
728                })
729            }
730            (Argument::GPR(addr_src_gpr), Argument::GPR(_offset_gpr)) => {
731                // e.g. lwzx. The offset is in a register and was likely calculated from an index.
732                // Treat the offset as being 0 in this case to show the first element of the array.
733                // It may be possible to show all elements by figuring out the stride of the array
734                // from the calculations performed on the index before it's put into offset_gpr, but
735                // this would be much more complicated, so it's not currently done.
736                Some(PoolReference { addr_src_gpr, addr_offset: 0, addr_dst_gpr: None })
737            }
738            _ => None,
739        }
740    } else {
741        // If it's not a load/store instruction, there's two more possibilities we need to handle.
742        // 1. It could be loading a pointer to a string.
743        // 2. It could be moving the relocation address plus an offset into a different register to
744        //    load from later.
745        // If either of these match, we also want to return the destination register that the
746        // address is being copied into so that we can detect any future references to that new
747        // register as well.
748        match (ins.op, args[0], args[1], args[2]) {
749            (
750                // `addi` or `subi`
751                Opcode::Addi,
752                Argument::GPR(addr_dst_gpr),
753                Argument::GPR(addr_src_gpr),
754                Argument::Simm(simm),
755            ) => {
756                let offset = if simplified.mnemonic == "addi" { simm.0 } else { -simm.0 };
757                Some(PoolReference {
758                    addr_src_gpr,
759                    addr_offset: offset as i64,
760                    addr_dst_gpr: Some(addr_dst_gpr),
761                })
762            }
763            (
764                // `addis`
765                Opcode::Addis,
766                Argument::GPR(addr_dst_gpr),
767                Argument::GPR(addr_src_gpr),
768                Argument::Uimm(uimm), // Note: `addis` uses UIMM, unlike `addi`, `subi`, and `subis`
769            ) => {
770                assert_eq!(simplified.mnemonic, "addis");
771                let offset = (uimm.0 as i64) << 16;
772                Some(PoolReference {
773                    addr_src_gpr,
774                    addr_offset: offset,
775                    addr_dst_gpr: Some(addr_dst_gpr),
776                })
777            }
778            (
779                // `subis`
780                Opcode::Addis,
781                Argument::GPR(addr_dst_gpr),
782                Argument::GPR(addr_src_gpr),
783                Argument::Simm(simm),
784            ) => {
785                assert_eq!(simplified.mnemonic, "subis");
786                let offset = (simm.0 as i64) << 16;
787                Some(PoolReference {
788                    addr_src_gpr,
789                    addr_offset: offset,
790                    addr_dst_gpr: Some(addr_dst_gpr),
791                })
792            }
793            (
794                // `mr` or `mr.`
795                Opcode::Or,
796                Argument::GPR(addr_dst_gpr),
797                Argument::GPR(addr_src_gpr),
798                Argument::None,
799            ) => Some(PoolReference {
800                addr_src_gpr,
801                addr_offset: 0,
802                addr_dst_gpr: Some(addr_dst_gpr),
803            }),
804            (
805                Opcode::Add,
806                Argument::GPR(addr_dst_gpr),
807                Argument::GPR(addr_src_gpr),
808                Argument::GPR(_offset_gpr),
809            ) => Some(PoolReference {
810                addr_src_gpr,
811                addr_offset: 0,
812                addr_dst_gpr: Some(addr_dst_gpr),
813            }),
814            _ => None,
815        }
816    }
817}
818
819// Remove the relocation we're keeping track of in a particular register when an instruction reuses
820// that register to hold some other value, unrelated to pool relocation addresses.
821fn clear_overwritten_gprs(ins: powerpc::Ins, gpr_pool_relocs: &mut BTreeMap<u8, Relocation>) {
822    use powerpc::{Argument, Arguments, Opcode};
823    let mut def_args = Arguments::default();
824    ins.parse_defs(&mut def_args);
825    for arg in def_args {
826        if let Argument::GPR(gpr) = arg {
827            if ins.op == Opcode::Lmw {
828                // `lmw` overwrites all registers from rd to r31.
829                // powerpc only returns rd itself, so we manually clear the rest of them.
830                for reg in gpr.0..31 {
831                    gpr_pool_relocs.remove(&reg);
832                }
833                break;
834            }
835            gpr_pool_relocs.remove(&gpr.0);
836        }
837    }
838}
839
840// We create a fake relocation for an instruction, vaguely simulating what the actual relocation
841// might have looked like if it wasn't pooled. This is so minimal changes are needed to display
842// pooled accesses vs non-pooled accesses. We set the relocation type to R_PPC_NONE to indicate that
843// there isn't really a relocation here, as copying the pool relocation's type wouldn't make sense.
844// Also, if this instruction is accessing the middle of a symbol instead of the start, we add an
845// addend to indicate that.
846fn make_fake_pool_reloc(
847    offset: i64,
848    cur_addr: u32,
849    pool_reloc: &Relocation,
850    symbols: &[Symbol],
851) -> Option<Relocation> {
852    let pool_reloc = resolve_relocation(symbols, pool_reloc);
853    let offset_from_pool = pool_reloc.relocation.addend + offset;
854    let target_address = pool_reloc.symbol.address.checked_add_signed(offset_from_pool)?;
855    let target_symbol;
856    let addend;
857    if let Some(section_index) = pool_reloc.symbol.section {
858        // Find the exact data symbol within the pool being accessed here based on the address.
859        target_symbol = symbols.iter().position(|s| {
860            s.section == Some(section_index)
861                && s.size > 0
862                && !s.flags.contains(SymbolFlag::Hidden)
863                && !s.flags.contains(SymbolFlag::Ignored)
864                && s.kind != SymbolKind::Section
865                && (s.address..s.address + s.size).contains(&target_address)
866        })?;
867        addend = target_address.checked_sub(symbols[target_symbol].address)? as i64;
868    } else {
869        // If the target symbol is in a different object (extern), we simply copy the pool
870        // relocation's target. This is because it's not possible to locate the actual symbol if
871        // it's extern. And doing that for external symbols would also be unnecessary, because when
872        // the compiler generates an instruction that accesses an external "pool" plus some offset,
873        // that won't be a normal pool that contains other symbols within it that we want to
874        // display. It will be something like a vtable for a class with multiple inheritance (for
875        // example, dCcD_Cyl in The Wind Waker). So just showing that vtable symbol plus an addend
876        // to represent the offset into it works fine in this case.
877        target_symbol = pool_reloc.relocation.target_symbol;
878        addend = offset_from_pool;
879    }
880    Some(Relocation {
881        flags: RelocationFlags::Elf(elf::R_PPC_NONE),
882        address: cur_addr as u64,
883        target_symbol,
884        addend,
885    })
886}
887
888// Searches through all instructions in a function, determining which registers have the addresses
889// of pooled data relocations in them, finding which instructions load data from those addresses,
890// and returns a Vec of "fake pool relocations" that simulate what a relocation for that instruction
891// would look like if data hadn't been pooled.
892// This method tries to follow the function's proper control flow. It keeps track of a queue of
893// states it hasn't traversed yet, where each state holds an instruction address and a map of
894// which registers hold which pool relocations at that point.
895// When a conditional or unconditional branch is encountered, the destination of the branch is added
896// to the queue. Conditional branches will traverse both the path where the branch is taken and the
897// one where it's not. Unconditional branches only follow the branch, ignoring any code immediately
898// after the branch instruction.
899// Limitations: This method does not currently read switch statement jump tables.
900// Instead, we guess that any parts of a function we missed were switch cases, and traverse them as
901// if the last `bctr` before that address had branched there. This should be fairly accurate in
902// practice - in testing the only instructions it seems to miss are double branches that the
903// compiler generates in error which can never be reached during normal execution anyway.
904// It should be possible to implement jump tables properly by reading them out of .data. But this
905// will require keeping track of what value is loaded into each register so we can retrieve the jump
906// table symbol when we encounter a `bctr`.
907fn generate_fake_pool_relocations_for_function(
908    func_address: u64,
909    code: &[u8],
910    relocations: &[Relocation],
911    symbols: &[Symbol],
912    extensions: powerpc::Extensions,
913) -> Vec<Relocation> {
914    use powerpc::{Argument, InsIter, Opcode};
915    let mut visited_ins_addrs = BTreeSet::new();
916    let mut pool_reloc_for_addr = BTreeMap::new();
917    let mut ins_iters_with_gpr_state =
918        vec![(InsIter::new(code, func_address as u32, extensions), BTreeMap::new())];
919    let mut gpr_state_at_bctr = BTreeMap::new();
920    while let Some((ins_iter, mut gpr_pool_relocs)) = ins_iters_with_gpr_state.pop() {
921        for (cur_addr, ins) in ins_iter {
922            if visited_ins_addrs.contains(&cur_addr) {
923                // Avoid getting stuck in an infinite loop when following looping branches.
924                break;
925            }
926            visited_ins_addrs.insert(cur_addr);
927
928            let simplified = ins.simplified();
929
930            // First handle traversing the function's control flow.
931            let mut branch_dest = None;
932            for arg in simplified.args_iter() {
933                if let Argument::BranchDest(dest) = arg {
934                    let dest = cur_addr.wrapping_add_signed(dest.0);
935                    branch_dest = Some(dest);
936                    break;
937                }
938            }
939            if let Some(branch_dest) = branch_dest
940                && branch_dest >= func_address as u32
941                && (branch_dest - func_address as u32) < code.len() as u32
942            {
943                let dest_offset_into_func = branch_dest - func_address as u32;
944                let dest_code_slice = &code[dest_offset_into_func as usize..];
945                match ins.op {
946                    Opcode::Bc => {
947                        // Conditional branch.
948                        // Add the branch destination to the queue to do later.
949                        ins_iters_with_gpr_state.push((
950                            InsIter::new(dest_code_slice, branch_dest, extensions),
951                            gpr_pool_relocs.clone(),
952                        ));
953                        // Then continue on with the current iterator.
954                    }
955                    Opcode::B => {
956                        if simplified.mnemonic != "bl" {
957                            // Unconditional branch.
958                            // Add the branch destination to the queue.
959                            ins_iters_with_gpr_state.push((
960                                InsIter::new(dest_code_slice, branch_dest, extensions),
961                                gpr_pool_relocs.clone(),
962                            ));
963                            // Break out of the current iterator so we can do the newly added one.
964                            break;
965                        }
966                    }
967                    _ => unreachable!(),
968                }
969            }
970            if let Opcode::Bcctr = ins.op
971                && simplified.mnemonic == "bctr"
972            {
973                // Unconditional branch to count register.
974                // Likely a jump table.
975                gpr_state_at_bctr.insert(cur_addr, gpr_pool_relocs.clone());
976            }
977
978            // Then handle keeping track of which GPR contains which pool relocation.
979            let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
980            if let Some(reloc) = reloc {
981                // This instruction has a real relocation, so it may be a pool load we want to keep
982                // track of.
983                let args = &simplified.args;
984                match (ins.op, args[0], args[1], args[2]) {
985                    (
986                        // `lis` + `addi`
987                        Opcode::Addi,
988                        Argument::GPR(addr_dst_gpr),
989                        Argument::GPR(_addr_src_gpr),
990                        Argument::Simm(_simm),
991                    ) => {
992                        gpr_pool_relocs.insert(addr_dst_gpr.0, reloc.clone());
993                    }
994                    (
995                        // `lis` + `ori`
996                        Opcode::Ori,
997                        Argument::GPR(addr_dst_gpr),
998                        Argument::GPR(_addr_src_gpr),
999                        Argument::Uimm(_uimm),
1000                    ) => {
1001                        gpr_pool_relocs.insert(addr_dst_gpr.0, reloc.clone());
1002                    }
1003                    (Opcode::B, _, _, _) => {
1004                        if simplified.mnemonic == "bl" {
1005                            // When encountering a function call, clear any active pool relocations from
1006                            // the volatile registers (r0, r3-r12), but not the nonvolatile registers.
1007                            gpr_pool_relocs.remove(&0);
1008                            for gpr in 3..12 {
1009                                gpr_pool_relocs.remove(&gpr);
1010                            }
1011                        }
1012                    }
1013                    _ => {
1014                        clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
1015                    }
1016                }
1017            } else if let Some(pool_ref) = get_pool_reference_for_inst(ins, &simplified) {
1018                // This instruction doesn't have a real relocation, so it may be a reference to one of
1019                // the already-loaded pools.
1020                if let Some(pool_reloc) = gpr_pool_relocs.get(&pool_ref.addr_src_gpr.0) {
1021                    if let Some(fake_pool_reloc) =
1022                        make_fake_pool_reloc(pool_ref.addr_offset, cur_addr, pool_reloc, symbols)
1023                    {
1024                        pool_reloc_for_addr.insert(cur_addr, fake_pool_reloc);
1025                    }
1026                    if let Some(addr_dst_gpr) = pool_ref.addr_dst_gpr {
1027                        // If the address of the pool relocation got copied into another register, we
1028                        // need to keep track of it in that register too as future instructions may
1029                        // reference the symbol indirectly via this new register, instead of the
1030                        // register the symbol's address was originally loaded into.
1031                        // For example, the start of the function might `lis` + `addi` the start of the
1032                        // ...data pool into r25, and then later the start of a loop will `addi` r25
1033                        // with the offset within the .data section of an array variable into r21.
1034                        // Then the body of the loop will `lwzx` one of the array elements from r21.
1035                        let mut new_reloc = pool_reloc.clone();
1036                        new_reloc.addend += pool_ref.addr_offset;
1037                        gpr_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
1038                    } else {
1039                        clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
1040                    }
1041                } else {
1042                    clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
1043                }
1044            } else {
1045                clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
1046            }
1047        }
1048
1049        // Finally, if we're about to finish the outer loop and don't have any more control flow to
1050        // follow, we check if there are any instruction addresses in this function that we missed.
1051        // If so, and if there were any `bctr` instructions before those points in this function,
1052        // then we try to traverse those missing spots as switch cases.
1053        if ins_iters_with_gpr_state.is_empty() {
1054            let unseen_addrs = (func_address as u32..func_address as u32 + code.len() as u32)
1055                .step_by(4)
1056                .filter(|addr| !visited_ins_addrs.contains(addr));
1057            for unseen_addr in unseen_addrs {
1058                let prev_bctr_gpr_state = gpr_state_at_bctr
1059                    .iter()
1060                    .filter(|&(&addr, _)| addr < unseen_addr)
1061                    .min_by_key(|&(&addr, _)| addr)
1062                    .map(|(_, gpr_state)| gpr_state);
1063                if let Some(gpr_pool_relocs) = prev_bctr_gpr_state {
1064                    let dest_offset_into_func = unseen_addr - func_address as u32;
1065                    let dest_code_slice = &code[dest_offset_into_func as usize..];
1066                    ins_iters_with_gpr_state.push((
1067                        InsIter::new(dest_code_slice, unseen_addr, extensions),
1068                        gpr_pool_relocs.clone(),
1069                    ));
1070                    break;
1071                }
1072            }
1073        }
1074    }
1075
1076    pool_reloc_for_addr.values().cloned().collect()
1077}