Skip to main content

objdiff_core/diff/
display.rs

1use alloc::{
2    borrow::Cow,
3    collections::BTreeSet,
4    format,
5    string::{String, ToString},
6    vec::Vec,
7};
8use core::cmp::Ordering;
9
10use anyhow::Result;
11use itertools::Itertools;
12use regex::Regex;
13
14use crate::{
15    arch::LiteralInfo,
16    diff::{
17        DataDiffKind, DataDiffRow, DiffObjConfig, InstructionDiffKind, InstructionDiffRow,
18        ObjectDiff, SymbolDiff, data::resolve_relocation,
19    },
20    obj::{
21        FlowAnalysisValue, InstructionArg, InstructionArgValue, Object, ParsedInstruction,
22        ResolvedInstructionRef, ResolvedRelocation, SectionFlag, SectionKind, Symbol, SymbolFlag,
23        SymbolKind,
24    },
25};
26
27#[derive(Debug, Clone)]
28pub enum DiffText<'a> {
29    /// Basic text
30    Basic(&'a str),
31    /// Line number
32    Line(u32),
33    /// Instruction address
34    Address(u64),
35    /// Instruction mnemonic
36    Opcode(&'a str, u16),
37    /// Instruction argument
38    Argument(InstructionArgValue<'a>),
39    /// Branch destination
40    BranchDest(u64),
41    /// Branch source/destination arrow, scrolls to a specific row when clicked
42    BranchArrow(u32),
43    /// Symbol name
44    Symbol(&'a Symbol),
45    /// Relocation addend
46    Addend(i64),
47    /// Number of spaces
48    Spacing(u8),
49    /// End of line
50    Eol,
51}
52
53#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
54pub enum DiffTextColor {
55    #[default]
56    Normal, // Grey
57    Dim,      // Dark grey
58    Bright,   // White
59    DataFlow, // Light blue
60    Replace,  // Blue
61    Delete,   // Red
62    Insert,   // Green
63    Rotating(u8),
64}
65
66#[derive(Debug, Clone)]
67pub struct DiffTextSegment<'a> {
68    pub text: DiffText<'a>,
69    pub color: DiffTextColor,
70    pub pad_to: u8,
71}
72
73impl<'a> DiffTextSegment<'a> {
74    #[inline(always)]
75    pub fn basic(text: &'a str, color: DiffTextColor) -> Self {
76        Self { text: DiffText::Basic(text), color, pad_to: 0 }
77    }
78
79    #[inline(always)]
80    pub fn spacing(spaces: u8) -> Self {
81        Self { text: DiffText::Spacing(spaces), color: DiffTextColor::Normal, pad_to: 0 }
82    }
83}
84
85const EOL_SEGMENT: DiffTextSegment<'static> =
86    DiffTextSegment { text: DiffText::Eol, color: DiffTextColor::Normal, pad_to: 0 };
87
88#[derive(Debug, Default, Clone)]
89pub enum HighlightKind {
90    #[default]
91    None,
92    Opcode(u16),
93    Argument(InstructionArgValue<'static>),
94    Symbol(String),
95    Address(u64),
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum InstructionPart<'a> {
100    Basic(Cow<'a, str>),
101    Opcode(Cow<'a, str>, u16),
102    Arg(InstructionArg<'a>),
103    Separator,
104}
105
106impl<'a> InstructionPart<'a> {
107    #[inline(always)]
108    pub fn basic<T>(s: T) -> Self
109    where T: Into<Cow<'a, str>> {
110        InstructionPart::Basic(s.into())
111    }
112
113    #[inline(always)]
114    pub fn opcode<T>(s: T, o: u16) -> Self
115    where T: Into<Cow<'a, str>> {
116        InstructionPart::Opcode(s.into(), o)
117    }
118
119    #[inline(always)]
120    pub fn opaque<T>(s: T) -> Self
121    where T: Into<Cow<'a, str>> {
122        InstructionPart::Arg(InstructionArg::Value(InstructionArgValue::Opaque(s.into())))
123    }
124
125    #[inline(always)]
126    pub fn signed<T>(v: T) -> InstructionPart<'static>
127    where T: Into<i64> {
128        InstructionPart::Arg(InstructionArg::Value(InstructionArgValue::Signed(v.into())))
129    }
130
131    #[inline(always)]
132    pub fn unsigned<T>(v: T) -> InstructionPart<'static>
133    where T: Into<u64> {
134        InstructionPart::Arg(InstructionArg::Value(InstructionArgValue::Unsigned(v.into())))
135    }
136
137    #[inline(always)]
138    pub fn branch_dest<T>(v: T) -> InstructionPart<'static>
139    where T: Into<u64> {
140        InstructionPart::Arg(InstructionArg::BranchDest(v.into()))
141    }
142
143    #[inline(always)]
144    pub fn reloc() -> InstructionPart<'static> { InstructionPart::Arg(InstructionArg::Reloc) }
145
146    #[inline(always)]
147    pub fn separator() -> InstructionPart<'static> { InstructionPart::Separator }
148
149    pub fn into_static(self) -> InstructionPart<'static> {
150        match self {
151            InstructionPart::Basic(s) => InstructionPart::Basic(Cow::Owned(s.into_owned())),
152            InstructionPart::Opcode(s, o) => InstructionPart::Opcode(Cow::Owned(s.into_owned()), o),
153            InstructionPart::Arg(a) => InstructionPart::Arg(a.into_static()),
154            InstructionPart::Separator => InstructionPart::Separator,
155        }
156    }
157}
158
159pub fn display_row(
160    obj: &Object,
161    symbol_index: usize,
162    ins_row: &InstructionDiffRow,
163    diff_config: &DiffObjConfig,
164    mut cb: impl FnMut(DiffTextSegment) -> Result<()>,
165) -> Result<()> {
166    let Some(ins_ref) = ins_row.ins_ref else {
167        cb(EOL_SEGMENT)?;
168        return Ok(());
169    };
170    let Some(resolved) = obj.resolve_instruction_ref(symbol_index, ins_ref) else {
171        cb(DiffTextSegment::basic("<invalid>", DiffTextColor::Delete))?;
172        cb(EOL_SEGMENT)?;
173        return Ok(());
174    };
175    let base_color = match ins_row.kind {
176        InstructionDiffKind::Replace => DiffTextColor::Replace,
177        InstructionDiffKind::Delete => DiffTextColor::Delete,
178        InstructionDiffKind::Insert => DiffTextColor::Insert,
179        _ => DiffTextColor::Normal,
180    };
181    if let Some(line) = resolved.section.line_info.range(..=ins_ref.address).last().map(|(_, &b)| b)
182    {
183        cb(DiffTextSegment { text: DiffText::Line(line), color: DiffTextColor::Dim, pad_to: 5 })?;
184    }
185    cb(DiffTextSegment {
186        text: DiffText::Address(ins_ref.address.saturating_sub(resolved.symbol.address)),
187        color: DiffTextColor::Dim,
188        pad_to: 5,
189    })?;
190    if let Some(branch) = &ins_row.branch_from {
191        // Pick the first branch source if there are multiple.
192        let ins_idx = branch.ins_idx[0];
193        cb(DiffTextSegment {
194            text: DiffText::BranchArrow(ins_idx),
195            color: DiffTextColor::Rotating(branch.branch_idx as u8),
196            pad_to: 0,
197        })?;
198    } else {
199        cb(DiffTextSegment::spacing(4))?;
200    }
201    let mut arg_idx = 0;
202    let mut displayed_relocation = false;
203    let analysis_result = if diff_config.show_data_flow {
204        obj.get_flow_analysis_result(resolved.symbol)
205    } else {
206        None
207    };
208    obj.arch.display_instruction(resolved, diff_config, &mut |part| match part {
209        InstructionPart::Basic(text) => {
210            if text.chars().all(|c| c == ' ') {
211                cb(DiffTextSegment::spacing(text.len() as u8))
212            } else {
213                cb(DiffTextSegment::basic(&text, base_color))
214            }
215        }
216        InstructionPart::Opcode(mnemonic, opcode) => cb(DiffTextSegment {
217            text: DiffText::Opcode(mnemonic.as_ref(), opcode),
218            color: match ins_row.kind {
219                InstructionDiffKind::OpMismatch => DiffTextColor::Replace,
220                _ => base_color,
221            },
222            pad_to: 10,
223        }),
224        InstructionPart::Arg(arg) => {
225            let diff_index = ins_row.arg_diff.get(arg_idx).copied().unwrap_or_default();
226            arg_idx += 1;
227            if arg == InstructionArg::Reloc {
228                displayed_relocation = true;
229            }
230            let data_flow_value =
231            analysis_result.and_then(|result|
232                result.get_argument_value_at_address(
233                    ins_ref.address, (arg_idx - 1) as u8));
234            match (arg, data_flow_value, resolved.ins_ref.branch_dest) {
235                // If we have a flow analysis result, always use that over anything else.
236                (InstructionArg::Value(_) | InstructionArg::Reloc, Some(FlowAnalysisValue::Text(text)), _) => {
237                    cb(DiffTextSegment {
238                        text: DiffText::Argument(InstructionArgValue::Opaque(Cow::Borrowed(text))),
239                        color: DiffTextColor::DataFlow,
240                        pad_to: 0,
241                    })
242                },
243                (InstructionArg::Value(value), None, _) => {
244                    let color = diff_index
245                        .get()
246                        .map_or(base_color, |i| DiffTextColor::Rotating(i as u8));
247                    cb(DiffTextSegment {
248                        text: DiffText::Argument(value),
249                        color,
250                        pad_to: 0,
251                    })
252                },
253                (InstructionArg::Reloc, _, None) => {
254                    let resolved = resolved.relocation.unwrap();
255                    let color = diff_index
256                        .get()
257                        .map_or(DiffTextColor::Bright, |i| DiffTextColor::Rotating(i as u8));
258                    cb(DiffTextSegment {
259                        text: DiffText::Symbol(resolved.symbol),
260                        color,
261                        pad_to: 0,
262                    })?;
263                    if resolved.relocation.addend != 0 {
264                        cb(DiffTextSegment {
265                            text: DiffText::Addend(resolved.relocation.addend),
266                            color,
267                            pad_to: 0,
268                        })?;
269                    }
270                    Ok(())
271                }
272                (InstructionArg::BranchDest(dest), _, _) |
273                // If the relocation was resolved to a branch destination, emit that instead.
274                (InstructionArg::Reloc, _, Some(dest))  => {
275                    if let Some(addr) = dest.checked_sub(resolved.symbol.address) {
276                        cb(DiffTextSegment {
277                            text: DiffText::BranchDest(addr),
278                            color: diff_index
279                                .get()
280                                .map_or(base_color, |i| DiffTextColor::Rotating(i as u8)),
281                            pad_to: 0,
282                        })
283                    } else {
284                        cb(DiffTextSegment {
285                            text: DiffText::Argument(InstructionArgValue::Opaque(Cow::Borrowed(
286                                "<invalid>",
287                            ))),
288                            color: diff_index
289                                .get()
290                                .map_or(base_color, |i| DiffTextColor::Rotating(i as u8)),
291                            pad_to: 0,
292                        })
293                    }
294                }
295            }
296        }
297        InstructionPart::Separator => {
298            cb(DiffTextSegment::basic(diff_config.separator(), base_color))
299        }
300    })?;
301    // Fallback for relocation that wasn't displayed
302    if !displayed_relocation && let Some(resolved) = resolved.relocation {
303        cb(DiffTextSegment::basic(" <", base_color))?;
304        let diff_index = ins_row.arg_diff.get(arg_idx).copied().unwrap_or_default();
305        let color =
306            diff_index.get().map_or(DiffTextColor::Bright, |i| DiffTextColor::Rotating(i as u8));
307        cb(DiffTextSegment { text: DiffText::Symbol(resolved.symbol), color, pad_to: 0 })?;
308        if resolved.relocation.addend != 0 {
309            cb(DiffTextSegment {
310                text: DiffText::Addend(resolved.relocation.addend),
311                color,
312                pad_to: 0,
313            })?;
314        }
315        cb(DiffTextSegment::basic(">", base_color))?;
316    }
317    if let Some(branch) = &ins_row.branch_to {
318        cb(DiffTextSegment {
319            text: DiffText::BranchArrow(branch.ins_idx),
320            color: DiffTextColor::Rotating(branch.branch_idx as u8),
321            pad_to: 0,
322        })?;
323    }
324    cb(EOL_SEGMENT)?;
325    Ok(())
326}
327
328impl PartialEq<HighlightKind> for HighlightKind {
329    fn eq(&self, other: &HighlightKind) -> bool {
330        match (self, other) {
331            (HighlightKind::Opcode(a), HighlightKind::Opcode(b)) => a == b,
332            (HighlightKind::Argument(a), HighlightKind::Argument(b)) => a.loose_eq(b),
333            (HighlightKind::Symbol(a), HighlightKind::Symbol(b)) => a == b,
334            (HighlightKind::Address(a), HighlightKind::Address(b)) => a == b,
335            _ => false,
336        }
337    }
338}
339
340impl PartialEq<DiffText<'_>> for HighlightKind {
341    fn eq(&self, other: &DiffText) -> bool {
342        match (self, other) {
343            (HighlightKind::Opcode(a), DiffText::Opcode(_, b)) => a == b,
344            (HighlightKind::Argument(a), DiffText::Argument(b)) => a.loose_eq(b),
345            (HighlightKind::Symbol(a), DiffText::Symbol(b)) => a == &b.name,
346            (HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b)) => a == b,
347            _ => false,
348        }
349    }
350}
351
352impl PartialEq<HighlightKind> for DiffText<'_> {
353    fn eq(&self, other: &HighlightKind) -> bool { other.eq(self) }
354}
355
356impl From<&DiffText<'_>> for HighlightKind {
357    fn from(value: &DiffText<'_>) -> Self {
358        match value {
359            DiffText::Opcode(_, op) => HighlightKind::Opcode(*op),
360            DiffText::Argument(arg) => HighlightKind::Argument(arg.to_static()),
361            DiffText::Symbol(sym) => HighlightKind::Symbol(sym.name.to_string()),
362            DiffText::Address(addr) | DiffText::BranchDest(addr) => HighlightKind::Address(*addr),
363            _ => HighlightKind::None,
364        }
365    }
366}
367
368pub enum ContextItem {
369    Copy { value: String, label: Option<String>, copy_string: Option<String> },
370    Navigate { label: String, symbol_index: usize, kind: SymbolNavigationKind },
371    Separator,
372}
373
374#[derive(Debug, Clone, Default, Eq, PartialEq)]
375pub enum SymbolNavigationKind {
376    #[default]
377    Normal,
378    Extab,
379}
380
381#[derive(Debug, Clone, Default, Eq, PartialEq)]
382pub enum HoverItemColor {
383    #[default]
384    Normal, // Gray
385    Emphasized, // White
386    Special,    // Blue
387    Delete,     // Red
388    Insert,     // Green
389}
390
391pub enum HoverItem {
392    Text { label: String, value: String, color: HoverItemColor },
393    Separator,
394}
395
396pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
397    let Some(symbol) = obj.symbols.get(symbol_index) else {
398        return Vec::new();
399    };
400    let mut out = Vec::new();
401    out.push(ContextItem::Copy { value: symbol.name.clone(), label: None, copy_string: None });
402    if let Some(name) = &symbol.demangled_name {
403        out.push(ContextItem::Copy { value: name.clone(), label: None, copy_string: None });
404    }
405    if symbol.section.is_some()
406        && let Some(address) = symbol.virtual_address
407    {
408        out.push(ContextItem::Copy {
409            value: format!("{address:x}"),
410            label: Some("virtual address".to_string()),
411            copy_string: None,
412        });
413    }
414    out.append(&mut obj.arch.symbol_context(obj, symbol_index));
415    out
416}
417
418pub fn symbol_hover(
419    obj: &Object,
420    symbol_index: usize,
421    addend: i64,
422    override_color: Option<HoverItemColor>,
423) -> Vec<HoverItem> {
424    let Some(symbol) = obj.symbols.get(symbol_index) else {
425        return Vec::new();
426    };
427    let addend_str = match addend.cmp(&0i64) {
428        Ordering::Greater => format!("+{addend:x}"),
429        Ordering::Less => format!("-{:x}", -addend),
430        _ => String::new(),
431    };
432    let mut out = Vec::new();
433    out.push(HoverItem::Text {
434        label: "Name".into(),
435        value: format!("{}{}", symbol.name, addend_str),
436        color: override_color.clone().unwrap_or_default(),
437    });
438    if let Some(demangled_name) = &symbol.demangled_name {
439        out.push(HoverItem::Text {
440            label: "Demangled".into(),
441            value: demangled_name.into(),
442            color: override_color.clone().unwrap_or_default(),
443        });
444    }
445    if let Some(section) = symbol.section {
446        out.push(HoverItem::Text {
447            label: "Section".into(),
448            value: obj.sections[section].name.clone(),
449            color: override_color.clone().unwrap_or_default(),
450        });
451        out.push(HoverItem::Text {
452            label: "Address".into(),
453            value: format!("{:x}{}", symbol.address, addend_str),
454            color: override_color.clone().unwrap_or_default(),
455        });
456        if symbol.flags.contains(SymbolFlag::SizeInferred) {
457            out.push(HoverItem::Text {
458                label: "Size".into(),
459                value: format!("{:x} (inferred)", symbol.size),
460                color: override_color.clone().unwrap_or_default(),
461            });
462        } else {
463            out.push(HoverItem::Text {
464                label: "Size".into(),
465                value: format!("{:x}", symbol.size),
466                color: override_color.clone().unwrap_or_default(),
467            });
468        }
469        if let Some(align) = symbol.align {
470            out.push(HoverItem::Text {
471                label: "Alignment".into(),
472                value: align.get().to_string(),
473                color: override_color.clone().unwrap_or_default(),
474            });
475        }
476        if let Some(address) = symbol.virtual_address {
477            out.push(HoverItem::Text {
478                label: "Virtual address".into(),
479                value: format!("{address:x}"),
480                color: override_color.clone().unwrap_or(HoverItemColor::Special),
481            });
482        }
483    } else {
484        out.push(HoverItem::Text {
485            label: Default::default(),
486            value: "Extern".into(),
487            color: HoverItemColor::Emphasized,
488        });
489    }
490    out.append(&mut obj.arch.symbol_hover(obj, symbol_index));
491    out
492}
493
494pub fn relocation_context(
495    obj: &Object,
496    reloc: ResolvedRelocation,
497    ins: Option<ResolvedInstructionRef>,
498    diff_config: Option<&DiffObjConfig>,
499) -> Vec<ContextItem> {
500    let mut out = Vec::new();
501    out.append(&mut symbol_context(obj, reloc.relocation.target_symbol));
502    if let Some(ins) = ins {
503        let mut literals = display_ins_data_literals(obj, ins);
504        literals.retain(|lit_info| !lit_info.hidden(diff_config));
505        if !literals.is_empty() {
506            out.push(ContextItem::Separator);
507            for lit_info in literals {
508                out.push(ContextItem::Copy {
509                    value: lit_info.literal,
510                    label: lit_info.label_override,
511                    copy_string: lit_info.copy_string,
512                });
513            }
514        }
515    }
516    out
517}
518
519pub fn data_row_hover(obj: &Object, diff_row: &DataDiffRow) -> Vec<HoverItem> {
520    let mut out = Vec::new();
521    let mut prev_reloc = None;
522    let mut first = true;
523    for reloc_diff in diff_row.relocations.iter() {
524        let reloc = &reloc_diff.reloc;
525        if prev_reloc == Some(reloc) {
526            // Avoid showing consecutive duplicate relocations.
527            // We do this because a single relocation can span across multiple diffs if the
528            // bytes in the relocation changed (e.g. first byte is added, second is unchanged).
529            continue;
530        }
531        prev_reloc = Some(reloc);
532
533        if first {
534            first = false;
535        } else {
536            out.push(HoverItem::Separator);
537        }
538
539        let reloc = resolve_relocation(&obj.symbols, reloc);
540        let color = match reloc_diff.kind {
541            DataDiffKind::None => HoverItemColor::Normal,
542            DataDiffKind::Replace => HoverItemColor::Special,
543            DataDiffKind::Delete => HoverItemColor::Delete,
544            DataDiffKind::Insert => HoverItemColor::Insert,
545        };
546        out.append(&mut relocation_hover(obj, reloc, Some(color)));
547    }
548    out
549}
550
551pub fn data_row_context(obj: &Object, diff_row: &DataDiffRow) -> Vec<ContextItem> {
552    let mut out = Vec::new();
553    let mut prev_reloc = None;
554    for reloc_diff in diff_row.relocations.iter() {
555        let reloc = &reloc_diff.reloc;
556        if prev_reloc == Some(reloc) {
557            // Avoid showing consecutive duplicate relocations.
558            // We do this because a single relocation can span across multiple diffs if the
559            // bytes in the relocation changed (e.g. first byte is added, second is unchanged).
560            continue;
561        }
562        prev_reloc = Some(reloc);
563
564        let reloc = resolve_relocation(&obj.symbols, reloc);
565        out.append(&mut relocation_context(obj, reloc, None, None));
566    }
567    out
568}
569
570pub fn relocation_hover(
571    obj: &Object,
572    reloc: ResolvedRelocation,
573    override_color: Option<HoverItemColor>,
574) -> Vec<HoverItem> {
575    let mut out = Vec::new();
576    if let Some(name) = obj.arch.reloc_name(reloc.relocation.flags) {
577        out.push(HoverItem::Text {
578            label: "Relocation".into(),
579            value: name.to_string(),
580            color: override_color.clone().unwrap_or_default(),
581        });
582    } else {
583        out.push(HoverItem::Text {
584            label: "Relocation".into(),
585            value: format!("<{:?}>", reloc.relocation.flags),
586            color: override_color.clone().unwrap_or_default(),
587        });
588    }
589    out.append(&mut symbol_hover(
590        obj,
591        reloc.relocation.target_symbol,
592        reloc.relocation.addend,
593        override_color,
594    ));
595    out
596}
597
598pub fn instruction_context(
599    obj: &Object,
600    resolved: ResolvedInstructionRef,
601    ins: &ParsedInstruction,
602    diff_config: &DiffObjConfig,
603) -> Vec<ContextItem> {
604    let mut out = Vec::new();
605    let mut hex_string = String::new();
606    for byte in resolved.code {
607        hex_string.push_str(&format!("{byte:02x}"));
608    }
609    out.push(ContextItem::Copy {
610        value: hex_string,
611        label: Some("instruction bytes".to_string()),
612        copy_string: None,
613    });
614    out.append(&mut obj.arch.instruction_context(obj, resolved));
615    if let Some(virtual_address) = resolved.symbol.virtual_address {
616        let offset = resolved.ins_ref.address - resolved.symbol.address;
617        out.push(ContextItem::Copy {
618            value: format!("{:x}", virtual_address + offset),
619            label: Some("virtual address".to_string()),
620            copy_string: None,
621        });
622    }
623    for arg in &ins.args {
624        if let InstructionArg::Value(arg) = arg {
625            out.push(ContextItem::Copy { value: arg.to_string(), label: None, copy_string: None });
626            match arg {
627                InstructionArgValue::Signed(v) => {
628                    out.push(ContextItem::Copy {
629                        value: v.to_string(),
630                        label: None,
631                        copy_string: None,
632                    });
633                }
634                InstructionArgValue::Unsigned(v) => {
635                    out.push(ContextItem::Copy {
636                        value: v.to_string(),
637                        label: None,
638                        copy_string: None,
639                    });
640                }
641                _ => {}
642            }
643        }
644    }
645    if let Some(reloc) = resolved.relocation {
646        out.push(ContextItem::Separator);
647        out.append(&mut relocation_context(obj, reloc, Some(resolved), Some(diff_config)));
648    }
649    out
650}
651
652pub fn instruction_hover(
653    obj: &Object,
654    resolved: ResolvedInstructionRef,
655    ins: &ParsedInstruction,
656    diff_config: &DiffObjConfig,
657) -> Vec<HoverItem> {
658    let mut out = Vec::new();
659    out.push(HoverItem::Text {
660        label: Default::default(),
661        value: format!("{:02x?}", resolved.code),
662        color: HoverItemColor::Normal,
663    });
664    out.append(&mut obj.arch.instruction_hover(obj, resolved));
665    if let Some(virtual_address) = resolved.symbol.virtual_address {
666        let offset = resolved.ins_ref.address - resolved.symbol.address;
667        out.push(HoverItem::Text {
668            label: "Virtual address".into(),
669            value: format!("{:x}", virtual_address + offset),
670            color: HoverItemColor::Special,
671        });
672    }
673    for arg in &ins.args {
674        if let InstructionArg::Value(arg) = arg {
675            match arg {
676                InstructionArgValue::Signed(v) => {
677                    out.push(HoverItem::Text {
678                        label: Default::default(),
679                        value: format!("{arg} == {v}"),
680                        color: HoverItemColor::Normal,
681                    });
682                }
683                InstructionArgValue::Unsigned(v) => {
684                    out.push(HoverItem::Text {
685                        label: Default::default(),
686                        value: format!("{arg} == {v}"),
687                        color: HoverItemColor::Normal,
688                    });
689                }
690                _ => {}
691            }
692        }
693    }
694    if let Some(reloc) = resolved.relocation {
695        out.push(HoverItem::Separator);
696        out.append(&mut relocation_hover(obj, reloc, None));
697        let bytes = obj.symbol_data(reloc.relocation.target_symbol).unwrap_or(&[]);
698        if let Some(ty) = obj.arch.guess_data_type(resolved, bytes) {
699            let mut literals = display_ins_data_literals(obj, resolved);
700            literals.retain(|lit_info| !lit_info.hidden(Some(diff_config)));
701            if !literals.is_empty() {
702                out.push(HoverItem::Separator);
703                for lit_info in literals {
704                    out.push(HoverItem::Text {
705                        label: lit_info.label_override.unwrap_or_else(|| ty.to_string()),
706                        value: format!("{:?}", lit_info.literal),
707                        color: HoverItemColor::Normal,
708                    });
709                }
710            }
711        }
712    }
713    out
714}
715
716#[derive(Debug, Copy, Clone)]
717pub enum SymbolFilter<'a> {
718    None,
719    Search(&'a Regex),
720    Mapping(usize, Option<&'a Regex>),
721}
722
723fn symbol_matches_filter(
724    symbol: &Symbol,
725    diff: &SymbolDiff,
726    filter: SymbolFilter<'_>,
727    show_hidden_symbols: bool,
728) -> bool {
729    // Ignore absolute symbols
730    if symbol.section.is_none() && !symbol.flags.contains(SymbolFlag::Common) {
731        return false;
732    }
733    if !show_hidden_symbols
734        && (symbol.size == 0
735            || symbol.flags.contains(SymbolFlag::Hidden)
736            || symbol.flags.contains(SymbolFlag::Ignored))
737    {
738        return false;
739    }
740    match filter {
741        SymbolFilter::None => true,
742        SymbolFilter::Search(regex) => {
743            regex.is_match(&symbol.name)
744                || symbol.demangled_name.as_deref().is_some_and(|s| regex.is_match(s))
745        }
746        SymbolFilter::Mapping(symbol_ref, regex) => {
747            diff.target_symbol == Some(symbol_ref)
748                && regex.is_none_or(|r| {
749                    r.is_match(&symbol.name)
750                        || symbol.demangled_name.as_deref().is_some_and(|s| r.is_match(s))
751                })
752        }
753    }
754}
755
756#[derive(Debug, Clone, Copy, Eq, PartialEq)]
757pub struct SectionDisplaySymbol {
758    pub symbol: usize,
759    pub is_mapping_symbol: bool,
760}
761
762#[derive(Debug, Clone)]
763pub struct SectionDisplay {
764    pub id: String,
765    pub name: String,
766    pub size: u64,
767    pub match_percent: Option<f32>,
768    pub symbols: Vec<SectionDisplaySymbol>,
769    pub kind: SectionKind,
770}
771
772pub fn display_sections(
773    obj: &Object,
774    diff: &ObjectDiff,
775    filter: SymbolFilter<'_>,
776    show_hidden_symbols: bool,
777    show_mapped_symbols: bool,
778    reverse_fn_order: bool,
779) -> Vec<SectionDisplay> {
780    let mut mapping = BTreeSet::new();
781    let is_mapping_symbol = if let SymbolFilter::Mapping(_, _) = filter {
782        for mapping_diff in &diff.mapping_symbols {
783            let symbol = &obj.symbols[mapping_diff.symbol_index];
784            if !symbol_matches_filter(
785                symbol,
786                &mapping_diff.symbol_diff,
787                filter,
788                show_hidden_symbols,
789            ) {
790                continue;
791            }
792            if !show_mapped_symbols {
793                let symbol_diff = &diff.symbols[mapping_diff.symbol_index];
794                if symbol_diff.target_symbol.is_some() {
795                    continue;
796                }
797            }
798            mapping.insert((symbol.section, mapping_diff.symbol_index));
799        }
800        true
801    } else {
802        for (symbol_idx, (symbol, symbol_diff)) in obj.symbols.iter().zip(&diff.symbols).enumerate()
803        {
804            if !symbol_matches_filter(symbol, symbol_diff, filter, show_hidden_symbols) {
805                continue;
806            }
807            mapping.insert((symbol.section, symbol_idx));
808        }
809        false
810    };
811    let num_sections = mapping.iter().map(|(section_idx, _)| *section_idx).dedup().count();
812    let mut sections = Vec::with_capacity(num_sections);
813    for (section_idx, group) in &mapping.iter().chunk_by(|(section_idx, _)| *section_idx) {
814        let mut symbols = group
815            .map(|&(_, symbol)| SectionDisplaySymbol { symbol, is_mapping_symbol })
816            .collect::<Vec<_>>();
817        if let Some(section_idx) = section_idx {
818            let section = &obj.sections[section_idx];
819            if section.kind == SectionKind::Unknown {
820                // Skip unknown and hidden sections
821                continue;
822            }
823            let section_diff = &diff.sections[section_idx];
824            let reverse_fn_order = section.kind == SectionKind::Code && reverse_fn_order;
825            if reverse_fn_order {
826                symbols.sort_by(|a, b| {
827                    let a = &obj.symbols[a.symbol];
828                    let b = &obj.symbols[b.symbol];
829                    section_symbol_sort(a, b)
830                        .then_with(|| b.address.cmp(&a.address))
831                        .then_with(|| a.size.cmp(&b.size))
832                });
833            }
834            sections.push(SectionDisplay {
835                id: section.id.clone(),
836                name: if section.flags.contains(SectionFlag::Combined) {
837                    format!("{} [combined]", section.name)
838                } else {
839                    section.name.clone()
840                },
841                size: section.size,
842                match_percent: section_diff.match_percent,
843                symbols,
844                kind: section.kind,
845            });
846        } else {
847            // Don't sort, preserve order of absolute symbols
848            sections.push(SectionDisplay {
849                id: ".comm".to_string(),
850                name: ".comm".to_string(),
851                size: 0,
852                match_percent: None,
853                symbols,
854                kind: SectionKind::Common,
855            });
856        }
857    }
858    sections.sort_by(|a, b| a.name.cmp(&b.name));
859    sections
860}
861
862fn section_symbol_sort(a: &Symbol, b: &Symbol) -> Ordering {
863    if a.kind == SymbolKind::Section {
864        if b.kind != SymbolKind::Section {
865            return Ordering::Less;
866        }
867    } else if b.kind == SymbolKind::Section {
868        return Ordering::Greater;
869    }
870    Ordering::Equal
871}
872
873pub fn display_ins_data_labels(obj: &Object, resolved: ResolvedInstructionRef) -> Vec<String> {
874    let Some(reloc) = resolved.relocation else {
875        return Vec::new();
876    };
877    if reloc.relocation.addend < 0 || reloc.relocation.addend as u64 >= reloc.symbol.size {
878        return Vec::new();
879    }
880    let Some(data) = obj.symbol_data(reloc.relocation.target_symbol) else {
881        return Vec::new();
882    };
883    let bytes = &data[reloc.relocation.addend as usize..];
884    obj.arch
885        .guess_data_type(resolved, bytes)
886        .map(|ty| ty.display_labels(obj.endianness, bytes))
887        .unwrap_or_default()
888}
889
890pub fn display_ins_data_literals(
891    obj: &Object,
892    resolved: ResolvedInstructionRef,
893) -> Vec<LiteralInfo> {
894    let Some(reloc) = resolved.relocation else {
895        return Vec::new();
896    };
897    if reloc.relocation.addend < 0 || reloc.relocation.addend as u64 >= reloc.symbol.size {
898        return Vec::new();
899    }
900    let Some(data) = obj.symbol_data(reloc.relocation.target_symbol) else {
901        return Vec::new();
902    };
903    let bytes = &data[reloc.relocation.addend as usize..];
904    obj.arch
905        .guess_data_type(resolved, bytes)
906        .map(|ty| ty.display_literals(obj.endianness, bytes))
907        .unwrap_or_default()
908}