Compare commits

..

9 Commits

Author SHA1 Message Date
Ethan Roseman 85fb18a21a Update rabbitizer to v2.0.0-alpha.4 (#226)
* Update rabbitizer to v2.0.0-alpha.4

* Cargo update
2025-07-27 22:13:42 -06:00
Luke Street 00ad0d8094 Support PPC64 ELFs (PS3); refactor relocation processing 2025-07-21 21:01:03 -06:00
Luke Street 8fac63c42c Remove temporary test 2025-07-17 21:23:12 -06:00
Luke Street 0fb7f3901c Add PPC COFF tests; fix IMAGE_REL_PPC_PAIR handling 2025-07-17 21:19:16 -06:00
Mark Langen 3385f58341 Fix data flow analysis for multiple text sections (#220)
* Fix data flow analysis for multiple text sections

* Data flow analysis results were only keyed by the symbol (function)
  address. That doen't work if there are multiple text sections, the
  result from the first function in one section will stomp the result
  from the first function in another because both have address zero.

* Remove the ambiguity by keying off of the section address as well.

* Formatting

* Satisfy wasm build

* Clippy

* Formatting again

* Thought that section was the section address not the section number.

---------

Co-authored-by: Luke Street <luke.street@encounterpc.com>
2025-07-17 16:04:56 -06:00
Luke Street 60b227f45e Support PowerPC COFF (Xenon, Xbox 360)
ppc750cl is superseded by the powerpc crate, which
supports PPC64, AltiVec and VMX128 extensions.
2025-07-17 13:33:12 -06:00
LagoLunatic 127ae5ae44 PPC pooled relocations: Ignore hidden symbols (#221)
* PPC pooled relocations: Ignore hidden symbols

* PPC pooled relocations: Also ignore 'ignored' symbols
2025-07-07 17:29:05 -06:00
Luke Street 5f48e69775 More clippy fixes 2025-07-07 15:29:30 -06:00
Luke Street 8756eee07b clippy fixes & version bump 2025-07-07 14:56:41 -06:00
46 changed files with 6128 additions and 1453 deletions
Generated
+591 -588
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -14,7 +14,7 @@ strip = "debuginfo"
codegen-units = 1
[workspace.package]
version = "3.0.0-beta.10"
version = "3.0.0-beta.11"
authors = ["Luke Street <luke@street.dev>"]
edition = "2024"
license = "MIT OR Apache-2.0"
+1 -1
View File
@@ -19,7 +19,7 @@ Supports:
- ARM (GBA, DS, 3DS)
- ARM64 (Switch)
- MIPS (N64, PS1, PS2, PSP)
- PowerPC (GameCube, Wii)
- PowerPC (GameCube, Wii, PS3, Xbox 360)
- SuperH (Saturn, Dreamcast)
- x86, x86_64 (PC)
+3 -7
View File
@@ -203,14 +203,10 @@ fn run_oneshot(
let output_format = OutputFormat::from_option(args.format.as_deref())?;
let (diff_config, mapping_config) = build_config_from_args(args)?;
let target = target_path
.map(|p| {
obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {}", p))
})
.map(|p| obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {p}")))
.transpose()?;
let base = base_path
.map(|p| {
obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {}", p))
})
.map(|p| obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {p}")))
.transpose()?;
let result =
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
@@ -399,7 +395,7 @@ fn run_interactive(
stdout(),
EnterAlternateScreen,
EnableMouseCapture,
SetTitle(format!("{} - objdiff", symbol_name)),
SetTitle(format!("{symbol_name} - objdiff")),
)?;
let backend = CrosstermBackend::new(stdout());
let mut terminal = Terminal::new(backend)?;
+5 -7
View File
@@ -177,16 +177,14 @@ fn report_object(
.target_path
.as_ref()
.map(|p| {
obj::read::read(p.as_ref(), diff_config)
.with_context(|| format!("Failed to open {}", p))
obj::read::read(p.as_ref(), diff_config).with_context(|| format!("Failed to open {p}"))
})
.transpose()?;
let base = object
.base_path
.as_ref()
.map(|p| {
obj::read::read(p.as_ref(), diff_config)
.with_context(|| format!("Failed to open {}", p))
obj::read::read(p.as_ref(), diff_config).with_context(|| format!("Failed to open {p}"))
})
.transpose()?;
let result =
@@ -433,8 +431,8 @@ fn read_report(path: &Utf8PlatformPath) -> Result<Report> {
std::io::stdin().read_to_end(&mut data)?;
return Report::parse(&data).with_context(|| "Failed to load report from stdin");
}
let file = File::open(path).with_context(|| format!("Failed to open {}", path))?;
let file = File::open(path).with_context(|| format!("Failed to open {path}"))?;
let mmap =
unsafe { memmap2::Mmap::map(&file) }.with_context(|| format!("Failed to map {}", path))?;
Report::parse(mmap.as_ref()).with_context(|| format!("Failed to load report {}", path))
unsafe { memmap2::Mmap::map(&file) }.with_context(|| format!("Failed to map {path}"))?;
Report::parse(mmap.as_ref()).with_context(|| format!("Failed to load report {path}"))
}
+4 -4
View File
@@ -87,7 +87,7 @@ impl UiView for FunctionDiffUi {
.and_then(|(_, _, d)| d.match_percent)
{
line_r.spans.push(Span::styled(
format!("{:.2}% ", percent),
format!("{percent:.2}% "),
Style::new().fg(match_percent_color(percent)),
));
}
@@ -97,7 +97,7 @@ impl UiView for FunctionDiffUi {
.and_then(|t| t.format(&state.time_format).ok())
.unwrap_or_else(|| "N/A".to_string());
line_r.spans.push(Span::styled(
format!("Last reload: {}", reload_time),
format!("Last reload: {reload_time}"),
Style::new().fg(Color::White),
));
line_r.spans.push(Span::styled(
@@ -538,7 +538,7 @@ impl FunctionDiffUi {
let label_text = match segment.text {
DiffText::Basic(text) => text.to_string(),
DiffText::Line(num) => format!("{num} "),
DiffText::Address(addr) => format!("{:x}:", addr),
DiffText::Address(addr) => format!("{addr:x}:"),
DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "),
DiffText::Argument(arg) => arg.to_string(),
DiffText::BranchDest(addr) => format!("{addr:x}"),
@@ -546,7 +546,7 @@ impl FunctionDiffUi {
sym.demangled_name.as_ref().unwrap_or(&sym.name).clone()
}
DiffText::Addend(addend) => match addend.cmp(&0i64) {
Ordering::Greater => format!("+{:#x}", addend),
Ordering::Greater => format!("+{addend:#x}"),
Ordering::Less => format!("-{:#x}", -addend),
_ => String::new(),
},
+3 -3
View File
@@ -92,7 +92,7 @@ ppc = [
"any-arch",
"dep:cwdemangle",
"dep:cwextab",
"dep:ppc750cl",
"dep:powerpc",
"dep:rlwinmdec",
]
x86 = [
@@ -128,7 +128,7 @@ itertools = { version = "0.14", default-features = false, features = ["use_alloc
log = { version = "0.4", default-features = false, optional = true }
memmap2 = { version = "0.9", optional = true }
num-traits = { version = "0.2", default-features = false, optional = true }
object = { git = "https://github.com/gimli-rs/object", rev = "a74579249e21ab8fcd3a86be588de336f18297cb", default-features = false, features = ["read_core", "elf", "pe"] }
object = { git = "https://github.com/gimli-rs/object", rev = "16ff70aa6fbd97d6bb7b92375929f4d72414c32b", default-features = false, features = ["read_core", "elf", "coff"] }
pbjson = { version = "0.7", default-features = false, optional = true }
prost = { version = "0.13", default-features = false, features = ["prost-derive"], optional = true }
regex = { version = "1.11", default-features = false, features = [], optional = true }
@@ -147,7 +147,7 @@ gimli = { version = "0.31", default-features = false, features = ["read"], optio
# ppc
cwdemangle = { version = "1.0", optional = true }
cwextab = { version = "1.0", optional = true }
ppc750cl = { version = "0.3", optional = true }
powerpc = { version = "0.4", optional = true }
rlwinmdec = { version = "1.1", optional = true }
# mips
+1 -1
View File
@@ -11,6 +11,6 @@ objdiff-core contains the core functionality of [objdiff](https://github.com/enc
- **`arm64`**: Enables the ARM64 backend powered by [yaxpeax-arm](https://github.com/iximeow/yaxpeax-arm).
- **`arm`**: Enables the ARM backend powered by [unarm](https://github.com/AetiasHax/unarm).
- **`mips`**: Enables the MIPS backend powered by [rabbitizer](https://github.com/Decompollaborate/rabbitizer).
- **`ppc`**: Enables the PowerPC backend powered by [ppc750cl](https://github.com/encounter/ppc750cl).
- **`ppc`**: Enables the PowerPC backend powered by [powerpc](https://github.com/encounter/powerpc-rs).
- **`superh`**: Enables the SuperH backend powered by an included disassembler.
- **`x86`**: Enables the x86 backend powered by [iced-x86](https://crates.io/crates/iced-x86).
+5 -5
View File
@@ -60,10 +60,10 @@ pub struct ConfigGroup {
}
fn build_doc(name: &str, description: Option<&str>) -> TokenStream {
let mut doc = format!(" {}", name);
let mut doc = format!(" {name}");
let mut out = quote! { #[doc = #doc] };
if let Some(description) = description {
doc = format!(" {}", description);
doc = format!(" {description}");
out.extend(quote! { #[doc = ""] });
out.extend(quote! { #[doc = #doc] });
}
@@ -443,9 +443,9 @@ pub fn generate_diff_config() {
}
impl core::fmt::Display for ConfigPropertyValue {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ConfigPropertyValue::Boolean(value) => write!(f, "{}", value),
ConfigPropertyValue::Choice(value) => write!(f, "{}", value),
match *self {
ConfigPropertyValue::Boolean(value) => write!(f, "{value}"),
ConfigPropertyValue::Choice(value) => f.write_str(value),
}
}
}
+48 -38
View File
@@ -11,7 +11,7 @@ use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, el
use unarm::{args, arm, thumb};
use crate::{
arch::Arch,
arch::{Arch, RelocationOverride, RelocationOverrideTarget},
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, display::InstructionPart},
obj::{
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
@@ -356,47 +356,57 @@ impl Arch for ArchArm {
Ok(())
}
fn implcit_addend(
fn relocation_override(
&self,
_file: &object::File<'_>,
section: &object::Section,
address: u64,
_relocation: &object::Relocation,
flags: RelocationFlags,
) -> Result<i64> {
let section_data = section.data()?;
let address = address as usize;
Ok(match flags {
// ARM calls
RelocationFlags::Elf(elf::R_ARM_PC24)
| RelocationFlags::Elf(elf::R_ARM_XPC25)
| RelocationFlags::Elf(elf::R_ARM_CALL) => {
let data = section_data[address..address + 4].try_into()?;
let addend = self.endianness.read_i32_bytes(data);
let imm24 = addend & 0xffffff;
(imm24 << 2) << 8 >> 8
relocation: &object::Relocation,
) -> Result<Option<RelocationOverride>> {
match relocation.flags() {
// Handle ELF implicit relocations
object::RelocationFlags::Elf { r_type } => {
if relocation.has_implicit_addend() {
let section_data = section.data()?;
let address = address as usize;
let addend = match r_type {
// ARM calls
elf::R_ARM_PC24 | elf::R_ARM_XPC25 | elf::R_ARM_CALL => {
let data = section_data[address..address + 4].try_into()?;
let addend = self.endianness.read_i32_bytes(data);
let imm24 = addend & 0xffffff;
(imm24 << 2) << 8 >> 8
}
// Thumb calls
elf::R_ARM_THM_PC22 | elf::R_ARM_THM_XPC22 => {
let data = section_data[address..address + 2].try_into()?;
let high = self.endianness.read_i16_bytes(data) as i32;
let data = section_data[address + 2..address + 4].try_into()?;
let low = self.endianness.read_i16_bytes(data) as i32;
let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff);
(imm22 << 1) << 9 >> 9
}
// Data
elf::R_ARM_ABS32 => {
let data = section_data[address..address + 4].try_into()?;
self.endianness.read_i32_bytes(data)
}
flags => bail!("Unsupported ARM implicit relocation {flags:?}"),
};
Ok(Some(RelocationOverride {
target: RelocationOverrideTarget::Keep,
addend: addend as i64,
}))
} else {
Ok(None)
}
}
// Thumb calls
RelocationFlags::Elf(elf::R_ARM_THM_PC22)
| RelocationFlags::Elf(elf::R_ARM_THM_XPC22) => {
let data = section_data[address..address + 2].try_into()?;
let high = self.endianness.read_i16_bytes(data) as i32;
let data = section_data[address + 2..address + 4].try_into()?;
let low = self.endianness.read_i16_bytes(data) as i32;
let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff);
(imm22 << 1) << 9 >> 9
}
// Data
RelocationFlags::Elf(elf::R_ARM_ABS32) => {
let data = section_data[address..address + 4].try_into()?;
self.endianness.read_i32_bytes(data)
}
flags => bail!("Unsupported ARM implicit relocation {flags:?}"),
} as i64)
_ => Ok(None),
}
}
fn demangle(&self, name: &str) -> Option<String> {
@@ -575,7 +585,7 @@ fn push_args(
arg_cb(InstructionPart::basic("}"))?;
}
args::Argument::CoprocNum(value) => {
arg_cb(InstructionPart::opaque(format!("p{}", value)))?;
arg_cb(InstructionPart::opaque(format!("p{value}")))?;
}
args::Argument::ShiftImm(shift) => {
arg_cb(InstructionPart::opaque(shift.op.to_string()))?;
+2 -13
View File
@@ -5,7 +5,7 @@ use alloc::{
};
use core::cmp::Ordering;
use anyhow::{Result, bail};
use anyhow::Result;
use object::elf;
use yaxpeax_arch::{Arch as YaxpeaxArch, Decoder, Reader, U8Reader};
use yaxpeax_arm::armv8::a64::{
@@ -108,17 +108,6 @@ impl Arch for ArchArm64 {
Ok(())
}
fn implcit_addend(
&self,
_file: &object::File<'_>,
_section: &object::Section,
address: u64,
_relocation: &object::Relocation,
flags: RelocationFlags,
) -> Result<i64> {
bail!("Unsupported ARM64 implicit relocation {:#x}:{:?}", address, flags)
}
fn demangle(&self, name: &str) -> Option<String> {
cpp_demangle::Symbol::new(name)
.ok()
@@ -2268,7 +2257,7 @@ where Cb: FnMut(InstructionPart<'static>) {
push_plain(args, "]");
push_separator(args);
// TODO does 31 have to be handled separate?
args(InstructionPart::opaque(format!("x{}", offset_reg)));
args(InstructionPart::opaque(format!("x{offset_reg}")));
}
// Fall back to original logic
Operand::SIMDRegister(_, _)
+67 -53
View File
@@ -7,14 +7,11 @@ use alloc::{
use anyhow::{Result, bail};
use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, elf};
use rabbitizer::{
IsaExtension, IsaVersion, Vram,
abi::Abi,
operands::{IU16, ValuedOperand},
registers_meta::Register,
IsaExtension, IsaVersion, Vram, abi::Abi, operands::ValuedOperand, registers_meta::Register,
};
use crate::{
arch::Arch,
arch::{Arch, RelocationOverride, RelocationOverrideTarget},
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory, display::InstructionPart},
obj::{
InstructionArg, InstructionArgValue, InstructionRef, Relocation, RelocationFlags,
@@ -225,53 +222,67 @@ impl Arch for ArchMips {
Ok(())
}
fn implcit_addend(
fn relocation_override(
&self,
file: &object::File<'_>,
section: &object::Section,
address: u64,
reloc: &object::Relocation,
flags: RelocationFlags,
) -> Result<i64> {
// Check for paired R_MIPS_HI16 and R_MIPS_LO16 relocations.
if let RelocationFlags::Elf(elf::R_MIPS_HI16 | elf::R_MIPS_LO16) = flags {
if let Some(addend) = self
.paired_relocations
.get(section.index().0)
.and_then(|m| m.get(&address).copied())
{
return Ok(addend);
}
}
relocation: &object::Relocation,
) -> Result<Option<RelocationOverride>> {
match relocation.flags() {
// Handle ELF implicit relocations
object::RelocationFlags::Elf { r_type } => {
if relocation.has_implicit_addend() {
// Check for paired R_MIPS_HI16 and R_MIPS_LO16 relocations.
if let elf::R_MIPS_HI16 | elf::R_MIPS_LO16 = r_type {
if let Some(addend) = self
.paired_relocations
.get(section.index().0)
.and_then(|m| m.get(&address).copied())
{
return Ok(Some(RelocationOverride {
target: RelocationOverrideTarget::Keep,
addend,
}));
}
}
let data = section.data()?;
let code = data[address as usize..address as usize + 4].try_into()?;
let addend = self.endianness.read_u32_bytes(code);
Ok(match flags {
RelocationFlags::Elf(elf::R_MIPS_32) => addend as i64,
RelocationFlags::Elf(elf::R_MIPS_26) => ((addend & 0x03FFFFFF) << 2) as i64,
RelocationFlags::Elf(elf::R_MIPS_HI16) => ((addend & 0x0000FFFF) << 16) as i32 as i64,
RelocationFlags::Elf(elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16) => {
(addend & 0x0000FFFF) as i16 as i64
}
RelocationFlags::Elf(elf::R_MIPS_GPREL16 | elf::R_MIPS_LITERAL) => {
let object::RelocationTarget::Symbol(idx) = reloc.target() else {
bail!("Unsupported R_MIPS_GPREL16 relocation against a non-symbol");
};
let sym = file.symbol_by_index(idx)?;
let data = section.data()?;
let code = self
.endianness
.read_u32_bytes(data[address as usize..address as usize + 4].try_into()?);
let addend = match r_type {
elf::R_MIPS_32 => code as i64,
elf::R_MIPS_26 => ((code & 0x03FFFFFF) << 2) as i64,
elf::R_MIPS_HI16 => ((code & 0x0000FFFF) << 16) as i32 as i64,
elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16 => {
(code & 0x0000FFFF) as i16 as i64
}
elf::R_MIPS_GPREL16 | elf::R_MIPS_LITERAL => {
let object::RelocationTarget::Symbol(idx) = relocation.target() else {
bail!("Unsupported R_MIPS_GPREL16 relocation against a non-symbol");
};
let sym = file.symbol_by_index(idx)?;
// if the symbol we are relocating against is in a local section we need to add
// the ri_gp_value from .reginfo to the addend.
if sym.section().index().is_some() {
((addend & 0x0000FFFF) as i16 as i64) + self.ri_gp_value as i64
// if the symbol we are relocating against is in a local section we need to add
// the ri_gp_value from .reginfo to the addend.
if sym.section().index().is_some() {
((code & 0x0000FFFF) as i16 as i64) + self.ri_gp_value as i64
} else {
(code & 0x0000FFFF) as i16 as i64
}
}
elf::R_MIPS_PC16 => 0, // PC-relative relocation
R_MIPS15_S3 => ((code & 0x001FFFC0) >> 3) as i64,
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
};
Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend }))
} else {
(addend & 0x0000FFFF) as i16 as i64
Ok(None)
}
}
RelocationFlags::Elf(elf::R_MIPS_PC16) => 0, // PC-relative relocation
RelocationFlags::Elf(R_MIPS15_S3) => ((addend & 0x001FFFC0) >> 3) as i64,
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
})
_ => Ok(None),
}
}
fn demangle(&self, name: &str) -> Option<String> {
@@ -335,14 +346,18 @@ fn push_args(
}
match op {
ValuedOperand::core_immediate(imm) => {
ValuedOperand::core_imm_i16(imm) => {
if let Some(resolved) = relocation {
push_reloc(resolved.relocation, &mut arg_cb)?;
} else {
arg_cb(match imm {
IU16::Integer(s) => InstructionPart::signed(s),
IU16::Unsigned(u) => InstructionPart::unsigned(u),
})?;
arg_cb(InstructionPart::signed(imm))?;
}
}
ValuedOperand::core_imm_u16(imm) => {
if let Some(resolved) = relocation {
push_reloc(resolved.relocation, &mut arg_cb)?;
} else {
arg_cb(InstructionPart::unsigned(imm))?;
}
}
ValuedOperand::core_label(..) | ValuedOperand::core_branch_target_label(..) => {
@@ -359,14 +374,13 @@ fn push_args(
))?;
}
}
ValuedOperand::core_immediate_base(imm, base) => {
ValuedOperand::core_imm_rs(imm, base) => {
if let Some(resolved) = relocation {
push_reloc(resolved.relocation, &mut arg_cb)?;
} else {
arg_cb(InstructionPart::Arg(InstructionArg::Value(match imm {
IU16::Integer(s) => InstructionArgValue::Signed(s as i64),
IU16::Unsigned(u) => InstructionArgValue::Unsigned(u as u64),
})))?;
arg_cb(InstructionPart::Arg(InstructionArg::Value(
InstructionArgValue::Signed(imm as i64),
)))?;
}
arg_cb(InstructionPart::basic("("))?;
arg_cb(InstructionPart::opaque(base.either_name(
+49 -39
View File
@@ -1,4 +1,10 @@
use alloc::{borrow::Cow, boxed::Box, format, string::String, vec::Vec};
use alloc::{
borrow::Cow,
boxed::Box,
format,
string::{String, ToString},
vec::Vec,
};
use core::{
ffi::CStr,
fmt::{self, Debug},
@@ -49,16 +55,16 @@ pub enum DataType {
impl fmt::Display for DataType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DataType::Int8 => write!(f, "Int8"),
DataType::Int16 => write!(f, "Int16"),
DataType::Int32 => write!(f, "Int32"),
DataType::Int64 => write!(f, "Int64"),
DataType::Float => write!(f, "Float"),
DataType::Double => write!(f, "Double"),
DataType::Bytes => write!(f, "Bytes"),
DataType::String => write!(f, "String"),
}
f.write_str(match self {
DataType::Int8 => "Int8",
DataType::Int16 => "Int16",
DataType::Int32 => "Int32",
DataType::Int64 => "Int64",
DataType::Float => "Float",
DataType::Double => "Double",
DataType::Bytes => "Bytes",
DataType::String => "String",
})
}
}
@@ -66,8 +72,8 @@ impl DataType {
pub fn display_labels(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
let mut strs = Vec::new();
for (literal, label_override) in self.display_literals(endian, bytes) {
let label = label_override.unwrap_or_else(|| format!("{}", self));
strs.push(format!("{}: {}", label, literal))
let label = label_override.unwrap_or_else(|| self.to_string());
strs.push(format!("{label}: {literal}"))
}
strs
}
@@ -100,7 +106,7 @@ impl DataType {
match self {
DataType::Int8 => {
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
strs.push((format!("{:#x}", i), None));
strs.push((format!("{i:#x}"), None));
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None));
@@ -108,7 +114,7 @@ impl DataType {
}
DataType::Int16 => {
let i = endian.read_i16_bytes(bytes.try_into().unwrap());
strs.push((format!("{:#x}", i), None));
strs.push((format!("{i:#x}"), None));
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None));
@@ -116,7 +122,7 @@ impl DataType {
}
DataType::Int32 => {
let i = endian.read_i32_bytes(bytes.try_into().unwrap());
strs.push((format!("{:#x}", i), None));
strs.push((format!("{i:#x}"), None));
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None));
@@ -124,7 +130,7 @@ impl DataType {
}
DataType::Int64 => {
let i = endian.read_i64_bytes(bytes.try_into().unwrap());
strs.push((format!("{:#x}", i), None));
strs.push((format!("{i:#x}"), None));
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None));
@@ -151,16 +157,16 @@ impl DataType {
));
}
DataType::Bytes => {
strs.push((format!("{:#?}", bytes), None));
strs.push((format!("{bytes:#?}"), None));
}
DataType::String => {
if let Ok(cstr) = CStr::from_bytes_until_nul(bytes) {
strs.push((format!("{:?}", cstr), None));
strs.push((format!("{cstr:?}"), None));
}
if let Some(nul_idx) = bytes.iter().position(|&c| c == b'\0') {
let (cow, _, had_errors) = SHIFT_JIS.decode(&bytes[..nul_idx]);
if !had_errors {
let str = format!("{:?}", cow);
let str = format!("{cow:?}");
// Only add the Shift JIS string if it's different from the ASCII string.
if !strs.iter().any(|x| x.0 == str) {
strs.push((str, Some("Shift JIS".into())));
@@ -351,14 +357,15 @@ pub trait Arch: Send + Sync + Debug {
None
}
fn implcit_addend(
fn relocation_override(
&self,
file: &object::File<'_>,
section: &object::Section,
address: u64,
relocation: &object::Relocation,
flags: RelocationFlags,
) -> Result<i64>;
_file: &object::File<'_>,
_section: &object::Section,
_address: u64,
_relocation: &object::Relocation,
) -> Result<Option<RelocationOverride>> {
Ok(None)
}
fn demangle(&self, _name: &str) -> Option<String> { None }
@@ -405,7 +412,9 @@ pub fn new_arch(object: &object::File) -> Result<Box<dyn Arch>> {
use object::Object as _;
Ok(match object.architecture() {
#[cfg(feature = "ppc")]
object::Architecture::PowerPc => Box::new(ppc::ArchPpc::new(object)?),
object::Architecture::PowerPc | object::Architecture::PowerPc64 => {
Box::new(ppc::ArchPpc::new(object)?)
}
#[cfg(feature = "mips")]
object::Architecture::Mips => Box::new(mips::ArchMips::new(object)?),
#[cfg(feature = "x86")]
@@ -450,16 +459,17 @@ impl Arch for ArchDummy {
Ok(())
}
fn implcit_addend(
&self,
_file: &object::File<'_>,
_section: &object::Section,
_address: u64,
_relocation: &object::Relocation,
_flags: RelocationFlags,
) -> Result<i64> {
Ok(0)
}
fn data_reloc_size(&self, _flags: RelocationFlags) -> usize { 0 }
}
pub enum RelocationOverrideTarget {
Keep,
Skip,
Symbol(object::SymbolIndex),
Section(object::SectionIndex),
}
pub struct RelocationOverride {
pub target: RelocationOverrideTarget,
pub addend: i64,
}
+50 -41
View File
@@ -11,7 +11,7 @@ use core::{
};
use itertools::Itertools;
use ppc750cl::Simm;
use powerpc::{Extensions, Simm};
use crate::{
arch::DataType,
@@ -19,8 +19,8 @@ use crate::{
util::{RawDouble, RawFloat},
};
fn is_store_instruction(op: ppc750cl::Opcode) -> bool {
use ppc750cl::Opcode;
fn is_store_instruction(op: powerpc::Opcode) -> bool {
use powerpc::Opcode;
matches!(
op,
Opcode::Stbux
@@ -52,8 +52,8 @@ fn is_store_instruction(op: ppc750cl::Opcode) -> bool {
)
}
pub fn guess_data_type_from_load_store_inst_op(inst_op: ppc750cl::Opcode) -> Option<DataType> {
use ppc750cl::Opcode;
pub fn guess_data_type_from_load_store_inst_op(inst_op: powerpc::Opcode) -> Option<DataType> {
use powerpc::Opcode;
match inst_op {
Opcode::Lbz | Opcode::Lbzu | Opcode::Lbzux | Opcode::Lbzx => Some(DataType::Int8),
Opcode::Lhz | Opcode::Lhzu | Opcode::Lhzux | Opcode::Lhzx => Some(DataType::Int16),
@@ -92,7 +92,7 @@ impl core::fmt::Display for RegisterContent {
// -i is safe because it's at most a 16 bit constant in the i32
{
if *i >= 0 {
write!(f, "0x{:x}", i)
write!(f, "0x{i:x}")
} else {
write!(f, "-0x{:x}", -i)
}
@@ -118,12 +118,12 @@ impl RegisterState {
// During a function call, these registers must be assumed trashed.
fn clear_volatile(&mut self) {
self[ppc750cl::GPR(0)] = RegisterContent::Unknown;
self[powerpc::GPR(0)] = RegisterContent::Unknown;
for i in 0..=13 {
self[ppc750cl::GPR(i)] = RegisterContent::Unknown;
self[powerpc::GPR(i)] = RegisterContent::Unknown;
}
for i in 0..=13 {
self[ppc750cl::FPR(i)] = RegisterContent::Unknown;
self[powerpc::FPR(i)] = RegisterContent::Unknown;
}
}
@@ -132,10 +132,10 @@ impl RegisterState {
// they get overwritten with another value before getting read.
fn set_potential_inputs(&mut self) {
for g_reg in 3..=13 {
self[ppc750cl::GPR(g_reg)] = RegisterContent::InputRegister(g_reg);
self[powerpc::GPR(g_reg)] = RegisterContent::InputRegister(g_reg);
}
for f_reg in 1..=13 {
self[ppc750cl::FPR(f_reg)] = RegisterContent::InputRegister(f_reg);
self[powerpc::FPR(f_reg)] = RegisterContent::InputRegister(f_reg);
}
}
@@ -172,34 +172,34 @@ impl RegisterState {
}
}
impl Index<ppc750cl::GPR> for RegisterState {
impl Index<powerpc::GPR> for RegisterState {
type Output = RegisterContent;
fn index(&self, gpr: ppc750cl::GPR) -> &Self::Output { &self.gpr[gpr.0 as usize] }
fn index(&self, gpr: powerpc::GPR) -> &Self::Output { &self.gpr[gpr.0 as usize] }
}
impl IndexMut<ppc750cl::GPR> for RegisterState {
fn index_mut(&mut self, gpr: ppc750cl::GPR) -> &mut Self::Output {
impl IndexMut<powerpc::GPR> for RegisterState {
fn index_mut(&mut self, gpr: powerpc::GPR) -> &mut Self::Output {
&mut self.gpr[gpr.0 as usize]
}
}
impl Index<ppc750cl::FPR> for RegisterState {
impl Index<powerpc::FPR> for RegisterState {
type Output = RegisterContent;
fn index(&self, fpr: ppc750cl::FPR) -> &Self::Output { &self.fpr[fpr.0 as usize] }
fn index(&self, fpr: powerpc::FPR) -> &Self::Output { &self.fpr[fpr.0 as usize] }
}
impl IndexMut<ppc750cl::FPR> for RegisterState {
fn index_mut(&mut self, fpr: ppc750cl::FPR) -> &mut Self::Output {
impl IndexMut<powerpc::FPR> for RegisterState {
fn index_mut(&mut self, fpr: powerpc::FPR) -> &mut Self::Output {
&mut self.fpr[fpr.0 as usize]
}
}
fn execute_instruction(
registers: &mut RegisterState,
op: &ppc750cl::Opcode,
args: &ppc750cl::Arguments,
op: &powerpc::Opcode,
args: &powerpc::Arguments,
) {
use ppc750cl::{Argument, GPR, Opcode};
use powerpc::{Argument, GPR, Opcode};
match (op, args[0], args[1], args[2]) {
(Opcode::Or, Argument::GPR(a), Argument::GPR(b), Argument::GPR(c)) => {
// Move is implemented as or with self for ints
@@ -270,11 +270,11 @@ fn execute_instruction(
}
}
fn get_branch_offset(args: &ppc750cl::Arguments) -> i32 {
fn get_branch_offset(args: &powerpc::Arguments) -> i32 {
for arg in args.iter() {
match arg {
ppc750cl::Argument::BranchDest(dest) => return dest.0 / 4,
ppc750cl::Argument::None => break,
powerpc::Argument::BranchDest(dest) => return dest.0 / 4,
powerpc::Argument::None => break,
_ => {}
}
}
@@ -316,7 +316,7 @@ fn clamp_text_length(s: String, max: usize) -> String {
fn get_register_content_from_reloc(
reloc: &Relocation,
obj: &Object,
op: ppc750cl::Opcode,
op: powerpc::Opcode,
) -> RegisterContent {
if let Some(bytes) = obj.symbol_data(reloc.target_symbol) {
match guess_data_type_from_load_store_inst_op(op) {
@@ -354,18 +354,18 @@ fn fill_registers_from_relocation(
reloc: &Relocation,
current_state: &mut RegisterState,
obj: &Object,
op: ppc750cl::Opcode,
args: &ppc750cl::Arguments,
op: powerpc::Opcode,
args: &powerpc::Arguments,
) {
// Only update the register state for loads. We may store to a reloc
// address but that doesn't update register contents.
if !is_store_instruction(op) {
match (op, args[0]) {
// Everything else is a load of some sort
(_, ppc750cl::Argument::GPR(gpr)) => {
(_, powerpc::Argument::GPR(gpr)) => {
current_state[gpr] = get_register_content_from_reloc(reloc, obj, op);
}
(_, ppc750cl::Argument::FPR(fpr)) => {
(_, powerpc::Argument::FPR(fpr)) => {
current_state[fpr] = get_register_content_from_reloc(reloc, obj, op);
}
_ => {}
@@ -384,11 +384,12 @@ pub fn ppc_data_flow_analysis(
func_symbol: &Symbol,
code: &[u8],
relocations: &[Relocation],
extensions: Extensions,
) -> Box<dyn FlowAnalysisResult> {
use alloc::collections::VecDeque;
use ppc750cl::InsIter;
let instructions = InsIter::new(code, func_symbol.address as u32)
use powerpc::InsIter;
let instructions = InsIter::new(code, func_symbol.address as u32, extensions)
.map(|(_addr, ins)| (ins.op, ins.basic().args))
.collect_vec();
@@ -449,7 +450,7 @@ pub fn ppc_data_flow_analysis(
// Only take a given (address, register state) combination once. If
// the known register state is different we have to take the branch
// again to stabilize the known values for backwards branches.
if op == &ppc750cl::Opcode::Bc {
if op == &powerpc::Opcode::Bc {
let branch_state = (index, current_state.clone());
if !taken_branches.contains(&branch_state) {
let offset = get_branch_offset(args);
@@ -468,7 +469,7 @@ pub fn ppc_data_flow_analysis(
}
// Update index
if op == &ppc750cl::Opcode::B {
if op == &powerpc::Opcode::B {
// Unconditional branch
let offset = get_branch_offset(args);
if offset > 0 {
@@ -502,7 +503,14 @@ pub fn ppc_data_flow_analysis(
}
// Store the relevant data flow values for simplified instructions
generate_flow_analysis_result(obj, func_address, code, register_state_at, relocations)
generate_flow_analysis_result(
obj,
func_address,
code,
register_state_at,
relocations,
extensions,
)
}
fn get_string_data(obj: &Object, symbol_index: usize, offset: Simm) -> Option<&str> {
@@ -530,14 +538,15 @@ fn generate_flow_analysis_result(
code: &[u8],
register_state_at: Vec<RegisterState>,
relocations: &[Relocation],
extensions: Extensions,
) -> Box<PPCFlowAnalysisResult> {
use ppc750cl::{Argument, InsIter};
use powerpc::{Argument, InsIter};
let mut analysis_result = PPCFlowAnalysisResult::new();
let default_register_state = RegisterState::new();
for (addr, ins) in InsIter::new(code, 0) {
for (addr, ins) in InsIter::new(code, 0, extensions) {
let ins_address = base_address + (addr as u64);
let index = addr / 4;
let ppc750cl::ParsedIns { mnemonic: _, args } = ins.simplified();
let powerpc::ParsedIns { mnemonic: _, args } = ins.simplified();
// If we're already showing relocations on a line don't also show data flow
let reloc = relocations.iter().find(|r| (r.address & !3) == ins_address);
@@ -546,7 +555,7 @@ fn generate_flow_analysis_result(
// they are being loaded.
// We need to do this before we break out on showing relocations in the
// subsequent if statement.
if let (ppc750cl::Opcode::Lfs | ppc750cl::Opcode::Lfd, Some(reloc)) = (ins.op, reloc) {
if let (powerpc::Opcode::Lfs | powerpc::Opcode::Lfd, Some(reloc)) = (ins.op, reloc) {
let content = get_register_content_from_reloc(reloc, obj, ins.op);
if matches!(
content,
@@ -566,7 +575,7 @@ fn generate_flow_analysis_result(
// Special case to show string constants on the line where they are
// being indexed to. This will typically be "addi t, stringbase, offset"
let registers = register_state_at.get(index as usize).unwrap_or(&default_register_state);
if let (ppc750cl::Opcode::Addi, Argument::GPR(rel), Argument::Simm(offset)) =
if let (powerpc::Opcode::Addi, Argument::GPR(rel), Argument::Simm(offset)) =
(ins.op, args[1], args[2])
{
if let RegisterContent::Symbol(sym_index) = registers[rel] {
@@ -625,7 +634,7 @@ fn generate_flow_analysis_result(
Some(FlowAnalysisValue::Text(reg_name))
}
Some(RegisterContent::Unknown) | Some(RegisterContent::Variable) => None,
Some(value) => Some(FlowAnalysisValue::Text(format!("{value}"))),
Some(value) => Some(FlowAnalysisValue::Text(value.to_string())),
None => None,
};
if let Some(analysis_value) = analysis_value {
+221 -85
View File
@@ -6,13 +6,13 @@ use alloc::{
vec::Vec,
};
use anyhow::{Result, bail, ensure};
use anyhow::{Result, anyhow, bail, ensure};
use cwextab::{ExceptionTableData, decode_extab};
use flagset::Flags;
use object::{Object as _, ObjectSection as _, ObjectSymbol as _, elf};
use object::{Object as _, ObjectSection as _, ObjectSymbol as _, elf, pe};
use crate::{
arch::{Arch, DataType},
arch::{Arch, DataType, RelocationOverride, RelocationOverrideTarget},
diff::{
DiffObjConfig,
data::resolve_relocation,
@@ -27,58 +27,80 @@ use crate::{
mod flow_analysis;
// Relative relocation, can be Simm, Offset or BranchDest
fn is_relative_arg(arg: &ppc750cl::Argument) -> bool {
fn is_relative_arg(arg: &powerpc::Argument) -> bool {
matches!(
arg,
ppc750cl::Argument::Simm(_)
| ppc750cl::Argument::Offset(_)
| ppc750cl::Argument::BranchDest(_)
powerpc::Argument::Simm(_)
| powerpc::Argument::Offset(_)
| powerpc::Argument::BranchDest(_)
)
}
// Relative or absolute relocation, can be Uimm, Simm or Offset
fn is_rel_abs_arg(arg: &ppc750cl::Argument) -> bool {
fn is_rel_abs_arg(arg: &powerpc::Argument) -> bool {
matches!(
arg,
ppc750cl::Argument::Uimm(_) | ppc750cl::Argument::Simm(_) | ppc750cl::Argument::Offset(_)
powerpc::Argument::Uimm(_) | powerpc::Argument::Simm(_) | powerpc::Argument::Offset(_)
)
}
fn is_offset_arg(arg: &ppc750cl::Argument) -> bool { matches!(arg, ppc750cl::Argument::Offset(_)) }
fn is_offset_arg(arg: &powerpc::Argument) -> bool { matches!(arg, powerpc::Argument::Offset(_)) }
#[derive(Debug)]
pub struct ArchPpc {
pub extensions: powerpc::Extensions,
/// Exception info
pub extab: Option<BTreeMap<usize, ExceptionInfo>>,
}
impl ArchPpc {
pub fn new(file: &object::File) -> Result<Self> {
Ok(Self { extab: decode_exception_info(file)? })
let extensions = match file.flags() {
object::FileFlags::Coff { .. } => powerpc::Extensions::xenon(),
object::FileFlags::Elf { e_flags, .. }
if (e_flags & elf::EF_PPC_EMB) == elf::EF_PPC_EMB =>
{
powerpc::Extensions::gekko_broadway()
}
_ => {
if file.is_64() {
powerpc::Extension::Ppc64 | powerpc::Extension::AltiVec
} else {
powerpc::Extension::AltiVec.into()
}
}
};
let extab = decode_exception_info(file)?;
Ok(Self { extensions, extab })
}
fn parse_ins_ref(&self, resolved: ResolvedInstructionRef) -> Result<ppc750cl::Ins> {
fn parse_ins_ref(&self, resolved: ResolvedInstructionRef) -> Result<powerpc::Ins> {
let mut code = u32::from_be_bytes(resolved.code.try_into()?);
if let Some(reloc) = resolved.relocation {
code = zero_reloc(code, reloc.relocation);
}
let op = ppc750cl::Opcode::from(resolved.ins_ref.opcode as u8);
Ok(ppc750cl::Ins { code, op })
let op = powerpc::Opcode::from(resolved.ins_ref.opcode);
Ok(powerpc::Ins { code, op })
}
fn find_reloc_arg(
&self,
ins: &ppc750cl::ParsedIns,
ins: &powerpc::ParsedIns,
resolved: Option<ResolvedRelocation>,
) -> Option<usize> {
match resolved?.relocation.flags {
RelocationFlags::Elf(elf::R_PPC_EMB_SDA21) => Some(1),
RelocationFlags::Elf(elf::R_PPC_REL24 | elf::R_PPC_REL14) => {
RelocationFlags::Elf(elf::R_PPC_REL24 | elf::R_PPC_REL14)
| RelocationFlags::Coff(pe::IMAGE_REL_PPC_REL24 | pe::IMAGE_REL_PPC_REL14) => {
ins.args.iter().rposition(is_relative_arg)
}
RelocationFlags::Elf(
elf::R_PPC_ADDR16_HI | elf::R_PPC_ADDR16_HA | elf::R_PPC_ADDR16_LO,
) => ins.args.iter().rposition(is_rel_abs_arg),
)
| RelocationFlags::Coff(pe::IMAGE_REL_PPC_REFHI | pe::IMAGE_REL_PPC_REFLO) => {
ins.args.iter().rposition(is_rel_abs_arg)
}
RelocationFlags::Elf(elf::R_PPC64_TOC16) => ins.args.iter().rposition(is_offset_arg),
_ => None,
}
}
@@ -96,11 +118,11 @@ impl Arch for ArchPpc {
ensure!(code.len() & 3 == 0, "Code length must be a multiple of 4");
let ins_count = code.len() / 4;
let mut insts = Vec::<InstructionRef>::with_capacity(ins_count);
for (cur_addr, ins) in ppc750cl::InsIter::new(code, address as u32) {
for (cur_addr, ins) in powerpc::InsIter::new(code, address as u32, self.extensions) {
insts.push(InstructionRef {
address: cur_addr as u64,
size: 4,
opcode: u8::from(ins.op) as u16,
opcode: u16::from(ins.op),
branch_dest: ins.branch_dest(cur_addr).map(u64::from),
});
}
@@ -131,16 +153,16 @@ impl Arch for ArchPpc {
// For @sda21, we can omit the register argument
if matches!(reloc.relocation.flags, RelocationFlags::Elf(elf::R_PPC_EMB_SDA21))
// Sanity check: the next argument should be r0
&& matches!(ins.args.get(idx + 1), Some(ppc750cl::Argument::GPR(ppc750cl::GPR(0))))
&& matches!(ins.args.get(idx + 1), Some(powerpc::Argument::GPR(powerpc::GPR(0))))
{
break;
}
} else {
match arg {
ppc750cl::Argument::Simm(simm) => cb(InstructionPart::signed(simm.0)),
ppc750cl::Argument::Uimm(uimm) => cb(InstructionPart::unsigned(uimm.0)),
ppc750cl::Argument::Offset(offset) => cb(InstructionPart::signed(offset.0)),
ppc750cl::Argument::BranchDest(dest) => cb(InstructionPart::branch_dest(
powerpc::Argument::Simm(simm) => cb(InstructionPart::signed(simm.0)),
powerpc::Argument::Uimm(uimm) => cb(InstructionPart::unsigned(uimm.0)),
powerpc::Argument::Offset(offset) => cb(InstructionPart::signed(offset.0)),
powerpc::Argument::BranchDest(dest) => cb(InstructionPart::branch_dest(
(resolved.ins_ref.address as u32).wrapping_add_signed(dest.0),
)),
_ => cb(InstructionPart::opaque(arg.to_string())),
@@ -168,7 +190,13 @@ impl Arch for ArchPpc {
relocations: &[Relocation],
symbols: &[Symbol],
) -> Vec<Relocation> {
generate_fake_pool_relocations_for_function(address, code, relocations, symbols)
generate_fake_pool_relocations_for_function(
address,
code,
relocations,
symbols,
self.extensions,
)
}
fn data_flow_analysis(
@@ -178,22 +206,109 @@ impl Arch for ArchPpc {
code: &[u8],
relocations: &[Relocation],
) -> Option<Box<dyn FlowAnalysisResult>> {
Some(flow_analysis::ppc_data_flow_analysis(obj, symbol, code, relocations))
Some(flow_analysis::ppc_data_flow_analysis(obj, symbol, code, relocations, self.extensions))
}
fn implcit_addend(
fn relocation_override(
&self,
_file: &object::File<'_>,
_section: &object::Section,
file: &object::File<'_>,
section: &object::Section,
address: u64,
_relocation: &object::Relocation,
flags: RelocationFlags,
) -> Result<i64> {
bail!("Unsupported PPC implicit relocation {:#x}:{:?}", address, flags)
relocation: &object::Relocation,
) -> Result<Option<RelocationOverride>> {
match relocation.flags() {
// IMAGE_REL_PPC_PAIR contains the REF{HI,LO} displacement instead of a symbol index
object::RelocationFlags::Coff {
typ: pe::IMAGE_REL_PPC_REFHI | pe::IMAGE_REL_PPC_REFLO,
} => section
.relocations()
.skip_while(|&(a, _)| a < address)
.take_while(|&(a, _)| a == address)
.find(|(_, reloc)| {
matches!(reloc.flags(), object::RelocationFlags::Coff {
typ: pe::IMAGE_REL_PPC_PAIR
})
})
.map_or(Ok(None), |(_, reloc)| match reloc.target() {
object::RelocationTarget::Symbol(index) => Ok(Some(RelocationOverride {
target: RelocationOverrideTarget::Keep,
addend: index.0 as u16 as i16 as i64,
})),
target => Err(anyhow!("Unsupported IMAGE_REL_PPC_PAIR target {target:?}")),
}),
// Skip PAIR relocations as they are handled by the previous case
object::RelocationFlags::Coff { typ: pe::IMAGE_REL_PPC_PAIR } => {
Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Skip, addend: 0 }))
}
// Any other COFF relocation has an addend of 0
object::RelocationFlags::Coff { .. } => {
Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend: 0 }))
}
// Handle ELF implicit relocations
flags @ object::RelocationFlags::Elf { r_type } => {
ensure!(
!relocation.has_implicit_addend(),
"Unsupported implicit relocation {:?}",
flags
);
match r_type {
elf::R_PPC64_TOC16 => {
let offset = u64::try_from(relocation.addend())
.map_err(|_| anyhow!("Negative addend for R_PPC64_TOC16 relocation"))?;
let Some(toc_section) = file.section_by_name(".toc") else {
bail!("Missing .toc section for R_PPC64_TOC16 relocation");
};
// If TOC target is a relocation, replace it with the target symbol
let Some((_, toc_relocation)) =
toc_section.relocations().find(|&(a, _)| a == offset)
else {
return Ok(None);
};
if toc_relocation.has_implicit_addend() {
log::warn!(
"Unsupported implicit addend for R_PPC64_TOC16 relocation: {toc_relocation:?}"
);
return Ok(None);
}
let addend = toc_relocation.addend();
match toc_relocation.target() {
object::RelocationTarget::Symbol(symbol_index) => {
Ok(Some(RelocationOverride {
target: RelocationOverrideTarget::Symbol(symbol_index),
addend,
}))
}
object::RelocationTarget::Section(section_index) => {
Ok(Some(RelocationOverride {
target: RelocationOverrideTarget::Section(section_index),
addend,
}))
}
target => {
log::warn!(
"Unsupported R_PPC64_TOC16 relocation target {target:?}"
);
Ok(None)
}
}
}
_ => Ok(None),
}
}
_ => Ok(None),
}
}
fn demangle(&self, name: &str) -> Option<String> {
cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default())
fn demangle(&self, mut name: &str) -> Option<String> {
if name.starts_with('?') {
msvc_demangler::demangle(name, msvc_demangler::DemangleFlags::llvm()).ok()
} else {
name = name.trim_start_matches('.');
cpp_demangle::Symbol::new(name)
.ok()
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
.or_else(|| cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default()))
}
}
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
@@ -208,9 +323,18 @@ impl Arch for ArchPpc {
elf::R_PPC_UADDR32 => Some("R_PPC_UADDR32"),
elf::R_PPC_REL24 => Some("R_PPC_REL24"),
elf::R_PPC_REL14 => Some("R_PPC_REL14"),
elf::R_PPC64_TOC16 => Some("R_PPC64_TOC16"),
_ => None,
},
RelocationFlags::Coff(r_type) => match r_type {
pe::IMAGE_REL_PPC_ADDR32 => Some("IMAGE_REL_PPC_ADDR32"),
pe::IMAGE_REL_PPC_REFHI => Some("IMAGE_REL_PPC_REFHI"),
pe::IMAGE_REL_PPC_REFLO => Some("IMAGE_REL_PPC_REFLO"),
pe::IMAGE_REL_PPC_REL24 => Some("IMAGE_REL_PPC_REL24"),
pe::IMAGE_REL_PPC_REL14 => Some("IMAGE_REL_PPC_REL14"),
pe::IMAGE_REL_PPC_PAIR => Some("IMAGE_REL_PPC_PAIR"),
_ => None,
},
_ => None,
}
}
@@ -238,7 +362,7 @@ impl Arch for ArchPpc {
// Pooled string.
return Some(DataType::String);
}
let opcode = ppc750cl::Opcode::from(resolved.ins_ref.opcode as u8);
let opcode = powerpc::Opcode::from(resolved.ins_ref.opcode);
if let Some(ty) = flow_analysis::guess_data_type_from_load_store_inst_op(opcode) {
// Numeric type.
return Some(ty);
@@ -336,11 +460,18 @@ impl ArchPpc {
fn zero_reloc(code: u32, reloc: &Relocation) -> u32 {
match reloc.flags {
RelocationFlags::Elf(elf::R_PPC_EMB_SDA21) => code & !0x1FFFFF,
RelocationFlags::Elf(elf::R_PPC_REL24) => code & !0x3FFFFFC,
RelocationFlags::Elf(elf::R_PPC_REL14) => code & !0xFFFC,
RelocationFlags::Elf(elf::R_PPC_REL24) | RelocationFlags::Coff(pe::IMAGE_REL_PPC_REL24) => {
code & !0x3FFFFFC
}
RelocationFlags::Elf(elf::R_PPC_REL14) | RelocationFlags::Coff(pe::IMAGE_REL_PPC_REL14) => {
code & !0xFFFC
}
RelocationFlags::Elf(
elf::R_PPC_ADDR16_HI | elf::R_PPC_ADDR16_HA | elf::R_PPC_ADDR16_LO,
) => code & !0xFFFF,
)
| RelocationFlags::Coff(pe::IMAGE_REL_PPC_REFHI | pe::IMAGE_REL_PPC_REFLO) => {
code & !0xFFFF
}
_ => code,
}
}
@@ -350,34 +481,38 @@ fn display_reloc(
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
match resolved.relocation.flags {
RelocationFlags::Elf(r_type) => match r_type {
elf::R_PPC_ADDR16_LO => {
cb(InstructionPart::reloc())?;
cb(InstructionPart::basic("@l"))?;
}
elf::R_PPC_ADDR16_HI => {
cb(InstructionPart::reloc())?;
cb(InstructionPart::basic("@h"))?;
}
elf::R_PPC_ADDR16_HA => {
cb(InstructionPart::reloc())?;
cb(InstructionPart::basic("@ha"))?;
}
elf::R_PPC_EMB_SDA21 => {
cb(InstructionPart::reloc())?;
cb(InstructionPart::basic("@sda21"))?;
}
elf::R_PPC_ADDR32 | elf::R_PPC_UADDR32 | elf::R_PPC_REL24 | elf::R_PPC_REL14 => {
cb(InstructionPart::reloc())?;
}
elf::R_PPC_NONE => {
// Fake pool relocation.
cb(InstructionPart::basic("<"))?;
cb(InstructionPart::reloc())?;
cb(InstructionPart::basic(">"))?;
}
_ => cb(InstructionPart::reloc())?,
},
RelocationFlags::Elf(elf::R_PPC_ADDR16_LO)
| RelocationFlags::Coff(pe::IMAGE_REL_PPC_REFLO) => {
cb(InstructionPart::reloc())?;
cb(InstructionPart::basic("@l"))?;
}
RelocationFlags::Elf(elf::R_PPC_ADDR16_HI)
| RelocationFlags::Coff(pe::IMAGE_REL_PPC_REFHI) => {
cb(InstructionPart::reloc())?;
cb(InstructionPart::basic("@h"))?;
}
RelocationFlags::Elf(elf::R_PPC_ADDR16_HA) => {
cb(InstructionPart::reloc())?;
cb(InstructionPart::basic("@ha"))?;
}
RelocationFlags::Elf(elf::R_PPC_EMB_SDA21) => {
cb(InstructionPart::reloc())?;
cb(InstructionPart::basic("@sda21"))?;
}
RelocationFlags::Elf(
elf::R_PPC_ADDR32 | elf::R_PPC_UADDR32 | elf::R_PPC_REL24 | elf::R_PPC_REL14,
)
| RelocationFlags::Coff(
pe::IMAGE_REL_PPC_ADDR32 | pe::IMAGE_REL_PPC_REL24 | pe::IMAGE_REL_PPC_REL14,
) => {
cb(InstructionPart::reloc())?;
}
RelocationFlags::Elf(elf::R_PPC_NONE) => {
// Fake pool relocation.
cb(InstructionPart::basic("<"))?;
cb(InstructionPart::reloc())?;
cb(InstructionPart::basic(">"))?;
}
_ => cb(InstructionPart::reloc())?,
};
Ok(())
@@ -461,16 +596,14 @@ fn decode_exception_info(
// Decode the extab data
let Some(extab_data) = extab_section.data_range(extab_start_addr, extab.size())? else {
log::warn!("Failed to get extab data for function {}", extab_func_name);
log::warn!("Failed to get extab data for function {extab_func_name}");
continue;
};
let data = match decode_extab(extab_data) {
Ok(decoded_data) => decoded_data,
Err(e) => {
log::warn!(
"Exception table decoding failed for function {}, reason: {}",
extab_func_name,
e
"Exception table decoding failed for function {extab_func_name}, reason: {e}"
);
return Ok(None);
}
@@ -517,19 +650,19 @@ fn make_symbol_ref(symbol: &object::Symbol) -> Result<ExtabSymbolRef> {
#[derive(Debug)]
struct PoolReference {
addr_src_gpr: ppc750cl::GPR,
addr_src_gpr: powerpc::GPR,
addr_offset: i16,
addr_dst_gpr: Option<ppc750cl::GPR>,
addr_dst_gpr: Option<powerpc::GPR>,
}
// Given an instruction, check if it could be accessing pooled data at the address in a register.
// If so, return information pertaining to where the instruction is getting that address from and
// what it's doing with the address (e.g. copying it into another register, adding an offset, etc).
fn get_pool_reference_for_inst(
opcode: ppc750cl::Opcode,
simplified: &ppc750cl::ParsedIns,
opcode: powerpc::Opcode,
simplified: &powerpc::ParsedIns,
) -> Option<PoolReference> {
use ppc750cl::{Argument, Opcode};
use powerpc::{Argument, Opcode};
let args = &simplified.args;
if flow_analysis::guess_data_type_from_load_store_inst_op(opcode).is_some() {
match (args[1], args[2]) {
@@ -594,15 +727,15 @@ fn get_pool_reference_for_inst(
// Remove the relocation we're keeping track of in a particular register when an instruction reuses
// that register to hold some other value, unrelated to pool relocation addresses.
fn clear_overwritten_gprs(ins: ppc750cl::Ins, gpr_pool_relocs: &mut BTreeMap<u8, Relocation>) {
use ppc750cl::{Argument, Arguments, Opcode};
fn clear_overwritten_gprs(ins: powerpc::Ins, gpr_pool_relocs: &mut BTreeMap<u8, Relocation>) {
use powerpc::{Argument, Arguments, Opcode};
let mut def_args = Arguments::default();
ins.parse_defs(&mut def_args);
for arg in def_args {
if let Argument::GPR(gpr) = arg {
if ins.op == Opcode::Lmw {
// `lmw` overwrites all registers from rd to r31.
// ppc750cl only returns rd itself, so we manually clear the rest of them.
// powerpc only returns rd itself, so we manually clear the rest of them.
for reg in gpr.0..31 {
gpr_pool_relocs.remove(&reg);
}
@@ -635,6 +768,8 @@ fn make_fake_pool_reloc(
target_symbol = symbols.iter().position(|s| {
s.section == Some(section_index)
&& s.size > 0
&& !s.flags.contains(SymbolFlag::Hidden)
&& !s.flags.contains(SymbolFlag::Ignored)
&& (s.address..s.address + s.size).contains(&target_address)
})?;
addend = target_address.checked_sub(symbols[target_symbol].address)? as i64;
@@ -682,12 +817,13 @@ fn generate_fake_pool_relocations_for_function(
code: &[u8],
relocations: &[Relocation],
symbols: &[Symbol],
extensions: powerpc::Extensions,
) -> Vec<Relocation> {
use ppc750cl::{Argument, InsIter, Opcode};
use powerpc::{Argument, InsIter, Opcode};
let mut visited_ins_addrs = BTreeSet::new();
let mut pool_reloc_for_addr = BTreeMap::new();
let mut ins_iters_with_gpr_state =
vec![(InsIter::new(code, func_address as u32), BTreeMap::new())];
vec![(InsIter::new(code, func_address as u32, extensions), BTreeMap::new())];
let mut gpr_state_at_bctr = BTreeMap::new();
while let Some((ins_iter, mut gpr_pool_relocs)) = ins_iters_with_gpr_state.pop() {
for (cur_addr, ins) in ins_iter {
@@ -719,7 +855,7 @@ fn generate_fake_pool_relocations_for_function(
// Conditional branch.
// Add the branch destination to the queue to do later.
ins_iters_with_gpr_state.push((
InsIter::new(dest_code_slice, branch_dest),
InsIter::new(dest_code_slice, branch_dest, extensions),
gpr_pool_relocs.clone(),
));
// Then continue on with the current iterator.
@@ -729,7 +865,7 @@ fn generate_fake_pool_relocations_for_function(
// Unconditional branch.
// Add the branch destination to the queue.
ins_iters_with_gpr_state.push((
InsIter::new(dest_code_slice, branch_dest),
InsIter::new(dest_code_slice, branch_dest, extensions),
gpr_pool_relocs.clone(),
));
// Break out of the current iterator so we can do the newly added one.
@@ -837,7 +973,7 @@ fn generate_fake_pool_relocations_for_function(
let dest_offset_into_func = unseen_addr - func_address as u32;
let dest_code_slice = &code[dest_offset_into_func as usize..];
ins_iters_with_gpr_state.push((
InsIter::new(dest_code_slice, unseen_addr),
InsIter::new(dest_code_slice, unseen_addr, extensions),
gpr_pool_relocs.clone(),
));
break;
+1 -1
View File
@@ -197,7 +197,7 @@ fn match_ni_f(
}
_ => {
parts.push(InstructionPart::basic(".word 0x"));
parts.push(InstructionPart::basic(format!("{:04X}", op)));
parts.push(InstructionPart::basic(format!("{op:04X}")));
parts.push(InstructionPart::basic(" /* unknown instruction */"));
}
}
+22 -33
View File
@@ -1,6 +1,6 @@
use alloc::{collections::BTreeMap, format, string::String, vec, vec::Vec};
use anyhow::{Result, bail};
use anyhow::Result;
use object::elf;
use crate::{
@@ -109,7 +109,7 @@ impl Arch for ArchSuperH {
);
parts.push(InstructionPart::basic(" /* "));
parts.push(InstructionPart::basic("0x"));
parts.push(InstructionPart::basic(format!("{:04X}", data)));
parts.push(InstructionPart::basic(format!("{data:04X}")));
parts.push(InstructionPart::basic(" */"));
} else if value.size == 4 && value.address as usize + 3 < symbol_data.len() {
let data = u32::from_be_bytes(
@@ -119,7 +119,7 @@ impl Arch for ArchSuperH {
);
parts.push(InstructionPart::basic(" /* "));
parts.push(InstructionPart::basic("0x"));
parts.push(InstructionPart::basic(format!("{:08X}", data)));
parts.push(InstructionPart::basic(format!("{data:08X}")));
parts.push(InstructionPart::basic(" */"));
}
}
@@ -132,17 +132,6 @@ impl Arch for ArchSuperH {
Ok(())
}
fn implcit_addend(
&self,
_file: &object::File<'_>,
_section: &object::Section,
address: u64,
_relocation: &object::Relocation,
flags: RelocationFlags,
) -> Result<i64> {
bail!("Unsupported SuperH implicit relocation {:#x}:{:?}", address, flags)
}
fn demangle(&self, name: &str) -> Option<String> {
cpp_demangle::Symbol::new(name)
.ok()
@@ -214,10 +203,10 @@ mod test {
impl Display for InstructionPart<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
InstructionPart::Basic(s) => write!(f, "{}", s),
InstructionPart::Opcode(s, _o) => write!(f, "{} ", s),
InstructionPart::Arg(arg) => write!(f, "{}", arg),
InstructionPart::Separator => write!(f, ", "),
InstructionPart::Basic(s) => f.write_str(s),
InstructionPart::Opcode(s, _o) => write!(f, "{s} "),
InstructionPart::Arg(arg) => write!(f, "{arg}"),
InstructionPart::Separator => f.write_str(", "),
}
}
}
@@ -225,9 +214,9 @@ mod test {
impl Display for InstructionArg<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
InstructionArg::Value(v) => write!(f, "{}", v),
InstructionArg::BranchDest(v) => write!(f, "{}", v),
InstructionArg::Reloc => write!(f, "reloc"),
InstructionArg::Value(v) => write!(f, "{v}"),
InstructionArg::BranchDest(v) => write!(f, "{v}"),
InstructionArg::Reloc => f.write_str("reloc"),
}
}
}
@@ -264,7 +253,7 @@ mod test {
)
.unwrap();
let joined_str: String = parts.iter().map(|part| format!("{}", part)).collect();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
@@ -342,7 +331,7 @@ mod test {
)
.unwrap();
let joined_str: String = parts.iter().map(|part| format!("{}", part)).collect();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
@@ -425,7 +414,7 @@ mod test {
)
.unwrap();
let joined_str: String = parts.iter().map(|part| format!("{}", part)).collect();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
@@ -462,7 +451,7 @@ mod test {
)
.unwrap();
let joined_str: String = parts.iter().map(|part| format!("{}", part)).collect();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
@@ -516,7 +505,7 @@ mod test {
)
.unwrap();
let joined_str: String = parts.iter().map(|part| format!("{}", part)).collect();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
@@ -557,7 +546,7 @@ mod test {
)
.unwrap();
let joined_str: String = parts.iter().map(|part| format!("{}", part)).collect();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
@@ -601,7 +590,7 @@ mod test {
)
.unwrap();
let joined_str: String = parts.iter().map(|part| format!("{}", part)).collect();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
@@ -645,7 +634,7 @@ mod test {
)
.unwrap();
let joined_str: String = parts.iter().map(|part| format!("{}", part)).collect();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
@@ -682,7 +671,7 @@ mod test {
)
.unwrap();
let joined_str: String = parts.iter().map(|part| format!("{}", part)).collect();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
@@ -716,7 +705,7 @@ mod test {
)
.unwrap();
let joined_str: String = parts.iter().map(|part| format!("{}", part)).collect();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
@@ -764,7 +753,7 @@ mod test {
)
.unwrap();
let joined_str: String = parts.iter().map(|part| format!("{}", part)).collect();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
@@ -814,7 +803,7 @@ mod test {
)
.unwrap();
let joined_str: String = parts.iter().map(|part| format!("{}", part)).collect();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
+25 -18
View File
@@ -9,7 +9,7 @@ use iced_x86::{
use object::{Endian as _, Object as _, ObjectSection as _, elf, pe};
use crate::{
arch::Arch,
arch::{Arch, RelocationOverride, RelocationOverrideTarget},
diff::{DiffObjConfig, X86Formatter, display::InstructionPart},
obj::{InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef},
};
@@ -225,40 +225,47 @@ impl Arch for ArchX86 {
Ok(())
}
fn implcit_addend(
fn relocation_override(
&self,
_file: &object::File<'_>,
section: &object::Section,
address: u64,
_relocation: &object::Relocation,
flags: RelocationFlags,
) -> Result<i64> {
match self.arch {
Architecture::X86 => match flags {
RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32)
| RelocationFlags::Elf(elf::R_386_32 | elf::R_386_PC32) => {
relocation: &object::Relocation,
) -> Result<Option<RelocationOverride>> {
if !relocation.has_implicit_addend() {
return Ok(None);
}
let addend = match self.arch {
Architecture::X86 => match relocation.flags() {
object::RelocationFlags::Coff {
typ: pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32,
}
| object::RelocationFlags::Elf { r_type: elf::R_386_32 | elf::R_386_PC32 } => {
let data =
section.data()?[address as usize..address as usize + 4].try_into()?;
Ok(self.endianness.read_i32_bytes(data) as i64)
self.endianness.read_i32_bytes(data) as i64
}
flags => bail!("Unsupported x86 implicit relocation {flags:?}"),
},
Architecture::X86_64 => match flags {
RelocationFlags::Coff(pe::IMAGE_REL_AMD64_ADDR32NB | pe::IMAGE_REL_AMD64_REL32)
| RelocationFlags::Elf(elf::R_X86_64_32 | elf::R_X86_64_PC32) => {
Architecture::X86_64 => match relocation.flags() {
object::RelocationFlags::Coff {
typ: pe::IMAGE_REL_AMD64_ADDR32NB | pe::IMAGE_REL_AMD64_REL32,
}
| object::RelocationFlags::Elf { r_type: elf::R_X86_64_32 | elf::R_X86_64_PC32 } => {
let data =
section.data()?[address as usize..address as usize + 4].try_into()?;
Ok(self.endianness.read_i32_bytes(data) as i64)
self.endianness.read_i32_bytes(data) as i64
}
RelocationFlags::Coff(pe::IMAGE_REL_AMD64_ADDR64)
| RelocationFlags::Elf(elf::R_X86_64_64) => {
object::RelocationFlags::Coff { typ: pe::IMAGE_REL_AMD64_ADDR64 }
| object::RelocationFlags::Elf { r_type: elf::R_X86_64_64 } => {
let data =
section.data()?[address as usize..address as usize + 8].try_into()?;
Ok(self.endianness.read_i64_bytes(data))
self.endianness.read_i64_bytes(data)
}
flags => bail!("Unsupported x86-64 implicit relocation {flags:?}"),
},
}
};
Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend }))
}
fn demangle(&self, name: &str) -> Option<String> {
+1 -1
View File
@@ -442,7 +442,7 @@ impl From<LegacyReportItem> for ReportItem {
#[cfg(feature = "serde")]
fn serialize_hex<S>(x: &Option<u64>, s: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
if let Some(x) = x { s.serialize_str(&format!("{:#x}", x)) } else { s.serialize_none() }
if let Some(x) = x { s.serialize_str(&format!("{x:#x}")) } else { s.serialize_none() }
}
#[cfg(feature = "serde")]
+7 -7
View File
@@ -189,7 +189,7 @@ pub fn display_row(
let mut arg_idx = 0;
let mut displayed_relocation = false;
let analysis_result = if diff_config.show_data_flow {
obj.flow_analysis_results.get(&resolved.symbol.address)
obj.get_flow_analysis_result(resolved.symbol)
} else {
None
};
@@ -217,7 +217,7 @@ pub fn display_row(
}
let data_flow_value =
analysis_result.and_then(|result|
result.as_ref().get_argument_value_at_address(
result.get_argument_value_at_address(
ins_ref.address, (arg_idx - 1) as u8));
match (arg, data_flow_value, resolved.ins_ref.branch_dest) {
// If we have a flow analysis result, always use that over anything else.
@@ -388,7 +388,7 @@ pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
if symbol.section.is_some() {
if let Some(address) = symbol.virtual_address {
out.push(ContextItem::Copy {
value: format!("{:x}", address),
value: format!("{address:x}"),
label: Some("virtual address".to_string()),
});
}
@@ -405,7 +405,7 @@ pub fn symbol_hover(
) -> Vec<HoverItem> {
let symbol = &obj.symbols[symbol_index];
let addend_str = match addend.cmp(&0i64) {
Ordering::Greater => format!("+{:x}", addend),
Ordering::Greater => format!("+{addend:x}"),
Ordering::Less => format!("-{:x}", -addend),
_ => String::new(),
};
@@ -456,7 +456,7 @@ pub fn symbol_hover(
if let Some(address) = symbol.virtual_address {
out.push(HoverItem::Text {
label: "Virtual address".into(),
value: format!("{:x}", address),
value: format!("{address:x}"),
color: override_color.clone().unwrap_or(HoverItemColor::Special),
});
}
@@ -526,7 +526,7 @@ pub fn instruction_context(
let mut out = Vec::new();
let mut hex_string = String::new();
for byte in resolved.code {
hex_string.push_str(&format!("{:02x}", byte));
hex_string.push_str(&format!("{byte:02x}"));
}
out.push(ContextItem::Copy { value: hex_string, label: Some("instruction bytes".to_string()) });
out.append(&mut obj.arch.instruction_context(obj, resolved));
@@ -609,7 +609,7 @@ pub fn instruction_hover(
out.push(HoverItem::Separator);
for (literal, label_override) in literals {
out.push(HoverItem::Text {
label: label_override.unwrap_or_else(|| format!("{}", ty)),
label: label_override.unwrap_or_else(|| ty.to_string()),
value: literal,
color: HoverItemColor::Normal,
});

Some files were not shown because too many files have changed in this diff Show More