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(&'a str),
31 Line(u32),
33 Address(u64),
35 Opcode(&'a str, u16),
37 Argument(InstructionArgValue<'a>),
39 BranchDest(u64),
41 BranchArrow(u32),
43 Symbol(&'a Symbol),
45 Addend(i64),
47 Spacing(u8),
49 Eol,
51}
52
53#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
54pub enum DiffTextColor {
55 #[default]
56 Normal, Dim, Bright, DataFlow, Replace, Delete, Insert, 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 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 (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 (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 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, Emphasized, Special, Delete, Insert, }
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 let mut literals = display_data_literals(obj, ins, Some(reloc));
503 literals.retain(|lit_info| !lit_info.hidden(diff_config));
504 if !literals.is_empty() {
505 out.push(ContextItem::Separator);
506 for lit_info in literals {
507 out.push(ContextItem::Copy {
508 value: lit_info.literal,
509 label: lit_info.label_override,
510 copy_string: lit_info.copy_string,
511 });
512 }
513 }
514 out
515}
516
517pub fn data_row_hover(obj: &Object, diff_row: &DataDiffRow) -> Vec<HoverItem> {
518 let mut out = Vec::new();
519 let mut prev_reloc = None;
520 let mut first = true;
521 for reloc_diff in diff_row.relocations.iter() {
522 let reloc = &reloc_diff.reloc;
523 if prev_reloc == Some(reloc) {
524 continue;
528 }
529 prev_reloc = Some(reloc);
530
531 if first {
532 first = false;
533 } else {
534 out.push(HoverItem::Separator);
535 }
536
537 let reloc = resolve_relocation(&obj.symbols, reloc);
538 let color = match reloc_diff.kind {
539 DataDiffKind::None => HoverItemColor::Normal,
540 DataDiffKind::Replace => HoverItemColor::Special,
541 DataDiffKind::Delete => HoverItemColor::Delete,
542 DataDiffKind::Insert => HoverItemColor::Insert,
543 };
544 out.append(&mut relocation_hover(obj, reloc, Some(color)));
545 }
546 out
547}
548
549pub fn data_row_context(obj: &Object, diff_row: &DataDiffRow) -> Vec<ContextItem> {
550 let mut out = Vec::new();
551 let mut prev_reloc = None;
552 for reloc_diff in diff_row.relocations.iter() {
553 let reloc = &reloc_diff.reloc;
554 if prev_reloc == Some(reloc) {
555 continue;
559 }
560 prev_reloc = Some(reloc);
561
562 let reloc = resolve_relocation(&obj.symbols, reloc);
563 out.append(&mut relocation_context(obj, reloc, None, None));
564 out.push(ContextItem::Separator);
565 }
566 out
567}
568
569pub fn relocation_hover(
570 obj: &Object,
571 reloc: ResolvedRelocation,
572 override_color: Option<HoverItemColor>,
573) -> Vec<HoverItem> {
574 let mut out = Vec::new();
575 if let Some(name) = obj.arch.reloc_name(reloc.relocation.flags) {
576 out.push(HoverItem::Text {
577 label: "Relocation".into(),
578 value: name.to_string(),
579 color: override_color.clone().unwrap_or_default(),
580 });
581 } else {
582 out.push(HoverItem::Text {
583 label: "Relocation".into(),
584 value: format!("<{:?}>", reloc.relocation.flags),
585 color: override_color.clone().unwrap_or_default(),
586 });
587 }
588 out.append(&mut symbol_hover(
589 obj,
590 reloc.relocation.target_symbol,
591 reloc.relocation.addend,
592 override_color,
593 ));
594 out
595}
596
597pub fn instruction_context(
598 obj: &Object,
599 resolved: ResolvedInstructionRef,
600 ins: &ParsedInstruction,
601 diff_config: &DiffObjConfig,
602) -> Vec<ContextItem> {
603 let mut out = Vec::new();
604 let mut hex_string = String::new();
605 for byte in resolved.code {
606 hex_string.push_str(&format!("{byte:02x}"));
607 }
608 out.push(ContextItem::Copy {
609 value: hex_string,
610 label: Some("instruction bytes".to_string()),
611 copy_string: None,
612 });
613 out.append(&mut obj.arch.instruction_context(obj, resolved));
614 if let Some(virtual_address) = resolved.symbol.virtual_address {
615 let offset = resolved.ins_ref.address - resolved.symbol.address;
616 out.push(ContextItem::Copy {
617 value: format!("{:x}", virtual_address + offset),
618 label: Some("virtual address".to_string()),
619 copy_string: None,
620 });
621 }
622 for arg in &ins.args {
623 if let InstructionArg::Value(arg) = arg {
624 out.push(ContextItem::Copy { value: arg.to_string(), label: None, copy_string: None });
625 match arg {
626 InstructionArgValue::Signed(v) => {
627 out.push(ContextItem::Copy {
628 value: v.to_string(),
629 label: None,
630 copy_string: None,
631 });
632 }
633 InstructionArgValue::Unsigned(v) => {
634 out.push(ContextItem::Copy {
635 value: v.to_string(),
636 label: None,
637 copy_string: None,
638 });
639 }
640 _ => {}
641 }
642 }
643 }
644 if let Some(reloc) = resolved.relocation {
645 out.push(ContextItem::Separator);
646 out.append(&mut relocation_context(obj, reloc, Some(resolved), Some(diff_config)));
647 }
648 out
649}
650
651pub fn instruction_hover(
652 obj: &Object,
653 resolved: ResolvedInstructionRef,
654 ins: &ParsedInstruction,
655 diff_config: &DiffObjConfig,
656) -> Vec<HoverItem> {
657 let mut out = Vec::new();
658 out.push(HoverItem::Text {
659 label: Default::default(),
660 value: format!("{:02x?}", resolved.code),
661 color: HoverItemColor::Normal,
662 });
663 out.append(&mut obj.arch.instruction_hover(obj, resolved));
664 if let Some(virtual_address) = resolved.symbol.virtual_address {
665 let offset = resolved.ins_ref.address - resolved.symbol.address;
666 out.push(HoverItem::Text {
667 label: "Virtual address".into(),
668 value: format!("{:x}", virtual_address + offset),
669 color: HoverItemColor::Special,
670 });
671 }
672 for arg in &ins.args {
673 if let InstructionArg::Value(arg) = arg {
674 match arg {
675 InstructionArgValue::Signed(v) => {
676 out.push(HoverItem::Text {
677 label: Default::default(),
678 value: format!("{arg} == {v}"),
679 color: HoverItemColor::Normal,
680 });
681 }
682 InstructionArgValue::Unsigned(v) => {
683 out.push(HoverItem::Text {
684 label: Default::default(),
685 value: format!("{arg} == {v}"),
686 color: HoverItemColor::Normal,
687 });
688 }
689 _ => {}
690 }
691 }
692 }
693 if let Some(reloc) = resolved.relocation {
694 out.push(HoverItem::Separator);
695 out.append(&mut relocation_hover(obj, reloc, None));
696 let bytes = obj.symbol_data(reloc.relocation.target_symbol).unwrap_or(&[]);
697 if let Some(ty) = obj.arch.guess_ins_data_type(resolved, bytes) {
698 let mut literals = display_ins_data_literals(obj, resolved);
699 literals.retain(|lit_info| !lit_info.hidden(Some(diff_config)));
700 if !literals.is_empty() {
701 out.push(HoverItem::Separator);
702 for lit_info in literals {
703 out.push(HoverItem::Text {
704 label: lit_info.label_override.unwrap_or_else(|| ty.to_string()),
705 value: format!("{:?}", lit_info.literal),
706 color: HoverItemColor::Normal,
707 });
708 }
709 }
710 }
711 }
712 out
713}
714
715#[derive(Debug, Copy, Clone)]
716pub enum SymbolFilter<'a> {
717 None,
718 Search(&'a Regex),
719 Mapping(usize, Option<&'a Regex>),
720}
721
722fn symbol_matches_filter(
723 symbol: &Symbol,
724 diff: &SymbolDiff,
725 filter: SymbolFilter<'_>,
726 show_hidden_symbols: bool,
727) -> bool {
728 if symbol.section.is_none() && !symbol.flags.contains(SymbolFlag::Common) {
730 return false;
731 }
732 if !show_hidden_symbols
733 && (symbol.size == 0
734 || symbol.flags.contains(SymbolFlag::Hidden)
735 || symbol.flags.contains(SymbolFlag::Ignored))
736 {
737 return false;
738 }
739 match filter {
740 SymbolFilter::None => true,
741 SymbolFilter::Search(regex) => {
742 regex.is_match(&symbol.name)
743 || symbol.demangled_name.as_deref().is_some_and(|s| regex.is_match(s))
744 }
745 SymbolFilter::Mapping(symbol_ref, regex) => {
746 diff.target_symbol == Some(symbol_ref)
747 && regex.is_none_or(|r| {
748 r.is_match(&symbol.name)
749 || symbol.demangled_name.as_deref().is_some_and(|s| r.is_match(s))
750 })
751 }
752 }
753}
754
755#[derive(Debug, Clone, Copy, Eq, PartialEq)]
756pub struct SectionDisplaySymbol {
757 pub symbol: usize,
758 pub is_mapping_symbol: bool,
759}
760
761#[derive(Debug, Clone)]
762pub struct SectionDisplay {
763 pub id: String,
764 pub name: String,
765 pub size: u64,
766 pub match_percent: Option<f32>,
767 pub symbols: Vec<SectionDisplaySymbol>,
768 pub kind: SectionKind,
769}
770
771pub fn display_sections(
772 obj: &Object,
773 diff: &ObjectDiff,
774 filter: SymbolFilter<'_>,
775 show_hidden_symbols: bool,
776 show_mapped_symbols: bool,
777 reverse_fn_order: bool,
778) -> Vec<SectionDisplay> {
779 let mut mapping = BTreeSet::new();
780 let is_mapping_symbol = if let SymbolFilter::Mapping(_, _) = filter {
781 for mapping_diff in &diff.mapping_symbols {
782 let symbol = &obj.symbols[mapping_diff.symbol_index];
783 if !symbol_matches_filter(
784 symbol,
785 &mapping_diff.symbol_diff,
786 filter,
787 show_hidden_symbols,
788 ) {
789 continue;
790 }
791 if !show_mapped_symbols {
792 let symbol_diff = &diff.symbols[mapping_diff.symbol_index];
793 if symbol_diff.target_symbol.is_some() {
794 continue;
795 }
796 }
797 mapping.insert((symbol.section, mapping_diff.symbol_index));
798 }
799 true
800 } else {
801 for (symbol_idx, (symbol, symbol_diff)) in obj.symbols.iter().zip(&diff.symbols).enumerate()
802 {
803 if !symbol_matches_filter(symbol, symbol_diff, filter, show_hidden_symbols) {
804 continue;
805 }
806 mapping.insert((symbol.section, symbol_idx));
807 }
808 false
809 };
810 let num_sections = mapping.iter().map(|(section_idx, _)| *section_idx).dedup().count();
811 let mut sections = Vec::with_capacity(num_sections);
812 for (section_idx, group) in &mapping.iter().chunk_by(|(section_idx, _)| *section_idx) {
813 let mut symbols = group
814 .map(|&(_, symbol)| SectionDisplaySymbol { symbol, is_mapping_symbol })
815 .collect::<Vec<_>>();
816 if let Some(section_idx) = section_idx {
817 let section = &obj.sections[section_idx];
818 if section.kind == SectionKind::Unknown {
819 continue;
821 }
822 let section_diff = &diff.sections[section_idx];
823 let reverse_fn_order = section.kind == SectionKind::Code && reverse_fn_order;
824 if reverse_fn_order {
825 symbols.sort_by(|a, b| {
826 let a = &obj.symbols[a.symbol];
827 let b = &obj.symbols[b.symbol];
828 section_symbol_sort(a, b)
829 .then_with(|| b.address.cmp(&a.address))
830 .then_with(|| a.size.cmp(&b.size))
831 });
832 }
833 sections.push(SectionDisplay {
834 id: section.id.clone(),
835 name: if section.flags.contains(SectionFlag::Combined) {
836 format!("{} [combined]", section.name)
837 } else {
838 section.name.clone()
839 },
840 size: section.size,
841 match_percent: section_diff.match_percent,
842 symbols,
843 kind: section.kind,
844 });
845 } else {
846 sections.push(SectionDisplay {
848 id: ".comm".to_string(),
849 name: ".comm".to_string(),
850 size: 0,
851 match_percent: None,
852 symbols,
853 kind: SectionKind::Common,
854 });
855 }
856 }
857 sections.sort_by(|a, b| a.name.cmp(&b.name));
858 sections
859}
860
861fn section_symbol_sort(a: &Symbol, b: &Symbol) -> Ordering {
862 if a.kind == SymbolKind::Section {
863 if b.kind != SymbolKind::Section {
864 return Ordering::Less;
865 }
866 } else if b.kind == SymbolKind::Section {
867 return Ordering::Greater;
868 }
869 Ordering::Equal
870}
871
872pub fn display_ins_data_labels(obj: &Object, resolved: ResolvedInstructionRef) -> Vec<String> {
873 let Some(reloc) = resolved.relocation else {
874 return Vec::new();
875 };
876 if reloc.relocation.addend < 0 || reloc.relocation.addend as u64 >= reloc.symbol.size {
877 return Vec::new();
878 }
879 let Some(data) = obj.symbol_data(reloc.relocation.target_symbol) else {
880 return Vec::new();
881 };
882 let bytes = &data[reloc.relocation.addend as usize..];
883 obj.arch
884 .guess_ins_data_type(resolved, bytes)
885 .map(|ty| ty.display_labels(obj.endianness, bytes))
886 .unwrap_or_default()
887}
888
889pub fn display_data_literals(
890 obj: &Object,
891 resolved: Option<ResolvedInstructionRef>,
892 reloc: Option<ResolvedRelocation>,
893) -> Vec<LiteralInfo> {
894 let Some(reloc) = reloc 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, Some(reloc), bytes)
906 .map(|ty| ty.display_literals(obj.endianness, bytes))
907 .unwrap_or_default()
908}
909
910pub fn display_ins_data_literals(
911 obj: &Object,
912 resolved: ResolvedInstructionRef,
913) -> Vec<LiteralInfo> {
914 display_data_literals(obj, Some(resolved), resolved.relocation)
915}