use std::cmp;
use parser::{
Cfi, CfiDirective, FileHash, Function, FunctionDetails, InlinedFunction, LocalVariable,
Parameter, ParameterType, Range, Type, TypeOffset, Unit,
};
use crate::code::{Call, Code};
use crate::print::{
self, DiffList, DiffState, Print, PrintHeader, PrintState, SortList, ValuePrinter,
};
use crate::{Options, Result, Sort};
pub(crate) fn print_ref(f: &Function, w: &mut dyn ValuePrinter) -> Result<()> {
w.link(f.id(), &mut |w| {
if let Some(namespace) = f.namespace() {
print::namespace::print(namespace, w)?;
}
w.name(f.name().unwrap_or("<anon>"))?;
Ok(())
})
}
fn print_name(f: &Function, w: &mut dyn ValuePrinter) -> Result<()> {
write!(w, "fn ")?;
if let Some(namespace) = f.namespace() {
print::namespace::print(namespace, w)?;
}
w.name(f.name().unwrap_or("<anon>"))?;
Ok(())
}
fn print_linkage_name(f: &Function, w: &mut dyn ValuePrinter) -> Result<()> {
if let Some(linkage_name) = f.linkage_name() {
write!(w, "{}", linkage_name)?;
}
Ok(())
}
fn print_symbol_name(f: &Function, w: &mut dyn ValuePrinter) -> Result<()> {
if let Some(symbol_name) = f.symbol_name() {
write!(w, "{}", symbol_name)?;
}
Ok(())
}
fn print_source(f: &Function, w: &mut dyn ValuePrinter, unit: &Unit) -> Result<()> {
print::source::print(f.source(), w, unit)
}
fn print_range(range: Option<&Range>, w: &mut dyn ValuePrinter) -> Result<()> {
if let Some(range) = range {
print::range::print_address(range, w)?;
}
Ok(())
}
fn print_size(f: &Function, w: &mut dyn ValuePrinter) -> Result<()> {
if let Some(size) = f.size() {
write!(w, "{}", size)?;
}
Ok(())
}
fn print_inline(f: &Function, w: &mut dyn ValuePrinter) -> Result<()> {
if f.is_inline() {
write!(w, "yes")?;
}
Ok(())
}
fn print_declaration(f: &Function, w: &mut dyn ValuePrinter) -> Result<()> {
if f.is_declaration() {
write!(w, "yes")?;
}
Ok(())
}
fn print_return_type(f: &Function, w: &mut dyn ValuePrinter, hash: &FileHash) -> Result<()> {
let ty = f.return_type(hash);
if ty.as_ref().map(|t| t.is_void()) != Some(true) {
match ty.as_ref().and_then(|t| t.byte_size(hash)) {
Some(byte_size) => write!(w, "[{}]", byte_size)?,
None => write!(w, "[??]")?,
}
write!(w, "\t")?;
print::types::print_ref(ty, w, hash)?;
}
Ok(())
}
impl<'input> PrintHeader for Function<'input> {
fn print_header(&self, state: &mut PrintState) -> Result<()> {
state.line(|w, _state| print_name(self, w))
}
fn print_body(&self, state: &mut PrintState, unit: &Unit) -> Result<()> {
state.field("linkage name", |w, _state| print_linkage_name(self, w))?;
state.field("symbol name", |w, _state| print_symbol_name(self, w))?;
if state.options().print_source {
state.field("source", |w, _state| print_source(self, w, unit))?;
}
let ranges = self.ranges();
if ranges.len() > 1 {
state.field_collapsed("addresses", |state| state.list(&(), ranges))?;
} else {
let range = ranges.first();
state.field("address", |w, _state| print_range(range, w))?;
}
state.field("size", |w, _state| print_size(self, w))?;
state.field("inline", |w, _state| print_inline(self, w))?;
state.field("declaration", |w, _state| print_declaration(self, w))?;
state.field_expanded("return type", |state| {
state.line(|w, state| print_return_type(self, w, state))
})?;
let details = self.details(state.hash());
state.field_expanded("parameters", |state| state.list(unit, details.parameters()))?;
if state.options().print_function_variables {
state.field_collapsed("variables", |state| state.list(unit, details.variables()))?;
}
if state.options().print_function_stack_frame {
let variables = frame_variables(&details, state.hash());
state.field_collapsed("stack frame", |state| state.list(&(), &variables))?;
}
state.inline(|state| {
state.field_collapsed("inlined functions", |state| {
state.list(unit, details.inlined_functions())
})
})?;
if state.options().print_function_calls {
state.field_collapsed("call sites", |state| state.list(unit, details.calls()))?;
let calls = calls(self, state.code);
state.field_collapsed("calls", |state| state.list(&(), &calls))?;
}
if state.options().print_function_instructions && !self.ranges().is_empty() {
state.field_detail("code", "instructions", |state| {
print_instructions(state, self, &details)
})?;
}
Ok(())
}
fn diff_header(state: &mut DiffState, a: &Self, b: &Self) -> Result<()> {
state.line(a, b, |w, _state, x| print_name(x, w))
}
fn diff_body(
state: &mut DiffState,
unit_a: &parser::Unit,
a: &Self,
unit_b: &parser::Unit,
b: &Self,
) -> Result<()> {
let flag = state.options().ignore_function_linkage_name;
state.ignore_diff(flag, |state| {
state.field("linkage name", a, b, |w, _state, x| {
print_linkage_name(x, w)
})
})?;
let flag = state.options().ignore_function_symbol_name;
state.ignore_diff(flag, |state| {
state.field("symbol name", a, b, |w, _state, x| print_symbol_name(x, w))
})?;
if state.options().print_source {
state.field(
"source",
(unit_a, a),
(unit_b, b),
|w, _state, (unit, x)| print_source(x, w, unit),
)?;
}
let flag = state.options().ignore_function_address;
state.ignore_diff(flag, |state| {
let ranges_a = a.ranges();
let ranges_b = b.ranges();
if ranges_a.len() > 1 || ranges_b.len() > 1 {
state.field_collapsed("addresses", |state| {
state.ord_list(&(), ranges_a, &(), ranges_b)
})
} else {
let range_a = ranges_a.first();
let range_b = ranges_b.first();
state.field("address", range_a, range_b, |w, _state, range| {
print_range(range, w)
})
}
})?;
let flag = state.options().ignore_function_size;
state.ignore_diff(flag, |state| {
state.field("size", a, b, |w, _state, x| print_size(x, w))
})?;
let flag = state.options().ignore_function_inline;
state.ignore_diff(flag, |state| {
state.field("inline", a, b, |w, _state, x| print_inline(x, w))
})?;
state.field("declaration", a, b, |w, _state, x| print_declaration(x, w))?;
state.field_expanded("return type", |state| {
state.line(a, b, |w, state, x| print_return_type(x, w, state))
})?;
let details_a = a.details(state.hash_a());
let details_b = b.details(state.hash_b());
state.field_expanded("parameters", |state| {
state.list(
unit_a,
details_a.parameters(),
unit_b,
details_b.parameters(),
)
})?;
if state.options().print_function_variables {
let mut variables_a: Vec<_> = details_a.variables().iter().collect();
variables_a.sort_by(|x, y| LocalVariable::cmp_id(state.hash_a(), x, state.hash_a(), y));
let mut variables_b: Vec<_> = details_b.variables().iter().collect();
variables_b.sort_by(|x, y| LocalVariable::cmp_id(state.hash_b(), x, state.hash_b(), y));
state.field_collapsed("variables", |state| {
state.list(unit_a, &variables_a, unit_b, &variables_b)
})?;
}
if state.options().print_function_stack_frame {
let variables_a = frame_variables(&details_a, state.hash_a());
let variables_b = frame_variables(&details_b, state.hash_b());
state.field_collapsed("stack frame", |state| {
state.ord_list(&(), &variables_a, &(), &variables_b)
})?;
}
state.inline(|state| {
state.field_collapsed("inlined functions", |state| {
state.list(
unit_a,
details_a.inlined_functions(),
unit_b,
details_b.inlined_functions(),
)
})
})?;
if state.options().print_function_calls {
state.field_collapsed("call sites", |state| {
state.list(unit_a, details_a.calls(), unit_b, details_b.calls())
})?;
let calls_a = calls(a, state.code_a);
let calls_b = calls(b, state.code_b);
state.field_collapsed("calls", |state| state.list(&(), &calls_a, &(), &calls_b))?;
}
if state.options().print_function_instructions {
state.field_collapsed("instructions", |state| {
state.ignore_diff(true, |state| {
state.block((a, &details_a), (b, &details_b), |state, (x, details)| {
print_instructions(state, x, details)
})
})
})?;
}
Ok(())
}
}
impl<'input> Print for Function<'input> {
type Arg = Unit<'input>;
fn print(&self, state: &mut PrintState, unit: &Self::Arg) -> Result<()> {
state.id(
self.id(),
|state| self.print_header(state),
|state| self.print_body(state, unit),
)?;
state.line_break()?;
Ok(())
}
fn diff(
state: &mut DiffState,
unit_a: &Self::Arg,
a: &Self,
unit_b: &Self::Arg,
b: &Self,
) -> Result<()> {
state.id(
unit_a.id(),
|state| PrintHeader::diff_header(state, a, b),
|state| PrintHeader::diff_body(state, unit_a, a, unit_b, b),
)?;
state.line_break()?;
Ok(())
}
}
impl<'input> SortList for Function<'input> {
fn cmp_id(
hash_a: &FileHash,
a: &Self,
hash_b: &FileHash,
b: &Self,
_options: &Options,
) -> cmp::Ordering {
Function::cmp_id(hash_a, a, hash_b, b)
}
fn cmp_id_for_sort(
hash_a: &FileHash,
a: &Self,
hash_b: &FileHash,
b: &Self,
_options: &Options,
) -> cmp::Ordering {
let ord = Function::cmp_id(hash_a, a, hash_b, b);
if ord != cmp::Ordering::Equal {
return ord;
}
for (parameter_a, parameter_b) in a.parameters().iter().zip(b.parameters().iter()) {
let ord = ParameterType::cmp_id(hash_a, parameter_a, hash_b, parameter_b);
if ord != cmp::Ordering::Equal {
return ord;
}
}
a.parameters().len().cmp(&b.parameters().len())
}
fn cmp_by(
hash_a: &FileHash,
a: &Self,
hash_b: &FileHash,
b: &Self,
options: &Options,
) -> cmp::Ordering {
match options.sort {
Sort::None => a.address().cmp(&b.address()),
Sort::Name => Self::cmp_id_for_sort(hash_a, a, hash_b, b, options),
Sort::Size => a.size().cmp(&b.size()),
}
}
}
fn print_call(
call: &Call,
w: &mut dyn ValuePrinter,
hash: &FileHash,
code: Option<&Code>,
options: &Options,
) -> Result<()> {
if !options.ignore_function_address {
write!(w, "0x{:x} -> 0x{:x} ", call.from, call.to)?;
}
if let Some(function) = hash.functions_by_address.get(&call.to) {
print_ref(function, w)?;
} else if let Some(plt) = code.and_then(|code| code.plt(call.to)) {
write!(w, "{}", plt)?;
} else if options.ignore_function_address {
write!(w, "0x{:x}", call.to)?;
}
Ok(())
}
impl Print for Call {
type Arg = ();
fn print(&self, state: &mut PrintState, _arg: &()) -> Result<()> {
let code = state.code;
let options = state.options();
state.line(|w, hash| print_call(self, w, hash, code, options))
}
fn diff(state: &mut DiffState, _arg_a: &(), a: &Self, _arg_b: &(), b: &Self) -> Result<()> {
let options = state.options();
state.line(
(a, state.code_a),
(b, state.code_b),
|w, hash, (x, code)| print_call(x, w, hash, code, options),
)
}
}
impl DiffList for Call {
fn step_cost(&self, _state: &DiffState, _arg: &()) -> usize {
1
}
fn diff_cost(state: &DiffState, _arg_a: &(), a: &Self, _arg_b: &(), b: &Self) -> usize {
let mut cost = 0;
match (
state.hash_a().functions_by_address.get(&a.to),
state.hash_b().functions_by_address.get(&b.to),
) {
(Some(function_a), Some(function_b)) => {
if <Function as SortList>::cmp_id(
state.hash_a(),
function_a,
state.hash_b(),
function_b,
state.options(),
) != cmp::Ordering::Equal
{
cost += 1;
}
}
(None, None) => {}
_ => {
cost += 1;
}
}
cost
}
}
pub(crate) fn calls(f: &Function, code: Option<&Code>) -> Vec<Call> {
let mut calls = Vec::new();
if let Some(code) = code {
for range in f.ranges() {
calls.extend(code.calls(*range));
}
}
calls
}
pub(crate) fn print_instructions(
state: &mut PrintState,
f: &Function,
details: &FunctionDetails,
) -> Result<()> {
let ranges = f.ranges();
if ranges.is_empty() {
return Ok(());
}
let code = match state.code {
Some(x) => x,
None => return Ok(()),
};
let disassembler = match code.disassembler() {
Some(x) => x,
None => return Ok(()),
};
for range in ranges.iter().copied() {
state.instruction(None, "", |w, _hash| {
write!(w, ".org 0x{:x}", range.begin)?;
Ok(())
})?;
let insns = match disassembler.instructions(code, range) {
Some(x) => x,
None => return Ok(()),
};
let cfis = state.hash().file.cfi(range);
let mut insns = insns.iter();
let mut cfis = cfis.iter();
let mut insn_next = insns.next();
let mut cfi_next = cfis.next();
loop {
match (&insn_next, cfi_next) {
(Some(insn), Some(cfi)) => {
if cfi.0.is_none() || cfi.0 <= insn.address() {
print_cfi(state, cfi, range)?;
cfi_next = cfis.next();
} else {
insn.print(state, code, &disassembler, details, range)?;
insn_next = insns.next();
}
}
(Some(insn), None) => {
insn.print(state, code, &disassembler, details, range)?;
insn_next = insns.next();
}
(None, Some(cfi)) => {
print_cfi(state, cfi, range)?;
cfi_next = cfis.next();
}
(None, None) => break,
}
}
}
Ok(())
}
fn print_cfi(state: &mut PrintState, cfi: &Cfi, range: Range) -> Result<()> {
let address = cfi.0.get().map(|x| x - range.begin).unwrap_or(0);
state.instruction(Some(address), "", |w, hash| {
macro_rules! write_reg {
($w:expr, $lead:expr, $r:expr) => {{
write!($w, $lead)?;
match $r.name(hash) {
Some(name) => write!($w, "{}", name),
None => write!($w, "{}", $r.0),
}
}};
}
macro_rules! write_ofs {
($w:expr, $lead:expr, $o:expr) => {{
write!($w, $lead)?;
if $o < 0 {
write!($w, "-0x{:x}", -$o)
} else {
write!($w, "0x{:x}", $o)
}
}};
}
match cfi.1 {
CfiDirective::StartProc => write!(w, ".cfi_startproc")?,
CfiDirective::EndProc => write!(w, ".cfi_endproc")?,
CfiDirective::Personality(a) => {
write!(w, ".cfi_personality 0x{:x}", a.get().unwrap_or(0))?;
}
CfiDirective::Lsda(a) => write!(w, ".cfi_lsda 0x{:x}", a.get().unwrap_or(0))?,
CfiDirective::SignalFrame => write!(w, ".cfi_signalframe")?,
CfiDirective::ReturnColumn(r) => {
write!(w, ".cfi_return_column")?;
write_reg!(w, " ", r)?;
}
CfiDirective::DefCfa(r, o) => {
write!(w, ".cfi_def_cfa")?;
write_reg!(w, " ", r)?;
write_ofs!(w, ", ", o)?;
}
CfiDirective::DefCfaRegister(r) => {
write!(w, ".cfi_def_cfa_register")?;
write_reg!(w, " ", r)?;
}
CfiDirective::DefCfaOffset(o) => write!(w, ".cfi_def_cfa_offset 0x{:x}", o)?,
CfiDirective::Offset(r, o) => {
write!(w, ".cfi_offset")?;
write_reg!(w, " ", r)?;
write_ofs!(w, ", ", o)?;
}
CfiDirective::ValOffset(r, o) => {
write!(w, ".cfi_val_offset")?;
write_reg!(w, " ", r)?;
write_ofs!(w, ", ", o)?;
}
CfiDirective::Register(r1, r2) => {
write!(w, ".cfi_register")?;
write_reg!(w, " ", r1)?;
write_reg!(w, ", ", r2)?;
}
CfiDirective::Restore(r) => {
write!(w, ".cfi_restore")?;
write_reg!(w, " ", r)?;
}
CfiDirective::Undefined(r) => {
write!(w, ".cfi_undefined")?;
write_reg!(w, " ", r)?;
}
CfiDirective::SameValue(r) => {
write!(w, ".cfi_same_value")?;
write_reg!(w, " ", r)?;
}
CfiDirective::RememberState => write!(w, ".cfi_remember_state")?,
CfiDirective::RestoreState => write!(w, ".cfi_restore_state")?,
CfiDirective::Other => write!(w, "<other cfi instruction>")?,
}
Ok(())
})
}
#[derive(Debug, PartialEq, Eq)]
struct FrameVariable<'input> {
prev_offset: Option<i64>,
offset: i64,
size: Option<u64>,
name: Option<&'input str>,
ty: TypeOffset,
}
impl<'input> Ord for FrameVariable<'input> {
fn cmp(&self, other: &FrameVariable<'input>) -> cmp::Ordering {
self.offset
.cmp(&other.offset)
.then_with(|| self.size.cmp(&other.size))
.then_with(|| self.name.cmp(&other.name))
.then_with(|| self.ty.cmp(&other.ty))
}
}
impl<'input> PartialOrd for FrameVariable<'input> {
fn partial_cmp(&self, other: &FrameVariable<'input>) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
fn print_frame_unknown(variable: &FrameVariable, w: &mut dyn ValuePrinter) -> Result<()> {
if let Some(offset) = variable.prev_offset
&& offset < variable.offset
{
write!(w, "{}[{}]\t<unknown>", offset, variable.offset - offset)?;
}
Ok(())
}
fn print_frame_variable(
variable: &FrameVariable,
w: &mut dyn ValuePrinter,
hash: &FileHash,
) -> Result<()> {
write!(w, "{}", variable.offset)?;
match variable.size {
Some(size) => {
write!(w, "[{}]", size)?;
}
None => {
debug!("no size for {:?}", variable);
write!(w, "[??]")?;
}
}
write!(w, "\t{}: ", variable.name.unwrap_or("<anon>"))?;
print::types::print_ref(Type::from_offset(hash, variable.ty), w, hash)?;
Ok(())
}
fn frame_variables<'input>(
details: &FunctionDetails<'input>,
hash: &FileHash<'input>,
) -> Vec<FrameVariable<'input>> {
let mut frame_variables = Vec::new();
for parameter in details.parameters() {
add_parameter_frame_locations(parameter, hash, &mut frame_variables);
}
for variable in details.variables() {
add_variable_frame_locations(variable, hash, &mut frame_variables);
}
for inlined_function in details.inlined_functions() {
add_inlined_function_frame_locations(inlined_function, hash, &mut frame_variables);
}
frame_variables.sort_unstable();
frame_variables.dedup();
let mut prev_offset = None;
for variable in &mut frame_variables {
variable.prev_offset = prev_offset;
prev_offset = variable.size.map(|size| variable.offset + size as i64);
}
frame_variables
}
fn add_inlined_function_frame_locations<'input>(
inlined_function: &InlinedFunction<'input>,
hash: &FileHash<'input>,
variables: &mut Vec<FrameVariable<'input>>,
) {
for parameter in inlined_function.parameters() {
add_parameter_frame_locations(parameter, hash, variables);
}
for variable in inlined_function.variables() {
add_variable_frame_locations(variable, hash, variables);
}
for inlined_function in inlined_function.inlined_functions() {
add_inlined_function_frame_locations(inlined_function, hash, variables);
}
}
fn add_parameter_frame_locations<'input>(
parameter: &Parameter<'input>,
hash: &FileHash<'input>,
variables: &mut Vec<FrameVariable<'input>>,
) {
let size = parameter.byte_size(hash);
let name = parameter.name();
let ty = parameter.type_offset();
for (_, location) in parameter.frame_locations() {
let offset = location.offset;
let size = if let Some(bit_size) = location.bit_size.get() {
Some(bit_size.div_ceil(8))
} else {
size
};
variables.push(FrameVariable {
prev_offset: None,
offset,
size,
name,
ty,
});
}
}
fn add_variable_frame_locations<'input>(
v: &LocalVariable<'input>,
hash: &FileHash<'input>,
variables: &mut Vec<FrameVariable<'input>>,
) {
let size = v.byte_size(hash);
let name = v.name();
let ty = v.type_offset();
for location in v.frame_locations() {
let offset = location.offset;
let size = if let Some(bit_size) = location.bit_size.get() {
Some(bit_size.div_ceil(8))
} else {
size
};
variables.push(FrameVariable {
prev_offset: None,
offset,
size,
name,
ty,
});
}
}
impl<'input> Print for FrameVariable<'input> {
type Arg = ();
fn print(&self, state: &mut PrintState, _arg: &()) -> Result<()> {
state.line(|w, _hash| print_frame_unknown(self, w))?;
state.line(|w, hash| print_frame_variable(self, w, hash))
}
fn diff(state: &mut DiffState, _arg_a: &(), a: &Self, _arg_b: &(), b: &Self) -> Result<()> {
state.line(a, b, |w, _hash, x| print_frame_unknown(x, w))?;
state.line(a, b, |w, hash, x| print_frame_variable(x, w, hash))
}
}