Skip to content

Commit

Permalink
Implement context menu copy functionality for data values (#163)
Browse files Browse the repository at this point in the history
* Implement context menu copy functionality for data values

* Clippy fixes
  • Loading branch information
SquareMan authored Feb 10, 2025
1 parent 6b7dcab commit 674c942
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 39 deletions.
113 changes: 82 additions & 31 deletions objdiff-core/src/arch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,36 @@ pub enum DataType {
String,
}

impl std::fmt::Display for DataType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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::Int128 => write!(f, "Int128"),
DataType::Float => write!(f, "Float"),
DataType::Double => write!(f, "Double"),
DataType::Bytes => write!(f, "Bytes"),
DataType::String => write!(f, "String"),
}
}
}

impl DataType {
pub fn display_bytes<Endian: ByteOrder>(&self, bytes: &[u8]) -> Option<String> {
pub fn display_labels<Endian: ByteOrder>(&self, bytes: &[u8]) -> Vec<String> {
let mut strs = Vec::new();
for literal in self.display_literals::<Endian>(bytes) {
strs.push(format!("{}: {}", self, literal))
}
strs
}

pub fn display_literals<Endian: ByteOrder>(&self, bytes: &[u8]) -> Vec<String> {
let mut strs = Vec::new();
if self.required_len().is_some_and(|l| bytes.len() < l) {
log::warn!("Failed to display a symbol value for a symbol whose size is too small for instruction referencing it.");
return None;
return strs;
}
let mut bytes = bytes;
if self.required_len().is_some_and(|l| bytes.len() > l) {
Expand All @@ -56,58 +81,61 @@ impl DataType {
match self {
DataType::Int8 => {
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
strs.push(format!("{:#x}", i));

if i < 0 {
format!("Int8: {:#x} ({:#x})", i, ReallySigned(i))
} else {
format!("Int8: {:#x}", i)
strs.push(format!("{:#x}", ReallySigned(i)));
}
}
DataType::Int16 => {
let i = Endian::read_i16(bytes);
strs.push(format!("{:#x}", i));

if i < 0 {
format!("Int16: {:#x} ({:#x})", i, ReallySigned(i))
} else {
format!("Int16: {:#x}", i)
strs.push(format!("{:#x}", ReallySigned(i)));
}
}
DataType::Int32 => {
let i = Endian::read_i32(bytes);
strs.push(format!("{:#x}", i));

if i < 0 {
format!("Int32: {:#x} ({:#x})", i, ReallySigned(i))
} else {
format!("Int32: {:#x}", i)
strs.push(format!("{:#x}", ReallySigned(i)));
}
}
DataType::Int64 => {
let i = Endian::read_i64(bytes);
strs.push(format!("{:#x}", i));

if i < 0 {
format!("Int64: {:#x} ({:#x})", i, ReallySigned(i))
} else {
format!("Int64: {:#x}", i)
strs.push(format!("{:#x}", ReallySigned(i)));
}
}
DataType::Int128 => {
let i = Endian::read_i128(bytes);
strs.push(format!("{:#x}", i));

if i < 0 {
format!("Int128: {:#x} ({:#x})", i, ReallySigned(i))
} else {
format!("Int128: {:#x}", i)
strs.push(format!("{:#x}", ReallySigned(i)));
}
}
DataType::Float => {
format!("Float: {:?}f", Endian::read_f32(bytes))
strs.push(format!("{:?}f", Endian::read_f32(bytes)));
}
DataType::Double => {
format!("Double: {:?}", Endian::read_f64(bytes))
strs.push(format!("{:?}", Endian::read_f64(bytes)));
}
DataType::Bytes => {
format!("Bytes: {:#?}", bytes)
strs.push(format!("{:#?}", bytes));
}
DataType::String => {
format!("String: {:?}", CStr::from_bytes_until_nul(bytes).ok()?)
if let Ok(cstr) = CStr::from_bytes_until_nul(bytes) {
strs.push(format!("{:?}", cstr));
}
}
}
.into()

strs
}

fn required_len(&self) -> Option<usize> {
Expand Down Expand Up @@ -154,19 +182,42 @@ pub trait ObjArch: Send + Sync {

fn guess_data_type(&self, _instruction: &ObjIns) -> Option<DataType> { None }

fn display_data_type(&self, _ty: DataType, bytes: &[u8]) -> Option<String> {
Some(format!("Bytes: {:#x?}", bytes))
fn display_data_labels(&self, _ty: DataType, bytes: &[u8]) -> Vec<String> {
vec![format!("Bytes: {:#x?}", bytes)]
}

fn display_data_literals(&self, _ty: DataType, bytes: &[u8]) -> Vec<String> {
vec![format!("{:#?}", bytes)]
}

fn display_ins_data_labels(&self, ins: &ObjIns) -> Vec<String> {
let Some(reloc) = ins.reloc.as_ref() else {
return Vec::new();
};
if reloc.addend >= 0 && reloc.target.bytes.len() > reloc.addend as usize {
return self
.guess_data_type(ins)
.map(|ty| {
self.display_data_labels(ty, &reloc.target.bytes[reloc.addend as usize..])
})
.unwrap_or_default();
}
Vec::new()
}

fn display_ins_data(&self, ins: &ObjIns) -> Option<String> {
let reloc = ins.reloc.as_ref()?;
fn display_ins_data_literals(&self, ins: &ObjIns) -> Vec<String> {
let Some(reloc) = ins.reloc.as_ref() else {
return Vec::new();
};
if reloc.addend >= 0 && reloc.target.bytes.len() > reloc.addend as usize {
self.guess_data_type(ins).and_then(|ty| {
self.display_data_type(ty, &reloc.target.bytes[reloc.addend as usize..])
})
} else {
None
return self
.guess_data_type(ins)
.map(|ty| {
self.display_data_literals(ty, &reloc.target.bytes[reloc.addend as usize..])
})
.unwrap_or_default();
}
Vec::new()
}

// Downcast methods
Expand Down
8 changes: 6 additions & 2 deletions objdiff-core/src/arch/ppc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,12 @@ impl ObjArch for ObjArchPpc {
guess_data_type_from_load_store_inst_op(Opcode::from(instruction.op as u8))
}

fn display_data_type(&self, ty: DataType, bytes: &[u8]) -> Option<String> {
ty.display_bytes::<BigEndian>(bytes)
fn display_data_labels(&self, ty: DataType, bytes: &[u8]) -> Vec<String> {
ty.display_labels::<BigEndian>(bytes)
}

fn display_data_literals(&self, ty: DataType, bytes: &[u8]) -> Vec<String> {
ty.display_literals::<BigEndian>(bytes)
}

fn ppc(&self) -> Option<&ObjArchPpc> { Some(self) }
Expand Down
4 changes: 2 additions & 2 deletions objdiff-core/src/diff/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,8 @@ fn reloc_eq(
|| address_eq(left, right))
&& (config.function_reloc_diffs == FunctionRelocDiffs::NameAddress
|| left.target.kind != ObjSymbolKind::Object
|| left_obj.arch.display_ins_data(left_ins)
== left_obj.arch.display_ins_data(right_ins))
|| left_obj.arch.display_ins_data_labels(left_ins)
== left_obj.arch.display_ins_data_labels(right_ins))
}
(Some(_), None) => false,
(None, Some(_)) => {
Expand Down
20 changes: 16 additions & 4 deletions objdiff-gui/src/views/function_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ fn ins_hover_ui(
appearance.highlight_color,
format!("Size: {:x}", reloc.target.size),
);
if let Some(s) = obj.arch.display_ins_data(ins) {
ui.colored_label(appearance.highlight_color, s);
for label in obj.arch.display_ins_data_labels(ins) {
ui.colored_label(appearance.highlight_color, label);
}
} else {
ui.colored_label(appearance.highlight_color, "Extern".to_string());
Expand All @@ -163,7 +163,13 @@ fn ins_hover_ui(
});
}

fn ins_context_menu(ui: &mut egui::Ui, section: &ObjSection, ins: &ObjIns, symbol: &ObjSymbol) {
fn ins_context_menu(
ui: &mut egui::Ui,
obj: &ObjInfo,
section: &ObjSection,
ins: &ObjIns,
symbol: &ObjSymbol,
) {
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
Expand Down Expand Up @@ -219,6 +225,12 @@ fn ins_context_menu(ui: &mut egui::Ui, section: &ObjSection, ins: &ObjIns, symbo
}
}
if let Some(reloc) = &ins.reloc {
for literal in obj.arch.display_ins_data_literals(ins) {
if ui.button(format!("Copy \"{literal}\"")).clicked() {
ui.output_mut(|output| output.copied_text.clone_from(&literal));
ui.close_menu();
}
}
if let Some(name) = &reloc.target.demangled_name {
if ui.button(format!("Copy \"{name}\"")).clicked() {
ui.output_mut(|output| output.copied_text.clone_from(name));
Expand Down Expand Up @@ -390,7 +402,7 @@ fn asm_col_ui(
let ins_diff = &ctx.diff.symbol_diff(symbol_ref).instructions[row.index()];
let response_cb = |response: Response| {
if let Some(ins) = &ins_diff.ins {
response.context_menu(|ui| ins_context_menu(ui, section, ins, symbol));
response.context_menu(|ui| ins_context_menu(ui, ctx.obj, section, ins, symbol));
response.on_hover_ui_at_pointer(|ui| {
ins_hover_ui(ui, ctx.obj, section, ins, symbol, appearance)
})
Expand Down

0 comments on commit 674c942

Please sign in to comment.